Skip to content

refactor(app): extract useForceGraphSimulation from MapPage#8581

Merged
MarkusNeusinger merged 10 commits into
mainfrom
refactor/frontend-mappage-hook
Jun 10, 2026
Merged

refactor(app): extract useForceGraphSimulation from MapPage#8581
MarkusNeusinger merged 10 commits into
mainfrom
refactor/frontend-mappage-hook

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

Part 10 of the frontend modernization roadmap. Extracts the force-graph simulation orchestration from the 1689-line MapPage.tsx into a dedicated hook — no behavior change.

  • New src/hooks/useForceGraphSimulation.ts (556 LOC): specs loading, weights/minSim state, settled/tick gating, node cache, search haystacks/matches, graph derivation (via MapPage.helpers), and a static forceConfig (engine params, link distance/strength, collide + outlier-squash force factories). Ref-decoupled — returns plain state/handlers; the single inversion is an onRepaint callback (thumbnail loads) that MapPage wires to fgRef.current.refresh().
  • MapPage.tsx: 1689 → 1292 LOC; keeps ForceGraph2D, canvas paint callbacks, UI overlays (search, legend, weights panel), and the ref-coupled handlers (camera fit, flyTo). The remaining bulk is JSX overlay markup — further extraction would be UI decomposition, out of scope here.
  • 17 new hook tests (graph derivation, weight handling, settling gate, search, force config; the outlierSquashForce suite moved over from MapPage.test.tsx with assertions unchanged). Hook + types exported from the hooks/ barrel.

Tests: 598 → 608.

Verification

yarn lint ✓ (one pre-existing react-refresh warning in MapPage resolved by the move) · yarn fm:check ✓ · yarn type-check ✓ · yarn test 608/608 ✓ · yarn build

🤖 Generated with Claude Code

- Move spec loading, similarity weights/minSim state, the KNN graph
  derivation (node cache + cluster seeding), the settling gate
  (settled/tickProgress), search matching, and the tuned d3-force
  configuration into src/hooks/useForceGraphSimulation.ts
- Hook stays decoupled from ForceGraph2D's imperative ref API: it
  returns plain state + handlers (markSettled, handleEngineTick,
  resetWeights) and a static forceConfig; the only inversion is an
  onRepaint callback fired when thumbnails finish loading
- MapPage keeps the canvas element, paint callbacks, UI overlays, and
  all ref-coupled interaction handlers (camera fit, force wiring,
  fly-to); behavior, analytics events, aria attributes and keyboard
  interactions are unchanged
- outlierSquashForce + cluster color constants move to the hook module;
  MapPage.helpers stays a pure math module (header comment updated);
  hook exported from the src/hooks barrel
- New useForceGraphSimulation.test.ts (17 tests) covers graph
  derivation, weight/minSim handling, the settling gate, search
  matching, and the relocated outlierSquashForce suite; MapPage.test.tsx
  keeps the rendering/interaction tests (frontend suite 552 -> 562)

Part 10 of the frontend modernization roadmap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 10, 2026 02:42
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.32710% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
app/src/hooks/useForceGraphSimulation.ts 95.60% 9 Missing ⚠️
app/src/pages/MapPage.tsx 88.88% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors the /map page by extracting force-graph simulation/data orchestration (spec loading, KNN graph derivation, settling gate, search matching, and force tuning) out of the large MapPage.tsx component into a dedicated useForceGraphSimulation hook, while keeping MapPage focused on the ForceGraph2D canvas, paint callbacks, and ref-coupled handlers.

Changes:

  • Added useForceGraphSimulation hook encapsulating map spec fetch + graph/simulation state and returning ref-decoupled state/handlers + force config.
  • Simplified MapPage.tsx by wiring the new hook outputs into the existing UI/canvas logic (including onRepaintfgRef.refresh()).
  • Moved/added tests to cover hook behavior (graph derivation, weights/minSim, settling gate, search, and outlier-squash force), and adjusted MapPage tests accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/src/pages/MapPage.tsx Removes in-component simulation/data orchestration and wires the new hook outputs into ForceGraph2D + UI handlers.
