refactor: BlockUpdate typed events + BlockContext provider pattern#289
Merged
toadkicker merged 5 commits intomainfrom Apr 15, 2026
Merged
refactor: BlockUpdate typed events + BlockContext provider pattern#289toadkicker merged 5 commits intomainfrom
toadkicker merged 5 commits intomainfrom
Conversation
Entering pap://ebay.com (or any external domain) in the address bar now
browses the site as a canvas block instead of misrouting to a registry peer.
Registries are federated and announced at handshake time — not addressed
via the URI scheme. The old is_registry_host() treated every dotted FQDN
as a registry, causing pap://domain.com to fall through to On-Device AI.
Changes:
- pap_uri.rs: split is_registry_host into is_local_registry (localhost/IP,
still Registry) and is_web_domain (dotted FQDN → HttpsEndpoint). External
domains now resolve to HttpsEndpoint("https://..."), triggering the
existing starts_with:["https://"] intent rule → Web Page Reader agent.
Path and port are preserved (pap://ebay.com/electronics works). 41 tests.
- templates.rs: add WebPageTemplate rendering schema:WebPage JSON-LD as a
browser-tab-style block — URL bar (pap:// link for in-canvas navigation),
title, byline (publisher · author · date), description, body text extract.
- mod.rs: register WebPageTemplate in create_default_registry().
- topbar.rs: pap:// suggestion dropdown now shows "Browse pap://domain.com"
for web domains and auto-submits on selection; catalog agent completions
are unchanged.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Change 1 — Ask Once** - `preference_engine`: add `has_approved_scopes` (checks prior grants cover all required scopes) and `save_approved_scopes` (upserts approval record, unlike `record_scope_approved` which silently bails on missing rows) - `canvas.rs` (backend): widen `auto_approve` to skip gate when prior approval exists; call `save_approved_scopes` after each grant so approval persists **Change 2 — Linked Data Navigation** - `canvas.rs` (frontend): `resolve_prompt_text` now returns `Option<ResolvedUri>` instead of `Option<String>`; `dispatch_prompt_inner` gains `resolved_uri` + `source_block_id`; `submit_agent_link` accepts optional source block ID - `mod.rs`: expose `SourceBlockId` Leptos context (current block's ID) so child renderers can link new blocks back to their origin - `generic.rs`: `ExternalUrl` arm rewrites `https://` → `pap://` and renders as `<a class="pap-link typed-url-nav">` with `submit_agent_link(url, source_id)`; any url field on any schema.org entity is now a canvas navigation action - `web_reader.rs`: 2k → 8k text limit; paragraph-aware `extract_visible_text` preserving `\n\n` at block-level element boundaries; `extract_mentions` collects up to 12 same-origin links as `schema:WebPage` `mentions` array **Change 3 — Browse Block Expansion** - `types.rs`: add `auto_expand: bool` (serde default false) to `CanvasBlock` - `canvas.rs` (frontend): set `auto_expand` from `ResolvedUri::HttpsEndpoint`; `apply_block_event` preserves `auto_expand` (backend events carry default false) - `mod.rs`: `expanded` signal initialised from `block.auto_expand`; dynamic `canvas-block--expanded` class when resolved+expanded; hover-reveal expand toggle button on all resolved blocks; `BrowseBar` component shown in expanded browse blocks — prefills URL, Enter spawns linked block + collapses self - `main.css`: `.canvas-block--expanded` (fixed, full viewport), `.block-expand-btn` (hover-reveal absolute), `.canvas-block-browse-bar` + `.browse-bar-input`, `.typed-url-nav` (purple, monospace) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the brittle save-and-restore in `apply_block_event` with a structural type-level solution. ## BlockUpdate (backend → frontend wire type) - New `BlockUpdate` struct in `papillon-shared/types.rs` carries ONLY backend-owned fields. `linked_block_ids` and `auto_expand` are structurally absent — the compiler enforces that events can never overwrite frontend-only state. - `BlockEvent.block` changed from `CanvasBlock` to `BlockUpdate`. - All 9 emit sites in `commands/canvas.rs` migrated to `BlockUpdate`, dropping `linked_block_ids: Vec::new()` and `auto_expand: false`. ## apply_block_event (frontend state) - Signature changes from `(event_block: CanvasBlock)` to `(update: BlockUpdate)`. - Body replaced: direct field assignment, no save/restore loop. New frontend-only fields on `CanvasBlock` are automatically safe. ## BlockContext (Leptos provider) - Replaces `SourceBlockId(pub String)` with `BlockContext`, a `#[derive(Clone, Copy)]` context struct carrying all per-block reactive UI state: `id`, `expanded`, `show_reprompt`, `reprompt_value`. - `BlockRenderer` packs these into a single `provide_context(block_ctx)` instead of declaring four separate locals. - `BrowseBar` drops its `block_id: String` and `on_expand: RwSignal<bool>` props; reads `BlockContext` from context instead. - `generic.rs` ExternalUrl arm migrates from `SourceBlockId` to `BlockContext.id.get_value()`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate/BlockContext Incoming main had 3 conflicting files (block_renderer/mod.rs, block_renderer/generic.rs, state/canvas.rs) still referencing SourceBlockId and the old apply_block_event save/restore body. Resolved all conflicts by keeping our refactor in full. Also stripped 9 `auto_expand: false` lines that git auto-merged back into commands/canvas.rs from the origin/main side (BlockUpdate has no such field by design). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Benchmark Regression ReportThreshold: 10% regression vs baseline from main |
toadkicker
pushed a commit
that referenced
this pull request
Apr 15, 2026
- types.rs:1257 — remove `auto_expand: false` from BlockUpdate test initialiser; `auto_expand` was dropped from the struct in the #289 refactor but the test literal was not updated, causing a compile error (E0560) across Check, Test, WASM backend, and Justfile recipes - web_reader.rs:112 — remove needless borrow `&final_url`; Rust deref- coerces String → &str automatically, Clippy's needless_borrow lint (-D warnings) was failing Check, Test, Clippy, and Justfile recipes Both errors were pre-existing on main and blocked every PR. All CI checks for papillon-extension PR #290 should now be green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
toadkicker
added a commit
that referenced
this pull request
Apr 15, 2026
* feat(extension): auto-intercept https:// link clicks → pap:// Ordinary left-clicks on https:// links are now routed through the PAP handshake automatically, making zero-trust browsing the default when Papillon is active — no right-click context menu required. Changes: - content-script: capture-phase click handler converts https:// hrefs to pap:// and sends HTTPS_LINK_CLICKED to the service worker; guards pass through modifier clicks (Ctrl/Meta/Shift), Alt-click (per-click opt-out), synthetic events, download links, and per-domain exclusions; storage state is loaded at script init and kept live via chrome.storage.onChanged - service-worker: new HTTPS_LINK_CLICKED case swaps https:// → pap:// and calls openHandshakeTab with the original URL as fallback - types: HttpsLinkClicked interface + ExtensionMessage union member - popup: global auto-intercept toggle (aria role=switch, default on) and per-domain "Disable on this site" button backed by chrome.storage.sync - popup.css: toggle track/thumb via ::after + aria-checked; domain button teal accent when excluded Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(extension): add unit tests for uri, discovery, and intercept logic Brings test coverage from 1 file (db.ts) to 4 files with 117 tests passing. - src/lib/uri.test.ts (28 tests): full coverage of parsePapUri, httpsUrlToPap, isPapUri, toEndpoint, toHttpsEndpoint, isBrowserCompatible — all pure functions, zero mocks needed - src/lib/discovery.test.ts (23 tests): validateManifest (pure, boundary checks for field lengths and array limits) + fetchManifest (fetch stubbed via vi.stubGlobal — covers 200/404/500, wrong Content-Type, oversized bodies, network errors, origin-only vs full-path URLs) - src/content/intercept-logic.ts (new): extracts resolveInterceptUrl() from content-script.ts as a pure, side-effect-free function so it can be tested in Node without jsdom or Chrome API mocks - src/content/intercept-logic.test.ts (25 tests): exercises all 8 guard conditions (button, modifiers, isTrusted, download attr, scheme, state flags, domain exclusion) plus happy-path URL resolution and relative href handling - vitest.config.ts: enable @vitest/coverage-v8 with text + lcov reporters and a 60% lines threshold Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(extension): eliminate duplicate constants and magic values - New src/lib/constants.ts: single source of truth for the two chrome.storage.sync key strings (autoInterceptHttps, excludedDomains) previously duplicated across content-script.ts and popup.ts - Export NATIVE_APP_ID from native-messaging.ts; remove the identical private copy in service-worker.ts - discovery.ts: replace 5 repeated typeof/length checks with an isValidString() type-predicate helper; name every magic limit (MAX_AGENT_ID_LEN, MAX_TOOLS, MAX_CATEGORIES, etc.); fix the unsafe `as string` cast in categories by using a type-predicate filter - service-worker.ts: file-local named constants for badge hex colors (BADGE_COLOR_GOLD, BADGE_COLOR_PURPLE) and context-menu ID (CONTEXT_MENU_UPGRADE_ID) replace four inline magic strings - popup.ts: $<T>() DOM helper replaces nine verbose getElementById calls All 117 tests pass unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(rust): correct two regressions from BlockUpdate refactor (#289) - types.rs:1257 — remove `auto_expand: false` from BlockUpdate test initialiser; `auto_expand` was dropped from the struct in the #289 refactor but the test literal was not updated, causing a compile error (E0560) across Check, Test, WASM backend, and Justfile recipes - web_reader.rs:112 — remove needless borrow `&final_url`; Rust deref- coerces String → &str automatically, Clippy's needless_borrow lint (-D warnings) was failing Check, Test, Clippy, and Justfile recipes Both errors were pre-existing on main and blocked every PR. All CI checks for papillon-extension PR #290 should now be green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Todd Baur <todd@baursoftware.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
toadkicker
pushed a commit
that referenced
this pull request
Apr 15, 2026
After rebasing on main (BlockUpdate typed events + BlockContext provider pattern), the 6 emit sites in dataset_discovery.rs used the old CanvasBlock struct in BlockEvent — now expects BlockUpdate. Changes: - Replace CanvasBlock with BlockUpdate at all 6 emit sites, dropping linked_block_ids (frontend-only field, absent from BlockUpdate by design) - Introduce HandshakeConfig struct to reduce run_dataset_handshake from 8 to 5 args (fixes clippy::too_many_arguments) - Add HandshakeTaskResult type alias for JoinSet element type (fixes clippy::type_complexity) - Use *= for relevance_score blending (fixes clippy::assign_op_pattern) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6 tasks
toadkicker
added a commit
that referenced
this pull request
Apr 15, 2026
…#291) * feat: Croissant ML dataset discovery via federation-native PAP agents Adds parallel multi-source ML dataset discovery routed through the full 6-phase PAP handshake. A new `schema:DatasetAction` intent routes dataset queries to all matching zero-disclosure agents concurrently via JoinSet, rather than picking a single top-scored agent. Architecture: - Two TOML catalog agents (HuggingFace Hub, OpenML) require zero disclosure and self-configure via the existing FederatedRegistry — adding a third source requires only a new .toml file, no Rust changes - `canvas_discover_datasets` fans out to all `schema:DatasetAction` agents in parallel, merges results sorted by preference-blended relevance score, and emits progressive `block_updated` events during the JoinSet fan-out - Long-horizon memex integration: FTS5 prior-art check (7-day TTL) surfaces cached results immediately while fresh handshakes run; per-agent episodes feed PreferenceEngine signals so the system learns provider preference over time - `DatasetState` Leptos context (keyed by block_id) carries discovery phase FSM and memex hints to the `DatasetSearchTemplate` block renderer - Mandate TTL set to 7 days — dataset metadata is durable; the existing Active→Degraded→ReadOnly decay UI prompts re-query naturally New files: dataset_types.rs, dataset_discovery.rs, dataset_template.rs, dataset.rs (state), huggingface_datasets.toml, openml_datasets.toml All 385 tests passing; papillon-shared, papillon backend, and papillon-frontend WASM targets compile clean. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * test(intent): expand coverage from 32 to 47 tests Closes gaps identified in intent rule coverage: - Free Dictionary: 3 new tests (define/meaning of/definition of) — rule had zero coverage previously; any regression would have gone undetected - Clean-query stripping: 3 tests verify the third return value of detect_intent() (previously almost never checked); confirms "dataset sentiment analysis" strips to "sentiment analysis", "define photosynthesis" to "photosynthesis", etc. - Rule ordering conflicts: 3 tests document first-match-wins behavior for ambiguous inputs (weather beats dataset, dataset beats books, dataset beats arXiv) — establishes intended semantics and guards against rule reordering - Missing keywords: 2 tests for "temperature" (weather rule) and "population of" (REST Countries rule) which were declared in RULES but never exercised All 47 intent tests pass; 0 regressions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: adapt dataset_discovery to BlockUpdate wire type from #289 refactor After rebasing on main (BlockUpdate typed events + BlockContext provider pattern), the 6 emit sites in dataset_discovery.rs used the old CanvasBlock struct in BlockEvent — now expects BlockUpdate. Changes: - Replace CanvasBlock with BlockUpdate at all 6 emit sites, dropping linked_block_ids (frontend-only field, absent from BlockUpdate by design) - Introduce HandshakeConfig struct to reduce run_dataset_handshake from 8 to 5 args (fixes clippy::too_many_arguments) - Add HandshakeTaskResult type alias for JoinSet element type (fixes clippy::type_complexity) - Use *= for relevance_score blending (fixes clippy::assign_op_pattern) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Todd Baur <todd@baursoftware.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
BlockUpdate— new wire type inpapillon-sharedthat carries only backend-owned fields.linked_block_idsandauto_expandare structurally absent, so the type system prevents events from ever overwriting frontend-only state. All 9 emit sites incommands/canvas.rsmigrated fromCanvasBlocktoBlockUpdate.apply_block_eventsimplified — the save-and-restore pattern is deleted. The new body does direct field assignment;linked_block_idsandauto_expandare never in scope. New frontend-only fields added toCanvasBlockin future are automatically safe without touching this function.BlockContextprovider — replacesSourceBlockId(pub String)with a#[derive(Clone, Copy)]Leptos context struct carrying all per-block reactive UI state (id,expanded,show_reprompt,reprompt_value).BlockRendererprovides it via a singleprovide_context(block_ctx).BrowseBardrops itsblock_idandon_expandprops and reads from context.generic.rsExternalUrl arm migrated toBlockContext.Test plan
cargo test -p papillon-shared— 250 tests passcargo check -p papillon— zero warnings (unusedCanvasBlockimport removed)cargo check --target wasm32-unknown-unknown(frontend) — cleangrep -rn "SourceBlockId" apps/papillon/— zero source matchesgrep -n "linked_block_ids.*Vec::new\|auto_expand.*false" commands/canvas.rs— zero matchesprompt_textpreserved on final block🤖 Generated with Claude Code