Commit Graph
122 Commits
Author SHA1 Message Date
chienvon 7254852f4a sync egonex organization metadata 2026-06-09 14:26:48 -07:00
Asim Raza 96c412bcc1 fix(dashboard): add explicit @source for Tailwind v4 detection
Tailwind v4's default source detection walks the nearest .git and
collects tracked files via git ls-files. When the dashboard sources
sit inside a gitignored subtree of an ancestor repo (e.g. the default
marketplace install path ~/.claude/plugins/cache/, which is ignored by
~/.claude/.gitignore), detection returns 0 files and the Oxide engine
skips all utility generation — the dashboard renders unstyled.

Adding explicit @source directives is the supported Tailwind v4 escape
hatch and is a no-op for installs where automatic detection works.

Verified: built CSS bundle jumps from ~9 KB to ~55 KB and utility
classes (.flex, .grid, .absolute, .w-full, .h-full) are present.

Fixes #179
2026-05-23 18:23:17 +05:00
Lum1104 9dfcce73a5 refactor(onboarding): theme tokens, lifted state, a11y
- Replace hardcoded hex with var(--color-*) and CJK font stacks with
  var(--font-sans) / var(--font-heading) so the overlay tracks the theme
  picker and uses the project's typography (DM Serif Display).
- Lift dismiss/visibility state to Dashboard (shouldShowOnboarding +
  showOnboarding useState + dismissOnboarding callback). Gate the
  Suspense mount with a boolean so the lazy chunk is only fetched on
  first visit, matching the PathFinderModal / KeyboardShortcutsHelp
  mount pattern.
- Add capture-phase Escape handler (stopPropagation prevents the global
  shortcut chain from also firing) and role="dialog" / aria-modal /
  aria-labelledby on the card for screen readers.
2026-05-21 11:07:02 +08:00
Lum1104 a29585874e fix(onboarding): include class/function in node-type description
Schema (packages/core/src/schema.ts) defines node types: file, function,
class, module, concept, config, document, ... The welcome step body only
listed "file, concept, entity, claim", missing the most common code-side
types. Updated all 6 locales (en / zh / zh-TW / ja / ko / ru) to mention
file / class / function from code plus concept / entity / claim from the
knowledge wiki.
2026-05-21 11:06:54 +08:00
Lum1104 8f85b85679 fix(onboarding): wire to i18n + add missing ua-fade-in keyframes
- Replace hardcoded Chinese strings with t.onboarding.* via useI18n
- Add `onboarding` namespace to en / zh / zh-TW / ja / ko / ru locales
- Inject @keyframes ua-fade-in (was referenced in inline style but
  never defined, so the overlay popped in instead of fading)
2026-05-21 10:56:10 +08:00
Lum1104 cd09d4fe09 Merge branch 'main' into feat/onboarding-overlay 2026-05-21 10:33:16 +08:00
Lum1104 9d1318a0e7 feat(i18n): add Russian language support 2026-05-19 15:01:43 +08:00
zhushen 2083342199 fix: Address code review feedback for PR #142
- Remove invalid allowBuilds from pnpm-workspace.yaml (use onlyBuiltDependencies in root package.json)
- Use data-testid for search input selector (fixes / keyboard shortcut for non-English locales)
2026-05-12 11:02:13 +08:00
zhushen a3ec91bf39 feat(dashboard): Complete i18n translation for all UI components 2026-05-12 01:53:58 +08:00
zhushen e1650f627c fix: Wrap MobileLayout with I18nProvider; use outputLanguage key in config
P1: MobileLayout was missing I18nProvider wrapper, causing useI18n
    to throw error on mobile devices. Now both desktop and mobile
    layouts are wrapped with I18nProvider.

P2: SKILL.md used 'language' key but Dashboard reads 'outputLanguage'.
    Fixed config.json key name to match ProjectConfig type definition.

All tests passed:
- Core: 670 tests
- Dashboard: 42 tests
2026-05-11 20:30:50 +08:00
zhushen 752fe59e0c feat(dashboard): Add i18n support for localized UI text
- Add outputLanguage field to ProjectConfig type
- Create /config.json endpoint in vite.config.ts
- Build locale files for 5 languages (en, zh, zh-TW, ja, ko)
- Add I18nProvider context and useI18n hook
- Update 5 components (ProjectOverview, NodeInfo, FileExplorer, FilterPanel, PersonaSelector)
- Dashboard reads language from config.json and displays localized UI

