Order of work for the calendar redesign (#187-#198) #199

Closed
opened 2026-08-23 22:54:30 +00:00 by nalum · 2 comments
Owner

The calendar surfaces are being rebuilt from a design handoff
(design_handoff_calendar, held outside the repo). It is a sibling of the jobs
bundle (#184), not a part of it: the two share the row component, the token
contract and the laws, and meet at exactly one contract — a job opened from a
calendar surface is the jobs bundle's card, unmodified.

Blocking first

#187 decides who may see a household event, and nothing else here is safe to
build until it is settled.
ListOccurrences returns every event in the window
to any authenticated caller while Read and every mutation require event
membership. The design stands on both sides of that gap: the grid's "everyone"
default needs the unfiltered list, and the card-as-editor breaks on an event you
are not on. #188 is the server change if the decision goes the recommended way.

Order

Step Issue Why here
0a #187 The visibility decision. Building the views first means building the who-filter twice
0b #188 Relax Read, allow self-add on AddUser — only if #187 goes household-wide
0c #170, #171, #186 Shared from the jobs bundle: token roles, the row component, the minute ticker
1 #189 The row grammar at calendar scale — everything composes from it
2 #190 Month, desktop and phone. The dot rule is the only new visual language here
3 #191 The day sheet
4 #192 Agenda, then week — agenda shakes out the sort rules week relies on
5 #193 The event card as editor, and its pickers
6 #194 Create, reusing those pickers
7 #195 The repeat sheet — the shared editor with multi-weekday and until
8 #196 Scope, the rule-change confirm, cancel and delete
— #197 The toolbar, regrouped by grammar. Independent of the rest
— #198 The occurrence_date proto comment says UTC and is wrong

What the code review settled

The handoff was checked against the server, and these are now written into it:

  • occurrence_date is a local date in the request's zone, not UTC (#198).
    Split and cancel must pass the zone that produced the row the reader tapped.
  • Per-occurrence attendance splits the occurrence permanently. Leave this one and I'm going are SplitOccurrence plus AddUser/RemoveUser on the
    child, so both ask scope on a repeating event (#196).
  • A rule change deletes tombstones too. pruneOrphanedChildren removes every
    child whose slot no longer lands, so "cancelled days stay cancelled" is false
    for exactly the days the dialog is about (#196).
  • Kind is a label and a reminder rule, never a shape. Deadline adds a
    day-ahead nudge, Birthday broadcasts to everyone with an email (#193).
  • A Child has every event verb. Unlike jobs there is no child-restricted
    shape; membership is the only subtraction, which is why #187 matters so much.
  • The nth-weekday kind reads only the first weekday and validation accepts
    extras silently, so the strip collapses to single-select for that kind (#195).

Not in scope

The job/event link (Event.item), a Done action on an event, a second repeat
editor, a merged row for a daily_times span, a type chip on the event card,
and an Edit button anywhere.


Update — goldens ride with the PR that breaks them

Re-recording 196 golden images in one late PR would mean one author re-recording
work they did not do, and reviewing a diff nobody can read. The repo rule already
says a visible change re-records its goldens; this plan applies it per PR.

Every UI PR in this plan re-records the goldens it invalidates, in its own
commit inside that PR
(make web-screens-update, make android-screens-update,
and make web-pages-update where a whole-page shot moves). Both surfaces move
together or the other one's suite fails.

Update — the visibility decision, and the model per issue

#187 is closed. Everyone can read the calendar and the events on it, and
joining an event is open to everyone except a Guest. That settles option A:

  • Read's RequireEventMember drops to the role gate, Guest included, so the
    grid and the record agree about who may see an event.
  • AddUser gains self-add for the roles the matrix already admits — Admin,
    Member and Child — which is exactly not-Guest.
  • #188 is confirmed, stays in Wave 0, and still needs its ADR.
  • §0.1's read-only non-member sheet still applies: reading is open, editing
    is not. An event you are not on shows every field with no chevrons, no ...,
    and one I'm going action — which on a repeating event asks scope, because
    joining one occurrence splits it.

Order and the model each ticket runs on:

Issues Model
#196 fable — split permanence, tombstone pruning, zone-dependent slot keys
#189, #190, #193 opus
#191, #192, #194, #195, #197 sonnet

Wave 0's #188 comes first regardless, since every view depends on what the grid
is allowed to return.


Standing requirements for every remaining UI ticket

Added after #213 needed three deploys to come right. Both are now written into
each open ticket:

  1. Screenshot goldens are a deliverable, not a follow-up. New cases ride in
    the same commit as the code; re-recorded existing ones go in their own.
  2. A form-styling sweep before hand-off — raw inputs outside the row and
    picker components, number spinners, links where rows belong, (optional)
    labels, any Save button on a commit-on-blur surface, disabled-and-grey
    controls, and a record's title used as a field label.

The second one exists because all seven of #213's defects had the same cause:
the shipped app's form fragments reused inside the new grammar. Suites stay
green through every one of them, because they assert the pieces exist rather
than that the screen reads right.


Update — the demo joins the bundle; the server work is in flight (2026-08-25)

calendar-demo.dc.html is now part of the handoff and is the locked design, visual and behavioural, for everything in §2, §5 and §6 — the explorations file stays the reasoning, the demo is what the app must meet, mapped through the generated token roles (it asks for no new ones). Create (§4) is the one section it does not build; every picker create needs is wired and reachable from the event sheet.

Where the pre-work stands:

Issue State
#187 Closed — household-wide
#198 Fixed in PR #203 (open)
#188 Delivered in PR #205 (open) — ADR-0036, the attendance/authority split
#201 Delivered in PR #209 (open) — option B: JoinOccurrence/LeaveOccurrence, ATTENDEE refs
#257 New — the handoff's editability model vs ADR-0036's authority split (ownerless events are household-owned; does joining make you an editor). Blocks #193; decide before Wave 5

§9's open questions are all closed: unowned jobs appear when dated (dashed ? mark, sorts last); the calendar creates events only (every + reads New event); the Remind picker names its audience only on Birthday.

Supersessions the demo makes against the earlier text — recorded in the per-view issues: month week-rows are uniform height (sized to the busiest day in the month) rather than per-row; a wide row keeps the owner's glyph when done (its tick box says done); the completion stamp draws over the row that was ticked, at every site.


Update — #257 ruled; handoff being revised (2026-08-25)

#257's two questions are decided: ownerless events stay admin-only editable, with the state prevented instead — the last attendee cannot leave, leaving means deleting; and joining does not grant edit rights (the ADR-0036 split stands, §0.1's editor-after-joining sentence moves). The design project is updating the handoff and demo to match; the bundle gets revalidated before the client waves start. #257 keeps the remaining server work (the last-attendee guard, the ADR amendment, integration coverage).


Update — bundle revalidated and locked (2026-08-26)

The revised handoff passed revalidation over four passes: both #257 rulings applied (§0.1 struck in place, §0.2 rewritten, OWNER-ref gating and the last-attendee guard wired through the demo, refs travel on leave and on split children, JoinOccurrence/LeaveOccurrence named throughout, month-height supersession recorded). The final fix made the leave guard per-list — the occurrence's own attendees, never the parent's as well — matching RemoveUser-guards-parent / LeaveOccurrence-guards-occurrence. No open findings. The client waves (#189-#197) and #257's server work can start against this bundle.

The calendar surfaces are being rebuilt from a design handoff (`design_handoff_calendar`, held outside the repo). It is a sibling of the jobs bundle (#184), not a part of it: the two share the row component, the token contract and the laws, and meet at exactly one contract — a job opened from a calendar surface is the jobs bundle's card, unmodified. ## Blocking first **#187 decides who may see a household event, and nothing else here is safe to build until it is settled.** `ListOccurrences` returns every event in the window to any authenticated caller while `Read` and every mutation require event membership. The design stands on both sides of that gap: the grid's "everyone" default needs the unfiltered list, and the card-as-editor breaks on an event you are not on. #188 is the server change if the decision goes the recommended way. ## Order | Step | Issue | Why here | |---|---|---| | 0a | #187 | The visibility decision. Building the views first means building the who-filter twice | | 0b | #188 | Relax `Read`, allow self-add on `AddUser` — only if #187 goes household-wide | | 0c | #170, #171, #186 | Shared from the jobs bundle: token roles, the row component, the minute ticker | | 1 | #189 | The row grammar at calendar scale — everything composes from it | | 2 | #190 | Month, desktop and phone. The dot rule is the only new visual language here | | 3 | #191 | The day sheet | | 4 | #192 | Agenda, then week — agenda shakes out the sort rules week relies on | | 5 | #193 | The event card as editor, and its pickers | | 6 | #194 | Create, reusing those pickers | | 7 | #195 | The repeat sheet — the shared editor with multi-weekday and `until` | | 8 | #196 | Scope, the rule-change confirm, cancel and delete | | — | #197 | The toolbar, regrouped by grammar. Independent of the rest | | — | #198 | The `occurrence_date` proto comment says UTC and is wrong | ## What the code review settled The handoff was checked against the server, and these are now written into it: - **`occurrence_date` is a local date in the request's zone**, not UTC (#198). Split and cancel must pass the zone that produced the row the reader tapped. - **Per-occurrence attendance splits the occurrence permanently.** `Leave this one` and `I'm going` are `SplitOccurrence` plus `AddUser`/`RemoveUser` on the child, so both ask scope on a repeating event (#196). - **A rule change deletes tombstones too.** `pruneOrphanedChildren` removes every child whose slot no longer lands, so "cancelled days stay cancelled" is false for exactly the days the dialog is about (#196). - **Kind is a label and a reminder rule, never a shape.** Deadline adds a day-ahead nudge, Birthday broadcasts to everyone with an email (#193). - **A Child has every event verb.** Unlike jobs there is no child-restricted shape; membership is the only subtraction, which is why #187 matters so much. - **The nth-weekday kind reads only the first weekday** and validation accepts extras silently, so the strip collapses to single-select for that kind (#195). ## Not in scope The job/event link (`Event.item`), a `Done` action on an event, a second repeat editor, a merged row for a `daily_times` span, a type chip on the event card, and an Edit button anywhere. --- ## Update — goldens ride with the PR that breaks them Re-recording 196 golden images in one late PR would mean one author re-recording work they did not do, and reviewing a diff nobody can read. The repo rule already says a visible change re-records its goldens; this plan applies it per PR. **Every UI PR in this plan re-records the goldens it invalidates, in its own commit inside that PR** (`make web-screens-update`, `make android-screens-update`, and `make web-pages-update` where a whole-page shot moves). Both surfaces move together or the other one's suite fails. ## Update — the visibility decision, and the model per issue **#187 is closed.** Everyone can read the calendar and the events on it, and joining an event is open to everyone except a Guest. That settles option A: - `Read`'s `RequireEventMember` drops to the role gate, Guest included, so the grid and the record agree about who may see an event. - `AddUser` gains self-add for the roles the matrix already admits — Admin, Member and Child — which is exactly not-Guest. - **#188 is confirmed**, stays in Wave 0, and still needs its ADR. - **§0.1's read-only non-member sheet still applies**: reading is open, editing is not. An event you are not on shows every field with no chevrons, no `...`, and one `I'm going` action — which on a repeating event asks scope, because joining one occurrence splits it. Order and the model each ticket runs on: | Issues | Model | |---|---| | #196 | **fable** — split permanence, tombstone pruning, zone-dependent slot keys | | #189, #190, #193 | opus | | #191, #192, #194, #195, #197 | sonnet | Wave 0's #188 comes first regardless, since every view depends on what the grid is allowed to return. --- ## Standing requirements for every remaining UI ticket Added after #213 needed three deploys to come right. Both are now written into each open ticket: 1. **Screenshot goldens are a deliverable**, not a follow-up. New cases ride in the same commit as the code; re-recorded existing ones go in their own. 2. **A form-styling sweep before hand-off** — raw inputs outside the row and picker components, number spinners, links where rows belong, `(optional)` labels, any Save button on a commit-on-blur surface, disabled-and-grey controls, and a record's title used as a field label. The second one exists because all seven of #213's defects had the same cause: the shipped app's form fragments reused inside the new grammar. Suites stay green through every one of them, because they assert the pieces exist rather than that the screen reads right. --- ## Update — the demo joins the bundle; the server work is in flight (2026-08-25) `calendar-demo.dc.html` is now part of the handoff and is the **locked design, visual and behavioural**, for everything in §2, §5 and §6 — the explorations file stays the reasoning, the demo is what the app must meet, mapped through the generated token roles (it asks for no new ones). Create (§4) is the one section it does not build; every picker create needs is wired and reachable from the event sheet. Where the pre-work stands: | Issue | State | |---|---| | #187 | Closed — household-wide | | #198 | Fixed in PR #203 (open) | | #188 | Delivered in PR #205 (open) — ADR-0036, the attendance/authority split | | #201 | Delivered in PR #209 (open) — option B: `JoinOccurrence`/`LeaveOccurrence`, ATTENDEE refs | | #257 | **New** — the handoff's editability model vs ADR-0036's authority split (ownerless events are household-owned; does joining make you an editor). Blocks #193; decide before Wave 5 | §9's open questions are all closed: unowned jobs appear when dated (dashed `?` mark, sorts last); the calendar creates **events only** (every + reads *New event*); the Remind picker names its audience only on Birthday. Supersessions the demo makes against the earlier text — recorded in the per-view issues: month week-rows are **uniform height** (sized to the busiest day in the month) rather than per-row; a wide row keeps the owner's glyph when done (its tick box says done); the completion stamp draws **over the row that was ticked**, at every site. --- ## Update — #257 ruled; handoff being revised (2026-08-25) #257's two questions are decided: ownerless events stay admin-only editable, with the state prevented instead — the last attendee cannot leave, leaving means deleting; and joining does not grant edit rights (the ADR-0036 split stands, §0.1's editor-after-joining sentence moves). The design project is updating the handoff and demo to match; **the bundle gets revalidated before the client waves start.** #257 keeps the remaining server work (the last-attendee guard, the ADR amendment, integration coverage). --- ## Update — bundle revalidated and locked (2026-08-26) The revised handoff passed revalidation over four passes: both #257 rulings applied (§0.1 struck in place, §0.2 rewritten, OWNER-ref gating and the last-attendee guard wired through the demo, refs travel on leave and on split children, `JoinOccurrence`/`LeaveOccurrence` named throughout, month-height supersession recorded). The final fix made the leave guard per-list — the occurrence's own attendees, never the parent's as well — matching RemoveUser-guards-parent / LeaveOccurrence-guards-occurrence. No open findings. The client waves (#189-#197) and #257's server work can start against this bundle.
Author
Owner

The whole order of work is built and up as one stacked chain (nothing merged — merge bottom-first, each fast-forward):

PR branch delivers
#258 feat/last-attendee-guard #257 — the server guard + ADR-0036 amendment
#259 feat/calendar-row-grammar #189 — the §2.1 row grammar, conformance + goldens
#260 feat/calendar-month #190 — hairline month + phone dots
#261 feat/calendar-day-sheet #191 — the §2.7 day sheet
#262 feat/calendar-agenda-week #192 — agenda rewrite + the new week view (synced WEEK pref)
#263 feat/calendar-event-editor #193, #196 — the card is the editor; scope, confirm, delete
#264 feat/calendar-create #194 — the two-screen create
#265 feat/calendar-toolbar #197 — the toolbar regrouped by grammar

#195 (the repeat sheet) rides inside #263/#264. The base of #258 is docs/park-handoff-bundles (yesterday's demo-stylesheet stack, pushed without a PR deliberately).

Known deferrals, each recorded on its issue: the demo's repeat-label rewording (conformance ripple — a #256-style sweep), the Android rule-change confirm counts generically, and a later golden pass may pose the day sheet's own screenshot cases.

The whole order of work is built and up as one stacked chain (nothing merged — merge bottom-first, each fast-forward): | PR | branch | delivers | |---|---|---| | #258 | `feat/last-attendee-guard` | #257 — the server guard + ADR-0036 amendment | | #259 | `feat/calendar-row-grammar` | #189 — the §2.1 row grammar, conformance + goldens | | #260 | `feat/calendar-month` | #190 — hairline month + phone dots | | #261 | `feat/calendar-day-sheet` | #191 — the §2.7 day sheet | | #262 | `feat/calendar-agenda-week` | #192 — agenda rewrite + the new week view (synced `WEEK` pref) | | #263 | `feat/calendar-event-editor` | #193, #196 — the card is the editor; scope, confirm, delete | | #264 | `feat/calendar-create` | #194 — the two-screen create | | #265 | `feat/calendar-toolbar` | #197 — the toolbar regrouped by grammar | #195 (the repeat sheet) rides inside #263/#264. The base of #258 is `docs/park-handoff-bundles` (yesterday's demo-stylesheet stack, pushed without a PR deliberately). Known deferrals, each recorded on its issue: the demo's repeat-label rewording (conformance ripple — a #256-style sweep), the Android rule-change confirm counts generically, and a later golden pass may pose the day sheet's own screenshot cases.
Author
Owner

The whole order of work is merged: chain #258–#268 (plus #269 for the parked handoff bundles) landed on main 2026-08-26, tip 68f5b3c1. Open remainders have their own issues: #266 (Android confirm counts), #267 (golden coverage), #255/#256 (shape roles, curly quotes).

The whole order of work is merged: chain #258–#268 (plus #269 for the parked handoff bundles) landed on main 2026-08-26, tip 68f5b3c1. Open remainders have their own issues: #266 (Android confirm counts), #267 (golden coverage), #255/#256 (shape roles, curly quotes).
nalum closed this issue 2026-08-26 18:48:03 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app#199
No description provided.