Skip to content

Commit 658692d

Browse files
mvalancyclaude
andauthored
chore(web): dedupe LOD_THRESHOLDS + document the web/UI architecture (#88)
Scrub of the last day's UI work: - Remove the shadowed/dead module-level LOD_THRESHOLDS in IGV; keep a single source of truth carrying the values the graph actually ran on (behavior- identical) and drop the unused MEDIUM/VERY_CLOSE keys. - Drop leftover console.log diagnostics from the node-expand-legibility spec (the assertion messages already carry the values). - New docs/guides/web-ui-architecture.md: a concise component diagram + notes for the view system, graph canvas, mobile shell, and node inspector — the client layer the existing (server-focused) architecture-overview.md didn't cover. Linked from architecture-overview.md + docs/README.md. No behavior change: typecheck + lint clean, smoke 5/5, node-inspector + node-expand-legibility diagnostics green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b8b602c commit 658692d

5 files changed

Lines changed: 68 additions & 24 deletions

File tree

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Welcome to the GraphDone documentation! This directory contains comprehensive gu
2121
### [Developer Guides](./guides/)
2222
- [Getting Started](./guides/getting-started.md) - Setup and first steps
2323
- [Architecture Overview](./guides/architecture-overview.md) - System design and technical decisions
24+
- [Web / UI Architecture](./guides/web-ui-architecture.md) - View system, graph canvas, mobile shell, node inspector
2425
- [Testing Guide](../tests/README.md) - **E2E testing with robust authentication system**
2526
- [SQLite Deployment Modes](./guides/sqlite-deployment-modes.md) - Local dev vs Docker authentication storage
2627
- [User Flows](./guides/user-flows.md) - How teams actually use GraphDone

docs/guides/architecture-overview.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ GraphDone is architected around three core principles:
88
2. **Real-Time First**: Changes propagate immediately to all participants
99
3. **Democratic Coordination**: Priority emerges from community validation, not top-down assignment
1010

11+
> **Client / UI layer:** for the web app's structure (view system, graph canvas,
12+
> mobile shell, node inspector) see [web-ui-architecture.md](./web-ui-architecture.md).
13+
> Some sections below describe the forward-looking server/infra target, not all of
14+
> which is wired up today.
15+
1116
## Current Architecture (v0.3.1-alpha)
1217

