Skip to content

Extract N-1 fetch and diagram highlights into custom hooks - #109

Merged
marota merged 6 commits into
mainfrom
claude/fix-standalone-parity-d2gug
Apr 22, 2026
Merged

marota merged 6 commits into
mainfrom
claude/fix-standalone-parity-d2gug

Conversation

@marota

@marota marota commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Refactored the App.tsx component by extracting two major concerns into dedicated custom hooks: useN1Fetch and useDiagramHighlights. This reduces App.tsx complexity while improving code organization and testability.

Key Changes

  • New hook: useN1Fetch — Encapsulates the N-1 diagram fetch logic, including:

    • Fast svgPatch DOM-recycling path (clones N-state SVG and patches only changed elements)
    • Full /api/n1-diagram fallback for session restore or when patching is unavailable
    • Contingency-change confirmation dialog routing
    • Short-circuit logic to avoid redundant fetches
  • New hook: useDiagramHighlights — Owns the per-tab SVG highlighting pipeline:

    • Per-tab Flow/Impacts view-mode state (detachedViewModes)
    • applyHighlightsForTab — DOM mutation pass for overload halos, contingency highlights, action-target halos, and delta visuals
    • Proper ordering of highlight operations (clone-based highlights before delta visuals)
    • Detached tab view-mode isolation with reattach cleanup
  • New components:

    • AppSidebar — Left-sidebar layout shell with sticky summary strip
    • SidebarSummary — Compact sticky strip showing selected contingency and N-1 overloads
    • StatusToasts — Fixed-position status banners for errors and info messages
  • Refactored ActionFeed:

    • Added onOverviewFiltersChange callback prop for shared filter state updates
    • Improved action type filtering with ActionTypeFilterChips component
    • Better separation of concerns between filter UI and filter logic
  • Updated utilities:

    • Added DEFAULT_ACTION_OVERVIEW_FILTERS constant to actionTypes.ts
    • Enhanced getActionTargetLines logic in svgUtils for better combined action handling
    • Improved SVG source handling to support both string and DOM element types
  • Test improvements:

    • Updated ActionFeed tests to use overviewFilters prop instead of internal state
    • Refactored action type filter tests to match new filter architecture
    • Added mock for actionPassesOverviewFilter in ActionFeed tests

Implementation Details

  • The N-1 fetch hook preserves the session-restore contract by using restoringSessionRef to force full fetches when rehydrating analysis state
  • Diagram highlights hook uses a ref-based approach to avoid setState-in-effect linting issues while preserving reattach-prune behavior
  • SVG source now supports both string (from API) and DOM element (from svgPatch) types for flexible diagram handling
  • App.tsx reduced from ~1000 to ~1150 lines (net increase due to new prop threading, but core logic extracted)

https://claude.ai/code/session_01B6mqrab2Pqjmr5AuLTNQxe

claude added 6 commits April 22, 2026 12:54
…references

