feat(jobs): two-screen creation flow for a new job #230

Merged
nalum merged 1 commit from feat/job-create-flow into main 2026-08-26 18:35:16 +00:00
Owner

Closes #177.

The shipped form asked nine questions in one scroll and labelled six of them
(optional) — which §8 calls the form admitting it should not have asked.

Screen one asks four things: the title, Who, When, and nothing else. Three
day chips sit under the When row so the common case never opens a picker. The
footer is More plus Create job — More rather than Next, because screen
two is optional and the primary must be reachable from screen one. Screen two
holds repeats, worth, the note and sub-jobs: everything a job can live without on
day one. Both screens commit; only one is required.

Creating is the one screen the laws allow an explicit Save, because nothing
exists to commit a row against until it is pressed. Everything else the sweep
bans is gone: nine .field blocks, six (optional) labels — recounted, the
earlier estimate of four was low — and the type="number" reward input, which is
now the same numeric text entry the Worth picker uses.

The three pickers are reused, not copied. They are wired through the field
row with no commit prop, and that omission is what keeps a create screen from
raising the §5 flash and snackbar: there is no record yet to have changed. Four
date helpers were exported so the day chips share the When picker's arithmetic
rather than growing a second copy of it.

No Cancel button — the sheet dismisses by drag-down, click-away or Escape,
like every other sheet. No hand-typed dates or times: a YYYY-MM-DD box
beside its own picker is two ways to enter one value, and the typed one is the
one that can be wrong.

The uid is minted client-side as a UUIDv7 on both surfaces now — web was relying
on the queue's fallback, Android already did it unconditionally — so an offline
replay converges on ALREADY_EXISTS rather than duplicating. The chained
create → deadline → points path reads the uid back off whichever response
arrives, real or synthesized, so it holds either way.

Sixteen tests, every one confirmed to fail first by breaking the production code
and restoring it.

One gap, flagged rather than fudged: no render-parity fixture or screenshot
golden for the two screens. That format fits presentational cards, not a stateful
two-screen flow with async commits, and building one that is worth having is its
own task. Existing goldens are unchanged and verified.

🤖 Generated with Claude Code

Closes #177. The shipped form asked nine questions in one scroll and labelled six of them `(optional)` — which §8 calls the form admitting it should not have asked. **Screen one asks four things**: the title, Who, When, and nothing else. Three day chips sit under the When row so the common case never opens a picker. The footer is `More` plus `Create job` — `More` rather than `Next`, because screen two is optional and the primary must be reachable from screen one. **Screen two** holds repeats, worth, the note and sub-jobs: everything a job can live without on day one. Both screens commit; only one is required. Creating is the one screen the laws allow an explicit Save, because nothing exists to commit a row against until it is pressed. Everything else the sweep bans is gone: nine `.field` blocks, six `(optional)` labels — recounted, the earlier estimate of four was low — and the `type="number"` reward input, which is now the same numeric text entry the Worth picker uses. **The three pickers are reused, not copied.** They are wired through the field row with no `commit` prop, and that omission is what keeps a create screen from raising the §5 flash and snackbar: there is no record yet to have changed. Four date helpers were exported so the day chips share the When picker's arithmetic rather than growing a second copy of it. **No Cancel button** — the sheet dismisses by drag-down, click-away or Escape, like every other sheet. **No hand-typed dates or times**: a `YYYY-MM-DD` box beside its own picker is two ways to enter one value, and the typed one is the one that can be wrong. The uid is minted client-side as a UUIDv7 on both surfaces now — web was relying on the queue's fallback, Android already did it unconditionally — so an offline replay converges on `ALREADY_EXISTS` rather than duplicating. The chained create → deadline → points path reads the uid back off whichever response arrives, real or synthesized, so it holds either way. Sixteen tests, every one confirmed to fail first by breaking the production code and restoring it. **One gap, flagged rather than fudged**: no render-parity fixture or screenshot golden for the two screens. That format fits presentational cards, not a stateful two-screen flow with async commits, and building one that is worth having is its own task. Existing goldens are unchanged and verified. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(jobs): two-screen creation flow for a new job
All checks were successful
check / commits (pull_request) Successful in 25s
check / go (pull_request) Successful in 2m49s
check / report (pull_request) Successful in 10s
check / web (pull_request) Successful in 4m16s
android / build (pull_request) Successful in 6m36s
android / report (pull_request) Successful in 4s
057d571e76
The shipped New job form asked nine questions in one scroll (six
labelled optional), typed dates beside their own picker, and ended
with a Cancel button no other sheet in the redesign carries. Handoff
§8 replaces it: screen one asks four things (title, Who, When, plus
day chips that answer "when" without opening a picker) and can commit
immediately; `More` reaches the optional screen two (repeats, worth,
description, sub-jobs), which can also commit — never a dead end.

