Skip to content

#180 — design integration PR-4: vendored Cytoscape engine + Mermaid⇄Cytoscape picker - #192

Merged
cmbays merged 4 commits into
mainfrom
render-180-cytoscape-engine-picker
Jun 10, 2026
Merged

cmbays merged 4 commits into
mainfrom
render-180-cytoscape-engine-picker

Conversation

@cmbays

@cmbays cmbays commented Jun 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Vendors Cytoscape 3.30.2 as the report's second graph engine behind a new settings-panel DAG-engine picker. Mermaid stays the static default; Cytoscape is the opt-in interactive engine (hover context card, click-to-highlight transitive lineage with dim-complement, pan/zoom). The swap happens in place (no reload) and persists with the appearance state under cute-dbt.appearance.v1.

Closes #180

Acceptance criteria — evidence

  • AC1 — vendored cytoscape.min.js 3.30.2 (UMD, never ESM) under assets/ with a MANIFEST.toml entry (version + SHA-256 83e8c54a6bec655bfd81df07df605649c268af69aeca67a5ea2da54ea42dac81 + SPDX MIT), fetched from the npm registry tarball (https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.2.tgz, bundle at package/dist/cytoscape.min.js inside). Embedded via include_str! (asset_embed::CYTOSCAPE_JS) with a version-banner pin test. Core only — no layout plugin (first-party longest-path preset layout in templates/cyto-dag.js; never EPL cytoscape-elk). The npm dist bundle ships no sourceMappingURL trailer (verified — nothing to strip).
  • AC2 — init hygiene: canvas-text labels only (XSS-safe by construction; no HTML-label extension), explicit non-webfont system fontFamily, no workers, no layout plugins; handlers bound from our JS to rendered elements (never click directives executing data); per-click mutates classes in place — a headless guard asserts the same cy instance survives a tap (no render re-entry).
  • AC3 — dark-bg palette variant: the Cytoscape engine reads colors exclusively through window.cuteDagPalette() over the existing JOIN_COLORS_LIGHT/_DARK tables — no second palette table exists (an asset_embed test rejects one), so the edge-vocab-completeness gate's grep target stays accurate unchanged. Verified visually on all 5 themes (light/dark/tokyo/solarized/gruvbox): from edges stay legible on every dark theme (the handoff's documented bug cannot recur — both engines share the dark palette by construction).
  • AC4 — engine picker in the settings panel, persisted in the extended cute-dbt.appearance.v1 POD via theme.js (closed vocabulary, unknown values coerce to mermaid); Mermaid stays the static default; switch is in place — headless guard pins a window sentinel surviving the flip.
  • AC5 — ADR-4 amendment: breezy-bays-labs/ops#391 — records the conscious reversal of the one-engine-per-page posture (explicitly NOT "exit criteria met"), pins the init contract, preserves the both-engines→eventual-drop exit path, and reconciles the stale 2026-05-29 text feature: interactive Cytoscape model-lineage DAG + highlight/focus interaction #101 references (which had never been committed; landed verbatim in the same change). Mirrored operationally in ARCHITECTURE.md §5 + AGENTS.md.
  • AC6 — per-engine zero-egress: tests/headless_zero_egress.rs gained every_committed_example_stays_zero_egress_with_the_cytoscape_engine_selected — flips the picker on each committed example over real file:// with DNS denied, waits for the live cy canvas, asserts zero external requests. Both arms pass locally.
  • AC7 — headless interaction guards (tests/headless_toggle.rs): dag_engine_picker_switches_engines_in_place_and_persists (default Mermaid SVG → cy canvas in place → blob persists → flip back destroys cy) and cytoscape_hover_card_appears_and_tap_highlights_lineage_in_place (hover card via textContent; tap dims exactly the non-lineage branch; same-instance assertion; Inspect panel syncs via __cuteSelectNode).

Size delta

Each golden grows +392,097 bytes (~+383 KiB) — cytoscape.min.js 373 KB + the first-party cyto-dag.js + picker markup (vs the ~+492 KB estimate, which assumed cytoscape-dagre; we ship core-only):

