Skip to content

Display human-readable schedule spec labels#3261

Merged
Alex-Tideman merged 3 commits intomainfrom
calendar-spec
Mar 31, 2026
Merged

Display human-readable schedule spec labels#3261
Alex-Tideman merged 3 commits intomainfrom
calendar-spec

Conversation

@Alex-Tideman
Copy link
Copy Markdown
Collaborator

@Alex-Tideman Alex-Tideman commented Mar 31, 2026

Description & motivation 💭

Summary

Create a human-readable Calendar Spec label for both StructuredCalendarSpec and Interval. Show on the Schedules table and on the Schedule details page. In the Schedule details page, give the option to show the full spec.

  • Add schedule-spec-label utility that converts StructuredCalendarSpec and IntervalSpec into
    human-readable strings (e.g., "Every weekday at 8:30 AM UTC", "Every 5 hours") instead of displaying raw
    JSON
  • Simplify schedule component APIs to accept ScheduleSpec directly instead of pre-merged frequency arrays
  • Migrate schedules-table-row to Svelte 5 runes syntax
  • Add "View full spec" toggle on the schedule detail panel for users who need the raw JSON

What changed

New utility (src/lib/utilities/schedule-spec-label.ts):

  • getScheduleSpecLabel(spec, timezone?) — top-level dispatcher
  • getCalendarSpecLabel(specs, timezone?) — handles calendar patterns: daily, weekday, weekend, specific
    days, monthly, annual, multi-spec
  • getIntervalLabel(spec) — handles interval durations using shared formatDuration
  • Supports both protobuf Duration objects and string format intervals
  • Returns cron comment string directly when present
  • Falls back to “Custom schedule” or empty string if no match

Component changes:

  • schedule-frequency.svelte — displays human-readable label instead of JSON code block
  • schedule-frequency-panel.svelte — accepts ScheduleSpec prop, adds "View full spec" toggle
  • schedules-table-row.svelte — migrated to Svelte 5 runes, uses ScheduleFrequency directly
  • schedules-calendar-view.svelte — simplified prop passing
  • schedule-view.svelte — simplified prop passing

Test plan

  • 53 unit tests covering intervals (standard, large, mixed durations), calendar specs (daily, weekday,
    weekend, specific days, monthly, annual, quarterly, step-based, multi-spec), cron comment passthrough,
    and dispatch logic
  • pnpm test -- --run passes all tests
  • pnpm check and pnpm lint pass with no new errors
  • Verify schedule list table shows readable labels instead of JSON
  • Verify schedule detail view shows readable label with "View full spec" toggle
  • Verify cron-based schedules still display the cron string

Screenshots (if applicable) 📸

Screenshot 2026-03-31 at 9 46 56 AM Screenshot 2026-03-31 at 9 47 05 AM Screenshot 2026-03-31 at 9 47 13 AM Screenshot 2026-03-31 at 9 47 23 AM Screenshot 2026-03-31 at 9 47 32 AM

Design Considerations 🎨

Testing 🧪

I've added a lot of unit tests to test as many possible combinations of specs. I've also manually created a lot of different schedules to see how it's rendered.

How was this tested 👻

  • Manual testing
  • E2E tests added
  • Unit tests added

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

Checklists

Draft Checklist

Merge Checklist

Issue(s) closed

Docs

Any docs updates needed?

Pure utility that converts StructuredCalendarSpec and IntervalSpec
into human-readable strings like "Every weekday at 8:30 AM UTC"
or "Every 5 hours". Covers common patterns including daily,
weekday, weekend, monthly, annual, and interval-based schedules.
Replace raw JSON display with human-readable schedule labels using
getScheduleSpecLabel. Simplify component APIs to accept ScheduleSpec
directly instead of pre-merged frequency arrays. Migrate
schedules-table-row to Svelte 5 runes syntax.
Switch getIntervalLabel to use shared formatDuration utility instead
of local duration formatting. Add 14 new test cases covering large
intervals, mixed durations, multi-spec calendars, step-based ranges,
and combined calendar+interval dispatch.
@Alex-Tideman Alex-Tideman requested review from a team and rossedfort as code owners March 31, 2026 14:52
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

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

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment Mar 31, 2026 2:52pm

