Skip to content

feat(studio): align the schedule inventory and detail page with the prototype - #2369

Open
peppescg wants to merge 7 commits into
mainfrom
feat/studio-schedules-2299-inventory
Open

peppescg wants to merge 7 commits into
mainfrom
feat/studio-schedules-2299-inventory

Conversation

@peppescg

@peppescg peppescg commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Stack (1 of 4) for #2299 and #2341: this PR → the dialog → the transcript dialog → the title= audit. Review bottom to top.

Review changes

Fixes for the review of 2026-10-16. Each point is a new commit on top of the reviewed head ced382a20.

  • Exact run dates without hover (71f18ea4d, 23df06bd0). Next and last run render as a <time dateTime> that also holds the exact local date as text.
    • The desktop table keeps the relative label and carries the date as visually hidden text. The cell keeps its hover title for the pointer.
    • The phone list's "last 10h ago" now has the date as hidden text too. It had none.
    • The detail page's Next run and Last run facts show the date: "in 13h · Sep 26, 2026, 4:00 AM". Their titles are gone, because the date is visible. A paused schedule's Next run is "—" with no date (it used to keep the date in its title).
    • At phone width the fact label no longer wraps, and a long value breaks after the " · ", not inside the date.
    • The pins no longer accept a title: exactTime() requires the formatted date as text inside the <time> for that instant.
  • Overdue next run (71f18ea4d). An enabled schedule whose next run has passed reads "Due now" for its first minute, then "Overdue", instead of "in <1m" indefinitely.
  • Row click guard (71f18ea4d, 621fab049). A row-body click with Ctrl, Cmd, Shift or Alt held, or one that ends a text selection, leaves the page on the list. There are unit tests for each modifier and for a selection. A Chromium test drags a selection across a row's prompt, Ctrl/Cmd- and Shift-clicks the row, then opens it with a plain click. With the selection guard removed, that drag navigated to the detail page in Chromium, which confirms the case the review couldn't check.
  • Nits.
    • The status dot and badge pulses carry motion-reduce:animate-none (71f18ea4d).
    • The schedules page passes the 320px phone-overflow check, so its pending: #2194 marker and the stale "Schedule task button" comment are gone (621fab049).
    • The user docs say Run log (98dfe5094).
  • Rebased onto main (e17d8394d). main's phone time-width check from fix(studio): scan schedules in the breakpoint guard, and widen the schedule time field #2365 opened the dialog through a "Schedule task" button, which this PR names "New scheduled task", so it timed out on the merge. It now uses the new name (d2be1f193).
    • The inventory already polls every 5 seconds (scheduleInventoryPolling) in a visible tab. A refetch with unchanged data doesn't re-render, though, so a relative time can be up to a minute stale until the data changes, a filter or sort changes, or the page remounts. No timer was added.

Tests. The schedules suite has 209 tests, all passing. schedules-alignment.test.tsx adds 9 tests, and schedule-detail.test.ts adds 1. The pins were tightened as described above. One pin date became relative to the clock, so its next run stays in the future. e2e/schedules.spec.ts adds 1 test.

Mutation proof (each applied alone, focused suite rerun):

Mutation Failing tests
Inventory date as title only (no hidden text) 5
<time> without dateTime 6
Detail facts hide the date 1
Phone list last run back to plain text 2
"Overdue" removed 2
"Due now" removed 1
Paused schedule's next run dated 5
Modifier guard removed 4
Ctrl / Meta / Shift / Alt each unchecked 1 each
Selection guard removed 1 (and the Chromium test)
Dot / badge pulse without motion-reduce 1 each
Fact label may shrink / date may wrap inside 1 each

Gates through the shared lock, after the rebase onto main: on this PR's head d2be1f193, task studio:check passes (server 252/252, web 1695/1695); on the top of the restacked stack (97f3f8c7f), task studio:browser 153 passed, 41 skipped, and task docs passes.

Screenshots

