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" 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