From ebbeeb8ebbc50ca453c88c997e2e10b78f4993e3 Mon Sep 17 00:00:00 2001 From: Todd Baur Date: Tue, 7 Apr 2026 12:28:05 -0700 Subject: [PATCH 1/4] feat(pitch-deck): add print CSS for full-slide PDF export Adds @media print styles so Ctrl+P / Save as PDF renders all 13 slides as full-bleed landscape pages rather than a single clipped view: - @page { size: landscape; margin: 0 } for edge-to-edge layout - print-color-adjust: exact to preserve dark bg and brand colors - Resets the horizontal scroll shell (overflow, transform) so all slides are visible to the print engine - page-break-after: always on each .s for one slide per page - Hides nav buttons, dot indicators, and slide counter Co-Authored-By: Claude Sonnet 4.6 --- docs/pitch-deck.html | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/pitch-deck.html b/docs/pitch-deck.html index 94ca3b9e..5e3c8d5d 100644 --- a/docs/pitch-deck.html +++ b/docs/pitch-deck.html @@ -610,6 +610,54 @@ color: var(--faint); z-index: 300; } + + /* ── Print ── */ + @media print { + @page { size: landscape; margin: 0; } + + *, *::before, *::after { + -webkit-print-color-adjust: exact !important; + print-color-adjust: exact !important; + } + + html, body { + overflow: visible; + width: 100%; + height: auto; + background: var(--bg); + } + + .deck { + position: static; + width: 100%; + height: auto; + overflow: visible; + } + + .track { + display: block; + transform: none !important; + transition: none; + height: auto; + } + + .s { + flex: none; + width: 100%; + height: 100vh; + page-break-after: always; + break-after: page; + overflow: hidden; + position: relative; + } + + .s:last-child { + page-break-after: auto; + break-after: auto; + } + + .nav-btn, .dots, .counter { display: none !important; } + } From f758100fd3e357805a8009e658a236ce9a294019 Mon Sep 17 00:00:00 2001 From: Todd Baur Date: Tue, 7 Apr 2026 12:44:03 -0700 Subject: [PATCH 2/4] fix(pitch-deck): use light-mode colors for print output Overrides CSS variables inside @media print so all slides render with a white background and dark text instead of the dark-mode palette: - --bg / slide backgrounds: white (#fff) - --surface: light lavender-grey for cards and rows - --text: near-black, --muted / --faint darkened for contrast - --purple / --violet: slightly deeper (#5b4bd4) for legibility on white - Card elements (sys-node, biz-row, ba-items, flow-box) re-skinned - Decorative radial glows hidden (they don't translate to paper) Co-Authored-By: Claude Sonnet 4.6 --- docs/pitch-deck.html | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/pitch-deck.html b/docs/pitch-deck.html index 5e3c8d5d..f02e6672 100644 --- a/docs/pitch-deck.html +++ b/docs/pitch-deck.html @@ -615,16 +615,25 @@ @media print { @page { size: landscape; margin: 0; } - *, *::before, *::after { - -webkit-print-color-adjust: exact !important; - print-color-adjust: exact !important; + /* Light-mode palette for print */ + :root { + --bg: #ffffff; + --surface: #f4f3fa; + --border: #dddcee; + --purple: #5b4bd4; + --violet: #5b4bd4; + --teal: #1a9e82; + --muted: #4a4a62; + --faint: #7a7a98; + --text: #16141f; } html, body { overflow: visible; width: 100%; height: auto; - background: var(--bg); + background: #fff; + color: #16141f; } .deck { @@ -649,6 +658,7 @@ break-after: page; overflow: hidden; position: relative; + background: #fff; } .s:last-child { @@ -656,6 +666,19 @@ break-after: auto; } + /* Decorative glows don't translate to print */ + .cover-glow, .vision-glow { display: none; } + + /* Cards / surfaces */ + .sys-node { background: var(--surface); border-color: var(--border); } + .biz-row { background: var(--surface); border-color: var(--border); } + .ba-before .ba-item { background: var(--surface); border-color: var(--border); } + .ba-after .ba-item { background: #eeebfc; border-color: rgba(91,75,212,0.35); color: var(--text); } + .flow-box { border-color: var(--border); } + .flow-box.highlight { background: #eeebfc; border-color: rgba(91,75,212,0.4); color: var(--violet); } + .vlink-p { background: #eeebfc; border-color: rgba(91,75,212,0.3); color: var(--violet); } + .vlink-t { background: #e6f7f3; border-color: rgba(26,158,130,0.3); color: var(--teal); } + .nav-btn, .dots, .counter { display: none !important; } } From b818704f4f1262c01c28aa9a52009fd5c22457c5 Mon Sep 17 00:00:00 2001 From: Todd Baur Date: Sun, 12 Apr 2026 17:44:52 -0700 Subject: [PATCH 3/4] fix: dynamic orchestrator status and active agent count in fleet view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace hardcoded "ACTIVE" string in PAP_ORCHESTRATOR settings section with a reactive closure driven by orchestrator.status signal; maps Ready/Unconfigured → ACTIVE (green), Downloading → LOADING (amber), Disconnected → OFFLINE (gray) - Filter ACTIVE AGENTS grid in fleet page to only render compiled agents, so the displayed list matches the "N ACTIVE / N ONLINE" count badges instead of showing all 309 catalog agents under the active section Co-Authored-By: Claude Sonnet 4.6 --- apps/papillon/frontend/src/pages/dashboard.rs | 5 ++++- apps/papillon/frontend/src/pages/settings.rs | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/papillon/frontend/src/pages/dashboard.rs b/apps/papillon/frontend/src/pages/dashboard.rs index 5756dfbb..f8a720d6 100644 --- a/apps/papillon/frontend/src/pages/dashboard.rs +++ b/apps/papillon/frontend/src/pages/dashboard.rs @@ -29,6 +29,9 @@ pub fn DashboardPage() -> impl IntoView { agents.get().iter().filter(|a| a.source == "compiled").count() }; let total_count = move || agents.get().len(); + let active_agents = move || -> Vec<_> { + agents.get().into_iter().filter(|a| a.source == "compiled").collect() + }; view! {
@@ -66,7 +69,7 @@ pub fn DashboardPage() -> impl IntoView { fallback=move || view! {
} diff --git a/apps/papillon/frontend/src/pages/settings.rs b/apps/papillon/frontend/src/pages/settings.rs index ecdb841b..86c92df6 100644 --- a/apps/papillon/frontend/src/pages/settings.rs +++ b/apps/papillon/frontend/src/pages/settings.rs @@ -210,6 +210,17 @@ fn GeneralTab() -> impl IntoView { }); }; + let orch_status_text = move || match orchestrator.status.get() { + OrchestratorStatus::Ready | OrchestratorStatus::Unconfigured => "ACTIVE", + OrchestratorStatus::Downloading { .. } => "LOADING", + OrchestratorStatus::Disconnected => "OFFLINE", + }; + let orch_status_color = move || match orchestrator.status.get() { + OrchestratorStatus::Ready | OrchestratorStatus::Unconfigured => "#00b894", + OrchestratorStatus::Downloading { .. } => "#fdcb6e", + OrchestratorStatus::Disconnected => "#b2bec3", + }; + view! {
// ── PAP Orchestrator ───────────────────────────────────── @@ -223,7 +234,7 @@ fn GeneralTab() -> impl IntoView {
"STATUS" - "ACTIVE" + {move || orch_status_text()}
"MANDATE TTL" From 9936a261c335b51b4b8b04e4c17194fc7825f003 Mon Sep 17 00:00:00 2001 From: Todd Baur Date: Sun, 12 Apr 2026 21:02:33 -0700 Subject: [PATCH 4/4] fix(e2e): fix pap:// suggestions, typed-block flakiness, and mock alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tauri-mock: add `live: true` to list_agents mock agents so build_catalog() indexes them for pap:// topbar suggestions (agents without live=true are filtered by the Rust serde default) - tauri-mock: add canvas_plan_prompt handler matching the current Tauri IPC command name (was canvas_prompt); add receipt sentinel to block content so render_typed_content unwraps the result field correctly - tauri-mock: add 2 compiled-source agents (Web Page Reader, On-Device AI) to align with the fleet page's ACTIVE AGENTS grid expectations - agent-prompts.spec.ts: increase submitAndAwaitTypedBlock timeout 8s→15s and fix hardcoded canvas lifecycle timeout to match; update source/command name assertions to reflect current mock shape - app.spec.ts: increase pap:// suggestion timeout 5s→10s; update empty-state and settings selectors to match current UI (.canvas-stream, agent-tile, INFERENCE_SUBSTRATE); remove setup-prompt test that no longer applies - agents.spec.ts: update fleet counts and source badge assertions to match the 5-agent (2 compiled + 2 catalog + 1 user_created) mock shape - templates.spec.ts: fix schema input placeholder selector ("FlightReservation" not "Schema") - playwright.config.ts: retries 0→1 for residual WASM timing flakiness; cap CI workers at 4 to reduce CPU contention Result: 155 passed, 32 skipped (live-mode tests), 0 failed Co-Authored-By: Claude Sonnet 4.6 --- e2e/playwright.config.ts | 5 +- e2e/tests/agent-prompts.spec.ts | 16 ++-- e2e/tests/agents.spec.ts | 34 +++++---- e2e/tests/app.spec.ts | 35 +++++---- e2e/tests/tauri-mock.ts | 126 +++++++++++++++++++++++++++++++- e2e/tests/templates.spec.ts | 7 +- 6 files changed, 173 insertions(+), 50 deletions(-) diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 68be6147..c62c5366 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -6,7 +6,10 @@ export default defineConfig({ testDir: "./tests", // WASM compile+mount takes ~15ms; 60s covers slow CI runners. timeout: 60_000, - retries: 0, + retries: 1, + // Limit parallel workers in CI to reduce CPU contention from 8 concurrent + // WASM + Playwright instances (prevents typed-block render timeouts). + workers: isCI ? 4 : undefined, expect: { timeout: 30_000, }, diff --git a/e2e/tests/agent-prompts.spec.ts b/e2e/tests/agent-prompts.spec.ts index b8211bb7..a02b71dd 100644 --- a/e2e/tests/agent-prompts.spec.ts +++ b/e2e/tests/agent-prompts.spec.ts @@ -179,11 +179,11 @@ test.describe("LLM provider configuration", () => { expect(saved.mandate_ttl_hours).toBe(24); }); - test("settings UI General tab shows LLM provider select", async ({ page }) => { + test("settings UI General tab shows inference substrate select", async ({ page }) => { await page.goto("/settings", { waitUntil: "commit" }); await waitForApp(page); - await expect(page.locator("text=LLM Provider")).toBeVisible(); + await expect(page.locator("text=INFERENCE_SUBSTRATE")).toBeVisible(); // First select on the page is the provider dropdown await expect(page.locator("select").first()).toBeVisible(); }); @@ -209,7 +209,7 @@ async function submitAndAwaitTypedBlock( page: import("@playwright/test").Page, mockKeyword: string, cssClass: string, - timeout = 8000 + timeout = 15000 ): Promise { // Navigate to canvas await page.goto("/", { waitUntil: "commit" }); @@ -503,9 +503,9 @@ test.describe("Chrysalis federation commands", () => { }) ); - // Local agents have explicit source fields (catalog, user_created) + // Local agents have explicit source fields (compiled, catalog, user_created) for (const a of localAgents) { - expect(["catalog", "user_created"]).toContain(a.source); + expect(["compiled", "catalog", "user_created"]).toContain(a.source); } // Remote agents from a registry are a separate list @@ -690,7 +690,7 @@ test.describe("Canvas block lifecycle", () => { await page.locator(".topbar-address-input").press("Enter"); // Eventually resolves (event fires after 200ms mock delay) - await expect(page.locator(".typed-book").first()).toBeVisible({ timeout: 8000 }); + await expect(page.locator(".typed-book").first()).toBeVisible({ timeout: 15000 }); // After resolution the block should NOT have the resolving class const block = page.locator(".canvas-block").first(); @@ -702,7 +702,7 @@ test.describe("Canvas block lifecycle", () => { await page.addInitScript(` const origInvoke = window.__TAURI__.core.invoke; window.__TAURI__.core.invoke = async function(cmd, args) { - if (cmd === 'canvas_prompt') { + if (cmd === 'canvas_plan_prompt') { const blockId = (args && (args.block_id || args.blockId)) || 'block-fail'; setTimeout(function() { window.__TAURI__.event.emit('block_resolved', { @@ -741,7 +741,7 @@ test.describe("Canvas block lifecycle", () => { await page.addInitScript(` const origInvoke = window.__TAURI__.core.invoke; window.__TAURI__.core.invoke = async function(cmd, args) { - if (cmd === 'canvas_prompt') { + if (cmd === 'canvas_plan_prompt') { const blockId = (args && (args.block_id || args.blockId)) || 'block-ghost'; setTimeout(function() { window.__TAURI__.event.emit('block_resolved', { diff --git a/e2e/tests/agents.spec.ts b/e2e/tests/agents.spec.ts index 433967f4..b745a15b 100644 --- a/e2e/tests/agents.spec.ts +++ b/e2e/tests/agents.spec.ts @@ -44,41 +44,41 @@ test.describe("Agent fleet page", () => { test("active badge shows correct count", async ({ page }) => { await page.goto("/fleet", { waitUntil: "commit" }); await waitForApp(page); - // 2 catalog agents in mock → 2 ACTIVE + // 2 compiled agents in mock (Web Page Reader, On-Device AI) → 2 ACTIVE const activeBadge = page.locator(".fleet-badge.active"); await expect(activeBadge).toBeVisible(); await expect(activeBadge).toContainText("2 ACTIVE"); }); - test("total badge shows all 3 agents", async ({ page }) => { + test("total badge shows all 5 agents", async ({ page }) => { await page.goto("/fleet", { waitUntil: "commit" }); await waitForApp(page); const totalBadge = page.locator(".fleet-badge.total"); await expect(totalBadge).toBeVisible(); - await expect(totalBadge).toContainText("3 TOTAL"); + await expect(totalBadge).toContainText("5 TOTAL"); }); - test("renders 3 agent cards from list_local_agents", async ({ page }) => { + test("ACTIVE AGENTS section renders 2 compiled agent cards", async ({ page }) => { await page.goto("/fleet", { waitUntil: "commit" }); await waitForApp(page); - await expect(page.locator(".agent-card")).toHaveCount(3); + // Only compiled agents appear in the ACTIVE AGENTS grid + await expect(page.locator(".agent-card")).toHaveCount(2); }); - test("first agent card shows DuckDuckGo Search", async ({ page }) => { + test("first agent card shows Web Page Reader (first compiled agent)", async ({ page }) => { await page.goto("/fleet", { waitUntil: "commit" }); await waitForApp(page); const firstCard = page.locator(".agent-card").first(); await expect(firstCard.locator(".agent-card-name")).toContainText( - "DuckDuckGo Search" + "Web Page Reader" ); }); - test("agent cards show source badges", async ({ page }) => { + test("agent cards show compiled source badges", async ({ page }) => { await page.goto("/fleet", { waitUntil: "commit" }); await waitForApp(page); - // Two catalog + one user_created - await expect(page.locator(".agent-source-badge.catalog")).toHaveCount(2); - await expect(page.locator(".agent-source-badge.user")).toHaveCount(1); + // Only compiled agents are shown in the ACTIVE AGENTS grid + await expect(page.locator(".agent-source-badge.compiled")).toHaveCount(2); }); test("agent cards show truncated DID", async ({ page }) => { @@ -98,7 +98,7 @@ test.describe("Agent fleet page", () => { await waitForApp(page); const firstCard = page.locator(".agent-card").first(); const actionStat = firstCard.locator(".agent-stat").nth(1); - await expect(actionStat).toContainText("SearchAction"); + await expect(actionStat).toContainText("ReadAction"); // Must NOT include raw "schema:" prefix await expect(actionStat).not.toContainText("schema:"); }); @@ -119,7 +119,7 @@ test.describe("Agent fleet page", () => { // ── Agent command round-trips ───────────────────────────────── test.describe("Agent command round-trips", () => { - test("list_local_agents returns 3 seeded agents with new fields", async ({ + test("list_local_agents returns 5 seeded agents with new fields", async ({ page, }) => { await page.goto("/", { waitUntil: "commit" }); @@ -129,7 +129,8 @@ test.describe("Agent command round-trips", () => { window.__TAURI__.core.invoke("list_local_agents") ); - expect(agents).toHaveLength(3); + // 2 compiled + 2 catalog + 1 user_created = 5 + expect(agents).toHaveLength(5); // Every agent must have the new AgentInfo fields for (const agent of agents) { @@ -142,6 +143,7 @@ test.describe("Agent command round-trips", () => { // Sources must be valid const sources = agents.map((a: any) => a.source); + expect(sources).toContain("compiled"); expect(sources).toContain("catalog"); expect(sources).toContain("user_created"); }); @@ -176,7 +178,7 @@ test.describe("Agent command round-trips", () => { const agents = await page.evaluate(() => window.__TAURI__.core.invoke("list_local_agents") ); - expect(agents).toHaveLength(4); + expect(agents).toHaveLength(6); // 5 seeded + 1 new expect(agents.find((a: any) => a.name === "Test Agent")).toBeDefined(); }); @@ -227,7 +229,7 @@ test.describe("Agent command round-trips", () => { const after = await page.evaluate(() => window.__TAURI__.core.invoke("list_local_agents") ); - expect(after).toHaveLength(2); + expect(after).toHaveLength(4); // 5 seeded - 1 deleted = 4 expect(after.find((a: any) => a.agent_did === custom.agent_did)).toBeUndefined(); }); diff --git a/e2e/tests/app.spec.ts b/e2e/tests/app.spec.ts index 51888c90..3a09980a 100644 --- a/e2e/tests/app.spec.ts +++ b/e2e/tests/app.spec.ts @@ -49,15 +49,15 @@ test.describe("App shell", () => { // ── Canvas Page (Home) ─────────────────────────────────────── test.describe("Canvas page", () => { - test("shows empty state with inspiration lines on new canvas", async ({ page }) => { + test("shows empty state with agent tiles on new canvas", async ({ page }) => { await page.goto("/", { waitUntil: "commit" }); await waitForApp(page); - await expect(page.locator(".canvas-area")).toBeVisible(); + await expect(page.locator(".canvas-stream")).toBeVisible(); // Seed canvas has blocks — create a new empty canvas via brand dropdown await page.locator(".topbar-brand").click(); await page.locator("text=+ New Canvas").click(); - await expect(page.locator(".canvas-empty")).toBeVisible(); - await expect(page.locator(".inspiration-line").first()).toBeVisible(); + await expect(page.locator(".canvas-empty-state")).toBeVisible(); + await expect(page.locator(".agent-tile").first()).toBeVisible(); }); test("shows address bar prompt when orchestrator is ready", async ({ page }) => { @@ -67,11 +67,12 @@ test.describe("Canvas page", () => { await expect(page.locator(".topbar-address-input")).toBeVisible(); }); - test("address bar shows pap:// suggestion buttons", async ({ page }) => { + test("address bar shows pap:// suggestion buttons when typing pap://", async ({ page }) => { await page.goto("/", { waitUntil: "commit" }); await waitForApp(page); - await page.locator(".topbar-address-input").click(); - await expect(page.locator(".palette-suggestion").first()).toBeVisible(); + // Suggestions appear only when user types "pap://" prefix + await page.locator(".topbar-address-input").fill("pap://"); + await expect(page.locator(".palette-suggestion").first()).toBeVisible({ timeout: 10000 }); }); test("address bar input accepts text", async ({ page }) => { @@ -79,11 +80,11 @@ test.describe("Canvas page", () => { await waitForApp(page); await page.locator(".topbar-address-input").fill("Search for flights"); await expect(page.locator(".topbar-address-input")).toHaveValue("Search for flights"); - // Suggestions should hide when input has text + // Suggestions should hide when input has non-pap:// text await expect(page.locator(".palette-suggestion").first()).not.toBeVisible(); }); - test("shows setup prompt when orchestrator is disconnected", async ({ page }) => { + test("canvas renders normally when orchestrator is disconnected", async ({ page }) => { // Override mock to return Disconnected status await page.addInitScript(` const origInvoke = window.__TAURI__.core.invoke; @@ -94,12 +95,10 @@ test.describe("Canvas page", () => { `); await page.goto("/", { waitUntil: "commit" }); await waitForApp(page); - // Seed canvas has blocks — create a new empty canvas to see the setup prompt - await page.locator(".topbar-brand").click(); - await page.locator("text=+ New Canvas").click(); - await expect(page.locator(".canvas-prompt-setup")).toBeVisible(); - await expect(page.locator("text=Configure an LLM provider")).toBeVisible(); - await expect(page.locator("text=Open Settings")).toBeVisible(); + // Canvas still renders — deterministic routing works without LLM + await expect(page.locator(".canvas-stream")).toBeVisible(); + // Topbar address input is still available + await expect(page.locator(".topbar-address-input")).toBeVisible(); }); }); @@ -130,10 +129,10 @@ test.describe("Settings page", () => { await expect(page.locator(".settings-tab").nth(5)).toHaveText("MANDATES"); }); - test("General tab shows LLM Provider config", async ({ page }) => { + test("General tab shows inference substrate config", async ({ page }) => { await page.goto("/settings", { waitUntil: "commit" }); await waitForApp(page); - await expect(page.locator("text=LLM Provider")).toBeVisible(); + await expect(page.locator("text=INFERENCE_SUBSTRATE")).toBeVisible(); // Provider select is the first select on the page await expect(page.locator("select").first()).toBeVisible(); }); @@ -218,7 +217,7 @@ test.describe("Settings page", () => { await waitForApp(page); // Start on General - await expect(page.locator("text=LLM Provider")).toBeVisible(); + await expect(page.locator("text=INFERENCE_SUBSTRATE")).toBeVisible(); // Switch to Identity await page.locator(".settings-tab").nth(3).click(); diff --git a/e2e/tests/tauri-mock.ts b/e2e/tests/tauri-mock.ts index 2ed0672c..7866966b 100644 --- a/e2e/tests/tauri-mock.ts +++ b/e2e/tests/tauri-mock.ts @@ -93,6 +93,34 @@ window.__TAURI__ = { _mandates: {}, _orchestratorConfig: ${JSON.stringify(ORCHESTRATOR_CONFIG)}, _localAgents: [ + { + name: 'Web Page Reader', + provider_name: 'Papillon', + provider_did: 'did:key:z6MkPap001', + capabilities: ['schema:ReadAction'], + object_types: ['WebPage'], + requires_disclosure: [], + returns: ['WebPage'], + endpoint: null, + content_hash: 'web-reader-hash', + agent_did: 'did:key:z6MkPapAgent001', + source: 'compiled', + published_to: [], + }, + { + name: 'On-Device AI', + provider_name: 'Papillon', + provider_did: 'did:key:z6MkPap002', + capabilities: ['schema:AskAction'], + object_types: ['Answer'], + requires_disclosure: [], + returns: ['Answer'], + endpoint: null, + content_hash: 'on-device-ai-hash', + agent_did: 'did:key:z6MkPapAgent002', + source: 'compiled', + published_to: [], + }, { name: 'DuckDuckGo Search', provider_name: 'DuckDuckGo', @@ -310,10 +338,11 @@ window.__TAURI__ = { case 'list_agents': // Registry browser — returns remote/federated agents + // live: true is required so build_catalog() indexes them for pap:// suggestions return [ - { name: 'DuckDuckGo Search', provider_name: 'DuckDuckGo', provider_did: 'did:key:z6MkDDG', capabilities: ['schema:SearchAction'], object_types: ['SearchAction'], requires_disclosure: [], returns: ['results'], endpoint: null, content_hash: 'ddg-hash', agent_did: 'did:key:z6MkDDGFed', source: 'catalog', published_to: [] }, - { name: 'Wikipedia', provider_name: 'Wikimedia', provider_did: 'did:key:z6MkWiki', capabilities: ['schema:SearchAction'], object_types: ['SearchAction'], requires_disclosure: [], returns: ['article'], endpoint: null, content_hash: 'wiki-hash', agent_did: 'did:key:z6MkWikiFed', source: 'catalog', published_to: [] }, - { name: 'Mistral AI', provider_name: 'Mistral', provider_did: 'did:key:z6MkMistral', capabilities: ['schema:CreateAction'], object_types: ['InferenceAction'], requires_disclosure: [], returns: ['response'], endpoint: null, content_hash: 'mistral-hash', agent_did: 'did:key:z6MkMistralFed', source: 'catalog', published_to: [] }, + { name: 'DuckDuckGo Search', provider_name: 'DuckDuckGo', provider_did: 'did:key:z6MkDDG', capabilities: ['schema:SearchAction'], object_types: ['SearchAction'], requires_disclosure: [], returns: ['results'], endpoint: null, content_hash: 'ddg-hash', agent_did: 'did:key:z6MkDDGFed', source: 'catalog', published_to: [], live: true }, + { name: 'Wikipedia', provider_name: 'Wikimedia', provider_did: 'did:key:z6MkWiki', capabilities: ['schema:SearchAction'], object_types: ['SearchAction'], requires_disclosure: [], returns: ['article'], endpoint: null, content_hash: 'wiki-hash', agent_did: 'did:key:z6MkWikiFed', source: 'catalog', published_to: [], live: true }, + { name: 'Mistral AI', provider_name: 'Mistral', provider_did: 'did:key:z6MkMistral', capabilities: ['schema:CreateAction'], object_types: ['InferenceAction'], requires_disclosure: [], returns: ['response'], endpoint: null, content_hash: 'mistral-hash', agent_did: 'did:key:z6MkMistralFed', source: 'catalog', published_to: [], live: true }, ]; case 'list_local_agents': @@ -432,7 +461,8 @@ window.__TAURI__ = { prompt_id: (args && (args.prompt_id || args.promptId)) || 'p-mock', state: 'Resolved', schema_type: schemaType, - content: { result: contentObj }, + // Include receipt sentinel so render_typed_content unwraps 'result' + content: { result: contentObj, receipt: { status: 'ok', session: 'mock-session-001' } }, linked_block_ids: [], created_at: new Date().toISOString(), updated_at: new Date().toISOString(), @@ -814,6 +844,94 @@ window.__TAURI__ = { return child; } + // canvas_plan_prompt is the Tauri IPC entry point for the planning + // phase — same mock behaviour as canvas_prompt (emit block_resolved + // after a short delay so the Leptos UI can transition states). + case 'canvas_plan_prompt': { + const blockId = (args && (args.block_id || args.blockId)) || 'block-mock'; + const promptText = ((args && args.text) || '').toLowerCase(); + function typedBlock2(schemaType, contentObj) { + return { + id: blockId, + prompt_id: (args && (args.prompt_id || args.promptId)) || 'p-mock', + state: 'Resolved', + schema_type: schemaType, + // Include receipt sentinel so render_typed_content unwraps 'result' + content: { result: contentObj, receipt: { status: 'ok', session: 'mock-session-001' } }, + linked_block_ids: [], + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + } + var planBlock = null; + if (promptText.includes('mock:movie') || promptText.includes('__movie')) { + planBlock = typedBlock2('Movie', { name: 'Inception', datePublished: '2010', director: { name: 'Christopher Nolan' }, genre: 'Sci-Fi', aggregateRating: { ratingValue: '8.8' }, description: 'A mind-bending thriller.' }); + } else if (promptText.includes('mock:tvseries') || promptText.includes('__tvseries')) { + planBlock = typedBlock2('TVSeries', { name: 'Breaking Bad', startDate: '2008', broadcastChannel: 'AMC', numberOfSeasons: '5', description: 'A chemistry teacher turns drug lord.' }); + } else if (promptText.includes('mock:videogame') || promptText.includes('__videogame')) { + planBlock = typedBlock2('VideoGame', { name: 'The Legend of Zelda', genre: 'Adventure', gamePlatform: 'Nintendo Switch', author: { name: 'Nintendo' }, description: 'An epic adventure game.' }); + } else if (promptText.includes('mock:musicrecording') || promptText.includes('__musicrecording')) { + planBlock = typedBlock2('MusicRecording', { name: 'Bohemian Rhapsody', byArtist: { name: 'Queen' }, inAlbum: { name: 'A Night at the Opera' }, duration: 'PT5M55S' }); + } else if (promptText.includes('mock:musicgroup') || promptText.includes('__musicgroup')) { + planBlock = typedBlock2('MusicGroup', { name: 'The Beatles', genre: 'Rock', foundingDate: '1960', description: 'Legendary British rock band.' }); + } else if (promptText.includes('mock:book') || promptText.includes('__book')) { + planBlock = typedBlock2('Book', { name: 'The Rust Programming Language', author: { name: 'Steve Klabnik' }, publisher: { name: 'No Starch Press' }, datePublished: '2019', isbn: '978-1593278281', description: 'The official Rust book.' }); + } else if (promptText.includes('mock:newsarticle') || promptText.includes('__newsarticle')) { + planBlock = typedBlock2('NewsArticle', { headline: 'Rust Tops Developer Survey for 9th Year', publisher: { name: 'Stack Overflow' }, datePublished: '2024-06-01', description: 'Rust remains the most loved language.', url: 'https://survey.stackoverflow.co/2024' }); + } else if (promptText.includes('mock:scholarlyarticle') || promptText.includes('__scholarlyarticle')) { + planBlock = typedBlock2('ScholarlyArticle', { name: 'Attention Is All You Need', author: [{ name: 'Vaswani et al.' }], isPartOf: 'NeurIPS 2017', datePublished: '2017', identifier: '10.5555/3295222.3295349', abstract: 'We propose the Transformer architecture.' }); + } else if (promptText.includes('mock:person') || promptText.includes('__person')) { + planBlock = typedBlock2('Person', { name: 'Grace Hopper', jobTitle: 'Rear Admiral', affiliation: { name: 'US Navy' }, description: 'Pioneer of computer programming.', url: 'https://en.wikipedia.org/wiki/Grace_Hopper' }); + } else if (promptText.includes('mock:organization') || promptText.includes('__organization')) { + planBlock = typedBlock2('Organization', { name: 'Mozilla Foundation', '@type': 'Organization', address: { addressLocality: 'San Francisco', addressCountry: 'US' }, description: 'Champions of the open web.', url: 'https://mozilla.org' }); + } else if (promptText.includes('mock:weather') || promptText.includes('__weather')) { + planBlock = typedBlock2('WeatherForecast', { name: 'San Francisco', temperature: '65°F', description: 'Foggy with partial clearing', humidity: '78%', windSpeed: '15 mph' }); + } else if (promptText.includes('mock:geocoords') || promptText.includes('__geocoords')) { + planBlock = typedBlock2('GeoCoordinates', { name: 'Eiffel Tower', latitude: 48.8584, longitude: 2.2945, elevation: '330m', address: 'Champ de Mars, Paris, France' }); + } else if (promptText.includes('mock:product') || promptText.includes('__product')) { + planBlock = typedBlock2('Product', { name: 'Framework Laptop 16', brand: { name: 'Framework' }, offers: { price: '1049.00' }, aggregateRating: { ratingValue: '4.7' }, description: 'A modular, repairable laptop.' }); + } else if (promptText.includes('mock:event') || promptText.includes('__event')) { + planBlock = typedBlock2('Event', { name: 'RustConf 2024', startDate: '2024-09-10', endDate: '2024-09-11', location: { name: 'Montreal, Canada' }, organizer: { name: 'Rust Foundation' }, description: 'Annual Rust programming conference.' }); + } else if (promptText.includes('mock:sportsteam') || promptText.includes('__sportsteam')) { + planBlock = typedBlock2('SportsTeam', { name: 'World Cup Final 2026', homeTeam: { name: 'Spain' }, awayTeam: { name: 'Brazil' }, startDate: '2026-07-19', location: { name: 'MetLife Stadium, NJ' } }); + } else if (promptText.includes('mock:course') || promptText.includes('__course')) { + planBlock = typedBlock2('Course', { name: 'CS50: Introduction to Computer Science', provider: { name: 'Harvard / edX' }, description: 'A broad introduction to computer science.', url: 'https://cs50.harvard.edu' }); + } else if (promptText.includes('mock:nutrition') || promptText.includes('__nutrition')) { + planBlock = typedBlock2('NutritionInformation', { name: 'Avocado', servingSize: '100g', calories: '160', proteinContent: '2g', carbohydrateContent: '9g', fatContent: '15g' }); + } else if (promptText.includes('mock:jobposting') || promptText.includes('__jobposting')) { + planBlock = typedBlock2('JobPosting', { title: 'Senior Rust Engineer', hiringOrganization: { name: 'Fastly' }, jobLocation: { address: { addressLocality: 'Remote' } }, datePosted: '2024-05-01', baseSalary: { value: { minValue: 180000, maxValue: 250000 } }, description: 'Build high-performance networking software.' }); + } else if (promptText.includes('mock:visualartwork') || promptText.includes('__visualartwork')) { + planBlock = typedBlock2('VisualArtwork', { name: 'Starry Night', creator: { name: 'Vincent van Gogh' }, artMedium: 'Oil on canvas', dateCreated: '1889', locationCreated: { name: 'MoMA, New York' }, description: 'A swirling night sky over a village.' }); + } else if (promptText.includes('mock:definedterm') || promptText.includes('__definedterm')) { + planBlock = typedBlock2('DefinedTerm', { name: 'monad', inDefinedTermSet: 'noun', description: 'A design pattern in functional programming representing computations as chains.' }); + } else if (promptText.includes('mock:quotation') || promptText.includes('__quotation')) { + planBlock = typedBlock2('Quotation', { text: 'Programs must be written for people to read, and only incidentally for machines to execute.', spokenByCharacter: { name: 'Harold Abelson' }, citation: { name: 'SICP' } }); + } else if (promptText.includes('mock:flightreservation') || promptText.includes('__flightreservation')) { + planBlock = typedBlock2('FlightReservation', { reservationNumber: 'PX-4892', underName: { name: 'Ada Lovelace' }, departureAirport: 'SFO', arrivalAirport: 'JFK', departureDate: '2026-06-01', departureTime: '09:15', arrivalTime: '17:45', airline: 'United', totalPrice: 382.00 }); + } else if (promptText.includes('mock:hotel') || promptText.includes('__hotel')) { + planBlock = typedBlock2('LodgingReservation', { reservationNumber: 'H-78321', underName: { name: 'Grace Hopper' }, name: 'The Grand Pacific', checkinDate: '2026-07-10', checkoutDate: '2026-07-13', totalPrice: 540.00 }); + } else { + planBlock = { + id: blockId, + prompt_id: (args && (args.prompt_id || args.promptId)) || 'p-mock', + state: 'Resolved', + schema_type: null, + content: { result: 'Here is your answer.' }, + linked_block_ids: [], + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + } + var resolvedPlanBlock = planBlock; + setTimeout(function() { + window.__TAURI__.event.emit('block_resolved', { block: resolvedPlanBlock }); + }, 200); + return null; + } + + case 'get_recovery_status': + return { has_recovery: false, guardian_count: 0, threshold: 0 }; + default: console.warn('[tauri-mock] unhandled command:', cmd); return null; diff --git a/e2e/tests/templates.spec.ts b/e2e/tests/templates.spec.ts index fdf8cbd7..d91240d9 100644 --- a/e2e/tests/templates.spec.ts +++ b/e2e/tests/templates.spec.ts @@ -44,7 +44,8 @@ async function createTemplate( config: string = VALID_CONFIG, ) { await page.locator('input[placeholder*="Name"]').fill(name); - await page.locator('input[placeholder*="Schema"]').fill(schemaType); + // SchemaTypeInput placeholder is "e.g. FlightReservation" + await page.locator('input[placeholder*="FlightReservation"]').fill(schemaType); await page.locator("textarea").first().fill(config); await page.locator('button:has-text("Create Template")').click(); // Wait for success message @@ -180,7 +181,7 @@ test.describe("Templates", () => { // Try to create with malformed JSON const templateName = `BadJSON-${Date.now()}`; await page.locator('input[placeholder*="Name"]').fill(templateName); - await page.locator('input[placeholder*="Schema"]').fill("Recipe"); + await page.locator('input[placeholder*="FlightReservation"]').fill("Recipe"); await page.locator("textarea").first().fill("{invalid json"); await page.locator('button:has-text("Create Template")').click(); @@ -195,7 +196,7 @@ test.describe("Templates", () => { await goToTemplatesTab(page); // Try to create with empty name — just fill schema and config - await page.locator('input[placeholder*="Schema"]').fill("Recipe"); + await page.locator('input[placeholder*="FlightReservation"]').fill("Recipe"); await page.locator("textarea").first().fill(VALID_CONFIG); // Click Create and expect error