Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ followed it. Statuses: Complete, Partial, Missing, Intentionally changed.
| X-ray and provenance (FM-4) | Complete | Canvas click-to-trace, provenance card (node, catalog entry, creating/updating events, rules), reverse violet wash for the playhead delivery. This pass added a keyboard path: "trace" buttons in the inspector components tab. |
| Governed repair / "argues back" (FM-1) | Complete | fixture-001 carries two real repairs (`rule.destructive-requires-alertdialog`, `rule.alertdialog-action-label-specific`); gate ticker + gates tab show findings and verbatim repair messages. |
| Break-it mode (FM-8) | Complete | 7 conditions across 6 failure kinds through the ordinary RunView. This pass added offline behavior: three conditions replay their recorded catch when the local agent is absent (the deployed static site); live-only conditions say so plainly. |
| Scenarios | Partial by plan | 5 ready (project-deletion, appointment-booking, recipe-creator, support-triage, onboarding), 1 planned (hotel-reservations) presented honestly with its blockers. support-triage and onboarding shipped 2026-07-21 with owner-authored governance (record-collection, data-collection) and live gpt-oss recordings (fixtures 010-014). hotel-reservations needs new design-system components (plan §17, open question 2) — deferred, not a defect. |
| Scenarios | Complete | 6 ready (project-deletion, appointment-booking, recipe-creator, support-triage, onboarding, hotel-reservations); no planned placeholders remain. support-triage, onboarding, and hotel-reservations shipped 2026-07-21 with owner-authored governance (record-collection, data-collection, transactional-review) and live gpt-oss recordings (fixtures 010-016). hotel-reservations rode the selection vocabulary (list, selectable-card, metadata-list) that plan §17 deferred; the planned-entry shelf treatment stands by for the next expansion. |
| Recipe shared-state experience (FM-6) | Complete | Real dishes, per-serving amounts, constraint swaps; this pass added numbered cooking instructions that the constraint swaps rewrite, on both the deterministic and generated paths, and re-recorded fixture-006 (live `gpt-oss`, 2026-07-11) with the model laying out an instructions table and the labeled enhancement seeding responder content into it. |
| Live BYO-key mode | Intentionally changed | The plan's "browser BYO key" became "no credentials in the browser": modelRef selects `scripted` or local `ollama:*`; hosted keys live only in the agent's env. Stricter than the plan; documented in the UI. |
| HITL (FM-7) | Partial | Appointment booking pauses for slot selection and validates round-trips; the agent's question is not itself a governed AlertDialog. Post-MVP. |
Expand Down
52 changes: 52 additions & 0 deletions docs/IMPLEMENTATION_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1001,3 +1001,55 @@ five onboarding.spec.ts tests fail on the old registry.
`studio-shell.spec.ts`'s planned-reveal test retargeted
`scenario-onboarding` → `scenario-hotel-reservations` (the last planned
entry) — a retarget, not a new assertion; it passes on both builds.

## 2026-07-21 — selection vocabulary + hotel reservations: the shelf completes

Two PRs, per the approved platform design: the vocabulary first (inert
until governed), then the intent and the scenario that validates it,
atomically.

Vocabulary (PR A): `list`, `selectable-card`, `metadata-list` — each a
faithful projection of a real upstream Astryx component, none
hotel-specific. metadata-list is data-driven ({label,value} items,
grammar-required) because sub-components carry no props under
grammar-constrained generation; the one deliberate upstream divergence
is declared as `x-drift` on the prop, and drift-check now reports
x-drift props as acknowledged divergences. Renderers landed in BOTH
design systems (Dialog stays the sole shadcn placeholder, 11 of 12).
The record-collection table rule's "widen to a choice set" caveat was
revised honestly: choice sets are inexpressible (component-choice
require is conjunctive); requireOneOf noted as candidate dspack-gen
work.

Governance + scenario (PR B): intent `transactional-review` with five
floor rules (options-in-a-collection with an explicit action; ≥2
alternatives per list; every option states attributes; metadata carries
items; every option is named). Exact counts, upper bounds, and
"exactly one primary action per option" are inexpressible in current
rule types and remain prose guidance, stated in the rules' own
rationales. The worked example is deliberately NOT a hotel —
`ex.plan-comparison`, pricing plans — so the vocabulary's domain
neutrality is exercised before any hotel copy exists.

