From 926d6c9402cc4ccb6a55510cd66a3fae488e4bef Mon Sep 17 00:00:00 2001 From: abrichr Date: Sun, 18 Oct 2026 00:16:44 -0400 Subject: [PATCH] Calm UI: Check it, Watch it run and the steps view in plain words - Check it: finished parts fold to their title; saving steps that check the record the same way say it once; the last part is 'Approve this workflow' and says it is approved once, then stops only when something doesn't match. - Watch it run: 'Run it' and 'Replay the recording' instead of 'Run with safety gates' and 'Replay'; no 'Execute' eyebrow; outcome pills in words; contract labels Right record / Screen checks / Read-back; plain target form copy. - Steps view and Qualification: 'Checked' / 'Not checked' instead of 'Armed' / 'Not armed'; plain column names. - Results: shorter subtitle; the data note moves to the fine print. --- packages/desktop/src/screens/CheckIt.test.tsx | 4 +- packages/desktop/src/screens/CheckIt.tsx | 62 +++++++++++++++---- .../desktop/src/screens/Qualification.tsx | 8 +-- packages/desktop/src/screens/Results.tsx | 6 +- .../desktop/src/screens/WatchRun.test.tsx | 10 +-- packages/desktop/src/screens/WatchRun.tsx | 58 ++++++++--------- packages/desktop/src/styles/app.css | 26 +++++++- .../src/ui/ExecutionTargetForm.test.tsx | 2 +- .../desktop/src/ui/ExecutionTargetForm.tsx | 2 +- .../desktop/src/ui/ProgramWorkbench.test.tsx | 2 +- packages/desktop/src/ui/ProgramWorkbench.tsx | 26 ++++---- packages/desktop/src/ui/ReplayMonitor.tsx | 2 +- 12 files changed, 133 insertions(+), 75 deletions(-) diff --git a/packages/desktop/src/screens/CheckIt.test.tsx b/packages/desktop/src/screens/CheckIt.test.tsx index 3ce0ced31..9bdbb716a 100644 --- a/packages/desktop/src/screens/CheckIt.test.tsx +++ b/packages/desktop/src/screens/CheckIt.test.tsx @@ -312,7 +312,7 @@ it("approves, locks the version, and offers Run it", async () => { const onRun = vi.fn(); render( {}} onRun={onRun} />); - fireEvent.click(await screen.findByRole("button", { name: "Approve for use" })); + fireEvent.click(await screen.findByRole("button", { name: "Approve this workflow" })); expect(await screen.findByText("Approved and locked")).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Run it" })); @@ -322,7 +322,7 @@ it("approves, locks the version, and offers Run it", async () => { it("keeps approval off until the practice item passed", async () => { engine(project()); render( {}} onRun={() => {}} />); - const approve = await screen.findByRole("button", { name: "Approve for use" }); + const approve = await screen.findByRole("button", { name: "Approve this workflow" }); expect((approve as HTMLButtonElement).disabled).toBe(true); }); diff --git a/packages/desktop/src/screens/CheckIt.tsx b/packages/desktop/src/screens/CheckIt.tsx index 9c42c1495..86dfaed55 100644 --- a/packages/desktop/src/screens/CheckIt.tsx +++ b/packages/desktop/src/screens/CheckIt.tsx @@ -118,11 +118,25 @@ function CheckSection({ >
-
-

{title}