Request Review

@Alex-Tideman Alex-Tideman requested a review from laurakwhit March 31, 2026 14:53
@@ -113,13 +111,7 @@
<div class="mt-4 flex w-full flex-wrap gap-6">
{#if schedule}
<TabPanel id="existing-panel" tabId="existing-tab" class="w-full">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.

let { frequency, timezoneName = 'UTC' }: Props = $props();
let { spec }: Props = $props();

const hasCronString = $derived(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ 'spec.structuredCalendar.length' is possibly 'undefined'.


const hasCronString = $derived(
frequency.length > 0 && 'comment' in frequency[0] && !!frequency[0].comment,
spec?.structuredCalendar?.length > 0 &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ 'spec.structuredCalendar' is possibly 'null' or 'undefined'.

let { spec, class: className = '' }: Props = $props();

const timezoneName = $derived(spec?.timezoneName ?? 'UTC');
const cronString = $derived(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ 'spec.structuredCalendar.length' is possibly 'undefined'.

const cronString = $derived(
frequency.length > 0 && 'comment' in frequency[0] && frequency[0].comment
? frequency[0].comment
spec?.structuredCalendar?.length > 0 &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ 'spec.structuredCalendar' is possibly 'null' or 'undefined'.

<div class="flex flex-col gap-2">
{#if cronString}
<p>{cronString}</p>
{:else}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Argument of type 'IScheduleSpec' is not assignable to parameter of type '{ structuredCalendar?: IStructuredCalendarSpec[] | undefined; interval?: IIntervalSpec[] | undefined; }'.

});
const route = $derived(
routeForSchedule({
namespace,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'string | null | undefined' is not assignable to type 'string'.

@@ -81,13 +88,7 @@
</td>
{:else if label === translate('schedules.schedule-spec')}
<td class="cell">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.

@@ -418,13 +418,7 @@
<ScheduleInput
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'IPayloads | null | undefined' is not assignable to type 'IPayloads'.

@@ -418,13 +418,7 @@
<ScheduleInput
input={schedule?.schedule?.action?.startWorkflow?.input}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.

@temporal-cicd
Copy link
Copy Markdown
Contributor

temporal-cicd bot commented Mar 31, 2026

Warnings
⚠️

📊 Strict Mode: 30 errors in 5 files (2.8% of 1086 total)

src/lib/components/schedule/schedule-frequency.svelte (3)
  • L17:4: 'spec.structuredCalendar.length' is possibly 'undefined'.
  • L18:8: 'spec.structuredCalendar' is possibly 'null' or 'undefined'.
  • L29:31: Argument of type 'IScheduleSpec' is not assignable to parameter of type '{ structuredCalendar?: IStructuredCalendarSpec[] | undefined; interval?: IIntervalSpec[] | undefined; }'.
src/lib/components/schedule/schedule-frequency-panel.svelte (2)
  • L17:4: 'spec.structuredCalendar.length' is possibly 'undefined'.
  • L18:8: 'spec.structuredCalendar' is possibly 'null' or 'undefined'.
src/lib/components/schedule/schedules-table-row.svelte (5)
  • L45:6: Type 'string | null | undefined' is not assignable to type 'string'.
  • L68:33: Argument of type 'IScheduleActionResult[] | null | undefined' is not assignable to parameter of type 'IScheduleActionResult[]'.
  • L73:16: Type 'string | null | undefined' is not assignable to type 'string'.
  • L74:16: Type 'string | null | undefined' is not assignable to type 'string'.
  • L90:28: Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.
src/lib/components/schedule/schedule-form/schedules-calendar-view.svelte (1)
  • L113:27: Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.
src/lib/pages/schedule-view.svelte (19)
  • L138:18: Property 'message' does not exist on type '{}'.
  • L300:20: 'schedule.schedule' is possibly 'undefined'.
  • L300:20: 'schedule.schedule.state' is possibly 'null' or 'undefined'.
  • L400:12: Type 'IScheduleActionResult[] | null | undefined' is not assignable to type 'IScheduleActionResult[] | undefined'.
  • L404:12: Type 'ITimestamp[] | null | undefined' is not assignable to type 'ITimestamp[] | undefined'.
  • L407:12: Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.
  • L408:12: Type 'IScheduleState | null | undefined' is not assignable to type 'IScheduleState'.
  • L409:12: Type 'ISchedulePolicies | null | undefined' is not assignable to type 'ISchedulePolicies'.
  • L410:12: Type 'string | null | undefined' is not assignable to type 'string | undefined'.
  • L418:12: Type 'IPayloads | null | undefined' is not assignable to type 'IPayloads'.
  • L420:34: Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.
  • L428:19: 'schedule.schedule' is possibly 'undefined'.
  • L428:19: 'schedule.schedule.state' is possibly 'null' or 'undefined'.
  • L437:9: 'schedule.schedule' is possibly 'undefined'.
  • L437:9: 'schedule.schedule.state' is possibly 'null' or 'undefined'.
  • L443:11: 'schedule.schedule' is possibly 'undefined'.
  • L443:11: 'schedule.schedule.state' is possibly 'null' or 'undefined'.
  • L452:11: 'schedule.schedule' is possibly 'undefined'.
  • L452:11: 'schedule.schedule.state' is possibly 'null' or 'undefined'.

Generated by 🚫 dangerJS against 9b54ed5

@Alex-Tideman Alex-Tideman merged commit d85d61a into main Mar 31, 2026
27 checks passed
@Alex-Tideman Alex-Tideman deleted the calendar-spec branch March 31, 2026 17:39
temporal-cicd bot pushed a commit that referenced this pull request Apr 10, 2026
Auto-generated version bump from 2.48.1 to 2.48.2

Specific version: 2.48.2

Changes included:
- [`92cd681e`](92cd681) Re-wire auth to use a provider pattern. Lots of tests remove cloud references (#3230)
- [`3d92202b`](3d92202) Use --top-nav-height CSS variable for sticky element positioning (#3250)
- [`16295986`](1629598) Bump saved view limits from 20 to 50 (#3254)
- [`a9fa0e91`](a9fa0e9) Display cron string instead of calendar spec when schedule has a cron string in comment field (#3241)
- [`f1811715`](f181171) use full for 100% instead of 100vh (#3256)
- [`0dfadd74`](0dfadd7) Add samples-ruby to workflows table empty state (#3259)
- [`d85d61a3`](d85d61a) Display human-readable schedule spec labels (#3261)
- [`b63049c5`](b63049c) Add invite icon to Holocene design system (#3262)
- [`00c6418c`](00c6418) Bump google.golang.org/grpc from 1.66.1 to 1.79.3 in /server (#3232)
- [`b04a3676`](b04a367) Add back animation (#3251)
- [`7b651524`](7b65152) Bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4 in /server (#3268)
- [`45f4fdea`](45f4fde) use snippets for nexus CTAs (#3266)
- [`420f5c9d`](420f5c9) min-h-full instead of screen (#3270)
- [`f5b2fab6`](f5b2fab) feat(navigation): add NavSection Holocene component (#3263)
- [`657b2728`](657b272) Adds requested design changes to breadcrumb items (#3267)
- [`6763cc4d`](6763cc4) Remove serena (#3273)
- [`dfff353e`](dfff353) Display Principal fields in Event History (#3272)
- [`2d289bce`](2d289bc) Update CODEOWNERS to wildcard for temporalio/frontend-engineering (#3275)
- [`a2eaf16e`](a2eaf16) Persist workflow view and sort order preferences across navigation (#3260)
- [`c5d4c996`](c5d4c99) Add link from Event Card to jump to event id page from Timeline. Remove unnecessary padding (#3277)
- [`e5b3ea55`](e5b3ea5) fix(deps): upgrade lodash, svelte, kit, storybook, tar-fs for security (#3269)
- [`b44afbe6`](b44afbe) fix(deps): upgrade vite and add picomatch/svgo overrides for security (#3279)
- [`4e8cb4e9`](4e8cb4e) Fix unpause confirmation modal title (#3280)
- [`740b3529`](740b352) Add Slack notification when DESIGN FEEDBACK REQUESTED label is added to a PR (#3282)
- [`7e8170e4`](7e8170e) Add check for COLLABORATOR (#3283)
- [`dc27109d`](dc27109) fix: update nav item margin from mb-1 to mb-2 (#3290)
- [`3e6416d2`](3e6416d) Pass execution runId in workflow request for schedule recent run (#3289)
- [`ae3a1844`](ae3a184) Fix schedule edit double-encoding header fields (#3287)
- [`09c083e0`](09c083e) fix: prevent reset modal from closing on authorization error (#3291)
- [`0aa3b72b`](0aa3b72) Sort namespace picker alphabetically (#3286)
- [`4c3d0057`](4c3d005) Sort alphabetically utility (#3293)
- [`67a988b9`](67a988b) Bump @sveltejs/kit from 2.55.0 to 2.57.1 (#3294)
laurakwhit added a commit that referenced this pull request Apr 10, 2026
Auto-generated version bump from 2.48.1 to 2.48.2

Specific version: 2.48.2

Changes included:
- [`92cd681e`](92cd681) Re-wire auth to use a provider pattern. Lots of tests remove cloud references (#3230)
- [`3d92202b`](3d92202) Use --top-nav-height CSS variable for sticky element positioning (#3250)
- [`16295986`](1629598) Bump saved view limits from 20 to 50 (#3254)
- [`a9fa0e91`](a9fa0e9) Display cron string instead of calendar spec when schedule has a cron string in comment field (#3241)
- [`f1811715`](f181171) use full for 100% instead of 100vh (#3256)
- [`0dfadd74`](0dfadd7) Add samples-ruby to workflows table empty state (#3259)
- [`d85d61a3`](d85d61a) Display human-readable schedule spec labels (#3261)
- [`b63049c5`](b63049c) Add invite icon to Holocene design system (#3262)
- [`00c6418c`](00c6418) Bump google.golang.org/grpc from 1.66.1 to 1.79.3 in /server (#3232)
- [`b04a3676`](b04a367) Add back animation (#3251)
- [`7b651524`](7b65152) Bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4 in /server (#3268)
- [`45f4fdea`](45f4fde) use snippets for nexus CTAs (#3266)
- [`420f5c9d`](420f5c9) min-h-full instead of screen (#3270)
- [`f5b2fab6`](f5b2fab) feat(navigation): add NavSection Holocene component (#3263)
- [`657b2728`](657b272) Adds requested design changes to breadcrumb items (#3267)
- [`6763cc4d`](6763cc4) Remove serena (#3273)
- [`dfff353e`](dfff353) Display Principal fields in Event History (#3272)
- [`2d289bce`](2d289bc) Update CODEOWNERS to wildcard for temporalio/frontend-engineering (#3275)
- [`a2eaf16e`](a2eaf16) Persist workflow view and sort order preferences across navigation (#3260)
- [`c5d4c996`](c5d4c99) Add link from Event Card to jump to event id page from Timeline. Remove unnecessary padding (#3277)
- [`e5b3ea55`](e5b3ea5) fix(deps): upgrade lodash, svelte, kit, storybook, tar-fs for security (#3269)
- [`b44afbe6`](b44afbe) fix(deps): upgrade vite and add picomatch/svgo overrides for security (#3279)
- [`4e8cb4e9`](4e8cb4e) Fix unpause confirmation modal title (#3280)
- [`740b3529`](740b352) Add Slack notification when DESIGN FEEDBACK REQUESTED label is added to a PR (#3282)
- [`7e8170e4`](7e8170e) Add check for COLLABORATOR (#3283)
- [`dc27109d`](dc27109) fix: update nav item margin from mb-1 to mb-2 (#3290)
- [`3e6416d2`](3e6416d) Pass execution runId in workflow request for schedule recent run (#3289)
- [`ae3a1844`](ae3a184) Fix schedule edit double-encoding header fields (#3287)
- [`09c083e0`](09c083e) fix: prevent reset modal from closing on authorization error (#3291)
- [`0aa3b72b`](0aa3b72) Sort namespace picker alphabetically (#3286)
- [`4c3d0057`](4c3d005) Sort alphabetically utility (#3293)
- [`67a988b9`](67a988b) Bump @sveltejs/kit from 2.55.0 to 2.57.1 (#3294)

Co-authored-by: laurakwhit <15069288+laurakwhit@users.noreply.github.com>
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