# 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.