All tests passed:
- Core: 670 tests
- Dashboard: 42 tests
2026-05-11 19:00:05 +08:00
Xingkai98 e29f461574 fix(dashboard): reset fn toggle on view switch; hide detail toolbar in domain view
- Issue 1: setDetailLevel now resets showFunctionsInClassView so the fn
  toggle doesn't resurrect when re-entering class view after a file-view
  round-trip.
- Issue 2: detail-level toolbar (Files/+Classes/fn) now gated on
  viewMode !== "domain" so it doesn't render in domain view where it has
  no effect.
2026-05-10 16:24:26 +08:00
Kai XingandGitHub f23291f9ea Merge branch 'Lum1104:main' into feat/dashboard-file-class-views 2026-05-10 15:40:06 +08:00
nieaoandClaude Opus 4.7 562eba412c feat(dashboard): first-visit onboarding overlay
Add a 5-step modal that walks new users through the dashboard's core operations
on first visit. Auto-hides via localStorage after dismiss; can be force-shown
with `?onboard=force` for screenshots and demos.

## What it teaches

1. What the graph represents (entities/relations from code or wiki)
2. Three view buttons (Overview / Learn / Deep Dive) — each answers a different
   question
3. Search + node click — find by name, click for details panel
4. Layer switch + Project Tour — drill into a category, or follow a guided
   walkthrough
5. Hidden features (Filter / Export / Path / Theme) and Shift+? for keyboard
   shortcuts

## Design

- Inline styles, no extra CSS file — easier to land in the existing structure
- Lazy-loaded via Suspense like the other modals (KeyboardShortcutsHelp,
  PathFinderModal) so it ships in a separate chunk