Recording (Spark, ollama:gpt-oss:latest, published dspack-gen 0.1.2):
the very first live run on the deepest composite yet (card → list →
selectable-cards → metadata-lists) passed clean with populated
attributes — the 0.1.2 required-props grammar mechanism carrying over.
fixture-015 (clean): three named, badged hotels, each with a 3-item
metadata list, one "Book now" action, first pass. fixture-016
(argues-back): asked for a single no-options recommendation, the model
shipped a bare apology text node; rule.review-presents-options caught a
surface with nothing to choose (three findings — list, selectable-card,
button all absent) and the repair shipped two named hotels compared on
their attributes. First --require-repair attempt, unforced.

Break-it: `review-without-options` (recordedCatch → argues-back). The
planned-entry shelf machinery keeps its code path but nothing renders
it: studio-shell's planned-reveal test became "six ready, no planned
placeholders", and permalinks' not-ready-link test now uses an invented
id. Docs: AUDIT Scenarios row goes Complete (6 ready / 0 planned).

Fail-first evidence: pre-contract, recording with the new intent failed
with getIntent's "intent 'transactional-review' is not registered"; all
four hotel-reservations.spec.ts tests and the rewritten studio-shell
assertion failed on the pre-scenario build.
6 changes: 3 additions & 3 deletions docs/NAV-REFACTOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ session file, or run it live and download one."*
## 2. Current architecture

- **Scenario** = `scenarioId` (React state in `Studio`), canonical ids from
`packages/scenarios/src/registry.ts`: ready `recipe-creator`,
`packages/scenarios/src/registry.ts`: all six ready `recipe-creator`,
`appointment-booking`, `project-deletion`, `support-triage`,
`onboarding`; planned (not selectable) `hotel-reservations`.
`onboarding`, `hotel-reservations`. No planned entries remain.
- **Operation** = `view` state: `"replay" | "live" | "break" | "canvas"`.
X-ray and Fork/diff are not top-level views: they are controls inside
`RunView` and operate on whatever run is loaded — already
Expand Down Expand Up @@ -158,7 +158,7 @@ local agent · **gap** = no support yet. Cells marked † change with this work.
| *(any scenario)* | — | — | `malformed-import`: **universal** (client-side validator; mislabeled project-deletion → `scenarioIndependent`†) | — | — | — |
| `support-triage` | **recorded** ×2 (fixtures 010/011) | **live-only** (generation: `scripted` deterministic or `ollama:*`; zero agent code, same as project-deletion) | `records-as-prose` **recorded** (catch=argues-back) | **universal** via fixture-011 final surface | **universal** | **universal** |
| `onboarding` | **recorded** ×3 (fixtures 012/013/014) | **live-only** (generation: `scripted` deterministic or `ollama:*`; zero agent code) | `ask-without-a-form` **recorded** (catch=argues-back) | **universal** via fixture-012 final surface | **universal** | **universal** |
| `hotel-reservations` (planned) | **gap** (not selectable; shelf states needs) | **gap** | **gap** | **gap** | n/a until a run loads | n/a |
| `hotel-reservations` | **recorded** ×2 (fixtures 015/016) | **live-only** (generation: `scripted` deterministic or `ollama:*`; zero agent code) | `review-without-options` **recorded** (catch=argues-back) | **universal** via fixture-015 final surface | **universal** | **universal** |

Unused asset worth noting: `packages/replay/fixtures/fixture-004.json`
("Scheduling, generated live", intent `scheduling`, 12 events) is bundled but
Expand Down
64 changes: 64 additions & 0 deletions e2e/hotel-reservations.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Hotel reservations: the transactional-review scenario, replay-first,
* against the static export. Both fixtures are mode:"live" recordings
* (gpt-oss via Ollama) — nothing here is hand-scripted content.
*
* The governance story: a review presents named options as selectable cards
* in a list, compared on metadata attributes, with one explicit committing
* action — a platform vocabulary (plan pickers, product comparison) that
* this hotel scenario validates, not the other way round.
*/
import { expect, test, type Page } from "@playwright/test";

const scrubber = (page: Page) => page.getByTestId("scrubber");

async function scrubToEnd(page: Page) {
await scrubber(page).focus();
await page.keyboard.press("End");
}

test.beforeEach(async ({ page }) => {
await page.goto("/");
});

test("hotel-reservations is a ready, selectable scenario on the shelf", async ({ page }) => {
const shelf = page.getByTestId("scenario-hotel-reservations");
await expect(shelf).not.toContainText("(planned)");
await shelf.click();
await expect(page.getByTestId("fixture-argues-back")).toBeVisible();
await expect(page.getByTestId("fixture-clean")).toBeVisible();
});

