feat(desktop,web): each section says who last changed it, and what changed since you last looked is tinted
PR 5 of plan history (docs/proposals/plan-history-and-editing.md, C and D).
What
- Who last changed each section. Hover over or focus a section, and a quiet line at the end of its heading row reads "Edited by Luke during your call · 2h ago", "Edited by you · 5m ago", or "Edited by Command K board for plans · 1d ago". The time ticks every minute. Rules share one line, on the Rules heading. Assumptions have their own line. A section that hasn't been saved since the service started tracking writers shows nothing.
- Changes since you last looked. When you open a plan, every section someone other than you changed since your last look on this Mac gets a subtle tint in Luke's accent: no rail, and nothing that moves. A line under the header reads "Changed since you last looked · 3 sections · Dismiss". The tint stays until you press Dismiss or leave the plan.
- When it counts as looked at: the plan has been open for 3 s. After that, every save that lands while the plan stays open counts as seen. So Luke typing into the plan during a call never gets highlighted, on this visit or the next.
- What is stored: the newest save's timestamp per plan, keyed by plan id in renderer storage (
luke.planLooks). It is the service's timestamp, so the Mac's clock never matters. Nothing from the plan's text is stored. A plan you've never opened on this Mac shows no highlights. - What counts as changed: your own saves never do, on any Mac.
Data source, and why it extends storage slightly
The per-section info comes from plan.field_writers (PR 3), not from diffing revision snapshots. Diffing snapshots would mean reading the whole history every time a plan is opened. Each field-writer entry now also stores agentId and duringCall: two optional keys in the existing jsonb, so no migration. Without them a section could say "Luke" but not "during your call", and could not name the agent. Entries written before this read as "a coding agent" or as not on a call. The plan sends fieldEdits (optional on the wire). Agent labels come from the agent rows, the same way lastEdit does it.
To let planSchema name PLAN_FIELD, I moved it into plan-wire.ts, next to planFieldsSchema. Its values didn't change, and neither did any model-facing golden. Only wire goldens moved: planSchema, planAnswerSchema, planEditAnswerSchema, planDraftFrameSchema, and the new planFieldEditSchema.
On a call, the saved draft frame carries the plan's fieldEdits as the locked save named them (planDraftFrameSchema.fieldEdits, optional), and showDraft uses them, so section lines stay current without the window guessing who wrote what.
The look lives in the plan's region (desktop-plans.tsx), not in PlanBody, so time spent on a version from the History tab still counts.
Choices that differ from the brief:
- No jsdiff. Field writers already say which sections changed, so this compares no text, fetches no snapshot, and adds no bundle weight.
- No jump to a revision. Clicking a section line doesn't open History at that revision. Finding the revision would mean reading the history list on every hover.
ph-no-capture stays on the editor. The section line and the changed line are plain text, masked like any other. The section line fades in with --duration-hover, which reduced motion sets to zero, and the tint never animates.
Tests
apps/web/tests/plan-revisions.test.ts(PGlite): each field reports the save that last changed it. Covers Luke on a call, an agent by its label, and you.apps/web/tests/plan-scribe.test.ts: the saved draft carries the plan's field edits, with Luke on the call for the field it wrote.packages/host/src/compose-planning.test.ts: the open plan takes a saved draft's field edits.apps/desktop/src/renderer/planning/plan-looks.test.ts(jsdom,TestClock), all throughPlanBody:- section lines per field;
- tints and the count for fields changed after the stored look, but not your own;
- Dismiss clears them, and the next visit doesn't show them;
- holding the plan open for the look clears them on the next visit, and leaving early keeps them;
- time on a version view counts toward the look;
- Luke's live saves while the plan is open don't count;
- a plan never looked at shows nothing.
- I checked that they fail when the change is reverted.
Screenshots (headless Chrome, real PlanBody and real stylesheet, pointer rest simulated)
Light, with the pointer on Problem:

Dark, with the pointer on Rules:

CI could not verify the Mac UI, and ./scripts/verify.sh was not run. This was built on a Linux VM. LUKE-159 is the manual Mac pass. The screenshots come from headless Chrome, not Electron.
🤖 Generated with Claude Code