Who and When on screen one, and Worth on screen two, reuse the three
shared pickers (JobPickers.tsx/JobPickers.kt) via FieldRow's picker
rendering with no `commit` passed — the chevron still means "opens a
picker" everywhere, but nothing here raises the §5 flash/snackbar,
because nothing exists yet to commit a field against. That grammar
starts the instant the sheet opens: the create call now mints and
sends a client-side UUIDv7 (web parity with the Android repo, which
already did), the owner rides the create call rather than a separate
assign, and the caller opens the new job's detail sheet on success —
"create is the only different grammar in the product" ends there.

Sub-job creates and the reward grant/link stay best-effort second
promises after the job itself lands, matching the shipped form's
partial-success honesty. The Worth picker's "Make a new one" asks for
an amount only and names the reward after the job, which is what
finally drops the freeform reward-name field and the `type="number"`
input the build-review sweep flagged.

Not done: no new render-parity/screenshot-golden fixture for the two
screens (conformance/render-*.json + gallery.tsx + screens.spec.ts +
Android's RenderConformanceTest/ScreenshotTest) — that fixture format
fits a presentational card, not a stateful two-screen wizard with
async submit, and building it well is a separable follow-up. Existing
goldens are unchanged and verified clean (make web-screens, make
web-pages, verifyRoborazziDebug all pass with zero diffs).

Test report

Suite Tests Result Skipped
Unit 1435 ✅ pass 1
Integration 131 ✅ pass —

Coverage: 27.0%

Updated by the check workflow · commit d96f4369f1

<!-- ci-test-report --> ## Test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit | 1435 | ✅ pass | 1 | | Integration | 131 | ✅ pass | — | **Coverage:** 27.0% <sub>Updated by the check workflow · commit d96f4369f1c77b7b16c10de823012ee8e178ced4</sub>

Android test report

Suite Tests Result Skipped
Unit (debug) ❌ 1 failed

Coverage:

Updated by the android workflow · commit d96f4369f1

<!-- android-test-report --> ## Android test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit (debug) | | ❌ 1 failed | | **Coverage:** <sub>Updated by the android workflow · commit d96f4369f1c77b7b16c10de823012ee8e178ced4</sub>
nalum force-pushed feat/job-create-flow from 057d571e76
All checks were successful
check / commits (pull_request) Successful in 25s
check / go (pull_request) Successful in 2m49s
check / report (pull_request) Successful in 10s
check / web (pull_request) Successful in 4m16s
android / build (pull_request) Successful in 6m36s
android / report (pull_request) Successful in 4s
to d96f4369f1
Some checks failed
check / commits (pull_request) Successful in 14s
check / go (pull_request) Successful in 2m51s
check / web (pull_request) Successful in 4m24s
android / build (pull_request) Failing after 5m2s
check / report (pull_request) Successful in 4s
android / report (pull_request) Successful in 4s
android / build (push) Has been cancelled
android / report (push) Has been cancelled
check / commits (push) Has been cancelled
check / go (push) Has been cancelled
check / report (push) Has been cancelled
check / web (push) Has been cancelled
tag / tag (push) Has been cancelled
2026-08-26 07:03:39 +00:00
Compare
nalum changed target branch from fix/chain-end-refusal to main 2026-08-26 18:35:12 +00:00
nalum merged commit d96f4369f1 into main 2026-08-26 18:35:16 +00:00
nalum deleted branch feat/job-create-flow 2026-08-26 18:35:18 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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!230
No description provided.