app/src/pages/MapPage.test.tsx Drops direct unit tests for the extracted force and retains MapPage integration assertions (force wiring, overlay behaviors, etc.).
app/src/pages/MapPage.helpers.ts Updates module header to reflect shared usage between MapPage paint logic and the new hook.
app/src/hooks/useForceGraphSimulation.ts New hook implementing spec fetch, graph derivation + caching, settling/tick state, search matching, and force configuration/factories.
app/src/hooks/useForceGraphSimulation.test.ts New unit tests for the hook and the extracted outlier-squash force behavior.
app/src/hooks/index.ts Exports the new hook and its public types from the hooks barrel.

Comment on lines +207 to +208
// Static by construction — a single frozen object keeps the identity stable
// across renders so consumers can safely list it in dependency arrays.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Applied — FORCE_CONFIG is now Object.freeze()d, so the comment is literally true.

Comment on lines +522 to +527
const handleEngineTick = () => {
tickCountRef.current += 1;
const PROGRESS_TICK_BATCH = 6;
if (tickCountRef.current % PROGRESS_TICK_BATCH === 0) {
setTickProgress(Math.min(1, tickCountRef.current / COOLDOWN_TICKS));
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Applied — tick progress is denominated in FORCE_CONFIG.cooldownTicks now.

Copilot review findings: the 'frozen' comment is now literally true via
Object.freeze, and handleEngineTick derives the progress denominator from
FORCE_CONFIG.cooldownTicks instead of the raw constant so the progress bar
can't drift from the configured simulation lifetime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MarkusNeusinger MarkusNeusinger enabled auto-merge (squash) June 10, 2026 03:09
Copilot AI review requested due to automatic review settings June 10, 2026 03:14
@MarkusNeusinger MarkusNeusinger review requested due to automatic review settings June 10, 2026 03:14
Copilot AI review requested due to automatic review settings June 10, 2026 03:15
@MarkusNeusinger MarkusNeusinger review requested due to automatic review settings June 10, 2026 03:15
Copilot AI review requested due to automatic review settings June 10, 2026 03:17
@MarkusNeusinger MarkusNeusinger review requested due to automatic review settings June 10, 2026 03:17
Copilot AI review requested due to automatic review settings June 10, 2026 03:21
@MarkusNeusinger MarkusNeusinger review requested due to automatic review settings June 10, 2026 03:21
Copilot AI review requested due to automatic review settings June 10, 2026 03:27
@MarkusNeusinger MarkusNeusinger review requested due to automatic review settings June 10, 2026 03:27
Copilot AI review requested due to automatic review settings June 10, 2026 03:28
@MarkusNeusinger MarkusNeusinger review requested due to automatic review settings June 10, 2026 03:28
Copilot AI review requested due to automatic review settings June 10, 2026 03:29
@MarkusNeusinger MarkusNeusinger review requested due to automatic review settings June 10, 2026 03:29
@MarkusNeusinger MarkusNeusinger merged commit 5428fd9 into main Jun 10, 2026
7 checks passed
@MarkusNeusinger MarkusNeusinger deleted the refactor/frontend-mappage-hook branch June 10, 2026 03:37
MarkusNeusinger added a commit that referenced this pull request Jun 10, 2026
…tion (#8596)

## Summary

Part 11 (final) of the frontend modernization roadmap.

- **`app/ARCHITECTURE.md`**: directory layout
(routes/layouts/pages/sections/components/hooks/lib/theme), conventions
(`src/` alias imports, `paths.*` URL registry, `lib/api` client, theme
tokens, feature folders), data flow, testing pattern, quality gates, and
how-to-add recipes for pages/sections/hooks/endpoints
- **`agentic/docs/project-guide.md`**: frontend section now lists the
full gate (`lint`, `fm:check`, `type-check` incl. tests, `test`),
mentions the dev-time checker overlay, and links the new architecture
doc

With this, the roadmap is complete: tooling baseline (#8519), src/
aliases (#8520), theme split (#8525), global-config (#8529), API client
(#8531), routes registry (#8546), structure split (#8547), FilterBar
(#8559), SpecTabs (#8564), MapPage hook (#8581).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

2 participants