Skip to content

feat(cron): Redesign Cron Jobs page with card UI, inline editing, and run history#313

Merged
shanselman merged 7 commits into
openclaw:masterfrom
christineyan4:christiney/cron
May 12, 2026
Merged

feat(cron): Redesign Cron Jobs page with card UI, inline editing, and run history#313
shanselman merged 7 commits into
openclaw:masterfrom
christineyan4:christiney/cron

Conversation

@christineyan4
Copy link
Copy Markdown
Contributor

@christineyan4 christineyan4 commented May 11, 2026

Summary

Complete redesign of the Cron Jobs management page in the companion app, replacing the basic list view with an interactive card-based UI that supports inline editing, per-job run history, and real-time status indicators.

Demo

Video walkthrough

Changes

Card-based UI redesign

  • Expandable job cards with header badges showing schedule, enabled/disabled state, and last run result
  • Compact summary line with relative timestamps ("ran 2m ago · next in 5m")
  • Human-readable schedule display (e.g., "Every 15 minutes" instead of raw cron expressions)

Inline job editing

  • Edit job name, prompt, schedule, delivery target, and options directly from the card
  • Schedule presets (every 1/5/15/30 min, hourly, daily) with visual highlighting
  • Text-based date/time input for "at" schedules with proper timezone handling (UTC ↔ local)
  • Checkbox options for wake mode, delete after run, and enabled state

Job creation

  • "New Job" form with the same inline editing experience
  • Default scheduled time set to 5 minutes from now

Per-job run history

  • Expandable history panel within each card showing past runs
  • Status badges (success/error), run summaries, duration, and timestamps
  • Pagination with "Load more" button
  • Wired via cron.runs gateway RPC

Real-time status

  • "⏳ Running" badge on card header when a job is actively executing
  • Detects running state from gateway runningAtMs field AND infers from scheduled time passing
  • "overdue" label suppressed while job is running
  • "Run Now" button shows persistent "Running..." state across card rebuilds
  • Cards stay expanded across data refresh cycles

One-shot job feedback

  • InfoBar notification when a deleteAfterRun job completes and is removed from the list
  • Auto-dismisses after 10 seconds with proper timer cancellation

Bug fixes

  • Fixed RunCronJobAsync sending wrong API params ({id} not {jobId})
  • Fixed card expand state lost on data refresh
  • Fixed history panel staying open after card collapse
  • Fixed detail panel lookup crash when summary line is absent
  • Removed duplicate delete-after-run checkbox from Advanced section

Files changed

  • src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml — Page layout with InfoBar, job list, empty state
  • src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs — All card building, editing, history, state management
  • src/OpenClaw.Shared/OpenClawGatewayClient.cs — Fixed RunCronJobAsync params
  • src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml.cs — Event routing for CronRunsUpdated
  • src/OpenClaw.Tray.WinUI/App.xaml.cs — Event subscription for CronRunsUpdated

Testing

  • 1220 shared tests pass, 62 UI tests pass
  • 2 pre-existing CLI test failures (unrelated: SkillMd drift and ACL tests)
  • Manual verification of all features listed above

Demo

Christine Yan and others added 6 commits May 6, 2026 13:00
- Fix cron data flow: handle {jobs:[...]} response shape, nested state fields
- Add missing gateway API methods: AddCronJob, UpdateCronJob, RequestCronRuns
- Add auto-refresh on cron events and cron.add/update/remove/run responses
- Redesign cron cards with click-to-expand pattern matching agent events UI
- Add schedule formatting for cron/every/at types with human-readable output
- Add compact summary line with relative times (ran Xm ago, next in Xh)
- Add job actions: Run Now, Enable/Disable toggle, Remove
- Add inline badges for schedule, enabled status, and run result
- Add chips with labels for session target, wake mode, and delivery
- Cache cron data in HubWindow for page navigation persistence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ting

- FormatCronSchedule now shows readable text (e.g. 'daily at 9am')
  instead of raw cron expressions for common patterns
