Skip to content

#105 — explore external-drive contract: host bridge (postMessage + DOM attr), focusModel/setView, payload file paths - #225

Merged
cmbays merged 6 commits into
mainfrom
adapters-105-explore-host-bridge
Jun 11, 2026
Merged

cmbays merged 6 commits into
mainfrom
adapters-105-explore-host-bridge

Conversation

@cmbays

@cmbays cmbays commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements the explorer's external-drive contract (epic #99 V6, founder respec 2026-06-10 — IDE-arc alignment): the host bridge with dual binding (postMessage + the commit-only data-selected-model DOM attribute), the focusModel/setView forward hooks with the no-echo rule, per-node file paths in the explore payload, and a readable contract version — the seam the in-tree VS Code extension (epic #210) consumes.

Closes #105

The commit-event schema (contract version 1)

Posted via postMessage iff a host bridge registered at boot (detection-based: acquireVsCodeApi presence, or an injected window.cuteDbtHostBridge — presence checks only, inert standalone):

{
  type: "cute-dbt/commit",
  contractVersion: "1",
  modelId: "model.<package>.<name>",   // full manifest node id
  view: "lineage" | "cte",             // the active view at commit time (the V3 vocabulary)
  paths: {                             // the committed node's paths block (below)
    sql, schema_yaml, unit_tests: [{ name, yaml, fixtures }]
  }
}

