Skip to content

Schedules redesign 5/5: view page, list, and action modals#3552

Open
tegan-temporal wants to merge 6 commits into
tegan/schedules-pr4-formfrom
tegan/schedules-pr5-view
Open

Schedules redesign 5/5: view page, list, and action modals#3552
tegan-temporal wants to merge 6 commits into
tegan/schedules-pr4-formfrom
tegan/schedules-pr5-view

Conversation

@tegan-temporal

@tegan-temporal tegan-temporal commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description & motivation 💭

Final PR of the stack. After this merges, feature/schedules is byte-identical to tegan/schedules-update (verified with an empty git diff).

  • New schedule-view/ card-based page: spec card with full-spec toggle, advanced settings, recent/upcoming workflow runs, workflow input, custom search attributes
  • schedule-action-modals/: trigger, backfill, and pause modals rendered through a conditionally-mounted host keyed on the confirmation-modal store, so every open starts with fresh state (no stale selections, reasons, or page-load timestamps). Trigger pre-selects Allow All (matching server behavior for manual triggers) and both overlap pickers mark the schedule's current policy
  • Backfill modal interprets entered times in the schedule's timezone (caption shows it) and disables confirm when end ≤ start
  • List components move to schedules-list/
  • Final cleanup: deletes the superseded view components, schedule-spec-label, legacy schedule types (ScheduleParameters family, Unspecified overlap policy), and legacy i18n keys

Testing 🧪

How was this tested 👻

  • Manual testing
  • E2E tests added
  • Unit tests added

pnpm check 0 errors, pnpm lint 0 errors, 2,080 unit tests passing, full integration suite 233 passed / 0 failed. New schedule-actions.spec.ts covers trigger patch bodies, backfill request + post-success modal usability, modal state reset on reopen, timezone caption, end-before-start validation, and delete. The e2e spec now does a full create → verify summary → delete loop but needs a run against a real Temporal server (not runnable locally).

Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️

On a schedule view page: trigger, backfill (try an end time before the start time, and reopen modals to confirm state resets), pause/unpause with a reason, and delete.

Checklists

Merge Checklist

🤖 Generated with Claude Code

Replaces the schedule view page with card-based components (spec,
advanced settings, workflow runs, input, search attributes), adds the
trigger/backfill/pause modals behind a conditionally-mounted host so
modal state resets on every open, and moves the list components under
schedules-list/. The backfill modal interprets times in the schedule's
timezone and validates that the end time follows the start time.
Removes the superseded view components, legacy schedule types and i18n
keys, and schedule-spec-label. Adds action-modal integration coverage
and extends the e2e spec to a full create/verify/delete loop.

Co-Authored-By: Claude Fable 5 <[email protected]>
@tegan-temporal tegan-temporal requested a review from a team as a code owner June 11, 2026 18:10
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment Jun 15, 2026 9:23pm

Request Review

The payload input now validates JSON for json/plain encoding, so submit
a JSON string, and the schedule heading contains the status badge, so
match on containment.

Co-Authored-By: Claude Fable 5 <[email protected]>

@laurakwhit laurakwhit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looking good! Noticed a few small things, but no major blockers.

return runs
.filter(Boolean)
.sort(
(a, b) => getMilliseconds(a.actualTime) - getMilliseconds(b.actualTime),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we want the newest (not the oldest) to show here?

Suggested change
(a, b) => getMilliseconds(a.actualTime) - getMilliseconds(b.actualTime),
(a, b) => getMilliseconds(b.actualTime) - getMilliseconds(a.actualTime),

</dd>
</div>

{#if state.limitedActions}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
{#if state.limitedActions}
{#if state?.limitedActions}

data-testid="schedule-name"
>
<WorkflowStatus
status={schedule?.schedule.state.paused ? 'Paused' : 'Running'}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
status={schedule?.schedule.state.paused ? 'Paused' : 'Running'}
status={schedule?.schedule?.state?.paused ? 'Paused' : 'Running'}

</dd>
</div>

{#if schedule?.info.updateTime}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
{#if schedule?.info.updateTime}
{#if schedule?.info?.updateTime}


let { input, scheduleId }: { input: Payloads; scheduleId: string } = $props();
interface Props {
input: Payloads;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NIT: Can the input be null here?

{translate('schedules.schedule-specs')}
</h2>
<Button
variant="ghost"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NIT: Wondering if this should be a "secondary" button or maybe just have an up/down chevron icon? Or Maybe a toggle would be more intuitive here?

</h1>
</div>
</header>
<Loading />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NIT: Instead of a loading spinner, wondering if we should follow the pattern of a skeleton loader we have elsewhere when loading a full page.

<dd class="inline-flex items-center gap-1">
{schedule?.schedule?.action?.startWorkflow?.workflowType?.name}
<div class="flex items-center gap-2">
<Link

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we use the DetailList components here? E.g. DetailListLinkValue.

</header>

<PillContainer class="mr-auto flex flex-row p-1">
<Pill

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Curious why pills instead of our ToggleButtons or Tabs?

<span class="font-mono" data-testid="workflow-count"
>{$workflowCount.count.toLocaleString()}
</span>
<Button

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use the existing CountRefreshButton here?

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