Repository navigation
Add sticky contingency/overloads header with loading percentages - #88
Merged
Merged
Conversation
Rationalize the left feed so the operator always sees the selected contingency and the N / N-1 overloads while scrolling through the action list, and lets them zoom those items on whichever diagram is currently displayed. - Sidebar layout: contingency selector and OverloadPanel now sit in a non-scrolling sticky block; only the ActionFeed body scrolls (App.tsx + standalone_interface.html). - Selected contingency: rendered as a clickable link beneath the datalist input; clicking re-centers the currently-active diagram (N / N-1 / Action) on the contingency without switching tabs. - Overloads panel: the N and N-1 line names now show their loading ratio as "(XX.X%)" next to the name, pulled from a new lines_overloaded_rho field on the diagram response. Clicking a line zooms the active tab instead of force-switching to N or N-1. - Backend: _get_overloaded_lines optionally returns parallel rho values; get_network_diagram / get_n1_diagram expose them via the new lines_overloaded_rho field. - Action cards: the per-card "Loading before" line is removed — the information is now permanently visible in the sticky Overloads N-1 section and was duplicated on every card. - Tests updated (OverloadPanel, ActionCard, App.contingency sidebar layout). https://claude.ai/code/session_01JpNKJ8NCyrv4erGkKBp2Nz
Replace the heavy sticky block (full Select Contingency card + full
Overloads panel) with a thin sticky strip that only carries the
clickable fields of interest — saving vertical space in the feed.
- Sticky strip: two compact rows
- 🎯 Contingency: clickable selected branch → zooms on the
currently-active diagram tab (no tab switch).
- ⚠️ N-1: clickable N-1 overload names with "(XX.X%)" loading,
each opening the N-1 tab and zooming on the element.
- Full Select Contingency card (search input) and full Overloads
panel (with warnings, N/N-1 breakdown, rho %) move back into the
scroll area so they scroll out of view as the operator browses
actions, preserving the screen for the action cards.
- OverloadPanel click reverted to the per-tab switch behavior:
N overloads → N tab, N-1 overloads → N-1 tab. This matches the
old "Loading Before" link on action cards which always landed on
the N-1 state, so the info flow from the sticky strip and the
full panel is consistent.
- standalone_interface.html mirrored.
- Tests updated for the reverted OverloadPanel click API.
https://claude.ai/code/session_01JpNKJ8NCyrv4erGkKBp2Nz
Clicking the line name in the action card's "Max loading: X% on Y" row was silently falling back to the previously-zoomed element (the pre-action N-1 overload) whenever Y was a line that is not itself in the disconnectable `branches` list — typical case: a remedial action re-distributes flows and the new worst line is not a contingency target. Root cause: the auto-zoom effect guards `setInspectQuery(...)` with `knownItemsSet.has(targetId)` and that set was only built from `branches ∪ voltageLevels`. When the click landed on a line outside this set, the guard rejected it, `lastZoomState.query` stayed on the previous target, and the diagram stayed centered on whatever was zoomed last — giving the user the impression that the Max-loading link "always points to the overload before". Fix: widen the guard to also include every equipment ID present in any loaded NAD metadata index (nodes + edges for N / N-1 / Action). This is pulled out as a standalone `computeKnownItemsSet` helper so it can be unit-tested. Tests: - `useDiagrams.test.ts` — 6 unit tests for `computeKnownItemsSet` covering branches, voltage levels, metadata-only IDs, null indices, and the partial-text rejection still holding. - `ActionCard.test.tsx` — regression: clicking the "Max loading on LINE_B" button when `linesOverloaded=['LINE_A']` and `max_rho_line='LINE_B'` must call `onAssetClick` with `LINE_B`. https://claude.ai/code/session_01JpNKJ8NCyrv4erGkKBp2Nz
The selected action card used to show a rectangular "VIEWING" pill
in the top-right of its header, stealing a chunk of the one row of
horizontal space shared with the action ID and severity badge — on a
narrow sidebar with long equipment IDs this routinely forced the
title to wrap.
Re-render it as a vertical ribbon flush against the left border:
- Outer card becomes `display: flex; align-items: stretch; padding: 0`
with an inner content column that carries the card's original
`padding: 10px`.
- When `isViewing`, a sibling ribbon is inserted as the first child,
sitting between the 5px-colored left border and the content
column. It uses `writing-mode: vertical-rl` + `transform:
rotate(180deg)` — the cross-browser combination for bottom-to-top
"book-spine" orientation that works consistently on Chromium,
Firefox and Safari (unlike the newer `sideways-lr`).
- The inline top-right VIEWING pill is removed; the severity badge
("Solves overload" / "Still overloaded" / ...) keeps its place.
Standalone interface mirrored.
Tests (ActionCard.test.tsx):
- asserts the ribbon renders with `writing-mode: vertical-rl` when
`isViewing`, contains "VIEWING", and is queryable via a stable
test id.
- asserts no <span> in the card contains the old inline pill text.
- asserts the ribbon is absent when `isViewing=false`.
https://claude.ai/code/session_01JpNKJ8NCyrv4erGkKBp2Nz
marota
pushed a commit
that referenced
this pull request
Apr 14, 2026
Session reload no longer loses data introduced by PRs #73/#78/#83/#88: - handleRestoreSession now restores lines_overloaded_after, load_shedding_details, curtailment_details and pst_details on each ActionDetail. Previously these were dropped on reload, so the PST / load-shedding / curtailment editor cards rendered empty and the Remedial Action tab lost its post-action overload halos until the user re-ran analysis. - buildSessionResult persists the sticky-header rho arrays (n_overloads_rho / n1_overloads_rho) alongside the overload name lists, guarded on matching length so misaligned legacy data is omitted instead of saved. - committedNetworkPathRef is now updated on session restore so the "Change Network?" confirmation dialog no longer misfires (or silently drops the study) after a reload. Interaction logging now captures every user gesture the replay contract needs to faithfully reproduce a session: - config_loaded and settings_applied include the full settings payload (all paths, every recommender threshold including min_load_shedding and min_renewable_curtailment_actions, monitoring, pre-existing overload threshold, ignore_reconnections, pypowsybl_fast_mode). - settings_tab_changed emits { from_tab, to_tab } and skips no-op clicks on the already-active tab. - New event types action_mw_resimulated and pst_tap_resimulated are logged from ActionFeed.handleResimulate / handleResimulateTap with the raw user-entered target_mw / target_tap. useActions no longer logs manual_action_simulated from handleActionResimulated, which conflated the two flows and made replay impossible. docs/interaction-logging.md is rewritten to reflect all of the above: documented tab_detached / tab_reattached / tab_tied / tab_untied visualisation events (previously in types.ts but undocumented), corrected the details shape for view_mode_changed / asset_clicked / inspect_query_changed / sld_overlay_* / session_* to match the actual emitted payloads, documented the applySettings / loadStudy / changeNetwork cases on contingency_confirmed, and added a new "Session reload fidelity" section listing exactly which fields are persisted / restored and which are intentionally ephemeral. Tests: 695 frontend tests still pass; sessionUtils.test.ts gains coverage for rho persistence guards and useActions.test.ts now asserts that handleActionResimulated does not log from the hook. https://claude.ai/code/session_013qJjLFQWMR91ZfPTRCLFiu
marota
pushed a commit
that referenced
this pull request
Apr 19, 2026
…tton release
Four user-reported issues from the last screenshot batch:
## 1. Combine-modal Simulate button stuck in running mode
`simulatingActionId` was only reset in the outer try's finally, AFTER
the async action-variant-diagram fetch completed. On big grids
that's ~5-6 seconds of stale "spinner" UI even though the action
CARD (what matters to the sidebar feed) has already landed.
Fix: call setSimulatingActionId(null) + setIsSimulatingCombined(false)
the moment the simulate response arrives and the result has been
merged. The subsequent variant-diagram fetch continues
asynchronously — buttons release immediately, tab flips happen
naturally when the diagram is ready, matching React's perceived
responsiveness. Applied to both handleAddManualAction and
handleSimulateCombined.
## 2. Tab switching / action diagram loading felt sluggish
Two React performance optimisations were missing on the standalone:
### 2a. `/api/network-diagram` served as plain JSON → large JSON.parse
The backend exposes a `format=text` variant that returns
`{header}\n<svg…>` instead of embedding the multi-MB SVG inside a
JSON string. Skips a ~500 ms main-thread JSON.parse on the
PyPSA-EUR France grid (see docs/perf-loading-parallel.md). React
has used this since PR #71.
Ported to the standalone via a new `_fetchNetworkDiagramTextFormat()`
helper that fetches + splits on the first newline, with a graceful
fallback to the JSON endpoint if the text variant 404s (older
backends).
### 2b. Base NAD fetched AFTER branches + VLs + nominal-voltages
Previously the standalone did `Promise.all([branches, VLs, nomV])`
THEN called `fetchBaseDiagram`. Since network-diagram is by far
the slowest XHR, stacking it after the metadata round-trips adds
~1-2 s to the critical path. React parallelises all four since
PR #88 (docs/perf-loading-parallel.md).
Ported: both call sites (applySettingsImmediate + handleLoadConfig
equivalents) now issue a 4-way Promise.all that includes the
network-diagram. The SVG is processed synchronously after the
Promise.all resolves, matching the React flow.
## 3. Pin rendering
Prior implementation was an SVG circle rendered in a viewBox-
scaled layer, so pins shrunk / grew and drifted as the user
zoomed. Label was the rank number, not the loading percentage.
Anchor math was fine but the visual anchoring looked off because
the pin's centroid scaled with the map.
Rewrite:
- Pins rendered as absolutely-positioned `<div>` elements in
SCREEN pixel space. A `projectSvgPoint(x, y)` function maps
each pin's SVG-local coordinates through the current
overviewVb + overlayRect using the same `xMidYMid meet`
projection as the SVG itself, so pins stay exactly on the
line midpoint as the user pans + zooms.
- Teardrop glyph: inline <svg> with a <path> for the classic
Google-Maps pin shape (`d="M18 0 … Z"`), tip at (0, 48) so
`translate(-50%, -100%)` on the wrapper plants the tip on
the asset. Inner white circle for label contrast.
- Label is the loading percentage (rounded), falling back to
the rank when rho isn't set — matches the React ActionOverview
component's pin label.
- Size is 36x48 CSS-px regardless of zoom. A ResizeObserver
keeps overlayRect current when the window or sidebar resizes,
so pin projection stays accurate through responsive layout
changes.
Combined-pair curves (React's Bézier connecting two pins) are
still omitted — it's purely cosmetic and the per-pin events +
metadata round-trip already cover replay parity.
## 4. Deselecting an action jumped to N-1 tab instead of staying on Action
React switched this behaviour in PR #93 — clicking the selected
action card again (or the × chip) now keeps the user on the
Remedial Action tab, which falls through to the
ActionOverviewDiagram pin view. The standalone was still doing
`setActiveTab('n-1')` in the deselect branch of handleActionSelect.
Fix: drop that line. The tab stays on 'action', selectedActionId
becomes null, and the conditional render below picks the Overview
component because it's the only branch where
`result?.actions && !selectedActionId`.
---
Parity-script impact:
- Layer 1 still 51/51, exits 0.
- Layer 2 unchanged (26/30 React, 30/30 standalone).
- Layer 3a 22/22.
- Vitest 972/972.
https://claude.ai/code/session_01LGL7gvedQLrUGGzqVm4wAG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR introduces a compact sticky header in the sidebar that keeps the selected contingency and N-1 overloads visible while scrolling through the action feed. It also adds loading ratio (rho) percentages next to overloaded lines throughout the UI, and removes the redundant "Loading before" display from individual action cards.
Key Changes
Sticky Contingency & Overloads Header: Added a non-scrolling sticky strip at the top of the sidebar that displays:
Loading Ratio Display:
lines_overloaded_rho(per-element I/limit ratios) alongsidelines_overloadednamesNew
handleZoomOnActiveTabHandler: Allows zooming to an asset in the current diagram without switching tabs, used by the sticky contingency button and overload linksSidebar Layout Restructuring:
overflow: hiddento prevent nested scrollbarsflex-shrink: 0) stays fixed at topRemoved Redundant "Loading Before": Deleted the "Loading before" line from individual action cards since this information is now prominently displayed in the sticky N-1 overloads section
Updated OverloadPanel Component: Added
nOverloadsRhoandn1OverloadsRhoprops to render loading percentages; improved documentation of the asset click behaviorTest Updates: Updated tests to verify the new sticky header layout and rho percentage rendering
Implementation Details
wrappedAssetClickfor N-1 (switches tabs) vshandleZoomOnActiveTabfor contingency (stays on current tab)_get_overloaded_lines()method now supportswith_rho=Trueparameter to return both names and rho values as a tuple(rho * 100).toFixed(1) + '%'https://claude.ai/code/session_01JpNKJ8NCyrv4erGkKBp2Nz