Skip to content

feat(schedules): B3 — schedule management SPA + enablement#564

Merged
philmerrell merged 2 commits into
developfrom
feature/scheduled-runs-b3-spa
Jul 6, 2026
Merged

feat(schedules): B3 — schedule management SPA + enablement#564
philmerrell merged 2 commits into
developfrom
feature/scheduled-runs-b3-spa

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Summary

  • Schedules management page (frontend/ai.client/src/app/schedules/): signal-based list (label, cadence summary, next run, state badge, last-run status + link to session), create/edit form (label, prompt, bounded cadence — daily/weekday/weekly + hour + IANA timezone, optional assistant, optional tool selection), pause/resume, delete-with-confirm.
  • Enablement UX: grant-status banner + "Enable scheduled runs" (calls the new POST /runs/grant); paused_error with stateReason: "reauth_required" surfaces "Your access expired — log in and re-enable"; stateReason: "oauth_required" surfaces a "reconnect a tool" hint linking to /settings/connectors.
  • Visibility gating: no dedicated client-side capability signal exists yet, so the sidenav "Scheduled Runs" entry and page both ride the /schedules list call — a 403/404 flips ScheduleService.accessible$ to false and the UI fails gracefully (page shows a "not available" state; nav entry stays hidden).
  • Backend: POST /runs/grant added to apis/app_api/runs/routes.py, reusing the exact create-on-enable _resolve_grant logic POST /runs/now already used (no duplication) — same cookie + kill-switch + scheduled-runs capability gating. Lets a user enable scheduled runs without running a prompt first.

Known gap (flagged per the brief)

The B1 PATCH /schedules/{id} (update_scheduled_prompt) only writes a field when the incoming value is not None — there is currently no way to clear assistantId or enabledTools over the wire. The edit form has explicit "Detach assistant" / "Reset to all my tools" checkboxes to make the intent explicit, but they can only omit the field from the PATCH (a no-op against the current backend), not actually clear a previously-set value. A real fix needs a backend follow-up (e.g. a sentinel value, or splitting into a dedicated clear affordance) — out of scope for this PR per the brief's instruction to consume the B1 CRUD as-is.

Test plan

  • Backend: uv run python -m pytest tests/apis/app_api/test_runs_routes.py tests/routes/test_schedules_routes.py tests/architecture/ -q — 62 passed
  • Frontend: npx tsc --noEmit and npx ng build — clean
  • Frontend: npx ng test --watch=false --include='src/app/schedules/**/*.spec.ts' — 46 passed
  • Frontend: npx ng test --watch=false --include='src/app/components/sidenav/sidenav.spec.ts' — 8 passed
  • Frontend: full ng test suite — passes; one pre-existing spec (shared-view.page.spec.ts) intermittently times out under full-suite worker-pool load (reproduces the same way on develop without these changes once the suite is large enough — documented cross-spec pollution in shared worker pools, not a regression from this PR)

No new npm/uv packages were added — everything is built from existing dependencies (@angular/forms, @angular/cdk/dialog, @ng-icons/heroicons, Intl.supportedValuesOf at runtime with a static fallback).

🤖 Generated with Claude Code

Adds the user-facing schedules feature: a signal-based list/create/edit
page under frontend/ai.client/src/app/schedules/ (bounded cadence UI —
daily/weekday/weekly + hour + IANA timezone, optional assistant + tool
snapshot, pause/resume/delete with confirm), the headless-grant
enablement UX (status banner, "Enable scheduled runs", paused_error
reauth_required / oauth_required affordances), and capability-gated nav
visibility that rides the /schedules list call's 403/404 rather than a
dedicated client signal.

Backend: adds POST /runs/grant to apis/app_api/runs/routes.py, sharing
the existing _resolve_grant create-on-enable logic with /runs/now so a
user can turn on scheduled runs without running a prompt first. Same
kill-switch + scheduled-runs capability gating.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@philmerrell

Copy link
Copy Markdown
Contributor Author

Review (orchestrator pass)

Verdict: clean, no blockers. Backend POST /runs/grant reuses _resolve_grant (no duplication), rides require_scheduled_runs_user (cookie + SCHEDULED_RUNS_ENABLED 404 + scheduled-runs capability 403), and keeps the same 409 semantics. SPA mirrors the established service/signal pattern; paused-state UX (reauth_required → re-enable, oauth_required → reconnect) matches what B2's worker will write. Nav gating on the /schedules call's success/403 is the documented fallback — fine for now.

Real follow-up (flagged by the agent, confirmed)

The B1 update_scheduled_prompt skips None, so there's no wire-level way to clear assistantId / enabledTools on edit. B3 did the honest thing — "Detach assistant" / "Reset tools" checkboxes that omit the field (documented no-op) rather than faking a null. This needs a small backend follow-up (a sentinel or an explicit clear affordance in the PATCH contract) before those checkboxes actually work end-to-end. Tracked on the epic; not a blocker for this PR (the create path and all non-clearing edits work).

Note

The intermittent shared-view.page.spec timeout under full-suite load is pre-existing (the known shared-worker-pool flake), not from this PR — confirmed by isolation. Fine.

The schedule-form and schedules-list specs used provideRouter([]), but the
components navigate programmatically (router.navigate(['/schedules']) etc.)
on save/cancel/create/edit. With no matching route the navigation rejects
(NG04002) AFTER the test body, surfacing as unhandled rejections that fail
the whole vitest process even though all 1378 tests pass. Register the
target routes so the real router resolves them. Scoped ng test: 46 passed,
0 errors.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@philmerrell

Copy link
Copy Markdown
Contributor Author

CI fixed (commit 3355dcc). The frontend job failed on 4 unhandled rejections — NG04002: Cannot match any routes. URL Segment: 'schedules' — not test-assertion failures (all 1378 tests passed). The schedule-form and schedules-list specs used provideRouter([]), but the components navigate programmatically on save/cancel/create/edit; with no matching route the navigation rejects after the test body and fails the whole vitest process. Registered the target routes in both specs (fixed the list spec's latent case too). Frontend job now green (57s); infra + scan green; backend re-running (unchanged by this commit, was already passing).

@philmerrell philmerrell marked this pull request as ready for review July 6, 2026 03:21
@philmerrell philmerrell merged commit d4c47b7 into develop Jul 6, 2026
4 checks passed
@philmerrell philmerrell deleted the feature/scheduled-runs-b3-spa branch July 6, 2026 03:21
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.

1 participant