Fires ONLY on the Space focus commit — never hover/click/search/focusModel. The DOM attribute (data-selected-model) always writes on commit (the standalone file:// / cmux binding); the bridge event rides alongside when a host is present. Beyond the AC minimum (id + view + version), the event carries the committed node's paths so a host can open files without re-parsing the payload carrier.

Discovery answers

Paths derivability (verified against the REAL committed fixtures, fusion pinned @ 9977b6cb):

Version-string surface (the justified choice): BOTH a server-rendered DOM attribute (<body data-cute-dbt-contract="1"> — readable by attribute-only observers with zero JS execution, the same observation channel as data-selected-model) AND the window.cuteDbtContract JS global for script hosts. The attribute is the single source — the global reads it back at boot, so the two surfaces cannot drift.

Contract-doc location: a book page (book/src/explore-contract.md, wired into SUMMARY) — it is a consumer-facing integration contract, not internal code doc — folded into the existing release-discipline CLI-surface SemVer policy (amended prose: a contract break is a v0.x minor / v1.0+ major event; no new versioning system).

What changed

  • feat(domain): Node.patch_path (additive, ADR-5-tolerant; scheme-stripped at the adapter).
  • feat(adapters): NodePathsPayload/UnitTestPathsPayload on the lineage carrier (explicit null/[], never omitted keys; unit tests name-ordered, resolution via the same resolve_target_model bridge as the badges); EXPLORE_CONTRACT_VERSION + body attribute; detail-card files section (createElement/textContent only).
  • templates/explore-lineage.js: contract global + bridge detection at boot (inert standalone), window.focusModel (highlight + center, NO attr write, NO bridge event — the no-echo rule; unknown id ⇒ false fail-open), the dual-bound commitFocus. Still exactly ONE selectedModel write site and exactly ONE postMessage site (asset_embed pins).
  • templates/explore-cte.js: rebinds window.setView (the view-state owner; same gates as the toggle; bogus kind ⇒ false); pinned to never post bridge events.
  • BDD: features/explore_js_contract.feature + step module (5 scenarios; the patch-path strip exercised via a verbatim scheme'd wire splice through the real subprocess). Feature-count mirrors 20 → 21 in BOTH ci.yml and lefthook.yml (verified in-tree: 21 files).
  • Headless (CDP): new ignored test — fake bridge injected via Page.addScriptToEvaluateOnNewDocument BEFORE page scripts parse; focusModel no-echo (zero events, no attr, with a live bridge present); setView round-trip; search-select + real pointer click never commit; Space dual-binds with the full schema asserted; CTE-view commit carries view: "cte"; a second uninjected tab proves the standalone attr-only path; console-clean throughout. Pinned single-write-site, badges, card/tooltip tests untouched and green.
  • Docs: book/src/explore-contract.md + SUMMARY + release-discipline fold.
  • Golden: examples/explore/dag.html regenerated (the only example diff — report goldens + tests.html zero-diff).

Gate evidence (run directly in the worktree)

Gate Result
cargo fmt --check clean
cargo clippy --all-targets --locked -- -D warnings exit 0 (by bare exit code; an earlier pipe-masked check hid 2 pedantic doc lints — fixed in cb654a9)
cargo nextest run --all-targets --locked 1176 passed
cargo test --test bdd 150 scenarios / 965 steps passed (5 new)
cargo test --test headless_zero_egress -- --ignored 9 passed (incl. the new host-bridge contract test)
cargo test --test headless_toggle -- --ignored 45 passed
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --locked clean
cargo deny check advisories/bans/licenses/sources ok
mdbook build book clean
cargo llvm-cov nextest --locked --fail-under-lines 85 passed
crap4rs --config crap4rs.toml --coverage lcov.info (CI-pinned v0.4.0) PASS — 1741 fns, 0 above threshold, worst 23.0
Explore golden double-render dag.html + tests.html byte-identical
git diff --text -U0 -- examples/ audit only examples/explore/dag.html (+135/−2); report goldens zero-diff
Leak grep on regenerated pages 0 matches for `/Users
insta no pending .snap.new
feature-count 21 in-tree; expected=21 in both mirrors

Invariants held

Zero-egress per-page gates green (bridge detection is presence-checks only; postMessage to a host is in-process, not network); domain purity (std + serde only); synthetic-only fixtures; PreflightError stays 4 variants; explore stays fail-open; single crate; the document.body.dataset.selectedModel single write site stays pinned.

🤖 Generated with Claude Code


Open in Stage

Summary by CodeRabbit

  • New Features

    • Added external-drive JavaScript contract enabling embedding hosts to programmatically control the explorer (focus models, toggle views).
    • Model detail cards now display associated file paths: SQL source, schema YAML patch, and unit test files.
    • Explorer posts versioned commit events to host applications with model metadata.
  • Documentation

    • Added comprehensive documentation for the explorer's external-drive contract.
    • Updated release discipline documentation to reflect the new contract scope.

github-actions Bot and others added 6 commits June 11, 2026 03:29
The schema-properties YAML path joins the domain Node as an additive
ADR-5-tolerant field. Both engines serialize the wire patch_path as a
package URI (<package>://models/schema.yml — fusion
normalize_manifest_patch_path / package_uri_path, dbt-schemas
manifest/manifest.rs @ 9977b6cb, mirroring dbt-core); the manifest
adapter strips the scheme on ingestion so the domain carries a plain
relative path. Verified against BOTH real committed fixtures
(jaffle-shop = dbt-core 1.11, playground = fusion 2.0-preview), plus
the null-fill / omitted-key / scheme-less tolerance shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nding, focusModel/setView, payload paths (#105)

The explorer's host-facing surface, versioned as contract "1":

- window.focusModel(id): highlight + center ONLY — no
  data-selected-model write, no bridge event (the no-echo rule; a host
  pushing editor-sync focus never hears its own push back).
- window.setView(kind): programmatic lineage <-> cte switch, rebound by
  the CTE engine (the view-state owner); inert fail-open default.
- Dual-bound Space commit: the data-selected-model attribute always
  writes (the standalone file:// binding) AND, iff a host bridge
  registered at boot, a versioned commit event posts via postMessage
  ({type: cute-dbt/commit, contractVersion, modelId, view, paths}).
  Detection-based registration (acquireVsCodeApi presence or an
  injected window.cuteDbtHostBridge), presence checks only — inert
  standalone, zero-egress unaffected.
- Per-node paths block on the lineage payload (sql /
  schema_yaml / unit_tests[].{name,yaml,fixtures}) — all
  project-relative manifest facts; absence explicit (null/[]), fixture
  refs verbatim. Surfaced read-only in the detail card's files section.
- The version is server-rendered as <body data-cute-dbt-contract> (for
  attribute-only observers); window.cuteDbtContract reads it back — one
  source, no drift.

asset_embed pins: still exactly one selectedModel write site, exactly
one hostBridge.postMessage site, hooks present, CTE engine posts
nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-count mirrors 20 -> 21 (#105)

- features/explore_js_contract.feature + step module: the
  server-rendered halves through the real explore subprocess — the
  contract-version body attribute and the payload-paths carrier
  (package-URI strip exercised via a verbatim wire splice; external
  fixture refs verbatim incl. the bare dbt-core name; explicit-empty
  paths).
- headless (CDP, ignored suite): injected fake host bridge planted via
  Page.addScriptToEvaluateOnNewDocument BEFORE page scripts parse —
  focusModel highlights/centers with NO attr write and ZERO bridge
  events (no echo) and returns false on unknown ids; setView round-trips
  lineage <-> cte and rejects bogus kinds; search-select + real pointer
  click never commit on either binding; Space dual-binds (attr + exactly
  one versioned event carrying modelId/view/paths); a CTE-view commit
  carries view: cte; a second uninjected tab proves the standalone
  attr-only path; console-clean throughout.
- feature-count mirrors bumped 20 -> 21 in BOTH ci.yml and lefthook.yml
  (verified: find features -maxdepth 1 = 21 in-tree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…se-discipline SemVer policy (#105)

New Reference page documenting the full surface: the contract version
(DOM attribute + JS global, one source), both forward hooks with the
no-echo rule, the dual-bound commit signal + host-bridge registration,
and the payload-paths shape with the engines' path-derivability notes.
release-discipline.md folds the contract into the existing CLI-surface
SemVer policy prose — a contract break is a v0.x minor (v1.0+ major)
event; no new versioning system.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… surface

playground-rendered: the contract-version body attribute, the per-node
paths blocks on the carrier, the files card section + engine JS.
Double-render byte-identity verified; zero /Users|/home|root_path
matches; tests.html and all report goldens zero-diff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
)

clippy --locked failed on the pre-push gate (SemVer / root_path in
explore.rs doc comments); the earlier local sweep masked the exit code
behind a pipe. Verified clean by bare exit code now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR implements the external-drive JavaScript contract for the explorer DAG page. It adds server-rendered contract versioning, per-model file path metadata in lineage payloads, host-bridge integration with forward hooks (focusModel/setView) and dual-bound commit signals, comprehensive test coverage, and contract documentation.

Changes

Explorer External-Drive Contract

Layer / File(s) Summary
Domain & adapter: patch_path field
src/domain/manifest.rs, src/adapters/manifest.rs, tests/manifest_ingestion.rs
Node domain model gains optional patch_path field with serde defaults. WireNode ingests patch_path from manifest schema v12, strips package:// URI schemes, and domain translation normalizes the value. Integration test validates scheme stripping and null/omit handling.
Explore adapter: contract and payload
src/adapters/explore.rs, src/adapters/asset_embed.rs
EXPLORE_CONTRACT_VERSION constant introduced. NodePathsPayload and UnitTestPathsPayload types carry model SQL/schema paths and unit-test declarations with fixtures. Aggregation helpers group unit tests by resolved target model and assemble per-node paths. Template binding adds contract_version field; render_explore passes the version for server-side body attribute. Unit and integration tests verify path extraction, deterministic ordering, fixture preservation, explicit-null/empty serialization, and body attribute presence on dag.html (absent on tests.html). Asset embed tests check contract surface: exactly one postMessage site, window.cuteDbtContract global, focusModel and setView declarations.
Frontend: contract and hooks
templates/explore-dag.html, templates/explore-cte.js, templates/explore-lineage.js
explore-dag.html server-renders data-cute-dbt-contract attribute on body and adds .detail-paths styling. explore-cte.js exposes window.setView(kind) forward hook validating input and delegating to internal toggle. explore-lineage.js mirrors contract version into window.cuteDbtContract global, installs inert hook defaults, detects host-bridge (VS Code or injected), renders per-node "files" section in detail card, updates commitFocus to write dataset attribute and post versioned cute-dbt/commit event when bridge available, and implements window.focusModel(id) for in-page highlight+center without side effects.
Test infrastructure and scenarios
tests/steps/world.rs, tests/steps/explore_js_contract.rs, tests/steps/mod.rs, tests/steps/explore_full_manifest.rs, features/explore_js_contract.feature
ExplorePlan extended with path_tests collection; ExplorePathTestDecl captures test name, target model, YAML path, and fixture refs. ExploreModelDecl gains original_file_path and wire_patch_path. New step-definition module provides path_unit_test helper, model/test mutable lookups, and given/then steps for setting paths and asserting contract attributes, payload shape, and empty-paths cases. Synthetic manifest wiring includes path_tests via explore_js_contract helper. Five feature scenarios validate contract version rendering and lineage payload path propagation (SQL, schema YAML, unit tests, fixtures, empty).
E2E test
tests/headless_zero_egress.rs
New ignored test injects fake host bridge, verifies contract surface (body attribute + JS global + hook callability), asserts forward-hook no-echo behavior, drives Space commits and validates dual-bound payload (version, modelId, view, paths), repeats for CTE view, confirms second tab without bridge has no globals while Space still writes dataset, and asserts console cleanliness. Helper pathed_explore_model creates compiled model with SQL and patch paths.
Documentation and CI
book/src/explore-contract.md, book/src/release-discipline.md, book/src/SUMMARY.md, .github/workflows/ci.yml, lefthook.yml
New explore-contract.md documents zero-egress guarantee, contract versioning (version 1 via DOM attribute and JS global), forward hooks (focusModel/setView semantics and no-echo rule), commit signal (Space keypress, dual-bound via attribute and versioned postMessage), required paths payload structure (project-relative, explicit null/empty handling, sql/schema_yaml/unit_tests schema), and deliberate exclusions (no hover/click/search signaling, tests.html not drivable). release-discipline.md extends SemVer coverage to include explorer external-drive contract (hooks, commit, paths shape) with contract-specific version string and v0.x minor / v1.0+ major versioning. SUMMARY.md adds TOC entry. CI and lefthook feature-count guards incremented from 20 to 21.

Sequence Diagram

sequenceDiagram
    participant Host
    participant EmbedPage as explorer page
    participant HostBridge as host bridge<br/>(optional)
    
    Host->>EmbedPage: page load (injected window.cuteDbtHostBridge)
    activate EmbedPage
    EmbedPage->>EmbedPage: init: read data-cute-dbt-contract<br/>mirror to window.cuteDbtContract<br/>detect host bridge
    EmbedPage->>Host: expose window.focusModel(id)<br/>expose window.setView(kind)
    Note over EmbedPage,Host: forward hooks (no-echo)<br/>return boolean, fail-open
    
    Host->>EmbedPage: call window.focusModel(modelId)
    activate EmbedPage
    EmbedPage->>EmbedPage: highlight + center node<br/>NO dataset write<br/>NO bridge event
    EmbedPage->>Host: return true/false
    deactivate EmbedPage
    
    Host->>EmbedPage: user presses Space
    activate EmbedPage
    EmbedPage->>EmbedPage: commitFocus()
    EmbedPage->>EmbedPage: write document.body.dataset.selectedModel
    EmbedPage->>EmbedPage: center highlight
    
    opt hostBridge available
        EmbedPage->>HostBridge: postMessage({<br/>type: 'cute-dbt/commit',<br/>contractVersion: '1',<br/>modelId, view, paths<br/>})
    end
    deactivate EmbedPage
    
    Host->>EmbedPage: call window.setView(kind)
    activate EmbedPage
    EmbedPage->>EmbedPage: toggle view<br/>(if kind valid)
    EmbedPage->>Host: return kind===activeView
    deactivate EmbedPage
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • cute-dbt#105: This PR fully implements the external-drive host-bridge contract specified in the issue, adding contract versioning, forward hooks, commit signals, and per-model path metadata to support external tool integration.

Possibly related PRs

  • breezy-bays-labs/cute-dbt#72: Updates the same feature-count CI guard in .github/workflows/ci.yml, incrementing the expected feature file count baseline.
  • breezy-bays-labs/cute-dbt#162: Modifies the same feature-count gate configuration in both .github/workflows/ci.yml and lefthook.yml, adjusting expected count and documentation.

Poem

🐰 A bridge now spans the explorer's edge,
Where hosts and pages softly pledge:
"Space commits, and paths are true,
Window hooks to focus you."
No echo when you call our name—
Just highlight, view, and commit frame. 🎯

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: implementation of the explorer's external-drive contract with host bridge, dual binding mechanism, forward hooks, and payload file paths.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch adapters-105-explore-host-bridge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ghost

ghost commented Jun 11, 2026

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

📄 Rendered report preview

All golden examples regenerated cleanly.

🟡 Golden examples

Committed to examples/ and byte-identity gated — the canonical reports contributors and consumers browse. Stable across PRs.

Report View Download
diff-showcase-report.html ▶ Open ↗ ⬇ Download
jaffle-shop-report.html ▶ Open ↗ ⬇ Download
playground-report.html ▶ Open ↗ ⬇ Download

🐶 Live dogfood preview

This PR doesn't touch dbt-project/, so there's no live dogfood preview.

▶ Open ↗ opens the report in your browser in one click —
published to this repo's GitHub Pages under /pr-225/.
⬇ Download fetches the same self-contained HTML as a workflow
artifact (auth-gated; works fully offline). Either way the report
makes zero external resource requests.

The Pages preview may take ~1 min to update after this comment
posts. On PRs from forks the Open link is unavailable (read-only
token) — use Download.

Alternative: GitHub CLI
# gh CLI >= 2.63 extracts into ./report-preview-playground/.
gh run download 27331330386 -R breezy-bays-labs/cute-dbt -n report-preview-playground
open report-preview-playground/playground-report.html

Posted by report-preview.yml for cb654a92a31f6bd3f8e1d92a7f0a1d8e07498f98. Affordance only — never blocks merge.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements the explorer's external-drive contract (cute-dbt#105), enabling external tools to drive and observe the lineage view in dag.html. It introduces a versioned contract attribute on the body, forward hooks (window.focusModel and window.setView), a dual-bound commit signal, and project-relative file paths in the lineage payload. On the backend, manifest ingestion is updated to extract and strip the package URI scheme from patch_path. Feedback on the changes suggests optimizing the strip_package_uri_scheme helper function in src/adapters/manifest.rs to accept and return borrowed &str slices instead of owned Strings to eliminate redundant heap allocations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/adapters/manifest.rs
@cmbays
cmbays merged commit 5500c23 into main Jun 11, 2026
32 of 33 checks passed
@cmbays
cmbays deleted the adapters-105-explore-host-bridge branch June 11, 2026 07:39
github-actions Bot added a commit that referenced this pull request Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature: external-drive contract — host bridge (postMessage + DOM attr) + focusModel/setView + payload file paths

1 participant