Calendar: scope, the rule-change confirm, cancelling and deleting #196

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

Part of the calendar redesign. The design handoff (design_handoff_calendar, held outside the repo) is the source; the facts it depends on are repeated here so this issue stands alone.

Build last: these are the parts that need the occurrence model exercised.

Scope is asked once, after the change, naming it, and only on a repeating event:

Move just this Thursday, or every Tuesday and Thursday?
16:00 -> 17:30
[ Just Thu 14 Aug ] It stops following the series after this
[ All of them ] Past ones stay as they were

Just this one is SplitOccurrence plus an update on the child row; all of them is an update on the parent. It never appears on a non-repeating event or on an already-split occurrence. The jobs handoff bans this dialog outright — events derive occurrences and can materialise one, so both answers genuinely exist here. A real divergence, recorded in both documents.

Attendance is not exempt. Per-occurrence join and leave are SplitOccurrence followed by AddUser/RemoveUser on the child (Calendar.tsx:372,411,495, MoreRepositories.kt:249-264), so Leave this one — and I'm going — permanently detach that occurrence, and a later rule change can prune the child and take the decision with it. Both ask scope on a repeating event, and neither does on a plain one.

The rule-change confirm is the only confirm the edit flow adds, and only when there is something to lose. pruneOrphanedChildren (internal/services/event/update.go:180-211) deletes every child row whose slot no longer lands under the new rule — hand-edited occurrences and tombstones. So the dialog counts and names what it takes, and it must not promise that cancelled days stay cancelled without qualification: moving Tuesdays and Thursdays to Mondays and Wednesdays un-cancels the Thursdays that were cancelled. A series with no hand-edited occurrences changes silently, with the ordinary undo snackbar.

Pruning runs in the editing device's zone, so which occurrences a rule change destroys depends on who edits from where. That is the strongest argument for counting and naming them.

Stopping the series is not this: clear_repeat leaves the parent as a plain event and is mutually exclusive with a rule change, so it is a separate ... entry, Stop it repeating.

Cancel this one is CancelOccurrence; Delete the series is Delete on the parent, behind the ordinary confirm, in the destructive tone, the only coloured item in .... Both live in the menu. On the shipped card they are full-width buttons under a birthday, one mis-click from a recurring event.

Every occurrence action passes the slot key and the zone that produced the row the reader tapped. Never recompute the key at action time: the key is a local date in the request's zone, so a mismatch addresses a different day.


Two requirements added after #213 shipped

The job sheet went out to the family's cluster three times before it was right.
Seven visual defects, none caught by a suite: pickers that unfolded forms
instead of opening, three Save buttons on a commit-on-blur screen, (optional)
labels, a reward's name used as a field label, an unstyled add row, checkboxes
with a ghosted tick, and a create form with a browser spinner. Every one had to
be found by a person looking at a screen.

1. Screenshot goldens are part of this ticket

Record gallery (Playwright) and Roborazzi cases for every surface this ticket
builds or visibly changes, and treat them as a deliverable rather than a
follow-up. Three of #213's defects — the unstyled add row, the ghosted
checkboxes, the spinner in the create form — were pixel faults that a golden
would have caught before a deploy did.

#172 deferred its goldens on the reasoning that later tickets would re-record
them. That reasoning was sound then and is not now: the row grammar and the
pickers are settled, so an image recorded here stays valid.

Mechanics, from AGENTS.md: a new case rides in the same commit as the code
that adds it — a commit that adds a gallery or Roborazzi case without its image
is red on its own, because check / web runs npm run test:screens and
android / build runs verifyRoborazziDebug. A re-recorded existing case
goes in its own commit, so the reviewable diff stays code.

2. Check for leaked form styling before you finish

Every visual defect on #213 came from one place: the shipped app's form
fragments reused inside the new row grammar. They pass every test, because the
tests assert the pieces exist rather than that the screen reads right.

Before you call this done, grep the surfaces you touched and report what you
found and what you did about each:

  • a raw <input> / OutlinedTextField / BasicTextField outside the row and
    picker components — a form field dropped into a list of rows
  • type="number", which renders browser spinner arrows
  • btn, link-btn, btn-quiet or TextButton where a row or a quiet row
    verb belongs — underlined links reading as broken text
  • (optional) in any label — §8 names that pattern as the form admitting it
    should not have asked
  • any Save button on a surface that commits on blur, including Save changes and Save the points
  • a disabled-and-grey control where law 5 wants it absent
  • a record's title used as a field row's label — that column is for a
    field's name (Who, When, Worth), and a long title wraps in it