- Checkbox text vertically centered with explicit TextBlock content
- Font size bumped to 14 to match other form elements
- Selected preset gets bold text in addition to accent style

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Edit form now appears inline, replacing the job card in-place
- Replaced ListView with manually-managed StackPanel for full control
- Fixed timezone bug: 'at' schedule now correctly converts UTC to local
- Replaced scroll-wheel DatePicker/TimePicker with CalendarDatePicker + TextBox
- Added validation: 'at' schedule rejects past dates with error message
- Guard against card rebuild while editing inline

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add inline per-job run history with status badges, summaries, durations
- Fix RunCronJobAsync to send correct API params ({id} not {jobId})
- Add persistent card expand state across data refreshes
- Add Running badge on card header (detects via runningAtMs and scheduled time inference)
- Suppress overdue label when job is actively running
- Add InfoBar notification for completed one-shot (delete-after-run) jobs
- Fix InfoBar layout overlap (own Grid row)
- Fix timer race conditions (Run Now and InfoBar dismiss)
- Default scheduled time to 5 minutes from now
- Wire CronRunsUpdated event through App -> HubWindow -> CronPage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lean up unused param

- Remove redundant DeleteAfterRun checkbox from Advanced section (was unsynced with At schedule checkbox)
- Dispose CancellationTokenSource before replacing in InfoBar auto-dismiss
- Remove unused force parameter from RunCronJobAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolved conflicts from upstream connection manager refactor:
- Removed old direct _gatewayClient subscribe/unsubscribe blocks
- Kept upstream's _connectionManager pattern
- Added CronRunsUpdated event to OnOperatorClientChanged subscribe/unsubscribe

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@christineyan4 christineyan4 marked this pull request as ready for review May 11, 2026 17:33
@shanselman
Copy link
Copy Markdown
Contributor

Thanks for the Cron Jobs redesign — the card UI, inline editing direction, local-time handling, and run history UX are all useful improvements. CI is green, and I confirmed the interface was updated, so there is no compile blocker. I do think a few correctness/security items should be addressed before merge.

1. Advanced form fields are shown but not saved

The form exposes controls such as FormClearContext and FormOnFailure, but OnFormSaveClick does not appear to read them or include them in the add/update payload.

Examples from the PR:

  • XAML exposes user-facing choices like “Clear agent context before run” and “On failure”.
  • OnFormSaveClick builds job / patch payloads, but those values are not serialized.

Impact: users can toggle these settings and click save, but their choices are silently discarded. That is worse than not showing the controls because it looks like the setting was accepted.

Suggested fix:

  1. If the gateway supports these fields, serialize them consistently for both create and update.
  2. If the gateway does not support them yet, remove the controls or disable them with explanatory text.
  3. Add a small test or fixture-level check for the generated payload shape if possible.

2. Run history renders raw gateway error / summary strings

BuildRunEntry displays run error and summary values directly from the gateway payload. Truncating to a fixed length is useful for layout, but it is not redaction.

Impact: cron run failures can include stack traces, file paths, URLs, token fragments, or command output. This repository has been tightening ex.Message leaks recently, so this new surface should follow the same pattern.

Suggested fix:

  • Sanitize before rendering with the repo's existing redaction helper/pattern (SecretRedactor, TokenSanitizer, or the appropriate existing equivalent for UI text).
  • If the raw gateway error is useful for diagnostics, keep detailed text in logs/support artifacts after redaction, and show a safer summary in the UI.

3. Please verify and align cron API parameter names

This PR correctly changes cron.run to send id:

TrySendTrackedRequestAsync("cron.run", new { id = jobId, force });

But other cron calls still send jobId:

UpdateCronJobAsync(new { jobId, patch = ... })
RequestCronRunsAsync(...) -> new { jobId, limit, offset }

cron.remove already uses id. If the gateway contract expects id consistently across cron APIs, then toggles, edits, and run-history requests may fail even though run/remove work.

Suggested fix:

  1. Confirm the gateway contract for cron.update and cron.runs.
  2. If the contract is id, update payloads to use id consistently.
  3. If the contract intentionally differs by method, please add a comment near the client methods so future changes do not “normalize” the wrong way.

4. Run Now can get stuck in Running...

OnRunNowClick adds the job to _runningJobIds and disables the button. The state clears only when a later cron list refresh shows completion. If the request fails, the gateway never sends a refresh, or the list response is delayed/lost, that job can remain stuck as running until the user navigates away or refreshes enough state.

