# Frontend UI Improvements Plan > **Status**: Implemented > **Scope**: `frontend/src/` components. The auto-generated > `frontend/dist-standalone/standalone.html` (PR #101) inherits from > the React source via `npm run build:standalone`; the legacy > hand-maintained `standalone_interface.html` has been decommissioned. > **Date**: 2026-04-01 > **Note (historical)**: this is an early UI-improvements plan. Several > of these items have since been **superseded or extended** by later > work β€” notably the shared **Action Filter rings** strip (severity + > action-type + Max-loading, replacing the per-surface category toggles > here), the **collapsible readability sidebar**, the tiered **Notices > pill + DiagramLegend**, and **light / dark theme** (all 0.8.0). For > current behavior see [`docs/README.md`](../README.md), the relevant > feature docs (e.g. [`dark-mode.md`](dark-mode.md)) and the > [`CHANGELOG`](../../CHANGELOG.md). The content below is preserved as > the original plan of record. --- ## Table of Contents 1. [Move & Rename "Run Analysis" Button](#1-move--rename-run-analysis-button) 2. [Always-Visible Visualization Tabs with Placeholder Messages](#2-always-visible-visualization-tabs-with-placeholder-messages) 3. [Collapsible Voltage Filter](#3-collapsible-voltage-filter) 4. [Revised Color Code for Highlights](#4-revised-color-code-for-highlights) --- ## 1. Move & Rename "Run Analysis" Button ### Current State The "Run Analysis" button lives inside the **contingency selector card** in `App.tsx` (line ~812–829), directly below the contingency `` field. It is rendered as: ```tsx ``` The **ActionFeed** panel (below the OverloadPanel in the sidebar) already has its own button zone where: - A **processing spinner** appears while `analysisLoading` is true (line ~916) - A **"Display N prioritized actions"** button appears when `pendingAnalysisResult` is ready (line ~934) ### Target State Move the analysis trigger into the **ActionFeed** panel header area, at the location where the processing indicator and "Display" button appear. The three states become a single slot: | State | What is shown | |-------|---------------| | **Idle** (no analysis running, no pending result) | `πŸ” Analyze & Suggest` button (green, enabled only if `selectedBranch` is set) | | **Running** (`analysisLoading === true`) | `βš™οΈ Analyzing…` button (yellow, disabled) | | **Pending** (`pendingAnalysisResult !== null`) | `πŸ“Š Display N prioritized actions` button (green gradient, enabled) | ### Files to Change #### `App.tsx` - **Remove** the ` ) : pendingAnalysisResult ? ( ) : ( )} ``` #### `standalone_interface.html` - Apply the same relocation: remove the analysis button from the contingency selector area and add it into the action feed panel header. #### Test Files - Update `App.settings.test.tsx` and `App.session.test.tsx`: the `'πŸš€ Run Analysis'` text selector changes to `'πŸ” Analyze & Suggest'`. Locate the button inside the action feed panel instead of the sidebar header. --- ## 2. Always-Visible Visualization Tabs with Placeholder Messages ### Current State In `VisualizationPanel.tsx` (lines ~808–858), tabs are **conditionally rendered**: - **Network (N)**: Always shown - **Contingency (N-1)**: Only when `selectedBranch` is set - **Action**: Only when `selectedActionId` is set - **Overflow Analysis**: Only when `result?.pdf_url` exists ### Target State All four tabs are **always visible**. When a tab's content is not yet available, clicking it shows a placeholder message guiding the user on what to do. Additionally, the **Action** tab dynamically updates its label to include the selected action ID. #### Tab Definitions | Tab | Label (default) | Label (populated) | Placeholder message | |-----|------------------|--------------------|---------------------| | N | `Network (N)` | β€” | *(always populated after config)* `Configure a network path in Settings to view the base-case diagram.` | | N-1 | `Contingency (N-1)` | β€” | `Select a contingency element from the dropdown above to view the N-1 state.` | | Action | `Remedial Action` | `Remedial Action: {actionId}` | `Select an action card from the suggestions panel to view its effect on the network.` | | Overflow | `Overflow Analysis` | β€” | `Run an analysis to generate the overflow graph.` | #### Tab Styling - **Populated tabs**: Current active/inactive styling (bold + colored bottom border when active). - **Unpopulated tabs**: Slightly dimmer text (`#aab`), italic label, no colored border even when active. Still clickable. - **Active but unpopulated**: White background + placeholder message in content area (centered, grey italic text with a subtle icon). ### Files to Change #### `VisualizationPanel.tsx` - **Remove conditional rendering** of tab buttons. Render all four always. - Add availability flags: ```tsx const tabAvailable = { 'n': !!nDiagram?.svg, 'n-1': !!n1Diagram?.svg, 'action': !!actionDiagram?.svg, 'overflow': !!result?.pdf_url, }; ``` - Update the **Action** tab label: ```tsx const actionTabLabel = selectedActionId ? `Remedial Action: ${selectedActionId}` : 'Remedial Action'; ``` Truncate long action IDs with CSS `text-overflow: ellipsis` (max ~200px) and show full ID on hover via `title` attribute. - In the tab **content area**, wrap each tab's body: ```tsx {activeTab === 'action' && ( tabAvailable['action'] ? : )} ``` - Add a small `TabPlaceholder` component (inline or local): ```tsx const TabPlaceholder: React.FC<{ message: string }> = ({ message }) => (
{message}
); ``` #### `standalone_interface.html` - Mirror the same logic: always render all 4 tab buttons, show placeholder text when content is unavailable. #### `App.tsx` - Remove any logic that prevents switching to a tab when its content is unavailable (if present). The `onTabChange` handler should allow selecting any tab at any time. --- ## 3. Collapsible Voltage Filter ### Current State The voltage filter is a **vertical sidebar** on the right side of the visualization panel (`VisualizationPanel.tsx` lines ~1074–1125, CSS class `.voltage-sidebar`). It is always visible when `uniqueVoltages.length > 1`, consuming ~62px of horizontal space. ### Target State The voltage filter sidebar is **collapsed by default**, showing only a small toggle button. When expanded, it shows the full slider UI as today. #### Collapsed State - A small vertical button on the right edge: `β–Έ kV` (or a filter icon from lucide-react). - Width: ~24px, just enough for the icon/label. - Click expands the filter. #### Expanded State - Full current sidebar (62px width) with an additional collapse button (`β—‚` or `βœ•`) at the top. - Clicking the collapse button or clicking outside collapses it back. ### Files to Change #### `VisualizationPanel.tsx` - Add local state: `const [voltageFilterExpanded, setVoltageFilterExpanded] = useState(false);` - Wrap the existing voltage sidebar in a conditional: ```tsx {uniqueVoltages.length > 1 && ( voltageFilterExpanded ? (
{/* ... existing slider content ... */}
) : ( ) )} ``` #### `App.css` - Add `.voltage-sidebar-toggle` style: ```css .voltage-sidebar-toggle { position: absolute; top: 0; right: 0; bottom: 0; width: 24px; background: rgba(244, 244, 244, 0.85); border: none; border-left: 1px solid #ccc; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 15; color: #666; font-size: 12px; } .voltage-sidebar-toggle:hover { background: rgba(230, 230, 230, 0.95); color: #333; } ``` #### `standalone_interface.html` - Add equivalent collapse/expand toggle with the same CSS and JS logic. --- ## 4. Revised Color Code for Highlights ### Current State | Element | NAD Color | SLD Color | CSS Class | |---------|-----------|-----------|-----------| | Action targets | Yellow `#fffb00` | Yellow `#fffb00` | `.nad-action-target`, `.sld-highlight-action` | | Contingency | Orange `#ff9800` | Orange `#ff9800` | `.nad-contingency-highlight`, `.sld-highlight-contingency` | | Overloads | Orange `#ff8c00` | Orange `#ff8c00` dashed | `.nad-overloaded`, `.sld-highlight-overloaded` | | Breakers/switches | β€” | Purple `#e040fb` | `.sld-highlight-breaker` | **Problem**: Contingency and overloads both use orange, making them hard to distinguish. Actions use yellow which doesn't contrast well with orange on bright backgrounds. ### Target Color Scheme | Element | New Color | Hex | Rationale | |---------|-----------|-----|-----------| | **Remedial Actions** | Purple-pink | `#e040fb` | Distinctive, stands out against network blues/greens | | **Contingency** | Yellow | `#f5c542` | Warm "warning" tone, clearly different from orange | | **Overloads** | Orange | `#ff8c00` | Kept β€” strong "danger" association, universally understood | | **Breakers/switches** | Purple (lighter) | `#ce93d8` | Softer shade to distinguish from action purple-pink | ### Visual Hierarchy ``` 🟣 Purple-pink (#e040fb) β†’ Actions (what the operator chose) 🟑 Yellow (#f5c542) β†’ Contingency (the triggering event) 🟠 Orange (#ff8c00) β†’ Overloads (the problem to solve) 🟣 Light purple (#ce93d8) β†’ Breakers (topology detail) ``` ### Files to Change #### `App.css` β€” NAD Highlights **Action targets**: Yellow β†’ Purple-pink ```css /* Before */ .nad-action-target { filter: drop-shadow(0 0 8px #fffb00) drop-shadow(0 0 15px #fffb00) !important; } .nad-action-target path, .nad-action-target line, ... { stroke: #fffb00 !important; } .nad-action-target circle, .nad-action-target rect { stroke: #ffe600 !important; fill: #ffe600 !important; } /* After */ .nad-action-target { filter: drop-shadow(0 0 8px #e040fb) drop-shadow(0 0 15px #e040fb) !important; } .nad-action-target path, .nad-action-target line, ... { stroke: #e040fb !important; } .nad-action-target circle, .nad-action-target rect { stroke: #e040fb !important; fill: #e040fb !important; fill-opacity: 0.6 !important; } ``` **Contingency**: Orange β†’ Yellow ```css /* Before */ .nad-contingency-highlight { filter: drop-shadow(0 0 8px #ff9800) drop-shadow(0 0 15px #ff9800) !important; } .nad-contingency-highlight path, ... { stroke: #ff9800 !important; } /* After */ .nad-contingency-highlight { filter: drop-shadow(0 0 8px #f5c542) drop-shadow(0 0 15px #f5c542) !important; } .nad-contingency-highlight path, ... { stroke: #f5c542 !important; } .nad-contingency-highlight circle, .nad-contingency-highlight rect { stroke: #f5c542 !important; fill: #f5c542 !important; } ``` **Overloads**: No change (already orange `#ff8c00`). But add NAD highlight support for overloads using the same clone-based glow pattern used for contingency and actions, so overloaded lines get a visible orange halo instead of just a stroke-width bump. #### `App.css` β€” SLD Highlights ```css /* Action: yellow β†’ purple-pink */ .sld-highlight-clone.sld-highlight-action { filter: drop-shadow(0 0 4px #e040fb) drop-shadow(0 0 8px #e040fb); } .sld-highlight-clone.sld-highlight-action path, ... { stroke: #e040fb !important; } /* Contingency: orange β†’ yellow */ .sld-highlight-clone.sld-highlight-contingency { filter: drop-shadow(0 0 4px #f5c542) drop-shadow(0 0 8px #f5c542); } .sld-highlight-clone.sld-highlight-contingency path, ... { stroke: #f5c542 !important; } /* Overloads: unchanged (orange #ff8c00, dashed) */ /* Breakers: bold purple β†’ lighter purple */ .sld-highlight-clone.sld-highlight-breaker { filter: drop-shadow(0 0 3px #ce93d8) drop-shadow(0 0 6px #ce93d8); } .sld-highlight-clone.sld-highlight-breaker path, ... { stroke: #ce93d8 !important; } ``` #### `App.css` β€” Overload Highlight Enhancement Add clone-based glow for overloaded lines (currently overloads only get a stroke-width increase, no halo/glow like actions and contingency do): ```css .nad-overloaded { filter: drop-shadow(0 0 6px #ff8c00) drop-shadow(0 0 12px #ff8c00) !important; } ``` #### `svgUtils.ts` β€” Overload Highlighting Currently `highlightOverloadedLines()` (line ~186) only adds the `nad-overloaded` class to existing elements. To match the clone-based approach used by actions and contingency (for consistent glow rendering), consider upgrading it to also create cloned background elements with the `.nad-overloaded` class. This gives overloads the same visual treatment (halo behind the original element). Alternatively, the simpler CSS-only `filter: drop-shadow()` addition above may be sufficient if the existing stroke-width increase already makes overloads visible enough. Start with the CSS-only approach and upgrade to clones only if needed. #### `standalone_interface.html` - Update all matching CSS rules with the same color changes. - The standalone file has its own embedded `