Skip to content

UI: graph polish + persistence + mobile reload + README#13

Merged
aksOps merged 1 commit into
mainfrom
ui-graph-polish
Apr 19, 2026
Merged

UI: graph polish + persistence + mobile reload + README#13
aksOps merged 1 commit into
mainfrom
ui-graph-polish

Conversation

@aksOps

@aksOps aksOps commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Iterative polish on top of the merged UI redesign (#12).

Graph view

  • Dropped @cosmograph/cosmos — rendered blank on mobile Safari. Replaced with SVG + d3-force + d3-zoom.
  • Pinch/wheel zoom 0.1×–40×; drag to pan; reset button top-right.
  • viewBox expanded to the container's aspect ratio → fills the viewport, no letterboxing.
  • Hover/tap highlights the node + its direct neighbours; rest dims to 20%. Orphans don't trigger dim.
  • Node radius scales with degree: r = 3.5 + √(degree) × 1.6. Hubs read clearly; extremes stay sane.
  • Labels disambiguated — docsiq/overview vs codeiq/overview etc.
  • Orphan nodes anchored stronger to centre so they don't fly off-screen. Charge force gets distanceMax(260).

Misc

  • Hard reload button in SiteHeader — unregisters SW, purges CacheStorage, reloads with ?_r=<ts>. Mobile-friendly ⌘⇧R substitute.
  • Persistence: useProjectStore.slug now sticks in localStorage["docsiq.project"]. Theme/sidebar/drawer pins already persisted.
  • README rewrite: reflects the post-redesign UI, three LLM providers + none, PWA, kbd shortcuts, new build path (npm build before go build; ui/dist/ is no longer committed).
  • Cleanup: dropped stale tracked ui/dist/assets/* and fonts that leaked through the squash merge of UI redesign — v1 (React 19 + Tailwind 4 + shadcn/ui) #12. ui/dist/index.html placeholder stays.

Test plan

  • `npm run typecheck` clean
  • `npm test -- --run` 52/52 passing
  • `npm run build` under 580 KB gate (lazy routes split)
  • `go vet -tags sqlite_fts5 ./...` clean
  • Local smoke: graph renders on desktop; pinch-zoom on mobile; reload button works; project slug survives reload
  • Cross-browser sanity on mobile Safari + Android Chrome

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Interactive knowledge graph with pan, zoom, pinch, and node pinning capabilities.
    • Hard reload button to clear cache and refresh the app.
    • SVG fallback graph rendering when WebGL is unavailable.
    • Persistent project selection across browser sessions.
  • Documentation

    • Streamlined README with simplified quickstart and updated architecture overview.
  • Style

    • Improved graph visualization layout and styling.

Graph view:
- Replace @cosmograph/cosmos (blank-screen issues on mobile Safari) with
  SVG + d3-force + d3-zoom. 0.1×–40× zoom range, pinch + wheel + pan,
  reset button. viewBox expanded to container aspect so the graph fills
  the screen instead of letterboxing.
- Hover / tap a node to highlight it and its direct neighbours; others
  dim to 20% opacity. Orphans don't trigger the dim effect.
- Node radius scales with degree: r = 3.5 + √(degree) × 1.6. Hubs stand
  out; sqrt keeps extremes sane.
- Labels disambiguated across projects: "overview" (collision) becomes
  "docsiq/overview", "codeiq/overview", etc.
- Orphan nodes anchored to centre (stronger forceX/Y) so they don't
  drift off-screen; distance-max on charge force.

Other:
- Hard reload button in SiteHeader — unregisters SW, purges CacheStorage,
  reloads with cache-busting query. Useful on mobile where there's no ⌘⇧R.
- useProjectStore persisted to localStorage (docsiq.project key).
- README rewritten to reflect: notes + wikilinks + cross-project, all
  three LLM providers (+ "none" for zero-LLM serve), PWA, UI stack,
  kbd shortcuts, build path that assumes npm build (no committed dist).
- Clean up stale tracked ui/dist assets that leaked through the squash
  merge of PR #12 — gitignore now correctly keeps only the placeholder
  index.html.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR refactors the UI graph visualization from WebGL-based (Cosmos) to SVG-based rendering with D3, introduces a fallback component for missing WebGL2, improves graph layout physics, adds cache-busting reload functionality, and extensively updates documentation to reflect architectural changes.

Changes

Cohort / File(s) Summary
Documentation
README.md
Removed status badges and legacy pipeline documentation; reframed product as single Go binary with MCP + embedded React SPA; updated quickstart, configuration section (new server/llm options), build instructions, and added UI architecture/MCP usage sections.
UI Dependencies
ui/package.json
Added D3 selection and zoom packages: d3-selection, d3-zoom, and their TypeScript type definitions.
Graph Rendering
ui/src/components/graph/GraphCanvas.tsx, ui/src/components/graph/SVGGraphFallback.tsx, ui/src/routes/Graph.tsx
Replaced Cosmos WebGL renderer with SVG-based GraphCanvas using layoutGraph() and d3-zoom for pan/scroll/pinch; added hover/click-to-pin node interaction, adjacency-driven dimming, and reset-zoom button. New SVGGraphFallback component renders static SVG graph when WebGL2 unavailable. Updated Graph route to handle empty node lists as unavailable data.
Graph Layout Engine
ui/src/lib/graph-layout.ts
Increased simulation ticks (200→300), weakened node repulsion (−60→−45), added repulsion distance limit (260), tightened link constraints (distance 40→42, strength 0.6→0.7), and implemented dynamic centering force for orphan nodes (0.25 vs 0.06).
Utilities
ui/src/lib/hard-reload.ts
New utility function for cache-busting hard reload: unregisters service workers, purges cache storage, appends timestamp query parameter, and reloads from network.
UI Components & State
ui/src/components/site-header.tsx, ui/src/stores/project.ts
Added hard-reload button with loading state to SiteHeader. Refactored useProjectStore to use Zustand persist middleware for cross-session state retention via localStorage.
Styling
ui/src/styles/globals.css
Added .site-header-reload button class; updated .graph-page with 100svh height calculation; added graph-related classes (.graph-fallback-banner, .graph-meta, .graph-reset, .graph-node) with opacity transitions and absolute positioning for overlay UI elements.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

Poem

🐰 A canvas of WebGL now sings in SVG—
D3 forces dance where physics once danced,
Zoom and pan with a whiskered finesse,
Fallback fallback when the GPU's at rest,
Reload the cache, start anew with a hop! 🍃

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'UI: graph polish + persistence + mobile reload + README' directly and clearly summarizes the main changes: graph UI improvements (SVG/d3-zoom rendering, visual polish), state persistence (useProjectStore localStorage), a hard reload feature for mobile, and README updates.

✏️ 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 ui-graph-polish

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

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​types/​d3-zoom@​3.0.81001007480100
Addednpm/​@​types/​d3-selection@​3.0.111001007680100

View full report

@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: 2

🧹 Nitpick comments (4)
ui/src/components/graph/SVGGraphFallback.tsx (1)

44-45: Consider expanding viewBox to match container aspect ratio.

Unlike GraphCanvas, this fallback uses a fixed viewBox matching container dimensions without aspect-ratio expansion. This may cause letterboxing when container aspect differs from content bounds. The main renderer in GraphCanvas.tsx (lines 58-70) handles this.

This is acceptable for a fallback renderer, but worth noting if visual consistency matters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/components/graph/SVGGraphFallback.tsx` around lines 44 - 45,
SVGGraphFallback currently sets the SVG viewBox directly to the container size
(viewBox={`0 0 ${size.w} ${size.h}`}) which can cause letterboxing when the
container aspect ratio differs; update SVGGraphFallback to expand or adjust its
viewBox to match the container aspect ratio similar to GraphCanvas (reference
GraphCanvas resizing logic around its viewBox handling) so the fallback uses an
aspect-ratio-aware viewBox calculation (based on size.w/size.h and desired
content bounds) to avoid letterboxing and maintain visual consistency with
GraphCanvas.
ui/package.json (1)

37-38: Move @types/* packages to devDependencies.

Type definition packages are only needed at compile time, not runtime. They should be in devDependencies to avoid bloating production installs.

♻️ Proposed fix

Move these two entries from dependencies to devDependencies:

  "dependencies": {
    ...
-   "@types/d3-selection": "^3.0.11",
-   "@types/d3-zoom": "^3.0.8",
    ...
  },
  "devDependencies": {
    ...
    "@types/d3-force": "^3.0.10",
+   "@types/d3-selection": "^3.0.11",
+   "@types/d3-zoom": "^3.0.8",
    "@types/markdown-it": "^14.1.2",
    ...
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/package.json` around lines 37 - 38, Move the type packages out of runtime
dependencies: remove "@types/d3-selection" and "@types/d3-zoom" from
dependencies and add them under devDependencies in package.json so type-only
packages are installed only for development/compile time; ensure version strings
remain the same and run npm/yarn install or update package-lock to reflect the
change.
ui/src/components/graph/GraphCanvas.tsx (1)

7-11: Consider extracting shared COLOR constant.

This COLOR map is duplicated in SVGGraphFallback.tsx (lines 7-11). Extract to a shared module to maintain consistency.

♻️ Extract to shared constant

Create ui/src/lib/graph-colors.ts:

export const NODE_COLOR: Record<string, string> = {
  entity: "var(--chart-1)",
  note: "var(--chart-3)",
  community: "var(--chart-2)",
};

Then import in both components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/components/graph/GraphCanvas.tsx` around lines 7 - 11, Extract the
duplicated COLOR map into a shared constant module and import it from both
components: create a new module (e.g., graph-colors.ts) that exports NODE_COLOR
(Record<string,string>) with the same entries currently in COLOR, then replace
the local COLOR definitions in GraphCanvas (COLOR) and SVGGraphFallback
(duplicate) with imports of NODE_COLOR and update any usages accordingly (e.g.,
where GraphCanvas references COLOR, switch to NODE_COLOR). Ensure
exports/imports use the same identifier in both components so the color mapping
remains consistent across GraphCanvas and SVGGraphFallback.
ui/src/components/site-header.tsx (1)

47-56: Consider using the Button component for consistency.

The Search button above uses the Button component with variant="outline" and size="sm", while this uses a plain <button>. Using Button would maintain visual consistency.

♻️ Optional: Use Button component
-        <button
-          type="button"
-          aria-label="Hard reload"
-          title="Hard reload (clears caches + service worker)"
-          onClick={() => { setReloading(true); void hardReload(); }}
-          disabled={reloading}
-          className="site-header-reload"
-        >
-          <RefreshCw className={reloading ? "size-4 animate-spin" : "size-4"} />
-        </button>
+        <Button
+          variant="ghost"
+          size="icon"
+          aria-label="Hard reload"
+          title="Hard reload (clears caches + service worker)"
+          onClick={() => { setReloading(true); void hardReload(); }}
+          disabled={reloading}
+          className="site-header-reload"
+        >
+          <RefreshCw className={reloading ? "size-4 animate-spin" : "size-4"} />
+        </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/components/site-header.tsx` around lines 47 - 56, Replace the plain
<button> in site-header.tsx with the existing Button component to match the
Search button styling: keep the same aria-label, title, onClick handler
(setReloading(true); void hardReload()), and disabled prop (reloading), pass
variant="outline" and size="sm", and render the RefreshCw icon as the Button
child with the same conditional className (reloading ? "size-4 animate-spin" :
"size-4"); also add an import for Button if it's not already imported so Button
is available in the module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 101: The fenced code block showing the project architecture (the
directory tree starting with "cmd/            CLI commands (cobra): index,
serve, search, projects, init, hooks, vec" and ending with "ui/            
React 19 + Vite 6 SPA, embedded at compile time") is missing a language
identifier; update the opening fence from ``` to ```text so markdownlint MD040
is satisfied and the block is explicitly marked as plain text.

In `@ui/src/styles/globals.css`:
- Line 419: The h-[calc(100svh-var(--header-height))] utility in the .graph-page
rule is invalid because calc() needs spaces around the minus operator; update
the utility to h-[calc(100svh - var(--header-height))] (i.e., change .graph-page
{ `@apply` ... h-[calc(100svh-var(--header-height))]; } to use h-[calc(100svh -
var(--header-height))]) so the height is parsed correctly.

---

Nitpick comments:
In `@ui/package.json`:
- Around line 37-38: Move the type packages out of runtime dependencies: remove
"@types/d3-selection" and "@types/d3-zoom" from dependencies and add them under
devDependencies in package.json so type-only packages are installed only for
development/compile time; ensure version strings remain the same and run
npm/yarn install or update package-lock to reflect the change.

In `@ui/src/components/graph/GraphCanvas.tsx`:
- Around line 7-11: Extract the duplicated COLOR map into a shared constant
module and import it from both components: create a new module (e.g.,
graph-colors.ts) that exports NODE_COLOR (Record<string,string>) with the same
entries currently in COLOR, then replace the local COLOR definitions in
GraphCanvas (COLOR) and SVGGraphFallback (duplicate) with imports of NODE_COLOR
and update any usages accordingly (e.g., where GraphCanvas references COLOR,
switch to NODE_COLOR). Ensure exports/imports use the same identifier in both
components so the color mapping remains consistent across GraphCanvas and
SVGGraphFallback.

In `@ui/src/components/graph/SVGGraphFallback.tsx`:
- Around line 44-45: SVGGraphFallback currently sets the SVG viewBox directly to
the container size (viewBox={`0 0 ${size.w} ${size.h}`}) which can cause
letterboxing when the container aspect ratio differs; update SVGGraphFallback to
expand or adjust its viewBox to match the container aspect ratio similar to
GraphCanvas (reference GraphCanvas resizing logic around its viewBox handling)
so the fallback uses an aspect-ratio-aware viewBox calculation (based on
size.w/size.h and desired content bounds) to avoid letterboxing and maintain
visual consistency with GraphCanvas.

In `@ui/src/components/site-header.tsx`:
- Around line 47-56: Replace the plain <button> in site-header.tsx with the
existing Button component to match the Search button styling: keep the same
aria-label, title, onClick handler (setReloading(true); void hardReload()), and
disabled prop (reloading), pass variant="outline" and size="sm", and render the
RefreshCw icon as the Button child with the same conditional className
(reloading ? "size-4 animate-spin" : "size-4"); also add an import for Button if
it's not already imported so Button is available in the module.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb85b042-1075-4bd1-8732-d6c66accbb45

📥 Commits

Reviewing files that changed from the base of the PR and between bb50e2a and e2dbaba.

⛔ Files ignored due to path filters (26)
  • ui/dist/assets/DocumentView-S_kRD8s0.js is excluded by !**/dist/**
  • ui/dist/assets/DocumentsList-DDiV8Alk.js is excluded by !**/dist/**
  • ui/dist/assets/Graph-DhboX9Bv.js is excluded by !**/dist/**
  • ui/dist/assets/MCPConsole-B2jF6C5Z.js is excluded by !**/dist/**
  • ui/dist/assets/NoteEditor-XWs05OuT.js is excluded by !**/dist/**
  • ui/dist/assets/NoteView-DbbP0PJj.js is excluded by !**/dist/**
  • ui/dist/assets/NotesLayout-B2b1sadk.js is excluded by !**/dist/**
  • ui/dist/assets/NotesSearch-BjwNYEol.js is excluded by !**/dist/**
  • ui/dist/assets/editor-l0sNRNKZ.js is excluded by !**/dist/**
  • ui/dist/assets/graph-mfGjgYhn.js is excluded by !**/dist/**
  • ui/dist/assets/index-4rAOjcug.css is excluded by !**/dist/**
  • ui/dist/assets/index-DEeQUcqL.js is excluded by !**/dist/**
  • ui/dist/assets/markdown-BU1qdTNu.js is excluded by !**/dist/**
  • ui/dist/assets/useDocs-EWsD7rBb.js is excluded by !**/dist/**
  • ui/dist/favicon.svg is excluded by !**/dist/**, !**/*.svg
  • ui/dist/fonts/Geist-400.woff2 is excluded by !**/dist/**, !**/*.woff2
  • ui/dist/fonts/Geist-500.woff2 is excluded by !**/dist/**, !**/*.woff2
  • ui/dist/fonts/Geist-600.woff2 is excluded by !**/dist/**, !**/*.woff2
  • ui/dist/fonts/GeistMono-400.woff2 is excluded by !**/dist/**, !**/*.woff2
  • ui/dist/fonts/GeistMono-500.woff2 is excluded by !**/dist/**, !**/*.woff2
  • ui/dist/icon-192.png is excluded by !**/dist/**, !**/*.png
  • ui/dist/icon-512.png is excluded by !**/dist/**, !**/*.png
  • ui/dist/icon-maskable-512.png is excluded by !**/dist/**, !**/*.png
  • ui/dist/manifest.webmanifest is excluded by !**/dist/**
  • ui/dist/sw.js is excluded by !**/dist/**
  • ui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • README.md
  • ui/package.json
  • ui/src/components/graph/GraphCanvas.tsx
  • ui/src/components/graph/SVGGraphFallback.tsx
  • ui/src/components/site-header.tsx
  • ui/src/lib/graph-layout.ts
  • ui/src/lib/hard-reload.ts
  • ui/src/routes/Graph.tsx
  • ui/src/stores/project.ts
  • ui/src/styles/globals.css

Comment thread README.md

# 5. Start server
docsiq serve --port 8080
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a language identifier to the architecture code fence.

Line 101 uses a fenced block without a language, which triggers markdownlint MD040.

Suggested fix
-```
+```text
 cmd/            CLI commands (cobra): index, serve, search, projects, init, hooks, vec
 internal/
   api/          REST API + /mcp handler
@@
 ui/             React 19 + Vite 6 SPA, embedded at compile time
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 101-101: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 101, The fenced code block showing the project
architecture (the directory tree starting with "cmd/            CLI commands
(cobra): index, serve, search, projects, init, hooks, vec" and ending with "ui/ 
React 19 + Vite 6 SPA, embedded at compile time") is missing a language
identifier; update the opening fence from ``` to ```text so markdownlint MD040
is satisfied and the block is explicitly marked as plain text.

Comment thread ui/src/styles/globals.css
.graph-page-head { @apply flex items-baseline justify-between px-6 py-4 border-b border-border; }
.graph-canvas-wrap { @apply flex-1 min-h-0 relative; }
.graph-canvas { @apply absolute inset-0 w-full h-full block; }
.graph-page { @apply flex flex-col flex-1 min-h-0 h-[calc(100svh-var(--header-height))]; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In CSS, is calc(100svh-var(--header-height))valid syntax, or must subtraction be written ascalc(100svh - var(--header-height))with spaces around-?

💡 Result:

No, calc(100svh-var(--header-height)) is not valid CSS syntax. Subtraction must be written as calc(100svh - var(--header-height)) with spaces around the - operator. The CSS specification requires whitespace around the + and - operators in calc expressions to distinguish them from unary minus signs in dimensions (e.g., negative lengths like -8px). Without spaces, 100svh-var(--header-height) is parsed as a dimension 100svh followed by an identifier or invalid token starting with -var(...), making it invalid. This rule applies regardless of using viewport units like svh or CSS custom properties like var(--header-height). All examples in MDN documentation and the CSS Values and Units spec use spaces around these operators.

Citations:


Fix invalid calc() syntax in graph page height.

At Line 419, h-[calc(100svh-var(--header-height))] produces invalid CSS. The CSS calc() function requires spaces around the - operator; without them, 100svh-var(--header-height) is parsed as a dimension followed by an invalid token, causing the height rule to be ignored.

✅ Proposed fix
-  .graph-page            { `@apply` flex flex-col flex-1 min-h-0 h-[calc(100svh-var(--header-height))]; }
+  .graph-page            { `@apply` flex flex-col flex-1 min-h-0; height: calc(100svh - var(--header-height)); }
📝 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-page { @apply flex flex-col flex-1 min-h-0 h-[calc(100svh-var(--header-height))]; }
.graph-page { `@apply` flex flex-col flex-1 min-h-0; height: calc(100svh - var(--header-height)); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/styles/globals.css` at line 419, The
h-[calc(100svh-var(--header-height))] utility in the .graph-page rule is invalid
because calc() needs spaces around the minus operator; update the utility to
h-[calc(100svh - var(--header-height))] (i.e., change .graph-page { `@apply` ...
h-[calc(100svh-var(--header-height))]; } to use h-[calc(100svh -
var(--header-height))]) so the height is parsed correctly.

@aksOps aksOps merged commit 353243d into main Apr 19, 2026
9 checks passed
@aksOps aksOps deleted the ui-graph-polish branch April 19, 2026 08:48
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.

1 participant