Before = ced382a (reviewed head), After = 23df06b, Prototype = stack-08. Red marks a real difference. Amber marks a cell that was reviewed and is unchanged: the inventory's Before and After are pixel-identical, because the dates there are hidden text. The phone detail shots are scrolled to the Frequency facts; the prototype column is its top.

Detail facts, desktop light. The exact dates are visible beside the relative ones.

Detail facts, phone dark.

Overdue next run, inventory desktop light (no prototype state):

Inventory, desktop light (unchanged pixels; the dates are now <time> text for screen readers):

All other frames










Summary

The schedule inventory and detail page now match the design prototype (stack/08-tooling) where #2194 stopped short: status dots, relative times, clickable rows, the header and copy, the card shape, the empty filter, and the detail page's "Run log". The data, actions, routes, accessible names and keyboard stops are unchanged, and pinned first.

Part of #2299
Part of #2210

What changed

  • Status. The table's Status column shows a dot and a Title-case label ("Scheduled"). The phone list shows the dot alone; its label is visually hidden text and the hover title. Claimed and Running pulse. Completed keeps its own hollow dot. The detail badge reads in Title case and pulses while live.
  • Times. Next and last run read as relative times ("in 13h", "10h ago"), with the full date in the cell's title. The phone list's last run is relative too.
  • Rows. Clicking anywhere on a desktop row opens the detail page. The name link stays the only keyboard stop besides the Actions button; the row takes no focus and no role. The actions cell and the name link stop the click from reaching the row, so a menu item, or a Ctrl/Cmd-click on the link (new tab), does not also open the row.
  • Header and copy. No subtitle. The create button reads "New scheduled task" ("New" on a phone) at size="sm". Its accessible name is "New scheduled task" at every width. The empty inventory's button uses the same component.
  • Card shape. The table, phone list, run log and fact groups are rounded-lg boxes without bg-card.
  • Empty filter. A plain dashed box with the message and, for a search, a ghost Clear filter button. It keeps role="status".
  • Detail page. The title uses the prototype's size (text-[44px], text-3xl on a phone). The content below the header is capped at max-w-4xl instead of the whole page at max-w-5xl. The fire history is headed Run log and shows a spinner and "Reading the run log…" while it loads.

Differences from the prototype

