Co-Study4Grid / docs /features /frontend-ui-improvements.md
github-actions[bot]
Deploy 7688ef1
13d4e44
|
Raw
History Blame Contribute Delete
17.3 kB
# 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 `<input>` field. It is rendered as:
```tsx
<button onClick={wrappedRunAnalysis} disabled={!selectedBranch || analysisLoading}>
{analysisLoading ? '⚙️ Running...' : '🚀 Run Analysis'}
</button>
```
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 `<button onClick={wrappedRunAnalysis}>` block (lines ~812–829) from the contingency selector card.
- **Pass** `onRunAnalysis={wrappedRunAnalysis}` as a new prop to `<ActionFeed>`.
- The `selectedBranch` value is already passed (indirectly via `analysisLoading` disable logic). Also pass `canRunAnalysis={!!selectedBranch && !analysisLoading}` so ActionFeed can enable/disable.
#### `ActionFeed.tsx`
- **Add props**: `onRunAnalysis: () => void`, `canRunAnalysis: boolean`.
- In the **Suggested Actions section** (around line ~916–956), replace the current conditional rendering with the unified three-state slot:
```tsx
{/* Unified analysis action slot */}
<div style={{ padding: '10px 15px' }}>
{analysisLoading ? (
<button disabled style={{ /* yellow processing style */ }}>
⚙️ Analyzing…
</button>
) : pendingAnalysisResult ? (
<button onClick={onDisplayPrioritizedActions} style={{ /* green gradient */ }}>
📊 Display {count} prioritized actions
</button>
) : (
<button onClick={onRunAnalysis} disabled={!canRunAnalysis} style={{ /* green style */ }}>
🔍 Analyze & Suggest
</button>
)}
</div>
```
#### `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']
? <MemoizedSvgContainer ... />
: <TabPlaceholder message="Select an action card from the suggestions panel to view its effect on the network." />
)}
```
- Add a small `TabPlaceholder` component (inline or local):
```tsx
const TabPlaceholder: React.FC<{ message: string }> = ({ message }) => (
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'center',
height: '100%', color: '#999', fontStyle: 'italic', padding: '40px',
textAlign: 'center', fontSize: '0.95rem'
}}>
{message}
</div>
);
```
#### `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 ? (
<div className="voltage-sidebar">
<button onClick={() => setVoltageFilterExpanded(false)}
style={{ alignSelf: 'flex-end', background: 'none', border: 'none', cursor: 'pointer', fontSize: '14px' }}>
</button>
{/* ... existing slider content ... */}
</div>
) : (
<button
className="voltage-sidebar-toggle"
onClick={() => setVoltageFilterExpanded(true)}
title="Show voltage filter"
>
<span style={{ writingMode: 'vertical-rl' }}>kV ▸</span>
</button>
)
)}
```
#### `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 `<style>` block — search for `#fffb00`, `#ff9800`, `#e040fb` and apply the same replacements.
#### `cssRegression.test.ts`
- Update expected color values in assertions:
- `nad-action-target` → expect `#e040fb` instead of `#fffb00`
- `nad-contingency-highlight` → expect `#f5c542` instead of `#ff9800`
- standalone equivalents likewise
#### Color Legend
Consider adding a small legend to the visualization panel (bottom-left corner, semi-transparent) so users understand the color coding:
```
● Purple-pink — Remedial action targets
● Yellow — Contingency element
● Orange — Overloaded lines
```
This is optional but recommended for discoverability.
---
## Implementation Order
Recommended sequence (each item is independently deployable):
1. **Color code revision** (item 4) — CSS-only changes, low risk, high visual impact. Update tests.
2. **Collapsible voltage filter** (item 3) — Self-contained UI change, no cross-component dependencies.
3. **Always-visible tabs** (item 2) — Moderate refactor of VisualizationPanel tab rendering.
4. **Move analysis button** (item 1) — Cross-component change (App.tsx ↔ ActionFeed.tsx), needs test updates.
---
## Summary of All Files Affected
| File | Changes |
|------|---------|
| `frontend/src/App.tsx` | Remove analysis button from contingency card, pass new props to ActionFeed |
| `frontend/src/components/ActionFeed.tsx` | Add `onRunAnalysis`/`canRunAnalysis` props, unified three-state button slot |
| `frontend/src/components/VisualizationPanel.tsx` | Always-visible tabs with placeholders, dynamic action tab label, collapsible voltage filter |
| `frontend/src/App.css` | Color scheme update (actions → purple-pink, contingency → yellow), overload glow, voltage toggle style |
| `frontend/src/utils/svgUtils.ts` | (Optional) Upgrade overload highlighting to clone-based approach |
| `frontend/src/utils/cssRegression.test.ts` | Update expected color values |
| `frontend/src/App.settings.test.tsx` | Update button text selector |
| `frontend/src/App.session.test.tsx` | Update button text selector |
| `standalone_interface.html` | Mirror all four changes in embedded CSS/JS |