docs: post-attendance flow v2 design
Replaces the forced post-attendance SiteReport redirect with 3 explicit buttons (Log Work → dashboard / + Site Journal / + Absences) + a parallel "Save Site Journal + Add Absences" on the journal page. Renames the user-facing "Site Report" → "Site Journal" (display-only, Path-A; frees "Journal" for the parked voice feature). Reuses the Round C next_action POST mechanism — no model/migration/URL changes. NOT to be deployed until Konrad verifies locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d7015b9210
commit
110545b11e
203
docs/plans/2026-05-15-post-attendance-flow-v2-design.md
Normal file
203
docs/plans/2026-05-15-post-attendance-flow-v2-design.md
Normal file
@ -0,0 +1,203 @@
|
||||
# Post-Attendance Flow v2 — Design
|
||||
|
||||
**Date:** 15 May 2026
|
||||
**Status:** Approved by Konrad on 15 May 2026; ready for implementation plan.
|
||||
**Branch:** `ai-dev`. **NOT to be pushed/deployed until Konrad confirms it works locally.**
|
||||
|
||||
## Goal (one sentence)
|
||||
|
||||
Replace the forced auto-redirect into the Site Report form after every
|
||||
attendance submit with three explicit, clearly-prioritised choices —
|
||||
"just log work", "log work + site journal", "log work + absences" — and
|
||||
add a parallel "save + add absences" path on the site-journal page,
|
||||
while renaming the user-facing vocabulary to "Site Journal" to free up
|
||||
"Journal" for the (parked) voice-transcript feature.
|
||||
|
||||
## Why
|
||||
|
||||
Phase A.1 shipped a two-step flow: attendance submit → forced redirect
|
||||
to the SiteReport form (with a Skip link). In real use Konrad found the
|
||||
forced redirect intrusive — most days he just wants to log attendance
|
||||
and be done. The fix is to make the site-journal step (and the absences
|
||||
step) explicit opt-in buttons rather than a mandatory interstitial.
|
||||
|
||||
## Decisions locked in (from the brainstorm)
|
||||
|
||||
| # | Question | Decision |
|
||||
|---|----------|----------|
|
||||
| 1 | "Journal" naming collision (parked voice JournalEntry vs the structured SiteReport form) | Rename the SiteReport-the-page to **"Site Journal"** in all UI text. Model/view/URL stay `SiteReport`/`site_report_*` in code (Path-A display-only rename, zero migration). The parked voice feature will be renamed (e.g. "Voice Notes") so it doesn't collide. |
|
||||
| 2 | Where does plain "Log Work" land now? | **Dashboard** (home) + the existing green "work log(s) created" toast. No more forced SiteReport redirect. |
|
||||
| 3 | Button structure | **Approach C** — exactly the 3 explicit buttons Konrad asked for, but with deliberate visual hierarchy (primary "Log Work" + two secondary "+ …" buttons) so 3 buttons don't feel like a wall. |
|
||||
| 4 | End of the "+ Add Absences" chain | The absences form's own buttons (Log Absences → list, Cancel) are the natural end. No extra chaining. |
|
||||
|
||||
## § 1 — Vocabulary rename (display-only, zero migration)
|
||||
|
||||
User-facing text changes from "Site Report" / "Log Today's Work" →
|
||||
**"Site Journal"**:
|
||||
|
||||
- `core/templates/core/site_report_edit.html` — `<h1>` "Log Today's
|
||||
Work" → "Site Journal"; save button "Save Site Report" → "Save Site
|
||||
Journal"; `{% block title %}` text.
|
||||
- `core/templates/core/site_report_detail.html` — heading + `<title>`.
|
||||
- `core/templates/core/work_history.html` — the clipboard-icon tooltip
|
||||
/ link title text that references "site report".
|
||||
- Success-toast string in `core/views.py::site_report_edit` — "Site
|
||||
report saved …" → "Site journal saved …".
|
||||
|
||||
**Unchanged in code (Path-A pattern, per CLAUDE.md "UI-vs-DB naming
|
||||
drift"):** `SiteReport` model, `site_report_edit` /
|
||||
`site_report_detail` views, `/site-report/<id>/edit/` + `/site-report/
|
||||
<id>/` URLs, the `work_log.site_report` related-name, `SiteReportForm`,
|
||||
`core/site_report_schema.py`, all existing tests. Only user-visible
|
||||
strings move.
|
||||
|
||||
**Parked-work doc note:** add a line under the Backburner section that
|
||||
the future voice-transcript feature must NOT be called "Journal" (the
|
||||
name is now taken by the site-progress form) — suggest "Voice Notes".
|
||||
|
||||
## § 2 — Attendance form: 3 buttons, clear hierarchy
|
||||
|
||||
`/attendance/log/` submit block (top → bottom, Konrad's specified order):
|
||||
|
||||
| Button label | Bootstrap style | `next_action` value | Redirect on success |
|
||||
|---|---|---|---|
|
||||
| **Log Work** | `btn btn-lg btn-accent` (primary, orange) | `log_only` | `redirect('home')` + toast |
|
||||
| **Log Work + Site Journal** | `btn btn-lg btn-outline-secondary` | `log_journal` *(NEW)* | `redirect('site_report_edit', work_log_id=<last created>)` |
|
||||
| **Log Work + Add Absences** | `btn btn-lg btn-outline-secondary` | `log_absences` | `/absences/log/?date=&team=&project=` (unchanged) |
|
||||
|
||||
- The primary orange "Log Work" anchors the eye — the common case.
|
||||
- The two `+ …` buttons are visually secondary (outline) — opt-in.
|
||||
- `log_only` changes its redirect target from SiteReport → home.
|
||||
This is the behavioural reversal of Phase A.1's forced redirect.
|
||||
- `log_journal` is new; it does exactly what `log_only` used to do
|
||||
(go to the site-journal form for the last-created WorkLog).
|
||||
- `log_absences` is untouched (Round C).
|
||||
- The conflict-resolution re-render already passes `next_action`
|
||||
through via the `form.data.items` loop (Round C, commit `8c749f3`).
|
||||
`log_journal` rides those same rails for free — no extra work, but
|
||||
needs a regression test.
|
||||
|
||||
## § 3 — Site Journal page: 3 actions, same hierarchy
|
||||
|
||||
`/site-report/<id>/edit/` action row:
|
||||
|
||||
| Action label | Style | Behaviour |
|
||||
|---|---|---|
|
||||
| **Skip** | quiet `btn btn-outline-secondary` (or text link) | → `home`, nothing saved (unchanged) |
|
||||
| **Save Site Journal** | `btn btn-accent` (primary) | Save → `home` + toast (unchanged destination) |
|
||||
| **Save Site Journal + Add Absences** | `btn btn-outline-secondary` | Save → `/absences/log/?date=&team=&project=` prefilled from the WorkLog |
|
||||
|
||||
- Mirror of the attendance form's hierarchy.
|
||||
- The absence prefill query string is built from `work_log.date`,
|
||||
`work_log.team_id`, `work_log.project_id` — the view already
|
||||
`select_related('project', 'team', 'supervisor')`s the work_log, so
|
||||
no new query. Identical query-string construction to the attendance
|
||||
form's `log_absences` branch (DRY: consider a tiny shared helper
|
||||
`_absence_prefill_qs(work_log)` but YAGNI — two call sites, ~3 lines
|
||||
each; decide during implementation).
|
||||
- New submit button carries `name="next_action" value="save_absences"`.
|
||||
The view reads it after a successful `form.save()`.
|
||||
|
||||
## § 4 — View changes
|
||||
|
||||
### `core/views.py::attendance_log` (POST success branch)
|
||||
|
||||
Current Round C logic: `next_action == 'log_absences'` → absences
|
||||
prefill; else → `redirect('site_report_edit', …)`.
|
||||
|
||||
New logic:
|
||||
- `next_action == 'log_absences'` → absences prefill (unchanged)
|
||||
- `next_action == 'log_journal'` → `redirect('site_report_edit',
|
||||
work_log_id=created_log_ids[-1])` (what the old default did)
|
||||
- else (`log_only`, missing, or anything unrecognised) →
|
||||
`redirect('home')` + success toast
|
||||
|
||||
### `core/views.py::site_report_edit` (POST success branch)
|
||||
|
||||
Current: always `redirect('home')` after `instance.save()`.
|
||||
|
||||
New:
|
||||
```python
|
||||
instance.save()
|
||||
messages.success(request, f"Site journal saved for {work_log.project.name} on {work_log.date:%d %b %Y}.")
|
||||
if request.POST.get('next_action') == 'save_absences':
|
||||
from urllib.parse import urlencode
|
||||
params = {'date': work_log.date.isoformat()}
|
||||
if work_log.team_id:
|
||||
params['team'] = work_log.team_id
|
||||
if work_log.project_id:
|
||||
params['project'] = work_log.project_id
|
||||
return redirect(f"{reverse('absence_log')}?{urlencode(params)}")
|
||||
return redirect('home')
|
||||
```
|
||||
|
||||
(Exact variable names verified against the live view during planning.)
|
||||
|
||||
## Tests (~5, in `core/tests.py`)
|
||||
|
||||
1. `attendance_log` POST `next_action=log_only` → 302 to `/` (home),
|
||||
NOT to `/site-report/…`. (Regression — this reverses Phase A.1.)
|
||||
2. `attendance_log` POST `next_action=log_journal` → 302 to
|
||||
`/site-report/<id>/edit/`.
|
||||
3. `attendance_log` POST `next_action=log_absences` → 302 to
|
||||
`/absences/log/?...` (existing test, confirm still green).
|
||||
4. `site_report_edit` POST default (`next_action` absent) → saves +
|
||||
302 to `/` (home).
|
||||
5. `site_report_edit` POST `next_action=save_absences` → saves +
|
||||
302 to `/absences/log/?date=…&team=…&project=…`.
|
||||
6. Conflict-resolution path carries `next_action=log_journal` through
|
||||
to the final redirect (extend the existing Round C conflict test).
|
||||
|
||||
Update any existing test that asserted attendance submit redirects to
|
||||
the site report by default — that contract is intentionally changing.
|
||||
|
||||
## Files touched
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `core/templates/core/attendance_log.html` | +1 button (`log_journal`), restyle 3 buttons into primary/secondary hierarchy |
|
||||
| `core/templates/core/site_report_edit.html` | text → "Site Journal"; +1 submit button (`save_absences`) |
|
||||
| `core/templates/core/site_report_detail.html` | heading/title text → "Site Journal" |
|
||||
| `core/templates/core/work_history.html` | clipboard tooltip text → "site journal" |
|
||||
| `core/views.py::attendance_log` | 3-way `next_action` branch (log_only→home, log_journal→site report, log_absences→absences) |
|
||||
| `core/views.py::site_report_edit` | `next_action=save_absences` branch + toast text |
|
||||
| `core/tests.py` | ~6 new/updated tests |
|
||||
| `docs/plans/parked-work.md` | Backburner note: future voice feature ≠ "Journal" |
|
||||
| `CLAUDE.md` | Update the SiteReport line + URL-routes note: UI label is "Site Journal", model stays `SiteReport` (another Path-A entry) |
|
||||
|
||||
**No model / migration / URL / dependency changes.** ~120 LOC incl.
|
||||
tests. Pure flow + display change.
|
||||
|
||||
## Out of scope (deliberately)
|
||||
|
||||
- No change to the SiteReport model, schema, or `site_report_schema.py`.
|
||||
- No change to the absences feature itself (just the prefill entry point).
|
||||
- No actual building of the voice-transcript "Voice Notes" feature —
|
||||
still backburnered; this only reserves the name.
|
||||
- No "what next?" interstitial panel (rejected in Q2 — Konrad chose a
|
||||
plain dashboard landing for `log_only`).
|
||||
|
||||
## Verification (manual, local — Konrad)
|
||||
|
||||
1. `/attendance/log/` → submit with **Log Work** → lands on dashboard
|
||||
with toast, NOT the site-journal form.
|
||||
2. Submit with **Log Work + Site Journal** → lands on the Site Journal
|
||||
form (titled "Site Journal").
|
||||
3. Submit with **Log Work + Add Absences** → lands on `/absences/log/`
|
||||
prefilled (unchanged).
|
||||
4. On the Site Journal form, **Save Site Journal** → dashboard + toast.
|
||||
5. **Save Site Journal + Add Absences** → `/absences/log/` prefilled
|
||||
with the same date/team/project.
|
||||
6. Trigger an attendance conflict while having clicked "Log Work +
|
||||
Site Journal" → resolve it → still lands on the Site Journal form.
|
||||
7. Full suite green (`USE_SQLITE=true … manage.py test core.tests`).
|
||||
|
||||
Then — and only then — Konrad decides whether to push to `ai-dev` +
|
||||
deploy.
|
||||
|
||||
## Branch / deploy
|
||||
|
||||
Build on `ai-dev`. **Do NOT push to origin or deploy** until Konrad
|
||||
has run the local verification above and explicitly approves. This is
|
||||
a UX-flow change to a daily-use path — it warrants a hands-on local
|
||||
check before it reaches production.
|
||||
Loading…
x
Reference in New Issue
Block a user