Skip to content

feat(desktop): anchored ghost graph with typed lenses — the note pane's third mode (#22) - #56

Merged
samkeen merged 1 commit into
mainfrom
claude/ghost-graph-primary-surface-dgy9j0
Jul 18, 2026
Merged

feat(desktop): anchored ghost graph with typed lenses — the note pane's third mode (#22)#56
samkeen merged 1 commit into
mainfrom
claude/ghost-graph-primary-surface-dgy9j0

Conversation

@samkeen

@samkeen samkeen commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Closes #22 — ships the design proposal's recommendation: the anchored ghost graph as the primary graph surface, with the typed-lens selector (default All, plus Lineage and Argument) as its second gear. Not a whole-vault force-directed hairball: one note's world, laid out so layout follows meaning.

What it looks like

A Graph toggle joins the note bar (sibling of </> and Edit), sticky across notes — you can browse the vault in graph mode, each node click re-anchoring on the opened note. Escape (or clicking the anchor) returns to reading.

  • All — the ghost graph. Authored edges on a category-banded orbit around the open note; the top similar candidates float as a dashed-teal outer halo, score-tagged. Clicking a ghost opens the link palette; committing re-runs discovery, so the ghost solidifies into a typed colored edge in place — the discover→link loop made visual. Right-click on a ghost reuses the Similar-card menu (Open note / Link…).
  • Lineage. supersedes / derived-from on an older→newer time axis, every node dated (new NeighborView.created).
  • Argument. Supporters point at the claim from the left, refuters from the right; symmetric contradicts sits on a dotted vertical fault line, zone captions framing the map.
  • All three edge-target kinds render (per the first comment): notes are discs, resources squares with class glyphs, dangling links dashed hollow ⚠ nodes — the Wikilink to a folder (e.g. [[Hermes]]) is silently dropped from outgoing connections #12 repair surface at a glance. Lens filtering is by verb, never target kind, so a PDF that supports a claim appears on the argument map.
  • The reading key (color = category, solid = authored / dashed teal = latent, shape = node kind) sits as a quiet legend strip; each lens has an honest empty state, and the no-ghosts caveat mirrors Surface a 'semantic: N/M embedded' signal (honest keyword-only state) #26's embedding tiers (scanning… / run b2 init / Reindex).

How it's built

  • Deterministic hand-rolled SVG — no physics, no new deps, zero IPC on toggle. The scene is a pure projection of the discovery state the note-open already fetches (explain + similar), so entering the graph and switching lenses are instant.
  • Layout lives in a new pure module ui/src/graph.ts (category mapping, lens filters, orbit/column/fault-line geometry, parallel-edge curves, ghost cap) with 13 unit checks in ui/src/graph.test.ts, wired into npm test beside the pane-sizing suite.
  • Polish: staggered entrance animation (prefers-reduced-motion aware), CSS-only hover that spotlights a node + its incident edges while the rest dims, halo'd verb labels, node labels placed away from the anchor so text never sits in its own edge's path, light + dark palettes for the five category colors. The note-pane render is memoized so toasts/progress ticks never replay the animation or reset reading scroll.

Façade additions (small, additive — both adapters inherit)

explain only exposed note↔note edges, so a graph over it would have hidden file links:

  • ExplainView.resources (ResourceLinkView) — a note's outbound resource links, from the note's side (db::outbound_resource_edges).
  • NeighborView.created (db::note_created) — the lineage time axis without file re-reads.
  • CLI: b2 explain prints a Resource links: section; --json picks both fields up automatically. The desktop host needed zero Rust changes — the thin-adapter discipline held.

Verification

  • cargo test -p b2-core — full suite green, including new explain tests (resource links surfaced; created carried).
  • cargo clippy --workspace --exclude b2-desktop — clean (one pre-existing warning in untouched db.rs trace-gate code).
  • CLI smoke end-to-end: reindexed a fixture vault; human + --json explain show connections / resource links / unresolved correctly.
  • ui: tsc --noEmit + vite build green; all 26 unit checks pass.
  • Every lens screenshot-reviewed in light and dark via the real stylesheet rendered in headless Chromium (screenshots iterated on for label collisions, paint order, and orbit sizing before landing).

🤖 Generated with Claude Code

