From 110545b11e3096bf9229f2499b57d3afb20859a0 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Fri, 15 May 2026 13:02:18 +0200 Subject: [PATCH] docs: post-attendance flow v2 design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- ...26-05-15-post-attendance-flow-v2-design.md | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docs/plans/2026-05-15-post-attendance-flow-v2-design.md diff --git a/docs/plans/2026-05-15-post-attendance-flow-v2-design.md b/docs/plans/2026-05-15-post-attendance-flow-v2-design.md new file mode 100644 index 0000000..abe2139 --- /dev/null +++ b/docs/plans/2026-05-15-post-attendance-flow-v2-design.md @@ -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` — `

` "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 + ``. +- `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.