surface_behavior_spec - mark-ik/graphshell GitHub Wiki
Date: 2026-03-02
Status: Canonical deliverable (D3)
Purpose: Standardize scroll, resize, overflow, and empty/loading/error behavior across Graphshell surfaces.
Related:
../research/2026-03-02_ux_integration_research.md../implementation_strategy/aspect_command/command_surface_interaction_spec.md../implementation_strategy/workbench/workbench_frame_tile_interaction_spec.md../implementation_strategy/viewer/viewer_presentation_and_fallback_spec.md../implementation_strategy/subsystem_focus/focus_and_region_navigation_spec.md
This spec applies to the following surface classes:
- Graph Pane
- Node Pane
- Tool Pane
- Floating Window
- Popup Menu
- Toast
- Dialog
This spec defines behavior contracts only; visual styling remains owned by theme/presentation specs.
- Vertical scroll is allowed on content-bearing surfaces by default.
- Graph Pane uses camera pan/zoom semantics instead of document scroll.
- Dialog bodies and floating-window content regions may scroll vertically when content exceeds viewport.
- Horizontal scroll is disabled by default.
- Horizontal scroll is allowed only for content classes that are inherently wide:
- URL/address fields
- tabular diagnostic output
- preformatted/code-like text blocks
- When horizontal overflow is disabled, truncation policy in §5 applies.
Use shared max-height computation for floating and overlay surfaces:
surface_max_height = viewport_height - margin- Default margin constants:
- Popup Menu:
16px - Command/Context surface shell:
24px - Dialog body:
48px - Floating utility windows:
24px
- Popup Menu:
Hard rule: no floating/overlay surface may silently clip vertically without either scroll affordance or explicit truncation cue.
| Surface class | Resizable | Minimum size | Position persistence |
|---|---|---|---|
| Graph Pane | Via tile split/resize handles | 200x150 |
Frame/tile layout authority |
| Node Pane | Via tile split/resize handles | 300x200 |
Frame/tile layout authority |
| Tool Pane | Via tile split/resize handles | 200x150 |
Frame/tile layout authority |
| Floating Window | Yes |
200x150 unless surface-specific override |
Session runtime memory |
| Popup Menu | No | Content-fit with max-height policy | None |
| Toast | No | Content-fit | None |
| Dialog | Width/height fixed per dialog type (no freeform drag) | Dialog-specific | None |
- Tile-hosted panes persist through Frame/Workbench layout state.
- Floating window position persists for the current session runtime only.
- Popup menus, toasts, and dialogs do not persist position.
- Single-line labels/titles truncate with trailing ellipsis.
- Truncation priority order:
- Keep interaction affordances visible (close/action controls).
- Truncate label text.
- Preserve icon/state indicators.
- Hover tooltip must expose full value for truncated text.
- For keyboard-only flow, focused truncated controls must expose full content through accessible name/description.
- Any clipped scroll container must show scroll affordance or explicit fade/cut indicator.
- Silent clipping without cue is forbidden.
- Empty states must include:
- clear reason text,
- current scope/context,
- optional primary recovery action.
- Loading indicators must be explicit for async operations.
- Surface class guidance:
- Pane surfaces: inline spinner/skeleton in content region.
- Dialogs: inline progress indicator near primary action area.
- Floating windows: inline loading indicator in body.
- Errors must include:
- concise reason,
- impact/scope,
- recovery action (retry/open settings/dismiss/fallback).
- Viewer fallback/degraded states remain governed by viewer fallback spec; this spec requires the error presentation to be explicit and actionable.
- Opening a floating surface must not produce ambiguous z-order.
- Closing returns semantic focus through canonical focus return-path rules.
- Dialogs are top-most within app-owned UI layers.
- Popup menus render above their invoking surface, below blocking dialogs.
- Toasts render in non-blocking overlay layer and must not capture semantic focus.
- Motion is optional and must remain non-blocking.
- Reduced-motion preference disables non-essential open/close animation.
| Surface class | Scroll | Overflow policy | Empty state | Loading state | Error state |
|---|---|---|---|---|---|
| Graph Pane | Camera pan/zoom (not document scroll) | Truncate UI labels; never clip controls silently | Explicit no-content graph message + create/open action | Inline lightweight loading cue during async graph operations | Inline graph-scope error + recovery action |
| Node Pane | Vertical content scroll | Horizontal scroll only for wide content classes; otherwise truncate + tooltip | Explicit node-content unavailable message + open/route action | Inline spinner/skeleton in content region | Inline error card + retry/fallback action |
| Tool Pane | Vertical scroll by default | Horizontal for diagnostic tables/code-like text; otherwise truncate + tooltip | Explicit empty tool-state message + primary action | Inline loading indicator in pane body | Inline tool error + recovery action |
| Floating Window | Vertical body scroll | Max-height policy + explicit scroll cue | Explicit empty body message | Inline loading indicator | Inline error block + action |
| Popup Menu | Vertical scroll when long | Max-height policy; no horizontal overflow without truncation cues | Explicit “no actions available” state | N/A (open should be immediate) | Inline unavailable/blocked reason |
| Toast | No scroll | Truncate long text with tooltip/accessibility description | N/A | N/A | Use toast variant with explicit action when recoverable |
| Dialog | Vertical scroll in body region as needed | Max-height policy + body scroll | Explicit no-content message when applicable | Inline progress indicator | Structured error + primary/secondary recovery actions |
- Canonical D3 artifact exists at
design_docs/graphshell_docs/design/surface_behavior_spec.md. - Covers Graph Pane, Node Pane, Tool Pane, Floating Window, Popup Menu, Toast, Dialog.
- Scroll, max-height, resize/min-size, truncation, empty/loading/error, and floating lifecycle rules are explicit.
- Control-plane and parity docs are linked/updated for D3 tracking.
- Graph empty state: explicit guidance and first-action path in command palette (
Create First Node). - Frame empty state: explicit actionable fallback remains required by workbench interaction contract.
- Pane empty states: covered by §6 empty-state contract and §8 matrix (
Node Pane,Tool Pane). - Search empty state: command/search surfaces must show explicit “no results” rather than blank list.
- History empty state: tool pane must show explicit no-history guidance with next action.
- Diagnostics empty state: tool pane must show explicit no-samples/no-events guidance.
- Palette surfaces must show disabled actions with explicit unmet-precondition reasons.
- Reason format:
"[Action] requires [precondition]. [How to satisfy it]." - Radial surfaces may omit disabled entries when spatial density constraints prevent readable explanations.
- Implementation path:
render/command_palette.rs- disabled-action reason tooltips for disabled entries,
- empty-graph discoverability message +
Create First Nodeaction.
- Regression checks:
disabled_node_delete_exposes_precondition_reasonempty_graph_message_present_when_graph_has_no_nodes
Maintenance rule: any change to surface overflow/empty/loading/error semantics must update this spec and UX parity trackers in the same PR.