Commit Graph

21 Commits

Author SHA1 Message Date
MechaCat02
bd9c3fa4f0 feat(workflows): M6 — dashboard run-history + DAG view
A per-app "Workflows" tab: list definitions, browse run history, and inspect a
run's per-step progress with a static layered DAG.

- API: the run-detail endpoint now returns each step's `depends_on` (loaded
  from the definition via a new `get_workflow_by_id` reader) so the dashboard
  can draw the graph edges — the run's step rows don't carry them.
- dashboard `$lib/api`: a `workflows` namespace (list / runs / start / run) +
  the matching types.
- `apps/[slug]/workflows/+page.svelte`: workflow table → drill into runs →
  drill into a run. The run view renders a step table plus an SVG DAG (nodes =
  steps colored by status, laid out by longest-path level; edges = depends_on,
  arrowed) and a "Start run" button. Nested/parked steps show their child run.
- AppTabBar + the per-app layout gain the Workflows tab (admin-only).

Tests: `npm run check` clean (0 errors); two Playwright smoke tests
(navigation tabs — workflows loads cleanly + renders its empty state) pass
against the full stack.

With M6 the v1.2 Workflows track (M1 schema/validation → M2 durable
orchestrator → M3 when/templating → M4 nested → M5 SDK/API/CLI → M6 dashboard)
is COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 17:57:56 +02:00
MechaCat02
bce44769dd feat(dashboard): unified design system + persistent per-app tab bar
Addresses two interrelated UX complaints:

1. Browser-default controls leaking through the dark theme
   (native <select> chevrons, OS checkbox/radio look, date-picker
   icons, <details> triangle marker, number input spinners).
2. Subroute pages dropping the main app's tab bar in favor of a back
   link, breaking navigation continuity across users/files/queues/
   dead-letters/queues-[name].

Design tokens (dashboard/src/routes/+layout.svelte):

- Token vocabulary expanded with 18 new variables covering
  text-strong, accent + accent-fg, danger/success/warning bg/fg/border
  triplets, bg-elevated-hover, radii (sm/md/lg/pill), shadow-elev-2,
  and z scale (popover/modal/toast). 7 alias tokens (--muted,
  --link, --text, --color-error, --color-border, --chip-bg,
  --code-bg) absorb the orphan references the F-U-004 remediation
  partially renamed.
- Global :global(...) resets for <select>, <input type='checkbox'>,
  <input type='radio'>, <input type='number'>, <input type='date'>,
  and <details>/<summary> ensure native controls track the dark
  palette out of the box. No per-page edits needed.

Tab consistency:

- New dashboard/src/lib/AppTabBar.svelte renders all 11 per-app
  tabs (Scripts, Domains, Members, Triggers, Topics, Secrets,
  Settings, Users, Files, Queues, Dead letters) as <a> links with
  an active highlight derived from the URL. Tabs that switch
  in-page panels go to ?tab=<id>; tabs that switch routes go to
  the subroute. Admin-only tabs are hidden when canAdmin is false.
- New dashboard/src/routes/apps/[slug]/+layout.svelte loads the
  app once, handles the historical-slug redirect, exposes the
  shared app + canAdmin + canWrite + dead-letter-count state via
  Svelte context, and renders the breadcrumb + AppTabBar above
  every per-app page. The 5 subroute pages drop their own "← back"
  headers since the layout owns them now.
- apps/[slug]/+page.svelte's local-state activeTab becomes URL-
  driven via $page.url.searchParams.get('tab'). Defense-in-depth
  redirect for non-admin viewers landing on admin-only tabs uses
  goto({replaceState:true}) instead of mutating state.

Light-theme leftovers swept on 5 subroute pages:

- dead-letters: error banner, badge, pre/code blocks all swap to
  --color-danger-*, --bg-elevated, --text-primary
