Add intent browser ghost UI and schema-driven search rendering#398
Add intent browser ghost UI and schema-driven search rendering#398sarbadaj wants to merge 2 commits into
Conversation
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
left a comment
There was a problem hiding this comment.
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-levelitemListElement,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()usesBTreeMapfor 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 toselected_agent_*fields whencandidatesis empty — good backward compat with olderIntentPlanshapes.
Issues
-
Potential duplicate items in
extract_search_results()— if content has@type: "SearchResult"AND hasitemListElement, the top-level item is added once from thehas_typecheck and potentially a second time viaitemListElement. Low probability in practice, but the function should guard against it (e.g. skip thehas_typepush ifitemListElementis also present). -
schema_types()registers"SearchResult"as a standalone entry type — a bareSearchResultdocument 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"fromschema_types()or rendering it differently. -
to_pap_url()on actiontargetvalues — inextract_actions(), thetargetfield may be a URI template (e.g.{+url}) as defined by schema.org'sEntryPoint. Callingto_pap_url("{+url}")producespap://{+url}— a malformed URI. Add a guard: only convert if the string starts withhttporhttpsand looks like a resolved URL. -
No loading state for
get_principal_attributes—spawn_localfires 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 whenhas_disclosuresis true. -
canvas-empty-herodesign 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 plainrgba(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 testablegrouped_approvals()— deterministic grouping logic, ideal for unit teststo_pap_url()— simple conversion, should have tests forhttps://,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 returningjavascript://evilas a URL would producepap://evilwhich is probably safe, but the originaljavascript:scheme should never reach the click handler — confirmsubmit_agent_link()validates the scheme on its end.- Disclosure field values are taken from
filled_values(aHashMap<String, String>) and passed toapprove_block(). Ensure the orchestrator enforces that only the fields listed inrequires_disclosureare forwarded, not arbitrary injected keys.
Minor
- The Cargo.lock bumps all crates from
0.8.2→0.8.3with no correspondingCargo.tomlchanges 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. ApprovalPlanInlineimport is removed fromcanvas_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
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
schema:SearchResultsPageandschema:SearchResult.Render selectedto approve selected agents and flip back to the rendered canvas.Deny groupto reject all approvals in a group.bindings/java/.gradle/.Why
Todd described Papillon as an intent browser:
This PR starts that flow by making the ghost approval UX and Schema.org rendering concrete.
Testing
Ran: