Repository navigation
Conversation
kantord
left a comment
There was a problem hiding this comment.
Thanks for this, the pins-first approach and the mutation table made the change easy to review, and the toast and the one-shot request body look right. I'm requesting changes for two things in the dialog, both small.
1. The time and date fields are not independent
The form shows a Date field and a Time field, but it keeps the run time as one combined string. When there is no date that string is empty, so the time has nowhere to live:
joinOneShotreturns""when the date is empty, which drops the time.oneShotPartsthen gives the controlled Time input an empty value.
What that means for a user, reading the code:
- Time typed before a date. The time is discarded, and picking a date afterwards gives 09:00 rather than what was typed.
schedule-dialog.test.tsxpins this ("a date on its own runs at 9:00"), so I understand it is deliberate. With two visibly separate fields it is still surprising. - Date cleared or edited after a time is set. This goes through the same path, so I expect the time to be lost the same way. I couldn't run it in a browser, so please confirm. There is a test that clearing the date disables Create, but none that checks the time afterwards.
- Asymmetry. The Time handler already ignores transient empty values (L286-L297: "Segment editing fires transient empty values; keep the last time"). The Date handler (L276-L283) has no equivalent guard.
Suggested fix: keep the time in its own state (or a oneShotTime field on FormValue) and derive the combined value from the date plus that time, defaulting to 09:00 only when no time has been chosen. Giving the Date input the same empty-value guard would cover the segment-editing case, but not the time-first one.
2. A disabled Create button gives no reason
scheduleFormProblem returns messages like "Name is required." (schedule-form-value.ts L142-L158), but problem is only used as a null check (L163, L168, L367). The message is never shown, so the button is disabled with no stated reason, and Enter in an incomplete form silently does nothing. The native required bubbles never appear either, because the form can't reach validation.
Suggested fix: show problem as muted helper text near the footer (or via aria-describedby on the button) once the form has been touched.
Nits, not blocking
toastScheduleSavedwas inserted betweencanEditScheduleand itsDECISIONdoc comment, so that comment now sits above the wrong function andcanEditSchedulehas none.sm:max-w-lg(L182) is the Dialog primitive's default already, so the change and its test ("is sm:max-w-lg wide") don't test anything.- The Date and Time inputs are now named only by
aria-labelinside the "Run at" fieldset, and the oldschedule-run-atid is gone. That looks fine, just noting it.
This PR is also stacked on #2369 (the toast tests rely on the Toaster added in its test kit), so it can't merge before that one.
Fully or partially written by an AI agent.
…reate is disabled Review of #2370. - The one-shot time has its own form field (`oneShotTime`), and the run time is derived from the date plus that time. A time chosen before the date is kept, and clearing or editing the date (or one of its segments) never touches the time. A date runs at 9:00 only when no time was ever chosen. - Once the form has been edited or submitted, the reason Create is disabled (`scheduleFormProblem`) shows as muted helper text above the footer and describes the Create button through `aria-describedby`. - `toastScheduleSaved` moves above the `DECISION` comment, so the comment documents `canEditSchedule` again. - Drop `sm:max-w-lg` and its test: it is the Dialog default. Pins first: four new pins in `schedule-dialog.pinning.test.tsx` pass on the parent commit and after. A browser test covers the time-first and the keyboard date-clear cases in Chromium. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lper text) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks Daniel, both points were right. I confirmed the date-cleared case in Chromium: Backspace on one date segment wiped the time. Everything is in 7418f81, on top of the original commits:
One difference from the prototype: its |
kantord
left a comment
There was a problem hiding this comment.
Thanks for the fast and thorough fix, and for confirming the date-cleared case in Chromium. I read 7418f81d5 against both of my requests and they are resolved:
- Date and Time are independent. The time now lives in its own
oneShotTimefield. A time typed before the date is kept, and clearing or editing the date never touches it, while an empty date still clears the run time so Create can't send a date the input no longer shows. - Disabled Create gives a reason. The
scheduleFormProblemmessage now shows once the form is touched or a submit is tried, and it is linked to Create witharia-describedby. - Nits. The doc comment is back above
canEditSchedule, and the no-opsm:max-w-lgand its test are gone.
I'm fine with keeping the date empty when only a time is set, rather than filling in today's date as the prototype does. I read the diff and CI but did not run the new Playwright test.
Approving. Merge order is still after #2369.
Fully or partially written by an AI agent.
b7ea534 to
893131a
Compare
…ng the run time Pins the create body (cron in the browser's time zone, and a one-shot wall clock sent as its absolute instant), the inline refusal, the required name and prompt, and the edit body that keeps the stored trigger and limits. Every test passes on the current dialog. Refs #2299 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A one-shot task takes its run time from separate Date and Time inputs, grouped under "Run at", instead of the native datetime-local widget, as in the prototype. A date alone runs at 9:00; a cleared time segment keeps the last time; no date means no run time. - The submit button stays disabled until scheduleFormProblem finds nothing missing: the name, the prompt, and the cron expression or a valid run time. Enter in an incomplete form sends nothing. The fields keep their native required marks. An edit checks the stored, locked trigger. - A Sonner toast confirms a created task and a saved edit, from the inventory and from the detail page. A refused save raises no toast. - The dialog states sm:max-w-lg, which the Dialog primitive already defaults to. Refs #2299 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reate is disabled Review of #2370. - The one-shot time has its own form field (`oneShotTime`), and the run time is derived from the date plus that time. A time chosen before the date is kept, and clearing or editing the date (or one of its segments) never touches the time. A date runs at 9:00 only when no time was ever chosen. - Once the form has been edited or submitted, the reason Create is disabled (`scheduleFormProblem`) shows as muted helper text above the footer and describes the Create button through `aria-describedby`. - `toastScheduleSaved` moves above the `DECISION` comment, so the comment documents `canEditSchedule` again. - Drop `sm:max-w-lg` and its test: it is the Dialog default. Pins first: four new pins in `schedule-dialog.pinning.test.tsx` pass on the parent commit and after. A browser test covers the time-first and the keyboard date-clear cases in Chromium. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t guard The hint that says why Create is disabled is right-aligned from sm:, the breakpoint where DialogFooter's buttons turn into a right-aligned row. Moving it to the 500px pivot would right-align it above stacked buttons between 500 and 639px, so it stays on sm: and the guard lists it with that reason. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
893131a to
c684c4e
Compare
Summary
The create and edit dialog follows the prototype: a one-shot run time takes separate date and time inputs, the submit button stays disabled until the form could be sent, and a Sonner toast confirms each save. The request bodies, the inline refusal and the edit locks are unchanged, and pinned first.
Part of #2299
Part of #2210
Review changes (
23b6788c8)In response to the review. Commit
23b6788c8sits on top of the original two commits and doesn't rewrite them.oneShotTime), and the run time is derived from the date plus that time.scheduleFormProblem's message ("Prompt is required.", "Run time is required.") shows as mutedtext-xshelper text above the footer. It is right-aligned under Create on desktop and above it on a phone, and it describes the Create button througharia-describedby. It goes away once the form could be sent. Enter in an incomplete form now shows it too.c684c4e03, after the rebase ontomain). The helper text'ssm:text-rightis listed inbreakpoints.test.ts, which now scans schedules: it follows DialogFooter's buttons, which become a right-aligned row fromsm:, so moving it to the 500px pivot would right-align it above stacked buttons between 500 and 639px.toastScheduleSavedmoved above theDECISIONcomment, so that comment documentscanEditScheduleagain.sm:max-w-lgand its test are gone, because it is the Dialog default. The "Width" bullet above no longer applies.Difference from the prototype. The prototype's
joinOneShotfills in today's date when only a time is set, or when the date is cleared. Studio leaves the date empty and keeps Create disabled. That way a half-edited date never snaps to today mid-edit.Tests.
Pins first. 4 new pins in
schedule-dialog.pinning.test.tsxpass on5557e7219and after: changing the date keeps the time; a one-shot phrase fills both inputs; the edit dialog shows a stored one-shot's date and time, locked; and a complete form names no problem.New tests.
schedule-dialog.test.tsx(5): time before date; date cleared and picked again; helper text hidden until touched; the text andaria-describedbyfollow the first problem; Enter in an untouched form.schedule-form-value.test.ts(6) cover the new patch helpers.e2e/schedules.spec.ts(1, desktop and phone) checks the time-first and keyboard date-clear cases in Chromium. Backspace on one date segment leaves the time at 15:30 and shows "Run time is required." as Create's description.Changed tests. "a date on its own runs at 9:00" now covers only the never-chosen case. The old
oneShotParts/joinOneShotunit test was replaced with tests of the helpers that replace them. The trigger-lock drift test also driftsoneShotTime.Mutation proof (each applied alone, focused suite rerun):
Gates (shared lock):
23b6788c8:task studio:checkpasses, with server tests 252/252 and web tests 1679/1679;task studio:browserreports 138 passed, 38 skipped.main, onc684c4e03:task studio:checkpasses (server 252/252, web 1727/1727). At the top of the restacked stack (97f3f8c7f):task studio:browserreports 153 passed, 41 skipped.Screenshots. Before =
5557e72(this PR before the review), After =23b6788, Prototype =stack-08. Desktop frames are cropped to the dialog column. Red boxes mark real Before/After differences: the Time input and the helper text. Amber boxes mark what was reviewed and is unchanged: the Date input and the disabled Create button.Time typed before the date, desktop light. Before, the time was dropped; After, it stays and the reason is shown.
Date segment cleared with Backspace, desktop light. Before, the time was lost; After, it stays at 15:30.
Helper text after typing only a name, desktop light.
Dark and 390 px frames
Time typed before the date:
Date segment cleared:
Helper text:
What changed
datetime-localwidget. A date on its own runs at 9:00. A cleared time segment keeps the last time. No date means no run time. The "Saves as …" line stays.scheduleFormProblem(inschedule-form-value.ts) checks the name, the prompt, and the cron expression or a valid run time. The submit button stays disabled until it finds nothing, and Enter in an incomplete form sends nothing. The fields keep their nativerequired. An edit checks the stored, locked trigger.Toasterwas already mounted.sm:max-w-lgon the dialog. The Dialog primitive already defaults to it, so nothing moves.Differences from the prototype
Kept deliberately (#2299): no timezone, run-limit or retry controls (the wire fields round-trip), the locked trigger and tool profile on edit, and no rename (#2145). The dialog keeps Studio's title, description and button labels ("Schedule a task", "Create schedule").
No regression
Pins first (
3ec761fe8,schedule-dialog.pinning.test.tsx, 6 tests), on the dialog as mecatui: subagent / Task visibility (agent hierarchy dashboard) #1 leaves it: the cron create body in the browser's time zone; a one-shot wall clock sent as its absolute instant (set through whichever inputs the dialog has); the inline refusal; the required name and prompt; the edit body that keeps the stored trigger and limits without a name; and a stored one-shot saved unchanged. 6/6 pass before and after.Change (
5557e7219):schedule-dialog.test.tsx(9 tests) and 4 new unit tests inschedule-form-value.test.ts. No existing test changed.Mutation proof:
Screenshots
Before =
8cc7c23(main), After =5557e72, Prototype =stack-08. Before ismain, so the inventory changes from #1 also show behind the dialog. The prototype's mock BFF gained echo handlers forPOST/PUT /schedulesso its toasts could be captured.Run once, desktop light. Date and Time are separate, as in the prototype, and Create is enabled once the date is set.
Toast after a create, desktop light. Matches the prototype's toast.
Empty create dialog, desktop light. Create starts disabled, as in the prototype.
All other frames
Run once, dark and phone:
Empty create dialog, dark and phone:
Toast after a create, dark and phone:
Edit dialog (unchanged apart from the gate):
Toast after an edit from the detail page:
Gates
Through the shared lock, on
5557e7219:task studio:checkpasses: lint, typecheck and the generated-artifact check pass; server tests 252/252; web tests 1666/1666 (192 files).task studio:browserat the top of the stack: 136 passed, 38 skipped.🤖 Generated with Claude Code