- files: button.danger, var(--muted,#666) → token-only
- queues + queues/[name]: bare hex fallbacks removed; .toolbar and
  .auto-refresh styled with tokens; data-testid for the queues
  empty state (already added by previous commit, reaffirmed here)
- users + users/invitations: badge-ok/badge-pending now use
  --color-success-bg/fg and --color-warning-bg/fg; chips use
  --bg-elevated + --text-strong; .create-form gets a token-styled
  surface; row-action buttons gain explicit dark-theme styling

E2E selector updates:

- members.spec.ts, integration.spec.ts, apps.spec.ts — the tab bar
  is now <a> elements (role=link) without a count suffix. Test
  selectors swap from getByRole('button', name: /^Scripts \(\d+\)$/)
  to getByRole('link', { name: 'Scripts' }), etc.
- New navigation/tabs.spec.ts still passes; existing 60+ tests
  unchanged except for the selector swap. Two pre-existing failures
  (routing.spec.ts:79, integration.spec.ts:89) untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 20:55:22 +02:00
MechaCat02
a1b7569d05 fix: post-review followups on slug-vs-UUID refactor
Addresses every finding from the four-agent review of commit aa493b9.

Dashboard — honor redirect_to on subtab loadApp() (closes the silent
historical-slug redirect UX gap):

  - queues/+page.svelte
  - queues/[name]/+page.svelte
  - files/+page.svelte
  - dead-letters/+page.svelte

  After a rename, the URL bar now reflects the canonical slug instead
  of silently rendering the renamed app's data under the stale URL.
  Mirrors the established pattern at apps/[slug]/+page.svelte:619-623.

manager-core:
  - queues_api.rs IntoResponse now uses the JSON envelope shape
    `{"error": "..."}` consistent with every sibling admin api file.
  - triggers_api::delete_trigger reordered: cap check fires BEFORE the
    trigger load, closing the 404-vs-403 existence side channel an
    unauthorized caller could otherwise probe.
  - InMemoryAppRepo mocks in topics_api + triggers_api now implement
    get_by_slug + get_by_slug_or_history (previously
    `unimplemented!()`), unblocking handler-level slug-input tests.
  - Added 4 slug-acceptance tests to topics_api and 2 to triggers_api
    (slug-resolves, unknown-slug-404, historical-slug-resolves,
    create-via-slug). Also added delete-without-cap-is-forbidden test
    pinning the new cap-first order.

e2e:
  - navigation/tabs.spec.ts split per-tab so a regression on one tab
    no longer masks regressions on the others.
  - Negative assertion widened: captures every /api/v1/admin/apps/*
    response and fails on any 4xx/5xx — not just the literal "Cannot
    parse" string. Catches a broader regression shape.
  - networkidle replaced with `expect(<main>).toBeVisible()` —
    networkidle is officially discouraged for SPAs and was at risk of
    timing out behind the queues auto-refresh.
  - Cleanup registration moved BEFORE the create-app API call so a
    flaky create still gets swept up.
  - Queue drilldown route /queues/[name] now covered.
  - Stable `data-testid="queues-empty-state"` replaces fragile
    UI-copy substring match for the positive assertion.
  - Header comment now spells out what this spec does and doesn't
    catch.

docs:
  - serverless_cloud_blueprint.md: slug-history described as
    "200 OK + redirect_to" JSON envelope rather than "301 redirect"
    — matches what apps_api actually implements (SPA can't honor a
    mid-tree HTTP redirect).

Unit-test gap (acknowledged): queues_api, files_api, secrets_api,
dead_letters_api have zero in-process tests. Adding them properly
needs a shared mock-repo helper crate — the standalone trait surface
(QueueRepo + TriggerRepo + ScriptRepository + AuthzRepo + repo-
specific) is ~30 methods per file. Documented inline in queues_api.rs
near the resolver. Integration coverage via crates/picloud/tests/ and
the new e2e spec cover the same paths end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 20:03:10 +02:00
MechaCat02
aa493b9326 fix(admin-api): accept slug or UUID on per-app endpoints
The Queues tab at /admin/apps/default/queues was returning
"Cannot parse `app_id` with value `default`: UUID parsing failed".
27 admin endpoints across 6 files used strict `Path<AppId>` instead
of the canonical `Path<String>` + `resolve_app()` pattern from
app_repo.rs:34. Working endpoints (apps, app_members, users_admin)
all use the lenient pattern; this commit brings the remaining 6
files into line:

- queues_api.rs       — 2 handlers
- files_api.rs        — 2 handlers
- secrets_api.rs      — 3 handlers
- topics_api.rs       — 4 handlers
- dead_letters_api.rs — 5 handlers
- triggers_api.rs     — 10 handlers

The handler bodies (authz, repo calls) are unchanged; only the path
extractor and the per-file `ensure_app_exists` helper (now renamed
`resolve_app`) move. Lib tests updated to pass `.to_string()` at
the call site (Path now takes String, not AppId).

email_inbound_api.rs deliberately stays strict-UUID — it's a public
webhook receiver consumed by external providers, not by the
slug-based dashboard.

Adds Playwright spec `dashboard/tests/e2e/navigation/tabs.spec.ts`
covering every per-app tab (queues, files, dead-letters, users,
invitations, plus the main page hosting triggers/secrets/topics)
with a negative assertion against the "Cannot parse" error text
plus a focused regression test for the original queues report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 18:34:42 +02:00
MechaCat02
f32ed73561 fix(e2e): surface cleanup HTTP failures instead of swallowing them
CleanupRegistry's catch-all was masking every kind of teardown error,
not just the intended "resource already gone" 404. A backend returning
500 on delete would leak orphans run after run without ever surfacing.

Now treat 2xx and 404 as success, log any other status (and any
thrown network error) to stderr with the resource label, and keep
running the remaining items. The suite stays best-effort but no
longer hides accumulating leaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:10:45 +02:00
MechaCat02
beb3bcb97c fix(e2e): use uniqueUsername helper in integration.spec
Date.now() can collide across workers running on the same millisecond
boundary. The worker-aware helper that the rest of the suite uses
side-steps that without changing the test's intent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:41:47 +02:00
MechaCat02
79c8db2cb7 fix(e2e): non-mutating reverse in CleanupRegistry
Array.reverse mutates in place — a defensive double-run() would have
re-reversed the items. Iterate over a copy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:41:42 +02:00
MechaCat02
f4cd883d76 test(e2e): drive the new deactivate confirm modal
Cancels once to assert the modal can be dismissed without side
effects, then confirms to flip the user to inactive, then reactivates
to assert that direction remains one-click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:40:52 +02:00
MechaCat02
b459b99fe9 test(e2e): role-shadowing specs in apps + scripts suites
Lifts loginAsUserToken + pageWithUserToken out of members.spec.ts into
fixtures/role-page.ts (third file that needs them). Adds shadowing
coverage: viewer member sees no New-app / Add-domain / Settings / Save
/ +Add-route, editor sees Save but no Delete header, and CodeMirror
renders contenteditable=false for viewers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:40:09 +02:00
MechaCat02
f694a6d504 test(e2e): orphan sweep at globalSetup
Wipes e2e-* apps and e2e* admin users before the suite starts so a
prior crashed run doesn't accumulate state across runs (45 rows
observed on 2026-05-28). Per-row try/catch keeps it best-effort; a
sweep failure never blocks the suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:37:57 +02:00
MechaCat02
ec3c768262 test(dashboard): add full-stack integration specs
Two scenarios that span the dashboard UI and the data/control plane
end-to-end:

- App + domain claim + script + route all created via the dashboard,
  then the script is invoked through the public URL with the
  matching Host header. Verifies the dashboard actions actually
  reach the orchestrator's route trie.
- API key minted via the dashboard, then used as a bearer token
  against /api/v1/admin/* (the CLI surface). Confirms the scope is
  enforced (script:read passes /scripts, 403s /admins) and that
  revoking via the dashboard immediately invalidates the token.

Also: the B7 copy-token test selected the mint-form Name input via
getByLabel('Name'), which became ambiguous once the integration
test created an app and the Binding dropdown was no longer empty.
Switched both B7 mint flows to placeholder-based selectors.

Suite: 57/57 passing in ~18s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:56:24 +02:00
MechaCat02
3e72ddde78 test(dashboard): stabilize the e2e suite under parallel runs
Three issues found while running the full B1–B8 suite together:

- The B1 logout test was driving the shared admin storageState
  token, invalidating it for every subsequent test. Switched it to
  a fresh login so its session is disposable.
- Bumped navigationTimeout to 30s and capped local workers at 4 to
  cope with the Vite dev server's first-compile cost under
  parallel load. Local also gets one retry to absorb intermittent
  warmup flakiness.
- Cleared a few lint warnings (unused appId / _adminPage vars) and
  belt-and-braces gitignore for playwright artifacts written to
  the repo root when the CLI is invoked from there by accident.

Suite now: 55/55 passing in ~21s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:44:07 +02:00
MechaCat02
cd20ffb580 test(dashboard): add e2e cross-cutting security spec (B8)
Five tests covering platform-wide guarantees: expired-token
redirect, HttpOnly session cookie, bootstrap password not leaked
into the DOM after login, missing-app slug fails gracefully, and
an XSS-sink probe across the main authed routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:43:51 +02:00
MechaCat02
cddd479fd2 test(dashboard): add e2e profile + API keys spec (B7)
Six tests covering /admin/profile: mint instance-wide key with the
reveal/ack flow, the app-binding mutual-exclusion guard (instance
scopes auto-disabled), revoke via the ConfirmModal, the
?denied=users banner, plus adversarial cases (empty-name button
disabled, copy-token writes the full token to the clipboard).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:31:58 +02:00
MechaCat02
8bbcdd86aa test(dashboard): add e2e instance users spec (B6)
Eight tests covering the Users admin page: invite happy path (form
→ reveal modal → ack-gated dismiss → row in table), live username
validation, search filter, deactivate/reactivate, delete with phrase
modal, member-role redirect to /profile?denied=users, plus
adversarial inputs (too-short username, script-tag email).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:28:04 +02:00
MechaCat02
2d56e42699 test(dashboard): add e2e app members spec (B5)
Four tests covering the Members tab: invite + remove (action-menu +
phrase modal), role change, the non-app-admin viewer who never sees
the Members tab at all (cross-context via a second admin login),
and an adversarial that the role dropdown only exposes the
documented set of values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:23:01 +02:00
MechaCat02
f9d9ed8cb4 test(dashboard): add e2e routing spec (B4)
Seven tests covering the Routing tab inside the script editor: add
+ list + remove (handling the window.confirm dialog), match-preview
round trip, path-kind mismatch warning, unclaimed-host warning,
duplicate-route 409, plus reserved-prefix rejection and a path-XSS
adversarial that checks no script tag escapes into the route list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:18:01 +02:00
MechaCat02
c17f8a5bd9 test(dashboard): add e2e script CRUD + editor spec (B3)
Seven tests covering script creation via the Scripts tab, the source
editor (CodeMirror typing + save + reload), Format-button error
surfaces for both Rhai and the test-invoke JSON body, the test-invoke
happy path, settings input validation, and an infinite-loop adversarial
that asserts the sandbox timeout reports cleanly and the editor stays
interactive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:14:09 +02:00
MechaCat02
7198fb4d0e test(dashboard): add e2e apps lifecycle spec (B2)
Seven tests covering app CRUD via the dashboard: create with
slug auto-derive, settings rename, delete with phrase-confirmation
modal, historical-slug takeover via the create form, plus adversarial
inputs (slug normalization, XSS in name/description, oversized name).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:10:47 +02:00
MechaCat02
029a4a199f test(dashboard): add e2e auth & navigation spec (B1)
Eight tests covering the login form, layout-level redirects, logout,
and the obvious adversarial inputs (XSS in username, empty submit,
password field type, leaked tokens). All targeted at /admin/login and
the bounce-back behaviors implemented in +layout.svelte.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:06:13 +02:00
MechaCat02
74f7b3b631 test(dashboard): add Playwright e2e scaffolding with smoke spec
Milestone A of the frontend test plan. Sets up the test rig — config,
globalSetup that probes the backend and seeds an admin session into
storageState, lightweight fixtures, and a 3-test smoke spec — without
yet covering any user journeys (those land in Milestone B).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:03:44 +02:00