Skip to content
This repository was archived by the owner on Jun 7, 2026. It is now read-only.

Add intent browser ghost UI and schema-driven search rendering#398

Open
sarbadaj wants to merge 2 commits into
Baur-Software:mainfrom
sarbadaj:feat/intent-browser-ghost-ui
Open

Add intent browser ghost UI and schema-driven search rendering#398
sarbadaj wants to merge 2 commits into
Baur-Software:mainfrom
sarbadaj:feat/intent-browser-ghost-ui

Conversation

@sarbadaj

@sarbadaj sarbadaj commented Jun 3, 2026

Copy link
Copy Markdown

Summary

This PR adds the first concrete UI slice for Papillon’s intent-browser model.

Papillon now treats agent output as structured Schema.org data and renders supported types directly in the canvas. It also improves the ghost/dry-run workflow by grouping pending approvals into intent groups, letting users select candidate agents, fill one shared disclosure form, and render approved results.

What Changed

  • Added a typed renderer for schema:SearchResultsPage and schema:SearchResult.
  • Registered the search results renderer in the default block renderer registry.
  • Replaced the empty rendered canvas state with the existing intent-browser empty state when there are no blocks.
  • Added grouped ghost approvals in the orchestrator/ghost panel.
  • Grouped approval blocks by action type and disclosure requirements.
  • Added candidate-agent checkboxes inside each approval group.
  • Added one shared disclosure form per grouped approval.
  • Added Render selected to approve selected agents and flip back to the rendered canvas.
  • Added Deny group to reject all approvals in a group.
  • Added styling for search result cards, grouped approval cards, agent choices, and disclosure fields.
  • Ignored generated Gradle state at bindings/java/.gradle/.

Why

Todd described Papillon as an intent browser:

  • users ask for an intent without needing AI,
  • Papillon maps that intent to matching agents,
  • the user sees a ghost/dry-run canvas before execution,
  • agents advertise required disclosures,
  • shared disclosure requirements become one approval form,
  • approved results render from Schema.org types,
  • canvases behave like browser tabs.

This PR starts that flow by making the ghost approval UX and Schema.org rendering concrete.

Testing

Ran:

git diff --check
cargo check --manifest-path apps/papillon/frontend/Cargo.toml
just check-wasm
just run-example pap-search-example

Run browser UI:
env -u NO_COLOR just web

Open:
http://127.0.0.1:1420

Add the first intent-browser UI slice for Papillon's schema-driven workflow.

- Render schema.org SearchResultsPage and SearchResult payloads as typed search result cards.

- Register the search result renderer in the default block renderer registry.

- Show the existing intent-browser empty state when a canvas has no blocks.

- Group pending approvals by action and disclosure shape in the ghost run panel.

- Let users select candidate agents, fill one shared disclosure form, render selected agents, or deny the group.

- Add UI styling for grouped ghost approvals, agent choices, disclosure fields, and search result cards.

- Ignore generated Gradle state under bindings/java/.gradle.

Verification: cargo check --manifest-path apps/papillon/frontend/Cargo.toml; just check-wasm; just run-example pap-search-example.

@toadkicker toadkicker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #398 — Intent Browser Ghost UI & Schema-Driven Search Rendering

Author: sarbadaj | +1050 / -85 | Touches: templates.rs, canvas_ghost_run_panel.rs, canvas.rs, main.css


Overview

This PR wires up two concrete slices of the Papillon intent-browser model: a typed SearchResultsPage renderer, and a grouped ghost/dry-run approval panel that lets users select candidate agents, review shared disclosure requirements, and approve or deny by intent group. Both are meaningful steps forward — the architecture is sound and largely follows project conventions.


Code Quality