https://claude.ai/code/session_01ChHM1jmL1DJVhaSbLii7pv


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added an interactive connection graph for notes, with All, Lineage, and Argument views.
    • Visualize notes, resources, unresolved links, and suggested “ghost” connections with categorized relationships.
    • Open notes and resources directly from graph nodes, or create links from suggested connections.
    • Added resource-link details to b2 explain, including paths, captions, embedding status, and explanations.
  • Improvements
    • Connection details now include neighboring notes’ creation dates.
    • Improved note-pane performance and graph-mode navigation.

…'s third mode (#22)

The graph surface the design pass on #22 recommended: not a whole-vault
force-directed hairball, but the open note's world, drawn so layout follows
meaning. Deterministic hand-rolled SVG, zero new deps, zero IPC on toggle —
the scene is a pure projection of the discovery state the note-open already
fetches (explain + similar).

Frontend:
- ui/src/graph.ts — pure, unit-tested scene builder (ui/src/graph.test.ts,
  wired into npm test). Three lenses: "all" (authored edges on a category-
  banded orbit + the top `similar` candidates as a dashed ghost halo),
  "lineage" (supersedes/derived-from on an older→newer time axis with
  created dates), "argument" (supports left, refutes right, contradicts on
  a vertical fault line).
- The reading key from the issue: color = edge category (5 CSS vars, light
  + dark), solid = authored / dashed teal = latent, disc = note / square =
  resource / dashed hollow ⚠ = dangling (#12's repair surface). Legend strip.
- Clicking a ghost opens the existing link palette — committing re-runs
  discovery, so the ghost solidifies into a typed edge in place; right-click
  reuses the Similar-card menu. Note/resource nodes ride the existing
  data-open delegations; the anchor (and Escape) returns to reading.
- Sticky graphOpen/graphLens; staggered entrance animation (reduced-motion
  aware); hover spotlights a node + its edges via CSS only; honest empty
  states (ghosts-need-embedding caveat mirrors #26's tiers). The note pane
  render is memoized so toasts/progress never replay the animation.

Façade (additive, both adapters inherit):
- ExplainView.resources (ResourceLinkView): a note's outbound resource
  links, so the graph draws all three edge-target kinds instead of silently
  hiding file links (db::outbound_resource_edges; issue comment on #22).
- NeighborView.created (db::note_created): the lineage lens's time axis
  without re-reading files.
- CLI: `b2 explain` prints a Resource links section; --json picks both up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ChHM1jmL1DJVhaSbLii7pv
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds outbound resource links and note creation dates to explain data, introduces deterministic graph scene generation with all, lineage, and argument lenses, and renders an interactive anchored ghost graph in the desktop UI with themed styling and expanded tests.

Changes

Graph visualization

Layer / File(s) Summary
Explain data and resource links
crates/b2-core/src/db.rs, crates/b2-core/src/vault.rs, crates/b2-cli/src/main.rs, crates/b2-core/tests/explain.rs
Explain results now include neighbor creation dates and outbound resource-link metadata, with CLI output and Rust coverage for both changes.
Deterministic graph scene model
ui/src/graph.ts, ui/src/graph.test.ts, ui/package.json
Graph inputs are normalized into deterministic nodes and edges with all, lineage, and argument layouts, ghost candidates, geometry rules, and focused scene tests.
Graph pane and interaction wiring
ui/src/types.ts, ui/src/state.ts, ui/src/main.ts, ui/src/render.ts
Resource-link state, graph lenses, SVG rendering, graph toggles, ghost-node actions, navigation resets, and Escape-key handling are integrated into the note pane.
Graph visual language
ui/style.css
Theme palettes and graph styles define relation categories, authored and ghost edges, node states, lens overlays, legends, hints, hover behavior, and reduced-motion behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ExplainAPI
  participant AppState
  participant buildScene
  participant GraphPane

  Client->>ExplainAPI: request note explanation
  ExplainAPI-->>AppState: return discovery data
  AppState->>buildScene: build selected lens
  buildScene-->>GraphPane: return positioned graph
  GraphPane-->>Client: render interactive graph
Loading

Possibly related PRs

  • AlteredCraft/B2#49: Extends explain empty-state handling for typed, resource, and unresolved links.
  • AlteredCraft/B2#53: Overlaps with note, resource, and vault-switching state flow in ui/src/main.ts.

Suggested reviewers: claude

Poem

I’m a rabbit with graphs in my burrow tonight,
Three lenses are glowing in soft colored light.
Ghost links drift outward, resources gleam,
Dates hop through lineage like carrots downstream.
I nudge Escape gently, the pane folds from view—
“Hop into the graph!” says this rabbit to you.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the desktop anchored ghost graph with typed lenses.
Linked Issues check ✅ Passed The PR delivers the graph visualization view requested in #22, including typed edges, backlinks, and a desktop review surface.
Out of Scope Changes check ✅ Passed The changes are all supporting pieces for the graph feature, with no obvious unrelated code paths introduced.
✨ 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 claude/ghost-graph-primary-surface-dgy9j0

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/src/graph.ts`:
- Around line 468-493: Update the axis placement logic in the axis bucket loop
so every contradiction remains within the scene’s bounded fault-line layout,
keeping x anchored around CENTER.x instead of increasing without limit. Preserve
the alternating above/below vertical positioning, and add coverage for layouts
containing at least three and seven contradiction nodes to verify none are
clipped.
- Around line 237-268: Update the edge layout using canonical anchor-to-node
geometry for perpendicular offsets, independent of each edge’s outbound
direction, so bidirectional neighbors receive distinct curves. Revise the
lineage bookkeeping around the left/right placement maps to explicitly preserve
mixed-direction neighbors instead of overwriting one temporal role. Add a
regression test covering inbound and outbound edges to the same target.

In `@ui/src/render.ts`:
- Around line 640-669: The interactive graph currently exposes only
pointer-driven actions and non-focusable SVG nodes. In ui/src/render.ts lines
640-669, update nodeGroupHtml to assign actionable nodes appropriate roles,
accessible names, and keyboard focusability; in ui/src/render.ts lines 807-812,
replace the atomic role="img" presentation or add an equivalent accessible
relationship list; in ui/src/main.ts lines 1496-1511, handle Enter and Space on
focused nodes by invoking the same open, link, and graph-toggle actions used by
pointer clicks.

In `@ui/style.css`:
- Around line 25-31: Update the light-theme category color variables in the
category palette, including --cat-referential through --cat-latent, to use
darker text-safe values with sufficient contrast against --bg for the 10.5px SVG
edge and ghost labels. Ensure the corresponding category colors at the
additional label rules also consume these accessible variables without changing
dark-theme styling.
- Around line 1720-1736: Update the .graph-hint styles to allow long
initialization and reindex hint text to wrap within resized panes by removing or
overriding white-space: nowrap and adding appropriate wrapping behavior.
Preserve the existing positioning and visual styling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 19f60b1c-ca5a-4d79-a29f-3245e5b03b9a

📥 Commits

Reviewing files that changed from the base of the PR and between 7d8fd79 and 41e51bb.

📒 Files selected for processing (12)
  • crates/b2-cli/src/main.rs
  • crates/b2-core/src/db.rs
  • crates/b2-core/src/vault.rs
  • crates/b2-core/tests/explain.rs
  • ui/package.json
  • ui/src/graph.test.ts
  • ui/src/graph.ts
  • ui/src/main.ts
  • ui/src/render.ts
  • ui/src/state.ts
  • ui/src/types.ts
  • ui/style.css

Comment thread ui/src/graph.ts
Comment on lines +237 to +268
// Parallel-edge bookkeeping: how many edges share a node, and each one's index.
const perNode = new Map<string, number>();
for (const it of items) perNode.set(it.nodeId, (perNode.get(it.nodeId) ?? 0) + 1);
const seen = new Map<string, number>();

return items.map((it) => {
const node = nodeAt.get(it.nodeId);
if (!node) throw new Error(`unplaced node ${it.nodeId}`);
const r = radiusOf.get(it.nodeId) ?? NODE_R.note;
const [a, b, ra, rb] = it.outbound
? [anchor, node, NODE_R.anchor, r]
: [node, anchor, r, NODE_R.anchor];
const seg = trim(a, ra, b, rb);

// Parallel edges between the same pair bow apart on a perpendicular offset.
const siblings = perNode.get(it.nodeId) ?? 1;
const index = seen.get(it.nodeId) ?? 0;
seen.set(it.nodeId, index + 1);
let cx: number | null = null;
let cy: number | null = null;
let lx = (seg.x1 + seg.x2) / 2;
let ly = (seg.y1 + seg.y2) / 2;
if (siblings > 1) {
const off = (index - (siblings - 1) / 2) * 34;
const dx = seg.x2 - seg.x1;
const dy = seg.y2 - seg.y1;
const len = Math.hypot(dx, dy) || 1;
cx = lx + (-dy / len) * off;
cy = ly + (dx / len) * off;
// Quadratic midpoint: B(0.5) = ¼·p0 + ½·c + ¼·p1.
lx = 0.25 * seg.x1 + 0.5 * cx + 0.25 * seg.x2;
ly = 0.25 * seg.y1 + 0.5 * cy + 0.25 * seg.y2;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle neighbors connected in both directions.

Parallel-curve offsets are relative to the authored segment direction, so reversing an edge also reverses its normal and can place opposite-direction edges on the same curve. The lineage maps similarly overwrite a node’s left placement when that neighbor also appears on the right, hiding one temporal role.

Use canonical anchor-to-node geometry for offsets and explicitly represent mixed-direction lineage neighbors. Add an inbound/outbound same-target regression test.

Also applies to: 438-451

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/graph.ts` around lines 237 - 268, Update the edge layout using
canonical anchor-to-node geometry for perpendicular offsets, independent of each
edge’s outbound direction, so bidirectional neighbors receive distinct curves.
Revise the lineage bookkeeping around the left/right placement maps to
explicitly preserve mixed-direction neighbors instead of overwriting one
temporal role. Add a regression test covering inbound and outbound edges to the
same target.

Comment thread ui/src/graph.ts
Comment on lines +468 to +493
const side = (verb: string) => (verb === "supports" ? "left" : verb === "refutes" ? "right" : "axis");

const nodeAt = new Map<string, Placed>();
const bucket = (want: string): string[] => {
const ids: string[] = [];
for (const it of kept) {
if (side(it.verb) === want && !ids.includes(it.nodeId) && !nodeAt.has(it.nodeId)) {
ids.push(it.nodeId);
}
}
return ids;
};
// A node arguing twice (e.g. supports + contradicts) is placed by its first
// bucket, left → right → axis, so placement stays deterministic.
const left = bucket("left");
column(195, left.length).forEach((p, i) => nodeAt.set(left[i], p));
const right = bucket("right");
column(805, right.length).forEach((p, i) => nodeAt.set(right[i], p));
const axis = bucket("axis");
axis.forEach((id, i) => {
// The fault line: contradicting peers alternate above/below the claim.
nodeAt.set(id, {
x: CENTER.x + Math.floor(i / 2) * 170,
y: i % 2 === 0 ? 96 : VIEW_H - 96,
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep every contradiction on a bounded fault-line layout.

After two nodes, x shifts away from CENTER.x; the seventh contradiction reaches x = 1010 and is clipped outside the 1000-wide scene. Use bounded vertical positions around the anchor and test at least three and seven contradiction nodes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/graph.ts` around lines 468 - 493, Update the axis placement logic in
the axis bucket loop so every contradiction remains within the scene’s bounded
fault-line layout, keeping x anchored around CENTER.x instead of increasing
without limit. Preserve the alternating above/below vertical positioning, and
add coverage for layouts containing at least three and seven contradiction nodes
to verify none are clipped.

Comment thread ui/src/render.ts
Comment on lines +640 to +669
function nodeGroupHtml(n: GraphNode, edges: GraphEdge[], order: number): string {
const attrs: string[] = [`class="gnode is-${n.kind}"`, `style="--i:${order}"`];
if (n.kind === "note" && n.path) attrs.push(`data-open="${escapeHtml(n.path)}"`);
if (n.kind === "anchor") attrs.push(`data-toggle-graph="1"`);
if (n.kind === "resource" && n.path) attrs.push(`data-open-resource="${escapeHtml(n.path)}"`);
if (n.kind === "ghost" && n.path) {
attrs.push(
`data-ghost-link="${escapeHtml(n.path)}"`,
`data-card-path="${escapeHtml(n.path)}"`,
`data-card-title="${escapeHtml(n.title ?? "")}"`,
);
}
const r = NODE_R[n.kind];
// Text goes on the side of the node facing *away* from the anchor (above for the
// upper half of the scene), so a label never sits in its own edge's path.
const above = n.kind !== "anchor" && n.y < VIEW_H / 2 - 20;
const label = `<text class="gnode-label" x="${px(n.x)}" y="${px(
above ? n.y - r - 14 : n.y + r + 18,
)}">${escapeHtml(n.label)}</text>`;
const sub = n.sub
? `<text class="gnode-sub" x="${px(n.x)}" y="${px(
above ? n.y - r - 29 : n.y + r + 33,
)}">${escapeHtml(n.sub)}</text>`
: "";
return `<g ${attrs.join(" ")}>
<title>${escapeHtml(nodeTitle(n))}</title>
${edges.map(edgeHtml).join("")}
${nodeShapeHtml(n)}
${label}${sub}
</g>`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the interactive graph keyboard and assistive-technology accessible. The graph is rendered as a static image containing non-focusable SVG groups, while its actions are handled only through pointer clicks.

  • ui/src/render.ts#L640-L669: give actionable nodes suitable roles, accessible names, and keyboard focus.
  • ui/src/render.ts#L807-L812: replace the atomic role="img" model or provide an equivalent accessible relationship list.
  • ui/src/main.ts#L1496-L1511: activate the same open/link/toggle actions on Enter and Space.
📍 Affects 2 files
  • ui/src/render.ts#L640-L669 (this comment)
  • ui/src/render.ts#L807-L812
  • ui/src/main.ts#L1496-L1511
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/render.ts` around lines 640 - 669, The interactive graph currently
exposes only pointer-driven actions and non-focusable SVG nodes. In
ui/src/render.ts lines 640-669, update nodeGroupHtml to assign actionable nodes
appropriate roles, accessible names, and keyboard focusability; in
ui/src/render.ts lines 807-812, replace the atomic role="img" presentation or
add an equivalent accessible relationship list; in ui/src/main.ts lines
1496-1511, handle Enter and Space on focused nodes by invoking the same open,
link, and graph-toggle actions used by pointer clicks.

Comment thread ui/style.css
Comment on lines +25 to +31
--cat-referential: #64748b;
--cat-expository: #d97706;
--cat-evidential: #d94f4f;
--cat-structural: #2f9e63;
--cat-versioning: #6366f1;
--cat-other: #8a8880;
--cat-latent: #0d9488;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use accessible colors for the small SVG labels.

The 10.5px edge and ghost labels inherit light-theme category colors with insufficient contrast against --bg; several are only around 3.0–3.9:1. Darken the light palette or introduce separate text-safe category variables.

Also applies to: 1558-1568, 1617-1619

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/style.css` around lines 25 - 31, Update the light-theme category color
variables in the category palette, including --cat-referential through
--cat-latent, to use darker text-safe values with sufficient contrast against
--bg for the 10.5px SVG edge and ghost labels. Ensure the corresponding category
colors at the additional label rules also consume these accessible variables
without changing dark-theme styling.

Comment thread ui/style.css
Comment on lines +1720 to +1736
.graph-hint {
position: absolute;
left: 50%;
bottom: 18px;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 8px;
padding: 5px 14px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface);
color: var(--muted);
font-size: 12px;
box-shadow: var(--shadow);
white-space: nowrap;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Allow graph hints to wrap inside resized panes.

Long initialization and reindex hints are clipped because white-space: nowrap combines with the pane’s hidden overflow.

Proposed fix
 .graph-hint {
+  box-sizing: border-box;
+  max-width: calc(100% - 32px);
   position: absolute;
   left: 50%;
   bottom: 18px;
   transform: translateX(-50%);
   display: flex;
   align-items: center;
+  justify-content: center;
   gap: 8px;
   padding: 5px 14px;
   border: 1px solid var(--border);
   border-radius: 999px;
   background: var(--surface);
   color: var(--muted);
   font-size: 12px;
   box-shadow: var(--shadow);
-  white-space: nowrap;
+  text-align: center;
+  white-space: normal;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.graph-hint {
position: absolute;
left: 50%;
bottom: 18px;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 8px;
padding: 5px 14px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface);
color: var(--muted);
font-size: 12px;
box-shadow: var(--shadow);
white-space: nowrap;
}
.graph-hint {
box-sizing: border-box;
max-width: calc(100% - 32px);
position: absolute;
left: 50%;
bottom: 18px;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 5px 14px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface);
color: var(--muted);
font-size: 12px;
box-shadow: var(--shadow);
text-align: center;
white-space: normal;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/style.css` around lines 1720 - 1736, Update the .graph-hint styles to
allow long initialization and reindex hint text to wrap within resized panes by
removing or overriding white-space: nowrap and adding appropriate wrapping
behavior. Preserve the existing positioning and visual styling.

@samkeen
samkeen merged commit 95927bf into main Jul 18, 2026
1 check passed
@samkeen
samkeen deleted the claude/ghost-graph-primary-surface-dgy9j0 branch July 18, 2026 20:32
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.

Desktop: graph visualization view

2 participants