example before after
jaffle-shop-report.html 3,719,194 4,111,291
playground-report.html 3,756,870 4,148,967
diff-showcase-report.html 3,762,692 4,154,789

Golden diffs spot-checked at -U0: non-asset regions are exactly the picker row, the .cte-dag-cyto host, and the two new script blocks. The default-rendered DAG stays Mermaid, so the visible default in the goldens is unchanged.

Gates (run directly — lefthook skips in fresh worktrees)

cargo fmt --check ✅ · cargo clippy --all-targets --locked -- -D warnings ✅ (exit 0) · cargo nextest run ✅ 895/895 · cargo test --test bdd ✅ 97 scenarios · headless_toggle -- --ignored ✅ 34/34 · headless_zero_egress -- --ignored ✅ 2/2 (incl. the Cytoscape arm) · RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --locked ✅ · cargo deny check ✅ · crap4rs ✅ (1303 fns, 0 above threshold 25, worst 23.0) · goldens + insta regenerated. No new .feature files → feature-count gates untouched.

Notes for reviewers

🤖 Generated with Claude Code


Open in Stage

…ker (#180)

Design integration PR-4. Vendors cytoscape.min.js 3.30.2 (UMD core only,
MIT, SHA-256-pinned in assets/MANIFEST.toml) as the report's second graph
engine behind a new settings-panel DAG-engine picker. Mermaid stays the
static default; the flip happens in place (no reload) and persists with
the appearance state under cute-dbt.appearance.v1.

- templates/cyto-dag.js (first-party): longest-path preset layout (no
  layout plugin, no cytoscape-dagre, never EPL cytoscape-elk), canvas-text
  labels (XSS-safe by construction), hover context card (textContent-only
  DOM), click-to-highlight lineage with dim-complement -- per-click is
  in-place class mutation, never a render re-entry.
- interaction.js: renderDag() becomes the engine dispatcher
  (renderMermaidDag keeps the existing path); window.cuteDagPalette is the
  single palette source for BOTH engines (the edge-vocab-completeness gate
  keeps grepping JOIN_COLORS_LIGHT/_DARK -- no second palette table
  exists), fixing the handoff's dark-theme from-edge-vanishing bug by
  construction; window.__cuteSelectNode drives the Inspect panel without
  rebuilding the DAG.
- theme.js: engine pref + picker wiring + validation (closed vocabulary,
  unknown values coerce to mermaid).
- Headless guards: picker switches engines in place (sentinel survives,
  cy instance lifecycle), hover-card appears, tap highlights lineage on
  the SAME cy instance; zero-egress gains a Cytoscape-selected arm over
  real file:// with DNS denied.
- Goldens + insta regenerated (report grows ~+392 KB: the 373 KB bundle +
  the first-party engine + picker markup).

Closes #180

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

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@cmbays, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 8 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10b4a8de-781d-4f9b-98f0-f947f731a219

📥 Commits

Reviewing files that changed from the base of the PR and between 43b68d2 and 663aa55.

⛔ Files ignored due to path filters (3)
  • assets/cytoscape-3.30.2.min.js is excluded by !**/*.min.js
  • tests/snapshots/golden_report__rendered_report_skeleton.snap is excluded by !**/*.snap
  • tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap is excluded by !**/*.snap
📒 Files selected for processing (16)
  • AGENTS.md
  • ARCHITECTURE.md
  • assets/MANIFEST.toml
  • examples/README.md
  • examples/diff-showcase-report.html
  • examples/jaffle-shop-report.html
  • examples/playground-report.html
  • src/adapters/asset_embed.rs
  • src/adapters/render.rs
  • templates/cyto-dag.js
  • templates/interaction.js
  • templates/report.html
  • templates/theme.js
  • tests/headless_toggle.rs
  • tests/headless_zero_egress.rs
  • tests/render_integration.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch render-180-cytoscape-engine-picker

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 10, 2026 •

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 10, 2026 •

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-192/.
⬇ 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 27312231575 -R breezy-bays-labs/cute-dbt -n report-preview-playground
open report-preview-playground/playground-report.html

Posted by report-preview.yml for 663aa55ab57e52cee732f279e46b0dd4852b4557. 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 introduces Cytoscape.js as an opt-in, interactive alternative DAG engine alongside the default static Mermaid engine, allowing users to switch engines in place via a settings-panel picker. The integration includes embedding the Cytoscape UMD core bundle, implementing a custom longest-path layout, and adding headless tests to verify zero-egress compliance and user interactions. Feedback on the implementation suggests using Object.create(null) for maps to prevent prototype lookup issues, applying the visibility class to the hover card before measuring its dimensions to ensure correct positioning, and simplifying the cy.fit call.

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 templates/cyto-dag.js Outdated
Comment thread templates/cyto-dag.js Outdated
Comment thread templates/cyto-dag.js Outdated
Comment thread templates/cyto-dag.js Outdated
github-actions Bot and others added 3 commits June 10, 2026 19:00
…ps, reveal-before-measure, fit(28)

- computeDepths/layoutPositions: Object.create(null) for every id-keyed
  map so a CTE legitimately named toString/constructor can never collide
  with a prototype property.
- showCard: add .is-visible before measuring offsetWidth/Height (the
  chassis hides via opacity so measurement was already real; ordering is
  now robust to a display-based hide).
- cy.fit(28): the documented number-as-padding form, verified against
  the vendored 3.30.2 getFitViewport source.
- Goldens + chrome snapshot regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cmbays
cmbays merged commit 33f2c5e into main Jun 10, 2026
32 checks passed
@cmbays
cmbays deleted the render-180-cytoscape-engine-picker branch June 10, 2026 23:13
github-actions Bot added a commit that referenced this pull request Jun 10, 2026
cmbays added a commit that referenced this pull request Jun 11, 2026
…irement, DAG final-stroke AA fix (#203)

* feat(render): three new AA-pinned themes — Dracula, Catppuccin Latte, Rosé Pine Dawn (8 total) + Solarized/Gruvbox AA touch-ups

Lands the design pass-2 theme delta on the chassis (cute-dbt#198):

- templates/report.css gains the dracula / latte / rosepine [data-theme]
  blocks verbatim from the pass-2 engine/tokens.css (incl. per-theme
  code-surface tokens); Solarized --diff-remove-fg #c5341c -> #b82d18 and
  Gruvbox --code-rem-sigil #fb4934 -> #fb6a5d per the pass-2 AA
  re-verification; the pinned AA table is replaced with the 8-theme
  README §2.3 table (do not re-brighten).
- templates/theme.js THEMES becomes the 8-entry list, light family first
  (light/solarized/latte/rosepine/dark/tokyo/gruvbox/dracula); dark-family
  themes still toggle html.dark for the vendored DataTables dark rules.
  Static-markup contract kept: only the DATA was lifted from pass-2, not
  its runtime DOM-injection approach.
- templates/report.html theme grid shows all 8 chips (static markup, the
  #188 contract), reordered light family first with pass-2 swatch dots.
- headless appearance guard extended: latte (light family) + dracula
  (dark family) chips flip [data-theme] + the html.dark DataTables sync,
  and the grid lists exactly 8 chips.
- insta snapshots (chrome + skeleton) regenerated for the above.

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

* feat(render): retire diff-cells colour/marks control per design pass-2

Founder-confirmed 2026-06-10 (epic #197 open question 1): the #188
"Diff cells: colour / marks" control is retired — diff cells always
render in colour (pass-2 README §2.3).

- templates/report.html: the Diff-cells settings row (static markup) is
  removed; the settings-menu comment documents the retirement.
- templates/theme.js: applyDiffStyle, the diffstyle pref default, the
  load() key, the .diff-seg sync/wire blocks and the boot apply are
  removed. A LEGACY persisted diffstyle key in cute-dbt.appearance.v1 is
  ignored gracefully: load() copies only the live keys and nothing sets
  data-diffstyle, so no error and no leftover attribute.
- templates/report.css: the :root[data-diffstyle="marks"] rules are
  removed (a tombstone comment records the retirement).
- headless appearance guard updated: asserts no .diff-seg /
  [data-diffstyle] markup survives, and a seeded legacy diffstyle key
  still hydrates the live keys with no data-diffstyle attribute after
  reload.
- insta snapshots (chrome + skeleton) regenerated for the above.

This commit is deliberately standalone so the retirement is trivially
revertable.

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

* feat(render): design-2 chassis chrome — 28rem settings panel, two-layer legend final swatch, cyto card wrap, role_final AA stroke

- Settings panel width 21rem -> 28rem (max-width 92vw, box-sizing
  border-box) with the narrow-viewport fallback min(28rem, 94vw), per the
  pass-2 engine/chrome.css — room for the 8-theme grid without wrapping.
- Legend final-node swatch becomes the two-layer hexagon: a color-mix
  darkened edge layer + a ::before fill inset (pass-2 engine/base.css),
  mirroring the darker role_final stroke.
- role_final stroke #E69F00 -> #b07400 (AA against the #fdf2dc fill) in
  BOTH DAG engines: the Mermaid classDef literal in renderDag and the
  ROLE_FILLS table cuteDagPalette feeds the Cytoscape engine. The #192
  dark-bg palette variants are untouched — they cover edges/anchor only;
  node fills deliberately stay light in every theme, so the darker stroke
  only gains contrast against its own light fill.
- Cytoscape node-card wrap fixes (width max-content, overflow-wrap
  anywhere, nc-row flex-wrap) per the pass-2 engine/chrome.css.
- insta snapshots (chrome) regenerated for the above.

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

* feat(render): per-theme AA token override for the #194 tier chips on Latte

The cute-dbt#198 discovery check ran the WCAG math on the findings-surface
tier chips (0.68em text) for all three new themes:

- Dracula:        total 6.55 / high 5.59 / advisory 6.08 — pass
- Rosé Pine Dawn: total 6.11 / high 5.68 / advisory 4.56 — pass
- Latte:          total 4.91 / high 4.34 / advisory 4.37 — high+advisory
  a hair under AA 4.5

Fix per the issue's discovery contract: a per-theme token override scoped
to .tier-chip (accent -> one-step-darker latte blue #1a5cdd, 5.13:1;
muted -> Catppuccin subtext1 #5c5f77, 5.53:1). The chip markup/rules and
the verbatim latte theme block are untouched.

Pre-existing, out of scope here: shipped Solarized tier-high is 3.41:1
on main today (accent #268bd2 on the cream surface) — flagged on the PR
for a follow-up.

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

* test: regenerate golden examples for the design-2 chassis delta

jaffle-shop / playground / diff-showcase re-rendered with the exact
example-report-check matrix commands. Reviewed with
git diff --text -U0 -- examples/: every changed line is one of the
intended deltas (3 new theme blocks + AA touch-ups, 8-chip theme grid,
diffstyle retirement, 28rem settings panel, two-layer legend swatch,
cyto node-card wrap, role_final #b07400 stroke, latte chip override).

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

* fix(render): un-cap the narrow-viewport settings panel at 94vw

CodeRabbit on #203: the base .settings-panel rule's max-width: 92vw
clipped the <=720px fallback width: min(28rem, 94vw), so the extra 2vw
never applied and the 8-chip theme grid wrapped early on small screens.
The media block now widens max-width alongside the fallback. (The same
latent cap exists in the pass-2 engine/chrome.css — this honors the
design's 94vw intent rather than its literal bytes.)

Goldens + chrome snapshot regenerated.

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

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
cmbays added a commit that referenced this pull request Jun 11, 2026
…217)

* feat(adapters): design-2 layout restructure + DAG node-detail shelf (#201)

Restructure the report body around the model -> test split (the pass-2
design canon, merged INTO shipped code - never a file replacement):

- .dag-stage wraps .dag-canvas-col (Mermaid + Cytoscape hosts + legend)
  beside the new aside.dag-shelf (head: title + close; body). Clicking a
  DAG node opens the shelf: model-detail card from the #200
  manifest_nodes lookup (tags -> name+materialized -> description ->
  model tests -> columns with click-to-expand test-count chips) +
  compiled SQL with the role badge riding the summary row. Given
  fixtures are deliberately NOT rendered in the shelf - they live in the
  Given panel (the #131 ordinal binding rides renderAllInputs' loop
  index).
- Node->model mapping: final-select -> this model; import -> the ref()
  target of its bound given (the #131 bound_to_node seam); unknown ->
  card omitted, compiled SQL still shows.
- window.__cuteSelectNode now opens the shelf - the #192 Cytoscape tap
  and the #194 findings pin both keep routing through this one seam
  (no renderDag per click; Mermaid's activate keeps its re-render).
  Model/test re-selection closes the shelf and re-centers the DAG.
- section.test-section/.test-card below the DAG: the unit-test selector
  IS the card title (sizeSelectToWidest: static width, max-width
  clamped), the #91 scope toggle to its right (data-testid retained),
  the .test-badges row (tb-tag / untagged tb-muted / tb-meta - the
  overrides badge arrives with #202), the larger-font always-open
  description + details body. Founder decision on epic #197: always-open
  card, NOT a drawer; only the authoring-YAML drawer stays collapsible.
- .model-findings (#194) preserved INTACT, placed ABOVE the model SQL +
  DAG near the model selection (founder decision on epic #197,
  superseding the audit's between-DAG-and-test-section proposal).
- test-description-section and the left .panel-toggle (Inspect /
  All-inputs) retired: the left panel header reads Given and always
  renders all inputs; the #74 no-empty-landmark intent carries over via
  the hidden attribute on the in-card description.
- The DAG hint subtitle becomes the #146-contract focusable button +
  CSS bubble (hover AND keyboard focus, aria-label parity).
- Kept shipped: theme-aware cyto-dag.js palette, ModelPayload.path in
  renderModelSql (no <name>.sql regression), the authoring-YAML drawer
  labels and Diff/File toggles.

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

* test: port-forward headless guards to the #201 shelf layout + new shelf guard

Every re-target is deliberate, never collateral (#201):

- findings_panel_renders_checklist_tiers_sketch_rationale_and_pin +
  cytoscape_hover_card_appears_and_tap_highlights_lineage_in_place:
  .left-panel-body .node-detail -> .dag-shelf-body .node-detail (the
  __cuteSelectNode seam survives; the pin still selects + scrolls +
  pin-flashes).
- same_ref_givens / changed_cell_null-vs-string / cell_diff_toggle /
  fusion-csv / sql-literal / external-fixture / column-tooltip /
  incremental guards drop the show_all_inputs panel-toggle click (the
  Given panel always renders every input now); the helper is retired.
- incremental_badges_modes_tooltip_and_this_given: BUBBLE_VIS scoped to
  the expected panel - the DAG hint is a second .expect-tooltip on the
  page since #201, so a bare first-match read the wrong bubble.
- stacked_panel_does_not_blow_out_viewport_at_375px re-verified on the
  new layout (the .dag-stage flex row wraps below on narrow - the
  #157/#158 contract holds).
- source_given fixture-card guard re-homed: the card renders in the
  Given panel; clicking the bound import CTE opens the shelf with
  compiled SQL and NO fixture card (the no-fixtures rule).
- NEW dag_node_click_opens_shelf_with_model_card_and_close_clears_selection:
  shelf opens with compiled SQL + role badge on the summary; the
  final-select node renders the manifest_nodes model card (description +
  tags); an unmapped node omits the card but keeps compiled SQL; the
  close button clears body + baked Mermaid selection; a fresh test
  selection closes the shelf; the DAG-hint info button honors the #146
  focus-reveals-bubble contract.
- BDD: the #74 ordering scenario re-homed - the test section (hosting
  the description) renders between the cte-dag section and the
  panel-row.

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

* test: regenerate golden examples + re-accept insta DOM snapshots for #201

examples/{jaffle-shop,playground,diff-showcase}-report.html regenerated
with the exact ci.yml example-report-check recipe; every changed byte
traces to the #201 templates (report.html DOM, interaction.js,
report.css) - audited via git diff --text -U0. No root_path/username
bytes. Insta skeleton + chrome snapshots re-accepted for the intended
restructure only.

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

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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: design integration PR-4 — vendored Cytoscape engine + Mermaid⇄Cytoscape picker + ADR-4 amendment

1 participant