test("clean replay: three named options, attributes as metadata, one action", async ({ page }) => {
await page.getByTestId("scenario-hotel-reservations").click();
await page.getByTestId("fixture-clean").click();
await expect(page.getByTestId("fixture-meta")).toContainText("live run");
await scrubToEnd(page);
await expect(page.getByTestId("audit-outcome")).toContainText("passed");
const canvas = page.locator("[data-canvas]");
// SelectableCard labels surface as accessible names (checkbox semantics).
await expect(canvas.getByRole("checkbox", { name: "Hotel Belém" })).toBeVisible();
await expect(canvas).toContainText("Best value");
await expect(canvas).toContainText("Book now");
});

test("repair replay: a bare apology caught by the review rules, options ship", async ({ page }) => {
await page.getByTestId("scenario-hotel-reservations").click();
await page.getByTestId("fixture-argues-back").click();
await scrubToEnd(page);
const ticker = page.getByTestId("gate-ticker");
await expect(ticker).toContainText(/attempt 0:\s*S1✓\s*S2✓\s*S3✗\s*→ repair/);
await expect(ticker).toContainText(/attempt 1:\s*S1✓\s*S2✓\s*S3✓/);
await expect(ticker).toContainText("outcome passed");
// The design system won: the apology became a comparison.
const canvas = page.locator("[data-canvas]");
await expect(canvas.getByRole("checkbox", { name: "Ritz-Carlton Lisboa" })).toBeVisible();
await expect(canvas).toContainText("Book Hotel");
});

test("permalink: a hotel-reservations deep link resolves", async ({ page }) => {
await page.goto("/#s=hotel-reservations&f=clean");
await page.reload();
await expect(page.getByTestId("fixture-meta")).toContainText("live run");
await expect(page.getByTestId("link-error")).toHaveCount(0);
});
3 changes: 2 additions & 1 deletion e2e/permalinks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ test("malformed and unknown links fail clearly and fall back", async ({ page })
await expect(page.getByTestId("link-error")).toContainText("did not parse");
await expect(page.getByTestId("fixture-meta")).toBeVisible(); // default view still works

await page.goto("/#s=hotel-reservations");
// No planned scenarios remain, so an invented id exercises the same path.
await page.goto("/#s=not-a-scenario");
await page.reload();
await expect(page.getByTestId("link-error")).toContainText("unknown or not-yet-ready");

Expand Down
30 changes: 17 additions & 13 deletions e2e/studio-shell.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,25 @@ test("the pipeline diagram names the honest pipeline in reading order", async ({
await expect(figure.locator("figcaption")).toContainText("One pipeline, inspectable at every joint");
});

test("planned scenarios reveal what they need, by keyboard", async ({ page }) => {
test("the shelf carries six ready scenarios and no planned placeholders", async ({ page }) => {
await page.goto("/");
// hotel-reservations is the last remaining planned scenario.
const planned = page.getByTestId("scenario-hotel-reservations");
await expect(planned).toContainText("(planned)");
await planned.focus();
await page.keyboard.press("Enter");
await expect(page.getByTestId("planned-needs")).toContainText(/planned, not built/i);
await expect(page.getByTestId("planned-needs")).toContainText(/needs/i);
// Revealing never selects the scenario: the ready experience is untouched
// (recipe-creator is the default, so its recording stays on screen).
await expect(page.getByTestId("fixture-generated-cooked")).toBeVisible();
// Tapping again dismisses the reveal.
await planned.click();
// The example-expansion milestone completed: every scenario is ready. The
// planned-entry treatment (dimmed chip revealing its needs) stands by in
// the shelf code for the next expansion, but nothing renders it today.
for (const id of [
"recipe-creator",
"appointment-booking",
"project-deletion",
"support-triage",
"onboarding",
"hotel-reservations",
]) {
await expect(page.getByTestId(`scenario-${id}`)).not.toContainText("(planned)");
}
await expect(page.getByTestId("planned-needs")).toHaveCount(0);
// The last-flipped scenario actually selects.
await page.getByTestId("scenario-hotel-reservations").click();
await expect(page.getByTestId("fixture-argues-back")).toBeVisible();
});

test("restyle themes the active scenario's own surface", async ({ page }) => {
Expand Down
Loading
Loading