If your ticket is the first to land after this was written, add the check to
AGENTS.md beside the goldens rule, so it stops being a thing I remember to say.


Update — the demo locks the copy, and the join path moved (2026-08-25)

Per-occurrence join and leave are no longer Split+AddUser from the client: PR #209 adds JoinOccurrence/LeaveOccurrence, which resolve the slot, split and edit one ref in a single transaction, and record an ATTENDEE ref. The scope dialogs stay exactly as described; only the RPC underneath changes. (Whether an attendee may then edit is #257.)

Locked wording from calendar-demo.dc.html:

  • Scope: Change just this Thursday, or every Tuesday and Thursday? (Join …/Leave … for attendance), the change stated in a panel above, options Just Thu 14 Aug · It stops following the series after this and All of them · Past ones stay as they were (join: You come onto the whole series; leave: You come off the whole series).
  • Rule change: counts splits and tombstones separately — 2 occurrences you'd changed by hand — 20 Aug and 3 Sep — don't exist under the new rule, so they go. One cancelled day stops existing too, and comes back if the day ever returns. Note: Then: <rule>, starting <date>. Cancelled days the new rule still covers stay cancelled. Buttons Leave it · Change it.
  • Delete: Delete "Swim class" and every date it runs? (curly quotes), the body names the rule, note 2 hand-edited occurrences go too. No undo., buttons Keep it · Delete in the destructive tone.
  • ⋯: Cancel this one <date> and Stop it repeating (both only on a repeating event), then Delete the series / Delete it — delete the only coloured entry.