Suggested fix:

  • Clear _runningJobIds on request failure.
  • Add a bounded timeout fallback, e.g. remove the running marker after 60 seconds and refresh the list.
  • Optionally show a non-blocking warning if no completion state arrives.

5. Card expand/collapse should not use raw PointerReleased

Cards currently toggle expansion via PointerReleased. In a ScrollViewer, pointer release can happen after a scroll/drag and accidentally expand/collapse a card. The old ListView behavior avoided much of that gesture ambiguity.

Suggested fix: use Tapped, or track pointer movement and suppress toggle when the pointer moved beyond a drag threshold.

6. Redundant list refreshes on run/remove

HandleKnownResponse now refreshes on cron mutations, and some UI handlers also manually refresh. For example remove can trigger a refresh via known-response handling and also via the ContinueWith in OnRemoveClick.

Impact: unnecessary gateway requests and possible flicker/rebuilds.

Suggested fix: centralize the refresh path. Prefer the gateway-client known-response refresh or the UI refresh, not both.

What looks good

  • The { jobs: [...] } and nested state fallback parsing is a good defensive compatibility improvement.
  • cron.run using id looks like a real contract fix.
  • Preserving expanded card IDs across refreshes is a nice UX detail.
  • Run history with pagination is a strong feature addition once the error text is sanitized.

The main blockers for me are the ignored form controls and raw history text. The API parameter alignment is the other thing I would like confirmed before merge.

Blockers:
- Remove unsupported FormOnFailure/FormClearContext controls (gateway doesn't support these fields)
- Add SanitizeForDisplay() for defense-in-depth error string sanitization (paths, tokens)
- Centralize cron API wire format: UpdateCronJobAsync(id, patch), consistent 'id' param naming

Non-blocking:
- Fix Run Now stuck state: add failure handling + 90s safety timeout
- Switch card click from PointerReleased to Tapped to prevent scroll-triggered toggles
- Remove redundant list refresh in OnRemoveClick (HandleKnownResponse already refreshes)

Other:
- Disabled job buttons use opacity dimming with click guards (WinUI disabled buttons leak pointer events)
- Add 12 unit tests: 6 payload shape tests + 6 display sanitization tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@shanselman shanselman merged commit 61fcd39 into openclaw:master May 12, 2026
9 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🤖 This is an automated response from Repo Assist.

Complementing @shanselman's review with a few specific bugs found in the diff that weren't yet mentioned:

1. RequestCronRunsAsync default limit mismatch

IOperatorGatewayClient declares limit = 20 but OpenClawGatewayClient implements limit = 50:

// IOperatorGatewayClient.cs
Task RequestCronRunsAsync(string? id = null, int limit = 20, int offset = 0);

// OpenClawGatewayClient.cs
public async Task RequestCronRunsAsync(string? id = null, int limit = 50, int offset = 0)

All current call sites pass limit: 20 explicitly, so there's no behavioral impact yet. But the interface contract is violated — any caller relying on the default value will silently get 50 records. The default should match (20) or the interface should be updated intentionally.

2. OnRemoveClick blocks removing disabled jobs

private void OnRemoveClick(object sender, RoutedEventArgs e)
{
    var vm = _jobs.Find(j => j.Id == jobId);
    if (vm != null && !vm.IsEnabled) return;  // ← blocks removal

The !vm.IsEnabled guard makes sense for "Run Now" (you shouldn't fire a disabled job), but blocking removal of disabled jobs seems unintended. A user who disables a job and then wants to delete it will be stuck — the Remove button does nothing.

3. ToggleEnabledGlyph copy-paste bug — same glyph for both states

public string ToggleEnabledGlyph => IsEnabled ? "\uE7E8" : "\uE7E8";

Both branches return the same glyph (\uE7E8 = Pause). The button icon never changes between enabled and disabled states. The second branch should presumably be a different glyph, e.g. \uE768 (▶ Play) or \uE7FD (toggle-on), matching the ▶ Enable emoji text in ToggleEnabledLabel.

Generated by 🌈 Repo Assist, see workflow run. Learn more.

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@97143ac59cb3a13ef2a77581f929f06719c7402a

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