The v0.6 snapshot pre-dated the /api/n1-diagram-patch and
/api/action-variant-diagram-patch endpoints added in the svg-dom-recycling
perf work (PR #108). check_standalone_parity.py (Layer-1) was failing
because those paths appeared in frontend/src/api.ts but not in the
committed snapshot that CI uses when standalone.html is absent.

Rebuilt the snapshot from the current React source via
`npm run build:standalone` and committed it as standalone_v0.7.html.
Layer-1 parity check now passes against both the auto-generated artifact
and the new versioned snapshot.

https://claude.ai/code/session_01B6mqrab2Pqjmr5AuLTNQxe
Options 1 + 2 from the App.tsx size-reduction plan. App.tsx shrinks from
1575 lines to ~1150 by relocating pure presentational JSX and two
self-contained effect pipelines. No behavior change.

Extracted components (Option 1):
  - components/SidebarSummary.tsx  — sticky contingency/N-1 strip
  - components/AppSidebar.tsx      — sidebar layout shell with
                                     summary + contingency selector
                                     + children slot
  - components/StatusToasts.tsx    — error/info floating banners

Extracted hooks (Option 2):
  - hooks/useN1Fetch.ts            — N-1 diagram fetch effect:
                                     svgPatch fast-path, full
                                     /api/n1-diagram fallback, and
                                     contingency-change confirm
                                     routing
  - hooks/useDiagramHighlights.ts  — per-tab SVG highlight pipeline
                                     (overload halos, contingency
                                     highlight, action-target halos,
                                     delta visuals) + the per-tab
                                     Flow/Impacts view-mode state

One unavoidable eslint-disable in useDiagramHighlights.ts on the
reattach-prune setState: the pre-extraction code in App.tsx was not
flagged by react-hooks/set-state-in-effect (position-sensitive
heuristic), but the observable behavior must be preserved byte-for-
byte, so the guarded setState stays with a justified suppression.

frontend/CLAUDE.md records what landed, lists deferred options
(Option 3 orchestrator hooks, Option 4 AppContext, the NDJSON parser
extraction), and explains the ESLint exception.

Verified: npm run test (1098 tests pass), npm run lint clean, tsc -b
clean, scripts/check_standalone_parity.py OK,
scripts/check_code_quality.py OK.

https://claude.ai/code/session_01B6mqrab2Pqjmr5AuLTNQxe
…overview

The three UI locations that render an action-type chip row (manual
selection search dropdown, explore-pairs tab in the combined modal,
action overview diagram) previously each owned independent filter
state with different implementations:

- ActionOverviewDiagram used ActionTypeFilterToken via ActionTypeFilterChips (reference)
- ExplorePairsTab had local useState<ActionTypeFilterToken>
- ActionFeed search dropdown had a legacy boolean map (typeFilters) with checkboxes

All three now share overviewFilters.actionType (owned by App.tsx):
- ExplorePairsTab: local state removed; accepts actionTypeFilter + onActionTypeFilterChange props
- CombinedActionsModal: threads the two new props through to ExplorePairsTab
- ActionFeed: typeFilters state removed; filteredActions and scoredActionsList
  both use matchesActionTypeFilter(overviewFilters?.actionType ?? 'all');
  onOverviewFiltersChange prop added and threaded to ActionSearchDropdown
  (chips) and CombinedActionsModal
- ActionSearchDropdown: TypeFilters type removed; checkbox row replaced with
  ActionTypeFilterChips; props renamed to actionTypeFilter / onActionTypeFilterChange
- App.tsx: onOverviewFiltersChange={setOverviewFilters} added to <ActionFeed>

Tests updated to match controlled-prop pattern (pass actionTypeFilter/
overviewFilters directly instead of clicking now-gone checkboxes).

https://claude.ai/code/session_01B6mqrab2Pqjmr5AuLTNQxe
…erage

Refactor:
- utils/actionTypes.ts: add DEFAULT_ACTION_OVERVIEW_FILTERS constant so
  the initial filter literal is declared once instead of being duplicated
  across App.tsx, ActionOverviewDiagram, and ActionFeed.
- App.tsx: use the shared constant for initial state.
- ActionOverviewDiagram.tsx: drop local DEFAULT_FILTERS, reuse the shared
  constant for the filter-normalisation fallback.
- ActionFeed.tsx: extract the actionType-patching callback as useCallback
  (previously duplicated at two call sites), and fall back to the shared
  constant instead of an inline object literal.

Tests added (17 new, 1100 → 1117):
- actionTypes.test.ts: coverage for DEFAULT_ACTION_OVERVIEW_FILTERS
  (actionType / categories / threshold / showUnsimulated defaults).
- ActionFeed.test.tsx: new 'shared action-type filter' describe block
  covering Selected-card filtering by bucket, undefined overviewFilters
  fallback, the onOverviewFiltersChange callback from dropdown chips
  (both default and non-default preserved fields), active chip
  aria-pressed state, scored-actions-table filtering, and silent
  no-op when the handler is not wired.
- ActionSearchDropdown.test.tsx: active chip aria-pressed state.
- ExplorePairsTab.test.tsx: default-to-all when prop omitted,
  active chip aria-pressed state, silent no-op on chip click
  when handler not wired.
- CombinedActionsModal.test.tsx: initial actionTypeFilter prop
  respected on first render, onActionTypeFilterChange forwarded
  to ExplorePairsTab.
- ActionFeed.test.tsx mock: expose actionPassesOverviewFilter
  so tests that exercise the severity-gate code path do not crash.

https://claude.ai/code/session_01B6mqrab2Pqjmr5AuLTNQxe
The style prop was being spread directly into the chip row's flex
container, changing chip visual appearance relative to the overview
version. Move the layout/border styles into a wrapper <div> so all
three call sites render ActionTypeFilterChips with no style override,
giving identical chip appearance everywhere.

https://claude.ai/code/session_01B6mqrab2Pqjmr5AuLTNQxe
The three chip rows (manual-selection dropdown, Explore Pairs modal,
action overview diagram) now each manage their own ActionTypeFilterToken
state independently — same component, same classification logic, same
visual appearance, but no cross-location synchronisation.

- ExplorePairsTab: local useState replaces the lifted actionTypeFilter/
  onActionTypeFilterChange props (both removed)
- CombinedActionsModal: no longer threads those props to ExplorePairsTab
- ActionFeed: dropdown + scored list use a new local dropdownTypeFilter
  state; action cards continue to use overviewFilters.actionType (the
  overview diagram chip still controls card visibility)
- Tests updated to drive filter changes via chip clicks rather than
  passing overviewFilters.actionType to components that no longer read it

https://claude.ai/code/session_01B6mqrab2Pqjmr5AuLTNQxe
@marota
marota merged commit ce7f45c into main Apr 22, 2026
7 of 8 checks passed
marota pushed a commit that referenced this pull request Apr 22, 2026
… dynamic fix

Captures the post-0.6.0 work: svgPatch DOM-recycling (PR #108),
Action Overview filters + unsimulated pins (PR #105, #107),
code-quality gate + 5 decomposition passes (PR #104, #106),
docs reorganisation (PR #103), App.tsx hook extraction
(PR #109), and the dynamic reco_ reconnection-action fix on
the current branch.

https://claude.ai/code/session_01Tzp2fdUas3Y9vNZy6dxxuC
marota pushed a commit that referenced this pull request Apr 24, 2026
Follow-up to the root/frontend/backend CLAUDE.md + README refresh.
Audits the remaining project docs against PRs merged 2026-04-21 →
2026-04-23 and updates the eight that had actually drifted.

frontend/README.md (HIGH)
  Replaced the stock Vite-template boilerplate with a real frontend
  orientation: npm scripts, source-tree map (hooks, components,
  utils incl. the utils/svg/* split from PR #104), pointer to the
  auto-generated standalone bundle, testing + lint commands, and
  cross-refs to CLAUDE.md / PARITY_AUDIT.md / docs/README.md.

expert_backend/tests/CLAUDE.md (MEDIUM)
  Full rewrite of the test-file inventory: added the PR #104
  decomposition suites (test_simulation_helpers.py,
  test_analysis_helpers.py, test_diagram_helpers.py), the patch-
  endpoint coverage (test_diagram_patch_helpers.py,
  test_n1_diagram_fast_path.py) from PR #108, the regression
  guards (test_resimulate_regression.py,
  test_second_contingency_reset.py,
  test_get_n1_variant_clones_from_n_state.py,
  test_configurable_mw.py) that were added since 2026-04-11, and
  the dynamic reco_* reconnection path from PR #110. Replaced the
  frontend inventory with a structural summary matching the
  current ~60-file Vitest layout, listed the scripts/pypsa_eur
  pytest coverage, and documented the tests removed by PR #103 /
  #104 (test_ui_regressions.py, standaloneInterface.test.ts,
  cssRegression.test.ts).

docs/features/combined-actions.md (MEDIUM)
  Added a "Recent updates (PR #114, release 0.6.5)" section
  covering LS/curtailment in combined pairs, Simulate Combined
  moved out of the card + clickable sub-action badges,
  target_max_rho on the user-selected overload set, and Explore-
  Pairs re-estimation aligned with the pre-computed betas.
  Rewrote the "Standalone Interface" section to describe the
  auto-generated bundle, and dropped the checklist line that
  required manual standalone_interface.html mirroring. Added a
  row to the key-files table for the ActionCard sub-action badges
  and the ComputedPairsTable / ExplorePairsTab split.

docs/features/state-reset-and-confirmation-dialogs.md (MEDIUM)
  Replaced the standalone_interface.html section with a pointer
  to the auto-generated bundle, and expanded the "What reset()
  clears" list to match the current RecommenderService.reset()
  implementation (drain order + fast-path caches +
  _layout_cache + NAD-prefetch state, with the
  add-a-new-cache-goes-here guardrail).

docs/features/detachable-viz-tabs.md (MEDIUM)
  Removed the "standalone_interface.html mirror" row and the
  follow-up caveat that said the single-file interface doesn't
  support detaching — both are obsolete now that the bundle is
  auto-generated from the React tree.

docs/architecture/app-refactoring-plan.md (MEDIUM)
  Added a "Status: SHIPPED" banner at the top summarising both
  refactor waves (Phase 1 via PR #74, Phase 2 hook extraction
  via PR #109) and pointing readers at the current source of
  truth (frontend/CLAUDE.md + CHANGELOG.md 0.6.5). Preserved the
  original plan below the banner for historical value.

docs/architecture/phase2-state-management-optimization.md (LOW)
  Added a "Status: Partially shipped" banner — memoization +
  React.memo pass shipped under PR #75, superseded in part by
  PR #109 (useN1Fetch / useDiagramHighlights extractions),
  orchestrator hooks still deferred.

docs/features/frontend-ui-improvements.md (LOW)
  Scope header now names `frontend/dist-standalone/standalone.html`
  (auto-generated, PR #101) instead of the decommissioned
  hand-maintained file.

https://claude.ai/code/session_01RsvHjjFbAauF3NvD9BNT5V
marota pushed a commit that referenced this pull request May 5, 2026
…ctions in README

Per request, surface two key 0.7.0 visualization features in the
README's Visualization section and add a new "Efficient interactions"
sub-section, plus refresh stale doc pointers caught in the sweep.

README.md
- Visualization: add interactive overflow analysis (layer toggles,
  hierarchical ↔ geo, SLD drill-down) + action pin overview
  (selected/rejected/combined/unsimulated semantics, mirrored on the
  overflow viewer via postMessage). Add VL-names toggle bullet.
- New "Efficient interactions" section bundling the operator-
  velocity affordances: synchronized filter chips across feed +
  overview + overflow, pin-driven workflow (single-click preview /
  double-click drill / unsimulated double-click triggers manual
  simulation), Combined-action explorer, Inspect search, Detachable
  tabs (relocated from Visualization), tiered notices + diagram
  legend, progressive-disclosure ActionCard, replay-ready log.

Doc staleness fixes
- docs/README.md: list the new interactive-overflow-analysis.md
  feature doc; refresh action-overview-diagram.md description to
  reflect the overflow-viewer mirror; update architecture entries
  to point at the post-0.7.0 reality (app-refactoring shipped,
  phase2 shipped 0.5.0, code-quality §14–15 deltas, dev-cycle
  through 0.7.0).
- docs/architecture/app-refactoring-plan.md: App.tsx is now ~1400
  lines (PR #109 dropped it to ~1150; 0.7.0 hooks brought it back
  up). 0.7.0 entry referenced in CHANGELOG note.
- docs/architecture/phase2-state-management-optimization.md: same
  App.tsx line-count refresh + reason.
- docs/features/save-results.md: standalone_interface_legacy.html
  IS tracked (committed as a frozen snapshot), not "untracked via
  .gitignore" — corrected.
- frontend/CLAUDE.md: App.tsx tree-line + refactor-history table
  updated to reflect the 0.7.0 ~1400-line state and why
  (useOverflowIframe, postMessage envelope, design-token glue,
  action-pin overflow handlers).
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