From d08509562d1a68170ea4a57d00ccc9397fa424f0 Mon Sep 17 00:00:00 2001 From: ktjn Date: Mon, 27 Jul 2026 08:59:09 +0200 Subject: [PATCH 1/4] docs: specify graph theme CSS cleanup --- .../2026-07-27-graph-theme-css-design.md | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-27-graph-theme-css-design.md diff --git a/docs/superpowers/specs/2026-07-27-graph-theme-css-design.md b/docs/superpowers/specs/2026-07-27-graph-theme-css-design.md new file mode 100644 index 00000000..73643a60 --- /dev/null +++ b/docs/superpowers/specs/2026-07-27-graph-theme-css-design.md @@ -0,0 +1,55 @@ +# 2026-07-27 Graph Theme CSS Design + +## Problem + +The playground redesign removed the internal graph-node styles while retaining +the semantic node markup. The later dark-mode work themed React Flow's outer +wrappers, so nodes now inherit a flat surface without padding, borders, type +distinction, or a clear selected state. The graph is technically usable, but +its hierarchy and contrast are weak in both themes. + +## Goal + +Restore a coherent graph presentation in light and dark themes without changing +the graph model or component structure. Node types should remain distinguishable +without relying on saturated fills, and the canvas, edges, controls, MiniMap, +selection state, and exported SVG should feel like one visual system. + +## Design + +Keep the existing `graph-node` markup and React Flow classes. Add graph-specific +theme tokens for the canvas, dots, surfaces, borders, muted text, edges, +selection, MiniMap, and restrained semantic accents. Define those tokens in the +default, system-dark, and explicit-dark theme blocks so the browser view and +theme-aware SVG export resolve the same values. + +Restore the base node treatment with compact padding, a small radius, a visible +border, and overflow-safe labels. Use the existing one-letter kind marker as a +small badge. Differentiate domain, entity, version, projection, and field nodes +with a restrained accent border and badge treatment rather than full-card +colors. Preserve the dashed version treatment because it communicates a +structural distinction independently of color. + +Remove the broad opaque background from React Flow's outer node wrapper so the +inner semantic node owns its complete appearance. Give keyboard focus and +selected nodes a clear theme-aware outline. Increase edge and handle contrast, +while keeping containment edges quieter than projection edges. Theme the +controls and MiniMap with dedicated canvas/surface/border tokens so neither is +washed out in light mode or overly bright in dark mode. + +## Scope + +The implementation should remain CSS-only unless verification exposes a missing +state hook. It will not alter graph layout, data, interactions, node dimensions, +or export behavior. + +## Verification + +- Render the graph in explicit light and dark themes and inspect normal, + selected, and focused nodes. +- Confirm every node kind is distinguishable and labels truncate safely. +- Confirm edges, handles, controls, canvas dots, and MiniMap remain legible. +- Confirm the SVG export uses the active theme without browser-only styling. +- Run the web tests and production build. +- Before committing, run all four repository gates from `cli/` as required by + `AGENTS.md`. From 26cb0fa22d98c3948af6ba9b8bd33e22be1f28dc Mon Sep 17 00:00:00 2001 From: ktjn Date: Mon, 27 Jul 2026 09:06:54 +0200 Subject: [PATCH 2/4] docs: plan graph theme CSS cleanup --- .../plans/2026-07-27-graph-theme-css.md | 385 ++++++++++++++++++ .../2026-07-27-graph-theme-css-design.md | 2 + 2 files changed, 387 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-27-graph-theme-css.md diff --git a/docs/superpowers/plans/2026-07-27-graph-theme-css.md b/docs/superpowers/plans/2026-07-27-graph-theme-css.md new file mode 100644 index 00000000..aff971de --- /dev/null +++ b/docs/superpowers/plans/2026-07-27-graph-theme-css.md @@ -0,0 +1,385 @@ +# Graph Theme CSS Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Restore semantic, accessible graph styling that is coherent in the playground's light and dark themes. + +**Architecture:** Keep the existing React Flow components and semantic `graph-node` markup unchanged. Lock the visual contract with a browser regression test, then define graph-specific theme tokens and component rules in the shared stylesheet so browser rendering and SVG export use the same cascade. + +**Tech Stack:** React 19, TypeScript 7, React Flow 12, CSS custom properties, Playwright 1.61 + +**Design Spec:** [Graph Theme CSS Design](../specs/2026-07-27-graph-theme-css-design.md) + +## Global Constraints + +- Keep the implementation CSS-only unless the failing browser test exposes a missing state hook. +- Do not alter graph layout, data, interactions, node dimensions, or export behavior. +- Use restrained accent borders and kind badges instead of saturated node fills. +- Preserve the dashed version-node treatment. +- Support default light, system dark, and explicit dark themes. +- Run all four `AGENTS.md` repository gates from `cli/` before every commit. + +--- + +### Task 1: Lock the graph visual contract with a browser regression test + +**Files:** +- Modify: `web/tests/playground.spec.ts` +- Test: `web/tests/playground.spec.ts` + +**Interfaces:** +- Consumes: the existing `openGraphTab(page)` and `waitForReady(page)` helpers +- Produces: an end-to-end contract for semantic node structure, transparent React Flow wrappers, selected-node visibility, and explicit-dark theming + +- [ ] **Step 1: Add a failing test for semantic graph styling** + +Add this test after `graph panel renders laid out nodes for every mode`: + +```ts +test('graph nodes retain semantic styling in light and dark themes', async ({ + page, +}) => { + await page.addInitScript(() => { + localStorage.setItem('modelable:theme', 'light'); + }); + await page.goto('?test=1'); + await waitForReady(page); + await openGraphTab(page); + + const graphSection = page.getByTestId('graph'); + const flowNode = graphSection.locator('.react-flow__node').first(); + const domainNode = graphSection.locator('.graph-node--domain').first(); + const kindBadge = domainNode.locator('.graph-node__kind'); + + await expect(domainNode).toBeVisible({ timeout: 30_000 }); + await expect(flowNode).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + await expect(domainNode).toHaveCSS('padding', '6px 10px'); + await expect(domainNode).toHaveCSS('border-radius', '6px'); + await expect(kindBadge).toHaveCSS('display', 'inline-flex'); + + await flowNode.click(); + await expect(flowNode).toHaveCSS( + 'outline-color', + 'rgb(37, 99, 235)', + ); + + await page.getByRole('button', { name: /Theme:/ }).click(); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); + await expect(domainNode).toHaveCSS( + 'background-color', + 'rgb(31, 41, 55)', + ); + await expect(flowNode).toHaveCSS( + 'outline-color', + 'rgb(96, 165, 250)', + ); +}); +``` + +- [ ] **Step 2: Run the test and verify the regression is exposed** + +Run: + +```bash +cd web +npx playwright test tests/playground.spec.ts --grep "graph nodes retain semantic styling" +``` + +Expected: FAIL because the outer React Flow node has an opaque background and +the semantic node lacks padding, radius, badge layout, and selected outline. + +- [ ] **Step 3: Commit the failing browser contract** + +Run the four required `cli/` gates, then: + +```bash +git add web/tests/playground.spec.ts +git commit -m "test(web): cover graph theme styling" +``` + +--- + +### Task 2: Restore theme-aware semantic graph CSS + +**Files:** +- Modify: `web/src/style.css` +- Test: `web/tests/playground.spec.ts` + +**Interfaces:** +- Consumes: existing `.graph-node`, `.graph-node--domain`, `.graph-node--entity`, `.graph-node--version`, `.graph-node--projection`, `.graph-node--field`, `.graph-node__label`, `.graph-node__kind`, `.graph-edge--contains`, and `.graph-edge--projects` classes +- Produces: graph-specific CSS custom properties resolved by default light, system dark, and explicit dark theme cascades + +- [ ] **Step 1: Replace the three broad graph tokens in every theme block** + +In `web/src/style.css`, replace `--graph-node-bg`, +`--graph-node-border`, and `--graph-edge` with this light palette: + +```css +--graph-canvas: #f8fafc; +--graph-dot: #cbd5e1; +--graph-surface: #ffffff; +--graph-surface-muted: #f1f5f9; +--graph-border: #94a3b8; +--graph-edge: #64748b; +--graph-selection: #2563eb; +--graph-minimap-node: #94a3b8; +--graph-minimap-mask: rgba(226, 232, 240, 0.7); +--graph-domain: #64748b; +--graph-entity: #2563eb; +--graph-version: #7c3aed; +--graph-projection: #047857; +--graph-field: #64748b; +``` + +Use this palette in both the system-dark and explicit-dark blocks: + +```css +--graph-canvas: #111827; +--graph-dot: #475569; +--graph-surface: #1f2937; +--graph-surface-muted: #273449; +--graph-border: #64748b; +--graph-edge: #94a3b8; +--graph-selection: #60a5fa; +--graph-minimap-node: #64748b; +--graph-minimap-mask: rgba(15, 23, 42, 0.72); +--graph-domain: #94a3b8; +--graph-entity: #60a5fa; +--graph-version: #c4b5fd; +--graph-projection: #34d399; +--graph-field: #94a3b8; +``` + +- [ ] **Step 2: Restore the semantic node rules** + +Replace the current graph-visualization block with rules equivalent to: + +```css +.graph-panel .react-flow { + background: var(--graph-canvas); + --xy-background-pattern-dots-color-default: var(--graph-dot); +} + +.graph-panel .react-flow__node { + background: transparent; + color: var(--text); +} + +.graph-panel .react-flow__node.selected, +.graph-panel .react-flow__node:focus-visible { + border-radius: 0.375rem; + outline: 2px solid var(--graph-selection); + outline-offset: 2px; +} + +.graph-node { + width: 100%; + padding: 0.375rem 0.625rem; + overflow: hidden; + border: 1.5px solid var(--graph-border); + border-radius: 0.375rem; + background: var(--graph-surface); + color: var(--text); + font-family: inherit; + font-size: 0.75rem; + cursor: pointer; +} + +.graph-node__label { + display: flex; + align-items: center; + min-width: 0; + gap: 0.375rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.graph-node__kind { + display: inline-flex; + width: 1.25rem; + height: 1.25rem; + flex: 0 0 1.25rem; + align-items: center; + justify-content: center; + border-radius: 0.25rem; + background: var(--graph-surface-muted); + color: var(--text-muted); + font-size: 0.625rem; + font-weight: 700; +} + +.graph-node--domain { + border-color: var(--graph-domain); + background: var(--graph-surface-muted); + font-weight: 600; +} + +.graph-node--entity { + border-color: var(--graph-entity); + border-width: 2px; +} + +.graph-node--version { + border-color: var(--graph-version); + border-style: dashed; + color: var(--text-muted); + font-size: 0.6875rem; +} + +.graph-node--projection { + border-color: var(--graph-projection); + border-width: 2px; +} + +.graph-node--field { + border-color: var(--graph-field); + color: var(--text-muted); + font-size: 0.6875rem; +} +``` + +Keep the existing controls rules. Update handles, edges, edge variants, and the +MiniMap to use the new tokens: + +```css +.graph-panel .react-flow__handle { + background: var(--graph-border); + border-color: var(--graph-surface); +} + +.graph-panel .react-flow__edge-path, +.graph-panel .react-flow__connection-path { + stroke: var(--graph-edge); +} + +.graph-panel .graph-edge--contains { + stroke: var(--graph-border); + stroke-width: 1.5; +} + +.graph-panel .graph-edge--projects { + stroke: var(--graph-projection); + stroke-width: 1.5; +} + +.graph-panel .react-flow__minimap { + border: 1px solid var(--graph-border); + background: var(--graph-surface-muted); +} + +.graph-panel .react-flow__minimap-node { + fill: var(--graph-minimap-node); +} + +.graph-panel .react-flow__minimap-mask { + fill: var(--graph-minimap-mask); +} +``` + +- [ ] **Step 3: Run the focused browser test and verify it passes** + +Run: + +```bash +cd web +npx playwright test tests/playground.spec.ts --grep "graph nodes retain semantic styling" +``` + +Expected: PASS. + +- [ ] **Step 4: Run the full web verification** + +Run: + +```bash +cd web +npm test +npm run check +npm run build +npx playwright test tests/playground.spec.ts +``` + +Expected: all commands exit 0. + +- [ ] **Step 5: Commit the CSS implementation** + +Run the four required `cli/` gates, then: + +```bash +git add web/src/style.css +git commit -m "fix(web): reconcile graph styles with dark mode" +``` + +--- + +### Task 3: Perform visual and export verification + +**Files:** +- Verify: `web/src/style.css` +- Verify: `web/src/visualization/useGraphExport.ts` +- Verify: `web/tests/playground.spec.ts` + +**Interfaces:** +- Consumes: the production preview and existing graph export action +- Produces: light/dark screenshots and confirmation that exported SVG resolves the active theme tokens + +- [ ] **Step 1: Start the production preview** + +Run: + +```bash +cd web +npm run build +npm run preview +``` + +Open `http://127.0.0.1:4173/modelable/playground/?test=1`. + +- [ ] **Step 2: Inspect the graph in explicit light mode** + +Open the Graph tab and check: + +- Domain, entity, version, projection, and field nodes retain distinct borders. +- Labels remain inside their nodes and truncate instead of overflowing. +- Selected and keyboard-focused nodes have a visible blue outline. +- Edges, handles, canvas dots, controls, and MiniMap are legible. + +Save `graph-light-fixed.png` under the ignored browser-output directory. + +- [ ] **Step 3: Inspect the graph in explicit dark mode** + +Switch the theme control to explicit dark and repeat every light-mode check. +Confirm that node fills remain dark, semantic accents remain restrained, and +the MiniMap mask does not become a bright block. + +Save `graph-dark-fixed.png` under the ignored browser-output directory. + +- [ ] **Step 4: Inspect an exported SVG** + +Use the graph Export action in light mode and dark mode. Open each SVG and +confirm its root has the matching `data-theme`, its canvas uses the active +theme, and its nodes and edges retain the browser styling. + +- [ ] **Step 5: Run final repository verification** + +From `cli/`: + +```bash +uv run ruff format . +uv run ruff check . +uv run python ../.github/scripts/check_mypy_baseline.py --baseline mypy-baseline.txt -- uv run mypy src/modelable --no-error-summary --show-error-codes +uv run pytest --tb=short +``` + +From `web/`: + +```bash +npm test +npm run check +npm run build +npx playwright test tests/playground.spec.ts +``` + +Expected: all commands exit 0 and the working tree contains only the intended +plan, test, and stylesheet changes. diff --git a/docs/superpowers/specs/2026-07-27-graph-theme-css-design.md b/docs/superpowers/specs/2026-07-27-graph-theme-css-design.md index 73643a60..ef44a3d8 100644 --- a/docs/superpowers/specs/2026-07-27-graph-theme-css-design.md +++ b/docs/superpowers/specs/2026-07-27-graph-theme-css-design.md @@ -1,5 +1,7 @@ # 2026-07-27 Graph Theme CSS Design +**Implementation Plan:** [Graph Theme CSS Implementation Plan](../plans/2026-07-27-graph-theme-css.md) + ## Problem The playground redesign removed the internal graph-node styles while retaining From 63c9f0c7e7e6bc2708315879c94bbda3c3a9af3f Mon Sep 17 00:00:00 2001 From: ktjn Date: Mon, 27 Jul 2026 09:08:38 +0200 Subject: [PATCH 3/4] test(web): cover graph theme styling --- web/tests/playground.spec.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/web/tests/playground.spec.ts b/web/tests/playground.spec.ts index 00250070..630382a4 100644 --- a/web/tests/playground.spec.ts +++ b/web/tests/playground.spec.ts @@ -768,6 +768,39 @@ test('graph panel renders laid out nodes for every mode', async ({ page }) => { } }); +test('graph nodes retain semantic styling in light and dark themes', async ({ + page, +}) => { + await page.addInitScript(() => { + localStorage.setItem('modelable:theme', 'light'); + }); + await page.goto('?test=1'); + await waitForReady(page); + await openGraphTab(page); + + const graphSection = page.getByTestId('graph'); + const flowNode = graphSection.locator('.react-flow__node').first(); + const domainNode = graphSection.locator('.graph-node--domain').first(); + const kindBadge = domainNode.locator('.graph-node__kind'); + + await expect(domainNode).toBeVisible({ timeout: 30_000 }); + await expect(flowNode).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + await expect(domainNode).toHaveCSS('padding', '6px 10px'); + await expect(domainNode).toHaveCSS('border-radius', '6px'); + await expect(kindBadge).toHaveCSS('display', 'inline-flex'); + + await flowNode.click(); + await expect(flowNode).toHaveCSS('outline-color', 'rgb(37, 99, 235)'); + + await page.getByRole('button', { name: /Theme:/ }).click(); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); + await expect(domainNode).toHaveCSS( + 'background-color', + 'rgb(31, 41, 55)', + ); + await expect(flowNode).toHaveCSS('outline-color', 'rgb(96, 165, 250)'); +}); + test('graph panel follows the source as it changes', async ({ page }) => { await page.goto('?test=1'); await waitForReady(page); From 5e6532bfb8c322a6fa71d27a33073852a25e9931 Mon Sep 17 00:00:00 2001 From: ktjn Date: Mon, 27 Jul 2026 09:21:17 +0200 Subject: [PATCH 4/4] fix(web): reconcile graph styles with dark mode --- .../plans/2026-07-27-graph-theme-css.md | 11 +- web/src/style.css | 175 ++++++++++++++++-- web/tests/playground.spec.ts | 7 +- 3 files changed, 170 insertions(+), 23 deletions(-) diff --git a/docs/superpowers/plans/2026-07-27-graph-theme-css.md b/docs/superpowers/plans/2026-07-27-graph-theme-css.md index aff971de..32e1a96a 100644 --- a/docs/superpowers/plans/2026-07-27-graph-theme-css.md +++ b/docs/superpowers/plans/2026-07-27-graph-theme-css.md @@ -55,9 +55,9 @@ test('graph nodes retain semantic styling in light and dark themes', async ({ await expect(flowNode).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); await expect(domainNode).toHaveCSS('padding', '6px 10px'); await expect(domainNode).toHaveCSS('border-radius', '6px'); - await expect(kindBadge).toHaveCSS('display', 'inline-flex'); + await expect(kindBadge).toHaveCSS('display', 'flex'); - await flowNode.click(); + await flowNode.focus(); await expect(flowNode).toHaveCSS( 'outline-color', 'rgb(37, 99, 235)', @@ -67,8 +67,9 @@ test('graph nodes retain semantic styling in light and dark themes', async ({ await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); await expect(domainNode).toHaveCSS( 'background-color', - 'rgb(31, 41, 55)', + 'rgb(39, 52, 73)', ); + await flowNode.focus(); await expect(flowNode).toHaveCSS( 'outline-color', 'rgb(96, 165, 250)', @@ -166,7 +167,7 @@ Replace the current graph-visualization block with rules equivalent to: } .graph-panel .react-flow__node.selected, -.graph-panel .react-flow__node:focus-visible { +.graph-panel .react-flow__node.selectable:focus { border-radius: 0.375rem; outline: 2px solid var(--graph-selection); outline-offset: 2px; @@ -196,7 +197,7 @@ Replace the current graph-visualization block with rules equivalent to: } .graph-node__kind { - display: inline-flex; + display: flex; width: 1.25rem; height: 1.25rem; flex: 0 0 1.25rem; diff --git a/web/src/style.css b/web/src/style.css index 14e9186b..1f2dda34 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -30,9 +30,20 @@ --chat-token-color: #006060; --ok-bg: #d1fae5; - --graph-node-bg: #ffffff; - --graph-node-border: #9ca3af; - --graph-edge: #9ca3af; + --graph-canvas: #f8fafc; + --graph-dot: #cbd5e1; + --graph-surface: #ffffff; + --graph-surface-muted: #f1f5f9; + --graph-border: #94a3b8; + --graph-edge: #64748b; + --graph-selection: #2563eb; + --graph-minimap-node: #94a3b8; + --graph-minimap-mask: rgba(226, 232, 240, 0.7); + --graph-domain: #64748b; + --graph-entity: #2563eb; + --graph-version: #7c3aed; + --graph-projection: #047857; + --graph-field: #64748b; } @media (prefers-color-scheme: dark) { @@ -64,9 +75,20 @@ --chat-token-color: #22d3ee; --ok-bg: #064e3b; - --graph-node-bg: #1f2937; - --graph-node-border: #6b7280; - --graph-edge: #6b7280; + --graph-canvas: #111827; + --graph-dot: #475569; + --graph-surface: #1f2937; + --graph-surface-muted: #273449; + --graph-border: #64748b; + --graph-edge: #94a3b8; + --graph-selection: #60a5fa; + --graph-minimap-node: #64748b; + --graph-minimap-mask: rgba(15, 23, 42, 0.72); + --graph-domain: #94a3b8; + --graph-entity: #60a5fa; + --graph-version: #c4b5fd; + --graph-projection: #34d399; + --graph-field: #94a3b8; } } @@ -98,9 +120,20 @@ --chat-token-color: #22d3ee; --ok-bg: #064e3b; - --graph-node-bg: #1f2937; - --graph-node-border: #6b7280; - --graph-edge: #6b7280; + --graph-canvas: #111827; + --graph-dot: #475569; + --graph-surface: #1f2937; + --graph-surface-muted: #273449; + --graph-border: #64748b; + --graph-edge: #94a3b8; + --graph-selection: #60a5fa; + --graph-minimap-node: #64748b; + --graph-minimap-mask: rgba(15, 23, 42, 0.72); + --graph-domain: #94a3b8; + --graph-entity: #60a5fa; + --graph-version: #c4b5fd; + --graph-projection: #34d399; + --graph-field: #94a3b8; } * { @@ -616,18 +649,111 @@ button.view-tab--active:hover:not(:disabled) { /* Graph visualization theming */ .graph-panel .react-flow { - background: var(--bg); + --xy-background-pattern-dots-color-default: var(--graph-dot); + background: var(--graph-canvas); } .graph-panel .react-flow__node { - background: var(--graph-node-bg); - border-color: var(--graph-node-border); + background: transparent; color: var(--text); } +.graph-panel .react-flow__node.selected, +.graph-panel .react-flow__node.selectable:focus { + border-radius: 0.375rem; + outline: 2px solid var(--graph-selection); + outline-offset: 2px; +} + +.graph-node { + width: 100%; + padding: 0.375rem 0.625rem; + overflow: hidden; + border: 1.5px solid var(--graph-border); + border-radius: 0.375rem; + background: var(--graph-surface); + color: var(--text); + font-family: inherit; + font-size: 0.75rem; + cursor: pointer; +} + +.graph-node__label { + display: flex; + align-items: center; + min-width: 0; + gap: 0.375rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.graph-node__kind { + display: flex; + width: 1.25rem; + height: 1.25rem; + flex: 0 0 1.25rem; + align-items: center; + justify-content: center; + border-radius: 0.25rem; + background: var(--graph-surface-muted); + color: var(--text-muted); + font-size: 0.625rem; + font-weight: 700; +} + +.graph-node--domain { + border-color: var(--graph-domain); + background: var(--graph-surface-muted); + font-weight: 600; +} + +.graph-node--domain .graph-node__kind { + color: var(--graph-domain); +} + +.graph-node--entity { + border-color: var(--graph-entity); + border-width: 2px; +} + +.graph-node--entity .graph-node__kind { + color: var(--graph-entity); +} + +.graph-node--version { + border-color: var(--graph-version); + border-style: dashed; + color: var(--text-muted); + font-size: 0.6875rem; +} + +.graph-node--version .graph-node__kind { + color: var(--graph-version); +} + +.graph-node--projection { + border-color: var(--graph-projection); + border-width: 2px; +} + +.graph-node--projection .graph-node__kind { + color: var(--graph-projection); +} + +.graph-node--field { + border-color: var(--graph-field); + color: var(--text-muted); + font-size: 0.6875rem; +} + +.graph-node--field .graph-node__kind { + color: var(--graph-field); +} + .graph-panel .react-flow__handle { - background: var(--border-strong); - border-color: var(--graph-node-bg); + background: var(--graph-border); + border-color: var(--graph-surface); } .graph-panel .react-flow__edge-path, @@ -635,6 +761,16 @@ button.view-tab--active:hover:not(:disabled) { stroke: var(--graph-edge); } +.graph-panel .graph-edge--contains { + stroke: var(--graph-border); + stroke-width: 1.5; +} + +.graph-panel .graph-edge--projects { + stroke: var(--graph-projection); + stroke-width: 1.5; +} + .graph-panel .react-flow__controls-button { background: var(--panel-elevated); border-color: var(--border); @@ -646,7 +782,16 @@ button.view-tab--active:hover:not(:disabled) { } .graph-panel .react-flow__minimap { - background: var(--panel-elevated); + border: 1px solid var(--graph-border); + background: var(--graph-surface-muted); +} + +.graph-panel .react-flow__minimap-node { + fill: var(--graph-minimap-node); +} + +.graph-panel .react-flow__minimap-mask { + fill: var(--graph-minimap-mask); } /* Tab strip (shared by graph & bottom panel) */ diff --git a/web/tests/playground.spec.ts b/web/tests/playground.spec.ts index 630382a4..8fdb4b19 100644 --- a/web/tests/playground.spec.ts +++ b/web/tests/playground.spec.ts @@ -787,17 +787,18 @@ test('graph nodes retain semantic styling in light and dark themes', async ({ await expect(flowNode).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); await expect(domainNode).toHaveCSS('padding', '6px 10px'); await expect(domainNode).toHaveCSS('border-radius', '6px'); - await expect(kindBadge).toHaveCSS('display', 'inline-flex'); + await expect(kindBadge).toHaveCSS('display', 'flex'); - await flowNode.click(); + await flowNode.focus(); await expect(flowNode).toHaveCSS('outline-color', 'rgb(37, 99, 235)'); await page.getByRole('button', { name: /Theme:/ }).click(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); await expect(domainNode).toHaveCSS( 'background-color', - 'rgb(31, 41, 55)', + 'rgb(39, 52, 73)', ); + await flowNode.focus(); await expect(flowNode).toHaveCSS('outline-color', 'rgb(96, 165, 250)'); });