- Architectural-minimalism dark palette consistent with the existing dashboard:
  off-black surface, warm accent (#c8a882), Noto Serif SC headings, generous
  whitespace
- localStorage key `ua-onboarding-dismissed-v1` — versioned so future content
  changes can re-trigger
- Accessible: keyboard-navigable buttons, click-outside to close (without
  remembering dismiss), explicit "不再显示" / "Skip" affordance

## Tested

- Windows 11 + Chrome via Playwright: 5 steps render, progress bar tracks,
  prev/next/dismiss/finish all work, localStorage persists dismiss across
  reloads, `?onboard=force` re-shows for testing
- No new dependencies (uses React 19 hooks already present)
- No changes to data flow, store, or other components — strictly additive

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:15:05 +08:00
Yuxiang LinandGitHub 3eb7700a8f Merge pull request #122 from Lum1104/feat/issue-113-tested-by-coverage
feat: deterministic tested_by edges + dashboard badge (#113)
2026-05-09 11:18:27 +08:00
Xingkai98andClaude Opus 4.6 c1bc4bfac1 feat(dashboard): add file/class dual-view toggle to reduce graph clutter
Add detailLevel state ("file" | "class") to separate architecture-level
file dependencies from code-structure class views. File view shows only
file nodes and file→file edges (imports/depends_on), eliminating ~85%
of nodes/edges that previously caused severe zoom/pan lag. Class view
adds class nodes via contains edges with an optional function toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-09 01:57:10 +08:00
Lum1104andClaude Opus 4.7 6c257a55f0 feat(dashboard): tested badge on node cards (#113)
Render a small green dot next to the complexity badge whenever a node's
tags contain "tested" — surfacing the deterministic linker's signal so
users can see at a glance which files have paired tests.

Plumb node.tags through both CustomNodeData construction sites in
GraphView.tsx; KnowledgeGraphView.tsx already passes tags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:20:40 +08:00
Buzzwoo Team E-Com 92a13eadb5 feat: customizable heading font via theme settings
Add a `headingFont` option to ThemeConfig that lets users switch
heading typography between Serif (default), Sans, and Mono via the
existing Theme Picker UI.

- New `--font-heading` CSS custom property (defaults to `--font-serif`)
- Theme engine applies the selected font on config change
- ThemePicker gets a "Heading Font" toggle section
- All 15 component files updated from `font-serif` to `font-heading`
- Selection persists in localStorage alongside other theme settings
- Backwards-compatible: existing configs without `headingFont` default to serif

Closes #120
2026-05-06 16:13:28 +02:00
Lum1104andClaude Opus 4.7 880e223bb4 feat(dashboard): add mobile layout and responsive fixes
Bumps to 2.6.0.

- MobileLayout activates via useIsMobile at <768px with bottom-tab
  navigation (Graph/Info/Files); panes stay mounted (visibility
  toggle) to preserve ReactFlow dimensions and FileExplorer state.
- MobileDrawer holds persona, view mode, diff, node-type filters,
  layers, and tool buttons (Filter/Export/Path/Theme/Help).
- Selecting a node auto-pivots to Info; CodeViewer is always
  fullscreen on mobile; SearchBar collapses to a 🔍 toggle.
- Homepage Hero/Footer/Install responsive: drop nowrap on title and
  tagline, stack title spans for editorial wrap, full-width CTAs at
  <480px, narrow-width spacing refinements.
- Desktop dashboard: sidebar telescopes 260/300/360px, header gaps
  tighten, Path button label collapses to icon at narrow widths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:25:56 +08:00
Lum1104andClaude Opus 4.7 61356da3dc fix(dashboard): suppress TourFitView overlay flicker after fallback
Follow-up on the Codex P2: now that `useNodes` is in the effect deps,
every node update during a step that already timed out re-enters the
poll, sets `tourFitPending=true`, runs RAF for 4s, hits the silent
fallback path, and clears the flag. Visually the "Locating tour
highlight…" overlay would flash on every reflow even though the user
has already given up waiting. Skip the pending flag once
`fallbackKeyRef` matches the current step — the retry still runs
silently so a late Stage 2 can still upgrade to the proper fit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:27:30 +08:00
Lum1104andClaude Opus 4.7 75368dc103 fix(dashboard): TourFitView timeout no longer freezes refit
Codex review on PR #114: when the RAF poll window expires before
highlighted nodes have been measured, the timeout fallback was setting
`fittedKeyRef.current = targetKey`, marking the step as fitted even
though the proper highlight fit never ran. If Stage 2 layout landed
after the 4s cap, the effect early-returned on the next nodes update
because the target key already matched, so the camera stayed pinned to
the fallback layer fit instead of zooming onto the actual highlights.

Fix:

  - Subscribe to React Flow's user-node array via `useNodes()` so the
    effect re-fires when Stage 2 finally produces the highlighted ids
    after the per-step RAF poll has already given up.
  - On timeout, pan into the layer for usability but do NOT set
    `fittedKeyRef`. The next nodes update gets another shot at the
    highlight fit, and on success `fittedKeyRef` records the proper fit.
  - Use a separate `fallbackKeyRef` to ensure the fallback `fitView`
    fires at most once per step — without this, every subsequent nodes
    update during the unready window would trigger a viewport jump.
  - Reset both refs when `tourHighlightedNodeIds` clears (stop tour).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:24:09 +08:00
Lum1104andClaude Opus 4.7 f58f0edd62 fix(dashboard): clear pendingFocusContainer on layer/state resets
Codex review on PR #114 flagged that `layerResetIfChanged` cleared
`containerLayoutCache` and `expandedContainers` but left
`pendingFocusContainer` intact. Because container ids collide across
layers (the very reason the cache reset exists), a manual expand in
layer A that hadn't yet hit its 1.2s clear timer could leak its id
into layer B's namespace and recenter the viewport on an unrelated
container right after navigation.

The same hazard applies to every other reset path that drops the
container caches. Add `pendingFocusContainer: null` to all of them:

  - layerResetIfChanged (tour cross-layer reset, the originally flagged
    site)
  - drillIntoLayer
  - navigateToOverview
  - setFocusNode
  - setPersona
  - setGraph
  - toggleNodeTypeFilter
  - clearContainerLayouts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:21:06 +08:00
Lum1104andClaude Opus 4.7 4b86c696a5 fix(dashboard): tour navigation glitches across layers
Four related issues that surfaced while walking the Learn-mode tour
through a multi-layer project (microservices-demo):

1. Tour auto-expand never released. The tour effect that expands
   highlighted nodes' containers had no corresponding collapse when the
   step changed, so containers accumulated open as the user advanced.
   Track the set of containers we expanded and release any not needed
   by the current step; user-toggled containers are never tracked here,
   so they're never auto-collapsed.

2. Manual container toggle yanked off-screen. Stage 2 reflow shifted
   the just-clicked container away from the cursor. `toggleContainer`
   now records `pendingFocusContainer` on expand; GraphView locks the
   viewport onto that container's centre with the current zoom so it
   appears to expand in place.

3. Tour fitView fired before highlighted children existed. A single
   RAF after `tourHighlightedNodeIds` change wasn't enough — child
   nodes only appear once Stage 2 layout writes
   `containerLayoutCache`, and React Flow only knows their absolute
   position after a measure pass. `useNodes()` doesn't fire on
   measure completion, so we poll `getInternalNode().measured` each
   frame (up to ~4s) and call `fitView({ nodes })` once every
   highlight is measured, with `maxZoom: 1.2 / minZoom: 0.4`. While
   waiting, a new `tourFitPending` flag drives a "Locating tour
   highlight…" overlay so the user knows the layout is still settling.

4. Cross-layer tour transitions reused stale Stage 2 cache. Container
   ids derive from per-layer state (folder names in folder strategy,
   `container:cluster-N` in community strategy) and collide across
   layers — API Contracts and Load Testing both produce
   `container:cluster-0`. `setTourStep` / `nextTourStep` /
   `prevTourStep` / `startTour` didn't reset the container caches the
   way `drillIntoLayer` does, so when tour crossed a layer the new
   layer's expanded containers hit the previous layer's cache, Stage 2
   skipped its rerun, and children never showed. Extracted
   `layerResetIfChanged` and applied it in all four tour actions.

Verified end-to-end against microservices-demo via headless Chrome:
all 15 tour steps now expand the right container(s), zoom onto the
referenced files, and collapse the previous step's auto-expansions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:06:39 +08:00
d 🔹andClaude Opus 4.7 988533a550 fix(dashboard): preserve any-layer-wins membership for filterNodes
Reviewer @Lum1104 (PR #112) caught a silent semantic regression: the new
`filterNodes` reads layer membership through `nodeIdToLayerId.get(node.id)`,
which is first-wins. The pre-#112 path was any-layer-wins —
`layers.some(layer => filters.layerIds.has(layer.id) && layer.nodeIds.includes(node.id))`.
For a node X listed in both L1 and L2 with only L2 selected, the old code
kept X; the new code dropped it. The schema permits multi-layer membership,
so this was a behavior change, not a bug fix.

Fix: keep two distinct indexes in the store. Both are rebuilt once on
`setGraph`, so the O(1)-per-node performance win from #112 is preserved.

  - `nodeIdToLayerId: Map<string, string>` — first-matching-layer wins.
    Drives navigation (drillIntoLayer / tour step → layer / sidebar
    history) where one canonical layer is the right answer. Unchanged.

  - `nodeIdToLayerIds: Map<string, Set<string>>` — every layer the node
    belongs to. Drives `filterNodes` membership checks. Restores
    any-layer-wins exactly.

`filterNodes` now iterates the (small) layer-id set per node looking for
intersection with `filters.layerIds`. ExportMenu reads
`nodeIdToLayerIds` from the store.

Verified locally:

  - Added `filters.test.ts` regression: node in (L1, L2) with only L2
    selected must pass. Failed against the first-wins implementation;
    passes now.
  - `pnpm --filter @understand-anything/dashboard test` — 42 / 42 pass
    (was 41; +1 multi-layer regression test; perf-guard at 100 layers ×
    100 nodes still <50 ms).
  - `pnpm --filter @understand-anything/dashboard exec tsc --noEmit` — clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 17:16:42 +08:00
d 🔹andClaude Opus 4.7 44e1fee31b fix(dashboard): O(N+K) per-layer aggregations, kill quadratic Array.includes (#102)
Three hot paths in the dashboard ran `layer.nodeIds.includes(node.id)`,
which is O(K) per check. Combined with their enclosing loops they
collectively spent quadratic time per render of the overview / per
filter recompute / per node-selection event. On the 4.8 MB knowledge
graph reported in #102, the overview render alone took ~470 ms of
synchronous main-thread work before ELK / React Flow ran — long
enough for the page to register as unresponsive.

Fix: precompute two indexes once when a graph is loaded.

  - `nodesById: Map<string, GraphNode>`
  - `nodeIdToLayerId: Map<string, string>`  (first layer wins, matching
    prior `findNodeLayer` semantics)

Both live in `useDashboardStore` and are rebuilt by `setGraph`. The
three call sites:

1. `useOverviewGraph` (GraphView.tsx) — per-layer complexity aggregation
   moved into a new `computeLayerStats(layer, nodesById)` helper that
   walks `layer.nodeIds` instead of filtering all `graph.nodes`. Search
   match counts now read straight from `nodeIdToLayerId` instead of
   rebuilding a layer index on every searchResults change.

2. `filterNodes` (utils/filters.ts) — takes `nodeIdToLayerId` instead of
   `Layer[]`; the layer-membership check is one Map.get() per node.
   Updated `ExportMenu.tsx` caller to pass the store-level index.

3. `findNodeLayer` (store.ts) — replaced with `nodeIdToLayerId.get()` at
   the four call sites. `navigateTourToLayer` helper updated to take the
   index rather than the whole graph.

Behavior is preserved exactly:

  - "First layer wins" semantics for nodes that appear in multiple
    layers (#102 schema doesn't forbid this).
  - 30 % aggregate-complexity threshold pinned by tests.
  - Layer filter that excludes layer-less orphans, but ungated when
    no layers are selected.

Verified locally:

  Bench (`scripts/benchmark-aggregations.mjs`, node 22):
    100 layers × 200 nodes (#102 shape):  475 ms → 2 ms  (232× faster)
    50  layers × 200 nodes:               116 ms → 0.6 ms (190× faster)
    30  layers × 100 nodes:                12 ms → 0.2 ms (63×  faster)

  Tests: `pnpm --filter @understand-anything/dashboard test`
    24 → 41 pass (+17 new tests across `layerStats.test.ts` and
    `filters.test.ts`, including a #102 perf-regression guard at
    100 layers × 100 nodes < 50 ms).
  `pnpm --filter @understand-anything/core test` — 654 / 654 pass.
  `pnpm --filter @understand-anything/dashboard exec tsc -b` — clean.
  `pnpm --filter @understand-anything/dashboard build` — clean.

Pre-existing on master and not from this branch: `pnpm lint` errors
out with "eslint: command not found" — `eslint` isn't installed by any
package and the root `lint` script is bare `eslint .`. Out of scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:49:20 +08:00
Lum1104andClaude Opus 4.7 2327cd74e4 fix(dashboard): address Codex review
P1: Anchor edges at the collapsed-side container atom when only one
side of an aggregated edge is expanded. The previous pass always
emitted file→file endpoints, but a collapsed container's children
aren't rendered as nodes — React Flow silently dropped those edges.
Now the collapsed endpoint stays as the container id, and the
expanded endpoint becomes the real file id; multiple files
collapsing onto the same (container → file) pair are deduped.

P2: Pending-fit ref + post-render trigger replaces the fixed 50ms
timer for layer-transition fitView. ELK takes ~125ms+ on medium
layers, so the timer was firing before positions arrived and the
viewport stayed on the old layer. Now we mark a pending fit on
navigation change and execute it once `nodes` populate, then
requestAnimationFrame so React Flow has placed the nodes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:49:32 +08:00
Lum1104andClaude Opus 4.7 a6612b17e1 fix(dashboard): render intra-container edges when container is expanded
Stage 1 stashes intra-folder file→file edges on `topo.intraContainer`
but `expandedEdges` only walked `topo.edges` (aggregated cross-
container only). When a user expanded a container the children
appeared but their internal wiring was invisible, even though the
NodeInfo sidebar listed the connections.

Now after the inflated cross-container pass, also push every
intra-container edge whose owning container is expanded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:29:59 +08:00
Lum1104andClaude Opus 4.7 09308631bc fix(dashboard): clear container cache on focus, deprecate applyDagreLayout
setFocusNode was missed in the C1 cache-reset pass. Focus mode narrows
filteredGraphNodes to focus + 1-hop neighbors, so a container that
survives still has a subset of its children — the cache must drop
or it'll keep returning positions for filtered-out ids.

Mark applyDagreLayout @deprecated. The structural views all use ELK
now; the helper is retained for one release as a quick fallback path
and removed in the next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:26:49 +08:00
Lum1104andClaude Opus 4.7 8d068549cb fix(dashboard): address final review C1 + C2
C1 (cache leak across topology changes):
Container ids derive from folder names (`container:auth`, etc.) and
collide across layers, personas, and node-type filters. Without a
reset, navigating from Layer A to Layer B left Stage 2's cache and
size memory keyed by Layer A children, so Stage 2 short-circuited
and Layer B's children silently disappeared.

Reset containerLayoutCache, containerSizeMemory, and
expandedContainers in:
- drillIntoLayer (most visible — layer drill)
- navigateToOverview (drilling out)
- toggleNodeTypeFilter (filter change shifts container.nodeIds)
- setPersona (persona filters node types)

setGraph already resets all three on full graph reload — unchanged.

C2 (bundle regression):
Add elkjs and graphology rules to vite manualChunks. The main index
chunk drops from 525KB gzipped back to 62KB, with ELK split into a
parallel-loadable 439KB chunk. Restores baseline first-paint cost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:14:04 +08:00
Lum1104 1329c93dbd feat(dashboard): layout-issue banner + Computing layout overlay 2026-05-03 17:01:41 +08:00
Lum1104andClaude Opus 4.7 69a0cd13a9 chore(dashboard): address Task 14 + 15 review concerns
Task 14 stale TODO:
- Delete the TODO(Task 14) comment that this very task already
  resolved (selection→container neighbor highlighting now flows
  through isFocusedViaChild).

Task 15 visible container size:
- mergeElkPositions now propagates width/height from the ELK output
  back onto the React Flow node, not just position. After a
  stage1Tick-driven re-layout, the container atom resizes to match
  the actual Stage 2 footprint instead of staying clipped at the
  pre-expansion estimate. ELK echoes back the same width/height we
  pass in for non-container nodes, so this is a no-op for them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:53:58 +08:00
Lum1104 ccb52545ad feat(dashboard): Stage 2 size-deviation triggers Stage 1 re-layout 2026-05-03 16:51:47 +08:00
Lum1104andClaude Opus 4.7 cb71e989e8 chore(dashboard): address Task 12 + 13 review concerns
Task 13 onMove auto-expand:
- Skip when event === null so React Flow's programmatic moves
  (fitView at layer entry, zoomTo from search) don't cascade-expand
  every container the moment the layer paints.
- Track previous zoom; only fire when zoom actually increased. Pans
  and zoom-outs are now no-ops, so a user who manually collapses a
  container while zoomed in can keep it that way.

Task 12 inflated edge ids:
- Append index suffix so parallel edges with identical
  (source, target, type) — which the schema doesn't disallow — don't
  collide in React Flow's edge map.

Other Task 12 concerns (per-container Stage 2 cancellation, O(K·E)
edge bucket pre-pass, extent:"parent" clipping pre-Task-15) are
known trade-offs documented in the plan; deferring to follow-ups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:46:43 +08:00
Lum1104 202c9e0481 feat(dashboard): container overlays — search/diff/focus 2026-05-03 16:42:39 +08:00
Lum1104 1e0849b528 feat(dashboard): auto-expand on zoom, focus, and tour 2026-05-03 16:38:53 +08:00
Lum1104andClaude Opus 4.7 7dd1c41906 chore(dashboard): address Task 11 review minors
- Stable handleContainerToggle via useCallback so ContainerNode's
  memo() actually short-circuits. Reading toggleContainer through
  getState() keeps Stage 1 from accidentally subscribing to
  expandedContainers.
- Cap pre-expansion container size at 800x600 so layers with many
  files don't render as huge empty boxes during Stage 1. Stage 2
  measurement and Task 15's reflow handle the real size.
- Update stale "dagre relayout" comment now that ELK is the layout
  engine; flag the Task 14 follow-up for selection→container
  neighbor highlighting via inline TODO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:35:39 +08:00
Lum1104 b19d93efc2 feat(dashboard): layer-detail Stage 2 — lazy children layout + edge expansion 2026-05-03 16:32:38 +08:00
Lum1104 4efeccc702 feat(dashboard): layer-detail Stage 1 — containers + ELK 2026-05-03 16:26:17 +08:00
Lum1104andClaude Opus 4.7 ff4ac3c265 chore(dashboard): address Task 9 + 10 review concerns
Cross-cutting fixes for the dagre→ELK migration pattern:

- nodesToElkInput accepts an optional layoutOptionsOverride so views
  can override defaults (e.g. direction) without forking the helper.
- DomainGraphView restores its original LR layout by passing
  { "elk.direction": "RIGHT" } — the previous commit silently shifted
  it to TB because the helper hardcoded DOWN.
- Both overview and DomainGraphView now .catch the ELK promise so
  strict-mode (DEV) failures don't surface as unhandled rejections.
- Both also console.warn returned issues until Task 16 wires the
  WarningBanner funnel; auto-corrected/dropped issues no longer
  silently disappear in production.
- Minor: DRY the duplicate `as unknown as Node[]` cast in GraphView.

Ranksep label-aware spacing in DomainGraphView is intentionally
deferred — see Task 14 polish or follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:19:50 +08:00
Lum1104 8d20efec94 feat(dashboard): DomainGraphView uses ELK 2026-05-03 16:17:26 +08:00
Lum1104andClaude Opus 4.7 47a80912c9 chore(dashboard): address Task 6 + 8 review minors
ContainerNode (Task 8 quality review):
- Add accessibility: role="button", tabIndex, aria-expanded,
  aria-label with item count + state, focus-ring on focus, Enter/Space
  keyboard activation. Containers are the primary lazy-load
  interaction so keyboard support shouldn't be a regression.
- Use var(--color-diff-changed) and the font-serif Tailwind class
  instead of literals so theme switching still works.
- Replace the 🔍 emoji with a typographic "N hits" pill matching the
  badge pattern used by LayerClusterNode.
- Add a displayName so React DevTools shows "ContainerNode" rather
  than "Memo(Anonymous)".

elk-layout.test.ts (Task 6 quality review):
- Drop the (c as { x?: number }).x casts now that ElkChild declares
  x?/y? directly. Tests now exercise the typed contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:14:10 +08:00
Lum1104 0b18a84014 feat(dashboard): overview view uses ELK 2026-05-03 16:12:17 +08:00
Lum1104andClaude Opus 4.7 a9b614ae2d fix(dashboard): complete the GraphEdge test fixture
Earlier review fix-up dropped the `as GraphEdge` cast but didn't add
the `direction` and `weight` fields, breaking `tsc -b`. Tests still
passed because vitest doesn't typecheck. Add the missing fields so
the dashboard build is green again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:08:52 +08:00
Lum1104andClaude Opus 4.7 00a75c27db chore(dashboard): address Task 5 review minors
- Re-export NODE_WIDTH/NODE_HEIGHT from utils/layout.ts as the
  fallback dimensions in elk-layout.ts. Coupling them with the dagre/
  force defaults prevents silent drift during the migration.
- Declare optional x/y on ElkChild so downstream consumers (Tasks 9-12)
  see the position contract in the type. Comment notes ELK sets them
  on output but they're absent on input.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:07:57 +08:00
Lum1104 e459e924ff feat(dashboard): ContainerNode component (visual + click toggle) 2026-05-03 16:07:26 +08:00
Lum1104 43fd232df7 test(dashboard): applyElkLayout integration cases 2026-05-03 16:06:49 +08:00
Lum1104andClaude Opus 4.7 9da085018e chore(dashboard): address Task 4 review minors
- Length-prefix the inter-container bucket key to eliminate collision
  risk when container ids contain the separator (e.g. folder names with
  spaces). Adds a regression test.
- Rename AggregatedContainerEdge.types -> edgeTypes for symmetry with
  the existing LayerEdgeAggregation.edgeTypes field.
- Drop the as-cast in the test helper by giving it the real EdgeType
  literal type and a complete object shape.
- Reword the JSDoc to match what the function actually does (silently
  drop unmapped endpoints; callers needing strict can pre-filter).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:04:57 +08:00
Lum1104 3ba65e67a0 feat(dashboard): store fields for expanded containers + layout cache 2026-05-03 16:04:34 +08:00
Lum1104andClaude Opus 4.7 a2b8f27810 chore(dashboard): address Task 2 + 3 review minors
- louvain.ts: rewrite the misleading "disconnected nodes get -1" comment
  (the library actually assigns unique ids; the reassignment loop is a
  defensive net for future API drift).
- containers.test.ts: add tags:[] to the test node helper for shape
  fidelity; replace the vacuously-true "every is community" assertion
  with one that actually fails if the community fallback regresses.
- containers.ts: collapse the two-pass groupByFolder into a single
  branching loop; guard Cluster A-Z naming so it doesn't wrap into
  punctuation past 26 communities.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:02:20 +08:00
Lum1104 8f3a6bf2c4 feat(dashboard): elk-layout repair pipeline + applyElkLayout 2026-05-03 16:00:49 +08:00