1318
```mermaid

docs/guides/web-ui-architecture.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Web / UI Architecture
2+
3+
How the client (`packages/web`, React 18 + Vite + Tailwind + D3) is structured.
4+
This is the companion to [architecture-overview.md](./architecture-overview.md),
5+
which covers the server / graph engine / data layer.
6+
7+
```mermaid
8+
graph TD
9+
App["App.tsx<br/>GraphProvider + ViewModeProvider"]
10+
App --> Layout["Layout<br/>sidebar · header · MobileBottomNav"]
11+
Layout --> WS["Workspace<br/>graph selector · data queries"]
12+
WS --> VM["ViewManager<br/>renders the active view mode"]
13+
WS --> Insp["NodeInspector (docked)<br/>+ on-canvas expand peek"]
14+
15+
VM -->|list / cards| Card["CardView"]
16+
VM -->|graph| SGV["SafeGraphVisualization<br/>→ InteractiveGraphVisualization (D3)"]
17+
VM -->|table · kanban · gantt<br/>calendar · dashboard · activity| Other["other views"]
18+
19+
VMC["ViewModeContext<br/>active mode + persistence"] -.-> VM
20+
VMC -.-> Layout
21+
Insp --> Modes["Card · Contents (lazy markdown) · Diagram (sub-graph)"]
22+
Audit["mobile-audit tests (CI)<br/>layout · contrast · dialogs"] -.->|gate every screen| VM
23+
```
24+
25+
## Pieces
26+
27+
- **App shell**`App.tsx` wraps the tree in `GraphProvider` (current graph +
28+
drill-in/ascend) and **`ViewModeContext`** (`contexts/ViewModeContext.tsx`), the
29+
single source of truth for the active view, persisted to `localStorage`. `Layout`
30+
draws the chrome.
31+
- **View system**`ViewManager` renders one of 8 modes: `cards`, `graph`,
32+
`table`, `kanban`, `gantt`, `calendar`, `dashboard`, `activity`. Phones default to
33+
**`cards`** (a readable list); desktop defaults to `graph`.
34+
- **Graph**`SafeGraphVisualization` error-boundary-wraps
35+
`InteractiveGraphVisualization`, the D3 force-directed canvas (one-shot physics,
36+
viewport culling, LOD by zoom — see `LOD_THRESHOLDS`).
37+
- **Node inspector** — a docked `NodeInspector` plus an on-canvas **expand-in-place**
38+
peek, each with a **Card / Contents / Diagram** toggle readable at any zoom
39+
(`NodeContentRenderer` lazy-loads markdown/Prism; `NodeSubgraphPreview` draws a
40+
capped static sub-graph). In-canvas card titles have a zoom-decoupled
41+
**legibility floor**.
42+
43+
## Responsive tiers (boundary: Tailwind `md`, 768px)
44+
45+
- **Phone (`<md`)**`MobileBottomNav` (List / Graph / More) is the primary nav;
46+
slim chrome; the sidebar/desktop header are hidden.
47+
- **Tablet & desktop (`≥md`)** — sidebar rail + full top view-strip + all filters.
48+
49+
## Quality gate
50+
51+
`tests/e2e/mobile-audit.spec.ts` + `mobile-dialogs.spec.ts` run the
52+
`tests/helpers/mobileAudit.ts` auditors (sideways-scroll, squeezed labels,
53+
low-contrast/invisible text, modals clipped under the nav) across **every** screen
54+
at phone width, in CI right after the smoke gate.

packages/web/src/components/InteractiveGraphVisualization.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ import { spawnCelebration } from '../lib/celebration';
5252
import { buildNeighborhood } from '../lib/graphAdjacency';
5353
import { UndoStack } from '../lib/undoStack';
5454

55-
// LOD thresholds for different zoom levels
55+
// Level-of-detail zoom thresholds (single source of truth): below each scale the
56+
// matching per-node detail is hidden, for legibility and paint cost. (Values are
57+
// the ones the graph actually ran on — a stale shadowing duplicate was removed.)
5658
const LOD_THRESHOLDS = {
57-
VERY_FAR: 0.1,
58-
FAR: 0.3,
59-
MEDIUM: 0.6,
60-
CLOSE: 1.0,
59+
VERY_FAR: 0.3, // below: viewport-cull, basic shapes only
60+
FAR: 0.5, // below: hide type text + edit/grow/expand/descend icons
61+
CLOSE: 0.6, // below: hide descriptions, edge labels, sub-graph counts
6162
};
6263

6364
// Above this node count a graph is "dense": the continuous living-graph effects
@@ -795,15 +796,6 @@ export function InteractiveGraphVisualization({ onResetLayout, onNodeSelected, i
795796
// Window will be controlled manually by user interactions
796797
}, [selectedNodes.size, editingEdge]);
797798

798-
// Level of detail thresholds
799-
const LOD_THRESHOLDS = {
800-
VERY_FAR: 0.3, // Only show basic shapes
801-
FAR: 0.5, // Add node icons
802-
MEDIUM: 0.8, // Add node titles
803-
CLOSE: 0.6, // Add edge labels (earlier)
804-
VERY_CLOSE: 2.0 // Full detail
805-
};
806-
807799
// Function to save node position to database
808800
const saveNodePosition = useCallback(async (nodeId: string, x: number, y: number) => {
809801
try {

tests/diagnostics/node-expand-legibility.spec.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ test.describe('node expand-in-place + legibility floor @geometry', () => {
111111
await expect(panel, 'peek stays anchored through zoom').toBeVisible();
112112
await page.keyboard.press('Escape');
113113
await expect(panel, 'Esc closes the peek').toBeHidden({ timeout: 5000 });
114-
115-
// eslint-disable-next-line no-console
116-
console.log('[expand] ok — anchored Card/Contents/Diagram peek verified');
117114
});
118115

119116
test('PR-4: title stays above the on-screen legibility floor when zoomed out', async ({ page }) => {
@@ -125,9 +122,7 @@ test.describe('node expand-in-place + legibility floor @geometry', () => {
125122
// Zoom OUT into the band where the native (un-counter-scaled) title would be
126123
// sub-readable (k < ~0.857) but the label is still on screen.
127124
const k = await zoomOutInto(page, 0.45, 0.7);
128-
// eslint-disable-next-line no-console
129-
console.log('[legibility] zoomed to k=' + k.toFixed(3));
130-
expect(k, 'reached the counter-scale band (k < 0.857)').toBeLessThan(0.857);
125+
expect(k, `reached the counter-scale band (k=${k.toFixed(3)} < 0.857)`).toBeLessThan(0.857);
131126

132127
const probe = await page.evaluate(() => {
133128
const texts = [...document.querySelectorAll('.graph-container svg .node-title-text')] as SVGTextElement[];
@@ -151,9 +146,6 @@ test.describe('node expand-in-place + legibility floor @geometry', () => {
151146
// (e.g. 14px * 0.5 = 7px).
152147
expect(probe.screenHeight, `title on-screen height >= floor (${LEGIBLE_FLOOR_PX}px)`).toBeGreaterThanOrEqual(LEGIBLE_FLOOR_PX - 3);
153148
// Zoomed into the band, the counter-scale should be actively boosting (> 1).
154-
expect(probe.groupScale, 'legibility counter-scale is engaged when zoomed out').toBeGreaterThan(1);
155-
156-
// eslint-disable-next-line no-console
157-
console.log('[legibility] title screenHeight=' + Math.round(probe.screenHeight) + 'px, groupScale=' + probe.groupScale);
149+
expect(probe.groupScale, `legibility counter-scale engaged when zoomed out (h=${Math.round(probe.screenHeight)}px, scale=${probe.groupScale})`).toBeGreaterThan(1);
158150
});
159151
});

0 commit comments

Comments
 (0)