mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
## Summary Rebuilds the routine detail page as **variation C** — a sub-sidebar shell that splits the page into **ROUTINE** (Overview · Triggers · Variables · Secrets · Delivery) and **OPERATE** (Runs · Activity · History), per the engineering spec on PAP-10730. Replaces the previous 5-tab `?tab=…` layout in `ui/src/pages/RoutineDetail.tsx`. Implements PAP-10732. Design source of truth: PAP-10730 `spec` document; approved direction PAP-10709. ## What changed - **Routing** (`ui/src/App.tsx`): real sub-routes under `routines/:routineId/:section`. Bare `/routines/:id` redirects to the last-viewed section (`localStorage`) or `overview`; old `?tab=…` URLs redirect to the matching section for back-compat. Every section URL is bookmarkable. - **Shell** (`RoutineDetail.tsx`): slim 56px sticky header (title + managed-by-plugin chip + Run / Active toggle), page-local sub-sidebar, full-canvas section body, per-section sticky save bar. All routine state/mutations stay in the shell and flow to sections via a `RoutineDetailContext`. - **New components**: `RoutineSubSidebar` (+ mobile `<Select>` picker, roving keyboard nav), `RoutineSaveBar` (scoped dirty count, ⌘/Ctrl+S save, Esc-discard confirm, 409 conflict recovery with Reload / Overwrite), `RadioCard` primitive (Delivery), `RoutineTriggerCard` (extracted from the inline editor, with human-readable cron), `RoutineActivityRow` (expandable JSON), `lib/cron-readable`, and the per-section components. - **Reuse**: History mounts the existing `RoutineHistoryTab`; Variables mounts `RoutineVariablesEditor` with a provenance banner; Secrets reuses `EnvVarEditor` + the one-time reveal banner. No backend or schema changes. - **States**: per-section loading/empty/error/save-conflict and read-only strip scaffolding (§1.6). ## Testing - New unit tests: sub-sidebar navigation/active/dirty markers, save-bar dirty + ⌘S + conflict recovery, cron helper. - Existing routine tests still pass: `Routines.test.tsx`, `RoutineHistoryTab.test.tsx`, `RoutineRunVariablesDialog.test.tsx`. - `vitest run` (routine scope): **36 passed**. Production `vite build`: **green**. - Screenshots at 1440×900 + 390×844 attached to [PAP-10732](https://example.invalid) (rendered via a new Storybook story with fixture data). ## Out of scope (per spec) - `/routines` list-page redo (follow-up). - Non-owner secret-value visibility (Open Q6 — CEO escalation; built with the spec default). --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
32 lines
975 B
TypeScript
32 lines
975 B
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { describeCron } from "./cron-readable";
|
|
|
|
describe("describeCron", () => {
|
|
it("describes a daily time", () => {
|
|
expect(describeCron("0 14 * * *")).toBe("Every day at 14:00");
|
|
});
|
|
|
|
it("describes every-weekday", () => {
|
|
expect(describeCron("0 14 * * 1-5")).toBe("Every weekday at 14:00");
|
|
});
|
|
|
|
it("describes a single day-of-week", () => {
|
|
expect(describeCron("30 9 * * 1")).toBe("Every Monday at 09:30");
|
|
});
|
|
|
|
it("describes every N minutes", () => {
|
|
expect(describeCron("*/15 * * * *")).toBe("Every 15 minutes");
|
|
});
|
|
|
|
it("describes hourly on a minute", () => {
|
|
expect(describeCron("5 * * * *")).toBe("Every hour at :05");
|
|
});
|
|
|
|
it("returns null for empty or unparseable input", () => {
|
|
expect(describeCron("")).toBeNull();
|
|
expect(describeCron(undefined)).toBeNull();
|
|
expect(describeCron("not a cron")).toBeNull();
|
|
expect(describeCron("1 2 3")).toBeNull();
|
|
});
|
|
});
|