Part of the calendar redesign. The design handoff (`design_handoff_calendar`, held outside the repo) is the source; the facts it depends on are repeated here so this issue stands alone. Build last: these are the parts that need the occurrence model exercised. **Scope** is asked once, *after* the change, naming it, and only on a repeating event: > **Move just this Thursday, or every Tuesday and Thursday?** > 16:00 -> 17:30 > **[ Just Thu 14 Aug ]** It stops following the series after this > **[ All of them ]** Past ones stay as they were *Just this one* is `SplitOccurrence` plus an update on the child row; *all of them* is an update on the parent. It never appears on a non-repeating event or on an already-split occurrence. The jobs handoff bans this dialog outright — events derive occurrences and can materialise one, so both answers genuinely exist here. A real divergence, recorded in both documents. **Attendance is not exempt.** Per-occurrence join and leave are `SplitOccurrence` followed by `AddUser`/`RemoveUser` on the child (`Calendar.tsx:372,411,495`, `MoreRepositories.kt:249-264`), so `Leave this one` — and `I'm going` — permanently detach that occurrence, and a later rule change can prune the child and take the decision with it. Both ask scope on a repeating event, and neither does on a plain one. **The rule-change confirm** is the only confirm the edit flow adds, and only when there is something to lose. `pruneOrphanedChildren` (`internal/services/event/update.go:180-211`) deletes every child row whose slot no longer lands under the new rule — hand-edited occurrences **and tombstones**. So the dialog counts and names what it takes, and it must not promise that cancelled days stay cancelled without qualification: moving Tuesdays and Thursdays to Mondays and Wednesdays un-cancels the Thursdays that were cancelled. A series with no hand-edited occurrences changes silently, with the ordinary undo snackbar. Pruning runs in the **editing device's zone**, so which occurrences a rule change destroys depends on who edits from where. That is the strongest argument for counting and naming them. **Stopping the series is not this:** `clear_repeat` leaves the parent as a plain event and is mutually exclusive with a rule change, so it is a separate `...` entry, *Stop it repeating*. **Cancel this one** is `CancelOccurrence`; **Delete the series** is `Delete` on the parent, behind the ordinary confirm, in the destructive tone, the only coloured item in `...`. Both live in the menu. On the shipped card they are full-width buttons under a birthday, one mis-click from a recurring event. Every occurrence action passes the slot key **and the zone that produced the row the reader tapped**. Never recompute the key at action time: the key is a local date in the request's zone, so a mismatch addresses a different day. --- ## Two requirements added after #213 shipped The job sheet went out to the family's cluster three times before it was right. Seven visual defects, none caught by a suite: pickers that unfolded forms instead of opening, three Save buttons on a commit-on-blur screen, `(optional)` labels, a reward's name used as a field label, an unstyled add row, checkboxes with a ghosted tick, and a create form with a browser spinner. Every one had to be found by a person looking at a screen. ### 1. Screenshot goldens are part of this ticket Record gallery (Playwright) and Roborazzi cases for every surface this ticket builds or visibly changes, and treat them as a deliverable rather than a follow-up. Three of #213's defects — the unstyled add row, the ghosted checkboxes, the spinner in the create form — were pixel faults that a golden would have caught before a deploy did. `#172` deferred its goldens on the reasoning that later tickets would re-record them. That reasoning was sound then and is not now: the row grammar and the pickers are settled, so an image recorded here stays valid. Mechanics, from AGENTS.md: a **new** case rides in the same commit as the code that adds it — a commit that adds a gallery or Roborazzi case without its image is red on its own, because `check / web` runs `npm run test:screens` and `android / build` runs `verifyRoborazziDebug`. A **re-recorded existing** case goes in its own commit, so the reviewable diff stays code. ### 2. Check for leaked form styling before you finish Every visual defect on #213 came from one place: the shipped app's form fragments reused inside the new row grammar. They pass every test, because the tests assert the pieces exist rather than that the screen reads right. Before you call this done, grep the surfaces you touched and report what you found and what you did about each: - a raw `<input>` / `OutlinedTextField` / `BasicTextField` outside the row and picker components — a form field dropped into a list of rows - `type="number"`, which renders browser spinner arrows - `btn`, `link-btn`, `btn-quiet` or `TextButton` where a row or a quiet row verb belongs — underlined links reading as broken text - `(optional)` in any label — §8 names that pattern as the form admitting it should not have asked - **any Save button on a surface that commits on blur**, including `Save changes` and `Save the points` - a disabled-and-grey control where law 5 wants it absent - a record's title used as a field row's `label` — that column is for a field's name (`Who`, `When`, `Worth`), and a long title wraps in it If your ticket is the first to land after this was written, add the check to AGENTS.md beside the goldens rule, so it stops being a thing I remember to say. --- ## Update — the demo locks the copy, and the join path moved (2026-08-25) Per-occurrence join and leave are no longer Split+AddUser from the client: PR #209 adds `JoinOccurrence`/`LeaveOccurrence`, which resolve the slot, split and edit one ref in a single transaction, and record an **ATTENDEE** ref. The scope dialogs stay exactly as described; only the RPC underneath changes. (Whether an attendee may then edit is #257.) Locked wording from `calendar-demo.dc.html`: - **Scope:** `Change just this Thursday, or every Tuesday and Thursday?` (Join …/Leave … for attendance), the change stated in a panel above, options `Just Thu 14 Aug · It stops following the series after this` and `All of them · Past ones stay as they were` (join: `You come onto the whole series`; leave: `You come off the whole series`). - **Rule change:** counts splits and tombstones separately — `2 occurrences you'd changed by hand — 20 Aug and 3 Sep — don't exist under the new rule, so they go. One cancelled day stops existing too, and comes back if the day ever returns.` Note: `Then: <rule>, starting <date>. Cancelled days the new rule still covers stay cancelled.` Buttons `Leave it` · `Change it`. - **Delete:** `Delete "Swim class" and every date it runs?` (curly quotes), the body names the rule, note `2 hand-edited occurrences go too. No undo.`, buttons `Keep it` · `Delete` in the destructive tone. - **`⋯`:** `Cancel this one <date>` and `Stop it repeating` (both only on a repeating event), then `Delete the series` / `Delete it` — delete the only coloured entry.
Author
Owner

Delivered inside PR #263: the scope dialog after a change on derived slots, the rule-change confirm (web counts splits and tombstones via listEvents; Android warns generically — recorded divergence in the commit), delete asks and names what survives, curly-quote titles. Cancelling: the create sheet has no Cancel by design (§4).

Delivered inside PR #263: the scope dialog after a change on derived slots, the rule-change confirm (web counts splits and tombstones via listEvents; Android warns generically — recorded divergence in the commit), delete asks and names what survives, curly-quote titles. Cancelling: the create sheet has no Cancel by design (§4).
Author
Owner

Delivered inside PR #263 (scope dialog, rule-change confirm, delete names what survives), merged to main 2026-08-26. The Android confirm-count divergence stays tracked in #266.

Delivered inside PR #263 (scope dialog, rule-change confirm, delete names what survives), merged to main 2026-08-26. The Android confirm-count divergence stays tracked in #266.
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#196
No description provided.