- {stateWord(state, doneWord)} -
- {children} + {/* A finished part folds to its title so the open question stands out; + the last part keeps its Run button in view. */} + {state === "done" && index < 6 ? ( +
+ +

{title}

+ {stateWord(state, doneWord)} +
+ {children} +
+ ) : ( + <> +
+

{title}

+ {stateWord(state, doneWord)} +
+ {children} + + )}
); @@ -301,6 +315,18 @@ function EffectQuestion({ ); } +/** Armed saving steps grouped by their identical sentence, in first-seen order. */ +function armedIdentityGroups( + writes: T[], +): { text: string; count: number }[] { + const groups = new Map(); + for (const step of writes) { + if (!step.identityArmed) continue; + groups.set(step.identityText, (groups.get(step.identityText) ?? 0) + 1); + } + return [...groups].map(([text, count]) => ({ text, count })); +} + export function CheckIt({ workflowId, onExactChecks, @@ -914,7 +940,16 @@ export function CheckIt({ it asks how to recognize the right record.

)} - {plan.writes.map((step) => ( + {/* Saving steps that check the record the same way say it once. */} + {armedIdentityGroups(plan.writes).map((group) => ( +
+

{group.text}

+ {group.count > 1 && ( +

For each of its {group.count} saving steps.

+ )} +
+ ))} + {plan.writes.filter((step) => !step.identityArmed).map((step) => (

{step.identityText}

{!step.identityArmed && step.canArm && plan.hasProject && ( @@ -1116,7 +1151,7 @@ export function CheckIt({ @@ -1127,7 +1162,8 @@ export function CheckIt({

This version is checked, approved, and encrypted on this - computer. It's ready to run on real items. + computer. It’s ready to run on real items, and it stops + to ask you only when something doesn’t match.

diff --git a/packages/desktop/src/screens/Qualification.tsx b/packages/desktop/src/screens/Qualification.tsx index df6dfef14..011e193e8 100644 --- a/packages/desktop/src/screens/Qualification.tsx +++ b/packages/desktop/src/screens/Qualification.tsx @@ -120,9 +120,9 @@ function signalNeedsExtraction(source: QualificationIdentitySourceKind): boolean } function identityLabel(node: QualificationNode): string { - if (!node.identity?.applicable) return "not applicable"; - if (node.identity.armed) return "armed"; - return node.identity.reason || "not armed"; + if (!node.identity?.applicable) return "not needed"; + if (node.identity.armed) return "checked"; + return node.identity.reason || "not checked"; } function actionTitle(node: QualificationNode): string { @@ -1259,7 +1259,7 @@ export function Qualification({ }} > - {selectedControls.identity.armed ? "armed" : "not armed"} + {selectedControls.identity.armed ? "checked" : "not checked"} {selectedControls.identity.policy && ( void }) {
@@ -309,7 +309,9 @@ export function Results({ onNeedsYou }: { onNeedsYou: () => void }) { Only items that OpenAdapt saved and then read back from the record count as done and checked. Items a person finished or confirmed count under Needed a person. “No person” marks an item saved with every - check passed while “{SWITCH_LABEL}” was on. + check passed while “{SWITCH_LABEL}” was on. OpenAdapt doesn't send + these results anywhere. If you turn on the hosted runner, it reports + only a PHI-free outcome for each Cloud job it runs.

diff --git a/packages/desktop/src/screens/WatchRun.test.tsx b/packages/desktop/src/screens/WatchRun.test.tsx index 5114ac668..ccf6651d9 100644 --- a/packages/desktop/src/screens/WatchRun.test.tsx +++ b/packages/desktop/src/screens/WatchRun.test.tsx @@ -118,19 +118,19 @@ it("clears prior terminal evidence while a new run is in flight", async () => { onTeach={() => {}} />, ); - await screen.findByText("Outcome evidence"); + await screen.findByText("What was checked"); - fireEvent.click(screen.getByRole("button", { name: "Run with safety gates" })); + fireEvent.click(screen.getByRole("button", { name: "Run it" })); await waitFor(() => - expect(screen.queryByText("Outcome evidence")).toBeNull(), + expect(screen.queryByText("What was checked")).toBeNull(), ); await act(async () => { finishRun(verified); await pendingRun; }); - expect(await screen.findByText("Outcome evidence")).toBeTruthy(); + expect(await screen.findByText("What was checked")).toBeTruthy(); }); it("keeps a report visible while local history is retried", async () => { @@ -174,7 +174,7 @@ it("keeps a report visible while local history is retried", async () => { await waitFor(() => expect(screen.queryByText("Local run history needs attention")).toBeNull(), ); - expect(screen.getByText("Outcome evidence")).toBeTruthy(); + expect(screen.getByText("What was checked")).toBeTruthy(); }); function firstWorkflowReview( diff --git a/packages/desktop/src/screens/WatchRun.tsx b/packages/desktop/src/screens/WatchRun.tsx index 00926aa4e..516634997 100644 --- a/packages/desktop/src/screens/WatchRun.tsx +++ b/packages/desktop/src/screens/WatchRun.tsx @@ -54,10 +54,10 @@ function issueForReport(report: RunReport): RunIssue | null { } const CONTRACT_LABELS = { - authorization: "Authorization", - identity: "Identity", - postcondition: "Postcondition", - effect: "Business effect", + authorization: "Allowed to act", + identity: "Right record", + postcondition: "Screen checks", + effect: "Read-back", } as const; function reviewRisk( @@ -385,9 +385,7 @@ export function WatchRun({
-

- {firstWorkflow ? "First supervised run" : "Execute"} -

+ {firstWorkflow &&

First supervised run

}

{firstWorkflow ? "Review the workflow, then watch it run" @@ -397,14 +395,14 @@ export function WatchRun({ {!firstWorkflow && (
)} @@ -413,8 +411,7 @@ export function WatchRun({ {firstWorkflow && ( {reviewReady && currentReview ? ( @@ -522,7 +519,6 @@ export function WatchRun({ {!firstWorkflow &&
- Advanced deployment details + Settings for the person who sets this up
- {report.outcome.replaceAll("_", " ").toLowerCase()} + {resultCopy(plainResultForReport(report)).label} + + + {report.outcome_details.execution_completed + ? "Ran to the end" + : "Stopped before the end"} - {report.outcome_details.profile && ( - {report.outcome_details.profile} - )} {report.outcome_details.production_eligible - ? "production eligible" - : "not production eligible"} - - - {report.outcome_details.execution_completed - ? "execution completed" - : "execution stopped"} + ? "Ready for real items" + : "Not ready for real items yet"} + {report.outcome_details.profile && ( + + {report.outcome_details.profile} + + )}
{( @@ -768,7 +765,7 @@ export function WatchRun({ {report?.metrics && ( - +
Steps @@ -797,8 +794,7 @@ export function WatchRun({ {firstRunFinished && (

diff --git a/packages/desktop/src/styles/app.css b/packages/desktop/src/styles/app.css index 0169d160a..925144efb 100644 --- a/packages/desktop/src/styles/app.css +++ b/packages/desktop/src/styles/app.css @@ -1195,7 +1195,7 @@ select.input:focus { .advanced-target summary { color: var(--text-muted); cursor: pointer; - font-family: var(--font-mono); + font-family: var(--font-ui); font-size: var(--fs-sm); } .advanced-target-body { @@ -1262,7 +1262,7 @@ select.input:focus { display: flex; justify-content: space-between; align-items: baseline; - font-family: var(--font-mono); + font-family: var(--font-ui); font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--space-3); @@ -3111,3 +3111,25 @@ select.input:focus { gap: var(--space-2); margin-bottom: var(--space-3); } +.check-detail-count { + margin: var(--space-1) 0 0; + color: var(--text-muted); + font-size: var(--fs-sm); +} +.check-step-fold > summary { + cursor: pointer; + list-style: none; +} +.check-step-fold > summary::-webkit-details-marker { + display: none; +} +.check-step-fold > summary .check-step-state::after { + content: "Show"; + margin-left: var(--space-3); + color: var(--text-faint); + text-decoration: underline; + text-underline-offset: 3px; +} +.check-step-fold[open] > summary .check-step-state::after { + content: "Hide"; +} diff --git a/packages/desktop/src/ui/ExecutionTargetForm.test.tsx b/packages/desktop/src/ui/ExecutionTargetForm.test.tsx index 64947c5f7..c788977a8 100644 --- a/packages/desktop/src/ui/ExecutionTargetForm.test.tsx +++ b/packages/desktop/src/ui/ExecutionTargetForm.test.tsx @@ -103,7 +103,7 @@ it("never claims Available before detection arrives", () => { it("keeps every surface selectable even when not available", () => { const changes: ExecutionTarget[] = []; renderForm({ backend: "web" }, report(), (t) => changes.push(t)); - const select = screen.getByLabelText("Application surface") as HTMLSelectElement; + const select = screen.getByLabelText("App type") as HTMLSelectElement; expect(select.options.length).toBe(6); fireEvent.change(select, { target: { value: "citrix" } }); expect(changes).toEqual([{ backend: "citrix" }]); diff --git a/packages/desktop/src/ui/ExecutionTargetForm.tsx b/packages/desktop/src/ui/ExecutionTargetForm.tsx index 08fcd0e89..2cb608d67 100644 --- a/packages/desktop/src/ui/ExecutionTargetForm.tsx +++ b/packages/desktop/src/ui/ExecutionTargetForm.tsx @@ -152,7 +152,7 @@ export function ExecutionTargetForm({ return ( <> - +