Kept deliberately (#2299, "Deviations from the prototype to keep"):

New in this PR:

  • Dot colours. Each status keeps its own colour. The prototype draws Scheduled and Paused in the same grey, so its dot alone does not tell them apart.
  • Next run reads "in 13h", as on the detail page, where the prototype's table shows a bare "13h".
  • Table status. The dot beside the visible label is hidden from screen readers, so the label is read once. The prototype also gives that dot a title and hidden text, so it is read twice.

Not changed here: the Schedule column's wording comes from Studio's describeCron ("At 02:00 AM") rather than the prototype's ("Daily at 2:00 AM"). #2299 doesn't list it.

No regression

  • Pins first (115d88a20, schedules-alignment.pinning.test.tsx, 14 tests), against the unchanged components: row data and name links, the full next and last run dates (as text or title), the dash and "Never" cells, the four row actions, the row's keyboard stops, the phone list's links and statuses, the create dialog's focus return, the distinct search and status-filter empty messages, and the detail page's facts, actions, loading status and fire rows. 14/14 pass before and after.

  • Change (ced382a20): schedules-alignment.test.tsx (13 tests) and schedule-status.test.tsx (5) cover the new behaviour.

  • Modified tests.

    • schedules-workspace.test.tsx: three lookups of the create button change from "Schedule task" to "New scheduled task" (the new name). No assertion changed.
    • schedule-status.test.tsx: the badge text is the Title-case label instead of the raw status. It still requires a distinct look per status.
    • e2e/schedules.spec.ts: the create button's new name, and the status label's new case. The phone lookup is scoped to main, because "Scheduled" now also matches the navigation link.
  • Mutation proof (each applied alone, focused suite rerun):

    Mutation Failing tests
    Actions cell lets clicks reach the row 2
    Next run back to the absolute date 1
    Row click removed 1
    Name link lets clicks reach the row 1 (modified click)
    Badge shows the raw lowercase status 2
    Run log spinner removed 1
    Heading back to "Run history" 2
    Last run title removed 2 (including a pin)

Screenshots

Before = 8cc7c23 (main), After = ced382a, Prototype = stack-08, from the offline Playwright fixtures with the prototype's data and a clock frozen at 2026-09-25 12:30 UTC. Red boxes mark Before/After differences. A second After capture was pixel-identical apart from focus-ring and hover antialiasing on two frames.

Inventory, desktop light. Matches the prototype apart from the dot colours, "in 13h" and the Schedule wording noted above.

Detail, desktop light. Matches the prototype's title size, max-w-4xl content and "Run log". The Back link, Model fact and outcome badges are kept deviations.

Phone list, light. Matches the prototype; the dot keeps Studio's colours.

All other frames

Inventory, dark and phone dark:


Row hover, desktop light and dark (the whole row opens the detail page):


Empty search, desktop and phone, light and dark (matches the prototype):




Detail, dark and phone light and dark:



Run log loading (the prototype column shows its loaded run log; its mock answers at once):




Gates

Through the shared lock, on ced382a20:

  • task studio:check passes: lint, typecheck and the generated-artifact check pass; server tests 252/252; web tests 1647/1647 (190 files).
  • The schedules browser spec passes 6/6 (desktop and mobile). task studio:browser ran at the top of the stack: 136 passed, 38 skipped.
  • No BFF contract changes, so the BFF integration suite was not needed.

🤖 Generated with Claude Code

@kantord kantord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, the pins-first approach, the mutation table and the careful row-click handling (the dialogs render outside the row, and the actions cell and name link stop propagation) made it easy to review. The port is faithful. I'm requesting changes for two things, both in the inventory.

1. The exact next-run and last-run dates are now hover-only

On main the inventory cells showed the date as text. Now the visible text is relative ("in 13h", "10h ago") and the date moved to a native title on a non-focusable TableCell:

  • Desktop cells: schedules-workspace.tsx L414-L424
  • Phone list: L465 shows "last 10h ago" with no title, and dropped the visible date.
  • The detail page's Next/Last run facts were already title-only on main (schedule-detail.tsx L307-L313), so that part isn't new. It does mean the exact time of an upcoming run is reachable nowhere without a mouse. The run log only shows past runs.

Keyboard and touch users can't reach a title on a non-focusable element, and screen readers announce it inconsistently. The pinning test hides this: disclosed() joins textContent with every title, so "keeps the full next and last run dates available" (L193-L197, L328-L330) passes on a tooltip alone.

Please make the exact date reachable without hover, for example a <time dateTime> element with a secondary or screen-reader-only line, on the inventory and the phone list (and ideally the detail facts), and make the test assert that instead of text-or-title.

A related, smaller point: describeNextRun goes through formatUntilTime, which returns "<1m" for a due-or-past time. An enabled schedule whose next run is overdue would read "in <1m" indefinitely, where the exact date used to show it was stuck. Worth a "due" or "overdue" label.

2. Row click has no modifier or selection guard

The row onClick calls navigate() unconditionally (L384-L395). Reading the code:

  • Ctrl/Cmd-click on the row body navigates in the same tab. Only the name link is protected.
  • Selecting text inside a row and releasing there fires a click and navigates. I couldn't confirm this in a browser.

A guard that skips navigation when a modifier key is held or window.getSelection() is non-empty is the minimal fix. No test covers a row-body modifier click or a selection (the modifier-click test uses the name link).

Nits, not blocking

  • The status dots' animate-pulse (schedule-status.tsx L51, L62) have no motion-reduce:animate-none; table.tsx already uses motion-reduce:.
  • phone-overflow.spec.ts L282-L284 still says "Schedule task button" and keeps the pending: #2194 marker at 320px. The shorter phone header may resolve it, so please check whether the marker can go.
  • User-docs still say "run history" (scheduled-tasks.md L88, studio.md L70) while the heading is now "Run log".
  • Relative times are computed at render, so they only refresh on a re-render. I didn't check whether the query has a refetch interval.

I read the code and CI only and didn't run the app or the tests.

Fully or partially written by an AI agent.

peppescg added a commit that referenced this pull request Oct 16, 2026
… became text

#2369's review fix shows the detail page's next and last run dates as
text, so their `<dd>` titles are gone. The guard now names the one
title left (the Repeat fact's raw cron) instead of counting three.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
peppescg added a commit that referenced this pull request Oct 16, 2026
…ript gate)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
peppescg added a commit that referenced this pull request Oct 16, 2026
… became text

#2369's review fix shows the detail page's next and last run dates as
text, so their `<dd>` titles are gone. The guard now names the one
title left (the Repeat fact's raw cron) instead of counting three.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
peppescg added a commit that referenced this pull request Oct 16, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
peppescg and others added 7 commits October 16, 2026 22:05
Pins what #2299's presentation changes must keep: row data and links,
the full next and last run dates, the row actions and keyboard stops,
the mobile list, the create dialog's focus return, the distinct empty
messages, and the detail page's facts, actions and fire history. Every
test passes on the current components.

Refs #2299

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rototype

- Status is a dot with a Title-case label: beside the label in the
  table, and alone (named for hover and screen readers) in the phone
  list. Claimed and Running pulse; Completed keeps its own hollow dot.
  The detail badge reads in Title case and pulses while live.
- Next and last run read as relative times ("in 13h", "10h ago"), with
  the full date in the cell's title. The phone list's last run is
  relative too.
- Clicking anywhere on a desktop row opens the detail page. The name
  link stays the keyboard stop, and the actions cell and the name link
  stop the click from reaching the row, so a menu item or a modified
  click on the link does not also open the row.
- The header drops its subtitle and uses "New scheduled task" ("New" on
  a phone) at size sm, with a stable accessible name.
- The table and lists are rounded-lg boxes without bg-card. A search or
  status filter with no matches is a plain dashed box with a ghost
  Clear filter button.
- The detail page uses the prototype's title size, caps its content at
  max-w-4xl, and names the fire history "Run log", with a spinner while
  it loads.

Refs #2299

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review of #2369:

- Next and last run render as a `<time dateTime>` that holds the exact
  local date as text. The inventory table and the phone list keep the
  prototype's relative label and carry the date as screen-reader text
  (the table cell keeps its hover title); the detail page's facts show
  it visibly ("in 13h · Oct 17, 2026, 2:00 AM") and drop the title.
- An enabled schedule whose next run has passed reads "Due now" for a
  minute, then "Overdue", instead of "in <1m" for ever.
- A row click with Ctrl, Cmd, Shift or Alt held, or one that ends a
  text selection, no longer opens the detail page.
- The status dot and badge pulses stop under reduced motion.

The pins no longer accept a title alone: they require the exact date
as text inside a `<time>` for the instant.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…px marker

The schedules spec drags a selection across a row's prompt and
Ctrl/Cmd- and Shift-clicks the row body, and stays on the list, then
opens the row with a plain click. The schedules page no longer
overflows at 320px, so its pending #2194 marker goes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At 390px the detail page's "Next run" label wrapped onto two lines and
the exact date broke mid-time. The fact label no longer shrinks, and
the visible date stays on one line, so a narrow row breaks after the
" · " separator.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dth check

main's phone time-width check (#2365) opens the dialog through a "Schedule
task" button. This stack names that button "New scheduled task", so the
check timed out on the merge with main. It now uses the new name, like the
other schedule journeys.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@peppescg
peppescg force-pushed the feat/studio-schedules-2299-inventory branch from 4ba8a73 to d2be1f1 Compare October 16, 2026 20:22
peppescg added a commit that referenced this pull request Oct 16, 2026
… became text

#2369's review fix shows the detail page's next and last run dates as
text, so their `<dd>` titles are gone. The guard now names the one
title left (the Repeat fact's raw cron) instead of counting three.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active (outdated) deployment
Preview — d02fc282 Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants