feat(jobs): the job options sheet and the repeat sheet #216
No reviewers
Labels
No labels
adr
android
area/calendar
area/design-system
area/i18n
area/jobs
area/offline
area/server
area/testing
bug
ci
duplicate
enhancement
help wanted
invalid
notifications
question
reliability
security
severity/low
severity/medium
tracking
web
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
eagraiclainne/app!216
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/job-options-repeat"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #175. Removes the last stand-in from the job sheet.
⋯opened an inline block holding the shipped repeat editor, a convertchooser and a hand-rolled delete confirm — the final form fragments on a
screen otherwise built from rows. It is now §6's sheet: three entries,
each something the rows cannot do, with the repeat rule's current value
shown inline because that is the entry people open to read.
Make it part of another jobis conditional, not merely consequential.SetParentrefuses a job carrying a repeat rule, a deadline, listmembership or children. So the entry pre-clears what it can and says so,
and is absent when the job has sub-jobs of its own, or when it repeats and
the reader may not clear the rule — a child may
SetParentbut notSetRepeat. The pre-clear travels as one queue entry through thegrouped-write primitive built for it earlier in this stack: three writes
that flush together or not at all, each carrying the value that undoes it,
captured at enqueue. A refusal restores the date and the rule and says so.
§6.1's five chips are rendered by calling the label helper on the rule
each chip would set, so chip, summary and fixture are one set of words by
construction rather than by discipline.
conformance/repeat.jsonmoveswith them, both surfaces.
The readback states one date, not four — nothing is scheduled, so a
four-date preview would be predicting the household rather than reading
the data — and it names the checklist coming back, because completing a
repeating job clones its sub-jobs. Monthly rules carry the line about the
31st landing on the 30th or the 28th.
The date maths is pinned three ways. The successor rule had to be
ported to TypeScript and Kotlin for the readback, so the same fixture
cases are now asserted by vitest, by Robolectric, and by a new Go test
against
internal/domain, which is the code that actually mintssuccessors.
§6.2's delete counts what goes, in the question and on the button, names
the ticked work, and promises the reward returns to the rewards list —
true since the unlink landed at the bottom of this stack. §6.3 asks before
a tick mints a successor.
One widening, cited as the new rule in AGENTS.md requires: the interim
block barred moving a job that had a linked reward.
setparent.gohas nosuch guard, and §11.B restored points on a sub-job, so the bar is dropped.
Parity gap recorded: Android has no grouped-write primitive, so its
pre-clear sends three writes with the same capture-and-restore and, offline,
simply fails on the first — the handoff's own fallback.
🤖 Generated with Claude Code
The interim `⋯` block was the last of the job sheet: an inline expander carrying the shipped RepeatEditor, a convert-to-step chooser and a delete confirm, all in `.field`/`choice-row` form fragments on a screen built from rows. §6 replaces it with a bottom sheet, because a menu opened from a card has to say WHICH job is about to be deleted — its header carries the title and a one-line summary (`Me Fox · Due Tue, Sep 8 · Every Tuesday`), assembled from the sheet's own helpers so the two cannot paraphrase each other. Three entries, each 54px, each something the sheet's rows cannot do: the repeat rule with its value inline (this is the entry people open to READ), `Make it part of another job`, then a separator and `Delete` — last, and the only coloured item. `Make it part of another job` is conditional, not consequential. SetParent refuses a candidate carrying a repeat rule, a deadline, list membership or children, so the entry pre-clears what it can and says so first, and is ABSENT (never disabled) on a job with sub-jobs and on a repeating job whose reader may not clear the rule — SetParent is Child-allowed and SetRepeat is not, so that reader would otherwise get an entry that cannot finish. The pre-clear rides the queue's grouped-write primitive on web: one entry, each step carrying the write that undoes it, built from the value read at enqueue. Android has no group primitive yet, so its three writes go straight to the API with the same compensation and are simply unavailable offline — the handoff's own alternative, and a recorded parity gap that a grouped OpQueue entry closes. §6.1's chips are rendered by calling jobRepeatLabel on the rule each of them would set, so chip, summary and `conformance/repeat.json` are one set of words by construction rather than by discipline. Three of them change: `Every fortnight, Tuesday` (an exact ICU `=2`, not the `two` plural category — English has no `two` category and the chip would have read the old generic form), `On the 8th, monthly` and `2nd Tuesday of the month`, because `Every 2nd Tuesday` reads as either of the last two. The ordinal comes from selectOrdinal, never an English suffix table. Chips, both helpers and the fixture move together. The readback states ONE date, from a port of internal/domain.NextOccurrence against max(deadline, now) — the argument CompleteItem passes. Both ports and the Go original are pinned to the same `next` cases in the fixture, so a drift is a lie about what the tick will do on three surfaces at once rather than a silent promise of a Tuesday the server will not give. The short-month line appears only past the 28th, where the surprise actually exists. No weekdays strip, no yearly rule, no end date on a job — expressed as a `kinds` filter the job caller passes to the shared editor, not as a capability removed from it: events legitimately keep all three. Widening cited, per the new AGENTS.md rule this change also writes down: the interim block barred the move on any job with a linked reward. setparent.go has no reward guard and §11.B restored points on a sub-job, so the bar is dropped. Form-fragment sweep, files touched: JobSheet.tsx the whole interim block deleted — `.field`, `choice-row`, the inline RepeatEditor and two hand-rolled confirms. FIXED. JobSheet.tsx quick-add `<form>` + `<input>`, inert read checkbox: pre-existing, verdicted with §5 — an add-and-submit is one field, drawn in the row silhouette. LEFT. JobOptionsSheet.tsx clean — every entry and both dialogs' answers are rows; no form-actions pair. JobRepeatSheet.tsx clean; its footer is the one commit button in the flow and it states the RULE, not "Save". RepeatEditor.tsx `type="number"` ×2 — the spinner is chrome nothing else on these screens has. FIXED (inputMode="numeric"). RepeatEditor.tsx `.field` + `Until (optional)` — render only under showUntil, which is the EVENT form. A form's field is right on a form; the job sheet passes false. LEFT, and the boxed interval is unboxed by a `.job-repeat` scope so it reads as a value in a sentence here without restyling the event form. JobSheet.kt interim options block deleted. FIXED. JobSheet.kt BasicTextField/TextButton in the quick-add: pre-existing, same verdict as web. LEFT. JobOptions.kt clean. RepeatEditor.kt OutlinedTextField ×2 and `Until (optional)`: the Compose twin of the web verdicts above. LEFT. New goldens ride with the code (a new case has no image to fall back on): jobsheet-job-options, -repeat-sheet, -delete-asks-and-counts and -ticking-a-repeating-job-asks, hearth light and dark, both surfaces. No existing case re-recorded.Test report
Coverage: 27.0%
Updated by the check workflow · commit
589eb49d1b5f380adfbe4847fb1325Force-pushed: a type-only import dropped while resolving this branch's rebase
onto the card commits, plus the gate hole that let it reach a deploy.
JobSheet.tsxuses theItemRepeattype, and the resolution took one side of aconflicting import line and lost it.
tscis clean now.make checknever type-checked the web app. Its recipe wasfmt vet lint test web-test web-i18n-check android-i18n-check tokens-check— noweb-build,so
tsc --noEmitnever ran.web-testcatches a missing binding only where atest happens to execute that line, which is why the earlier missing value import
was caught and this type-only one was not. The Docker build runs
tsc, so thedeploy found it.
web-buildis now part ofcheck. CI's web job always ran it; this only makesthe local gate agree with the pipeline.
AGENTS.md gains the corrected description of the gate and one line beside the
stacking rule: resolving a merge conflict is writing code, so run the suite
immediately after resolving rather than at the end of the chain — taking one
side of a conflicting import silently drops what the other side added.
Android test report
Coverage:
Updated by the android workflow · commit
589eb49d1b4847fb1325589eb49d1b