Strengths

  • extract_search_results() defensively handles multiple Schema.org shapes (mainEntity.itemListElement, top-level itemListElement, result, result.itemListElement) — good defensive parsing of real-world agent payloads.
  • has_type() correctly handles both @type: "SearchResult" and @type: ["SearchResult", "schema:Thing"] — array-form is common in the wild.
  • grouped_approvals() uses BTreeMap for stable ordering — good choice so the UI doesn't shuffle groups between renders.
  • group.ttl_hours = group.ttl_hours.min(plan.ttl_hours) is the correct conservative choice: use the shortest TTL when merging blocks.
  • plan_candidates() falls back to selected_agent_* fields when candidates is empty — good backward compat with older IntentPlan shapes.

Issues

  1. Potential duplicate items in extract_search_results() — if content has @type: "SearchResult" AND has itemListElement, the top-level item is added once from the has_type check and potentially a second time via itemListElement. Low probability in practice, but the function should guard against it (e.g. skip the has_type push if itemListElement is also present).

  2. schema_types() registers "SearchResult" as a standalone entry type — a bare SearchResult document would be rendered as a 1-result search-results-page UI rather than a content block. This probably isn't the desired behavior if an agent ever returns a standalone result. Consider removing "SearchResult" from schema_types() or rendering it differently.

  3. to_pap_url() on action target values — in extract_actions(), the target field may be a URI template (e.g. {+url}) as defined by schema.org's EntryPoint. Calling to_pap_url("{+url}") produces pap://{+url} — a malformed URI. Add a guard: only convert if the string starts with http or https and looks like a resolved URL.

  4. No loading state for get_principal_attributesspawn_local fires the Tauri IPC call to pre-fill disclosure fields, but if it hasn't returned before the user clicks "Render selected", the disclosure values will be submitted as empty. At minimum, add a loading indicator so the user waits. A better fix would be to disable "Render selected" until the fetch resolves when has_disclosures is true.

  5. canvas-empty-hero design regression — the original had the project's purple/teal radial gradients (rgba(108,92,231,0.18)) and 40px display type. This PR replaces it with a plain rgba(255,255,255,0.025) box and 28px text. Per the design system, the purple brand color should not be silently removed from prominent surfaces. Please confirm this is intentional.


Testing

This is the main blocker. The PR adds ~500 lines of logic with zero tests:

  • extract_search_results(), search_item_from_value(), textish(), has_type() — all pure functions that are directly testable
  • grouped_approvals() — deterministic grouping logic, ideal for unit tests
  • to_pap_url() — simple conversion, should have tests for https://, http://, pap:// passthrough, and URI template edge case

Per project standards: comprehensive tests are required for all features. Tests should be added before merge.


Security

  • to_pap_url() does not validate the URL structure before rewriting. A malicious agent returning javascript://evil as a URL would produce pap://evil which is probably safe, but the original javascript: scheme should never reach the click handler — confirm submit_agent_link() validates the scheme on its end.
  • Disclosure field values are taken from filled_values (a HashMap<String, String>) and passed to approve_block(). Ensure the orchestrator enforces that only the fields listed in requires_disclosure are forwarded, not arbitrary injected keys.

Minor

  • The Cargo.lock bumps all crates from 0.8.20.8.3 with no corresponding Cargo.toml changes visible in the diff. If this was a rebase onto an existing version bump, that's fine — worth confirming no unintended version was picked up.
  • ApprovalPlanInline import is removed from canvas_ghost_run_panel.rs — verify it's unused elsewhere before merging.

Overall: request changes. Great direction, architecture is solid. The missing tests are the hard blocker; the to_pap_url URI template edge case and the disclosure loading state are the next priority.

- keep SearchResultsTemplate registered only for SearchResultsPage
- reject unresolved URI templates and unsafe schemes before pap:// conversion
- avoid duplicate top-level SearchResult extraction when collection data exists
- gate grouped approvals while principal attributes load and submit only declared disclosures
- restore branded empty canvas hero styling
- add frontend unit tests for search parsing, URL conversion, grouping, and disclosure filtering
- update the frontend handshake test helper for the current AgentInfo shape
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants