hosts/docs/design-guide.md

18 KiB
Raw Blame History

TUI design guide

This guide is the normative design contract for the hosts terminal user interface. It evolves the application's existing dark, keyboard-first, master-detail interface into a consistent system without replacing its visual identity.

The words must, should, and may distinguish requirements, recommendations, and optional choices. The guide describes the target state; current deviations are implementation work, not exceptions or user-reachable features. User-facing documentation must continue to describe only behavior that the application currently provides.

Design character

hosts is a calm, trustworthy operations console. It should feel dense and capable without feeling busy, playful, or alarming during ordinary use.

Every screen follows these principles:

  1. Information before decoration. Structure, alignment, and concise text do more work than borders, color, or ornament.
  2. Safety remains visible. Mode, protection, failures, and consequences are explicit and persist for as long as they matter.
  3. The keyboard path is complete. Every workflow works without a mouse; natural mouse support remains available where Textual provides it.
  4. State has more than one signal. Color may reinforce a state but never carries essential meaning alone.
  5. The terminal is user-owned. The interface does not assume a particular font, truecolor renderer, glyph width outside predictable one-cell symbols, or a large viewport.

Viewports and composition

The interface has three viewport modes:

Terminal size Contract
At least 120 columns by 40 rows Design target. Show the full 60/40 master-detail workspace.
At least 100 columns by 30 rows Minimum usable size. Keep the 60/40 workspace, compact the details, and abbreviate contextual controls without hiding required actions.
Below 100 columns or below 30 rows Replace the workspace with a minimum-size presentation. Do not silently clip, overlap, or squeeze controls into unusable shapes.

The target layout is:

┌──────────────────────────── hosts ─────────────────────────────┐
│ message rail: transient result or persistent problem           │
├─ Search ────────────────────────────────────────────────────────┤
│ query                                                Filters: 2 │
├─ Host Entries (60%) ───────────────┬─ Entry Details (40%) ─────┤
│ state  IP address  hostname  DNS   │ IP address       value     │
│ ...                                │ Hostnames        value     │
│                                    │ DNS details      value     │
│                                    │ Comment          —         │
├────────────────────────────────────┴────────────────────────────┤
│ contextual keys                 count · filters · READ-ONLY     │
└─────────────────────────────────────────────────────────────────┘

At the minimum usable size, preserve the same regions but use compact label-value rows in Entry Details and shorter footer descriptions. If either dimension falls below the minimum, render a stable presentation such as:

┌──────────────────── hosts ────────────────────┐
│ Terminal too small                            │
│                                              │
│ hosts requires at least 100 columns × 30 rows │
│ Current size: 86 × 24                         │
│                                              │
│ q Quit                                        │
└───────────────────────────────────────────────┘

The minimum-size presentation must not permit a hidden or ambiguous mutation. It may retain safe global actions such as Quit.

Regions

  • The header identifies the application and exposes safety-critical mode when Privileged Mode is active.
  • The message rail occupies one reserved row below the header. It never covers the workspace.
  • Search remains one row above the master-detail workspace.
  • Host Entries and Entry Details use an approximate 60/40 split. Their minimum content widths take priority over an exact percentage.
  • The footer shows contextual keys on the left and durable state on the right. It must not rely on horizontal scrolling.

Theme and color

The application owns a semantic hosts-dark theme rather than inheriting unspecified library defaults. Its initial palette preserves the current Textual-dark appearance:

Token Initial dark value Purpose
background #121212 Screen background
surface #1E1E1E Inputs and alternating table rows
panel #242F38 Header, footer, and elevated containers
foreground #E0E0E0 Primary text
primary #0178D4 Structure, focus, and selection
secondary #004578 Secondary structure
accent #FFA62B Key hints and exceptional emphasis
success #4EBF71 Successful outcomes and confirmed healthy state
warning #FFA62B Caution, inactivity, and Privileged Mode emphasis
error #BA3C5B Failures and destructive actions

Components must consume semantic tokens such as $primary, $success, and $text-muted; they must not embed named Rich colors such as green or yellow. Accent and warning happen to share an initial value but remain separate roles.

Dark is the default appearance. A future light theme may choose different values, but it must preserve the same semantic roles, hierarchy, and state redundancy. Text should approximate a contrast ratio of at least 4.5:1 where the renderer permits controlled color. Focus and meaningful large text should approximate at least 3:1 against adjacent colors.

Use chromatic color sparingly:

  • Primary blue describes structure, focus, and selection.
  • Green describes a successful result or active mapping, never a generic decoration.
  • Amber describes caution, inactivity, or elevated privilege.
  • Red describes failure or destructive consequence.
  • Muted foreground describes secondary information, not information required to finish a workflow.

Text, symbols, and density

The terminal controls the font. Assume a legible monospace font and make no layout or branding decision that depends on a particular typeface.

  • Use bold for headings, keys, and the strongest current emphasis.
  • Use dim text for supporting information.
  • Use italics only as reinforcement; some terminals do not distinguish them.
  • Use sentence case for headings, labels, buttons, and messages.
  • Use one-cell spacing increments.
  • Use compact density for tables, details, footers, and status areas.
  • Use comfortable density for forms and modals so fields remain easy to navigate and errors have room to appear.

Predictable one-cell Unicode symbols such as , ×, and ! may reinforce a state. Emoji are not structural UI: their width, color, and availability vary too widely. Essential meaning must survive if a symbol is absent or rendered poorly. Details, help, and messages therefore use words in addition to any marker.

Borders and hierarchy

Rounded single-line borders define major regions: Search, Host Entries, and Entry Details. Nested borders should not make every value compete with its parent region. Within a region, prefer alignment, whitespace, text weight, and subtle surface changes.

Inputs receive an input treatment only while they are editable. Read-only values are label-value text, not disabled inputs. Thick borders are reserved for modals; the error color is reserved for destructive confirmation and failure states.

Good:

╭─ Entry Details ─────────────────────╮
│ IP address       192.0.2.10         │
│ Hostnames        api.local, api     │
│ Comment          —                  │
│ State            ✓ Active           │
╰─────────────────────────────────────╯

Avoid:

╭─ Entry Details ─────────────────────╮
│ ╭─ IP Address ────────────────────╮ │
│ │ disabled input                 │ │
│ ╰────────────────────────────────╯ │
│ ╭─ Comment ───────────────────────╮ │
│ │ disabled input                 │ │
│ ╰────────────────────────────────╯ │
╰─────────────────────────────────────╯

State grammar

Each interaction state has a distinct visual channel:

State Required treatment
Focus Bright primary border or cursor that remains visible independently of selection
Selection Contrasting row background; moving focus away must not erase which Host Entry is selected
Active Entry Stable marker plus normal or success-toned text
Inactive Entry Different marker plus warning-toned or muted text; italics may reinforce it
Default Entry Explicit protected marker plus muted treatment and an explanation in Entry Details
Disabled action Muted but still visible when hiding it would make a restriction mysterious
Work in progress Text such as Resolving… plus an optional one-cell activity marker
Failure Error token plus an actionable textual message
Privileged Mode Persistent PRIVILEGED wording plus restrained warning emphasis

Selection, focus, activity, and protection are independent. A selected Inactive Entry must still look selected; a focused Default Entry must still look protected.

Core components

Search and filters

The top Search field performs immediate text filtering. Ctrl+F opens advanced criteria. Both surfaces edit one coherent filter state and must show the same search term.

When non-text criteria are active, the workspace exposes a compact indicator, such as Filters: 2. A no-results state says that filters matched no Host Entries and presents the shortcut for changing or clearing them.

Placeholders may provide examples, but never replace a persistent label when a field's meaning would otherwise disappear after typing.

Host Entries table

The table is a scan surface, not a compressed details view. Preserve columns under horizontal pressure in this order:

  1. state;
  2. IP address;
  3. Canonical Hostname;
  4. DNS status.

Never truncate an IP address. Long hostnames may end in an ellipsis; Entry Details shows their complete values. Column widths remain stable while the selection moves. DNS status uses a short, predictable marker in the table and complete wording in Entry Details.

Entry Details

Display inspection data as compact aligned label-value rows. Ordinary IP-address Host Entries do not show empty DNS Name, DNS Status, or Last Resolved rows. DNS Entry fields appear only when they apply. Use for an absent optional value such as a comment.

When no Host Entry is selected, say so rather than showing a field-shaped empty screen. When no entries exist or no filter results match, name the condition and offer the relevant next action.

Entry Editor and forms

Forms use persistent labels, consistent required-field markers, and a visual order that matches their Tab order. Validation appears directly beneath the responsible field, retains the user's input, and explains how to correct the value.

The primary action is visually prominent and last in keyboard order. Cancel is always available and non-destructive. Arrow keys operate grouped controls such as radio buttons; Tab and Shift+Tab move between fields and action groups.

Modals

Modals are centered, bounded by the viewport, and share this structure:

  1. concise title;
  2. explanation or form content;
  3. inline validation or consequence text;
  4. action row.

Neutral and complex modals use the primary border. Only destructive confirmation uses the error border. Escape cancels without mutation. Initial focus lands on the safest sensible action; destructive confirmation never defaults to the destructive button.

The footer is contextual. It shows the small set of actions most relevant to the current state rather than the complete binding catalog. Descriptions may shorten in compact mode, but the keys and durable state remain visible. The right side reports entry count, active filters, and either READ-ONLY or PRIVILEGED.

? opens a dedicated overlay or screen instead of docking a panel that shrinks the workspace. Group help under General, Navigation, Filtering, and Privileged Mode. ? and Escape close it and restore previous focus.

Feedback and safety

The reserved message rail reports action results without covering content or moving the master-detail workspace.

  • Routine success and informational messages may expire.
  • Errors, warnings, and unresolved safety conditions persist until the user acknowledges them or a later result supersedes them.
  • Durable state such as mode and active filters belongs in the footer.
  • An error says what happened, whether state changed, and what the user can do next.

Use the canonical terms from CONTEXT.md: Read-only Mode, Privileged Mode, and Entry Editor. Edit Mode must not name Privileged Mode.

Entering Privileged Mode does not recolor the entire interface. A persistent, warning-toned PRIVILEGED indicator communicates elevated capability without normalizing alarm colors. Mutating actions become available contextually.

Default Entries have a visible protected marker and an explanation in Entry Details. When a protected mutation is unavailable, keep the action visible if hiding it would make the restriction unclear, and explain the restriction in the message rail or help.

Confirm destructive operations and attempts to leave an Entry Editor with unsaved changes. Name the affected Host Entry and initially focus the safest sensible action. Routine navigation and inspection do not require confirmation.

DNS resolution and other asynchronous work keep the interface responsive. Show Resolving… inline for the affected DNS Entry. Completion or failure must not move selection or steal focus unexpectedly.

Motion is optional, brief, and nonessential. It must not delay input, hide state, or be necessary to understand an outcome.

Input and focus

Every workflow must be keyboard-complete. Mouse support should work where Textual provides it naturally, but no action may depend on hover or a pointer.

  • Tab order follows visual reading order.
  • Arrow keys operate tables and controls that conventionally use them.
  • Escape backs out without mutation or opens the applicable unsaved-change confirmation.
  • Closing a modal or help restores focus to the control that opened it.
  • Application shortcuts must not fire while a user is typing into a field unless the shortcut is a conventional editing or escape command.
  • Focus remains visible in dark, light, 256-color, and degraded-color output.

Writing

English is the canonical interface language. Use concise, direct verbs and the domain language in CONTEXT.md. Write complete strings and avoid layout assumptions that make later localization needlessly difficult; an internationalization architecture is outside the current contract.

Good and avoid:

Good Avoid Reason
Enter Privileged Mode Toggle edit mode Names the resulting safety state
Delete Host Entry Delete Entry! Uses the canonical concept without decorative alarm
Save failed; previous state restored. Check write access and try again. ❌ Save error States outcome, data safety, and recovery
No Host Entries match the current filters. Nothing here Names the condition
N/A Represents an absent optional value without implying an error

Accessibility and terminal compatibility

The supported visual baseline is a common 256-color terminal. Truecolor may improve fidelity. Monochrome or degraded-color output may lose polish but must retain all essential meaning through wording, markers, position, and text style.

Do not depend on:

  • color as the only state signal;
  • italics, dim text, or animation as the only distinction;
  • emoji or ambiguous-width glyphs;
  • a particular terminal font;
  • mouse hover;
  • content hidden outside the supported viewport without a visible way to reach it.

Verification

Changes to the TUI must preserve behavior and essential visibility at 120×40 and 100×30. Tests should use Textual's test harness to cover:

  • keyboard-complete workflows;
  • visual-order focus traversal and focus restoration;
  • selection remaining distinct from focus and entry state;
  • mode, protection, validation, progress, and failure wording;
  • conditional detail fields and empty states;
  • absence of clipped or inaccessible essential controls at supported sizes.

Test structure and semantics rather than terminal pixels or exact glyph rendering. Significant visual changes also receive an agent-tui inspection at both supported sizes. Screenshots are illustrative evidence, not golden output.

Before review, check:

  • Does the change preserve the calm operations-console character?
  • Are all states legible without color?
  • Is the keyboard path complete and ordered like the screen?
  • Are canonical Hosts Management terms used?
  • Does feedback remain visible for as long as it matters?
  • Are 120×40 and 100×30 both usable without essential clipping?
  • Does the full key reference remain reachable through ??

Evolving the guide

This contract applies to the whole TUI, including existing screens. Track current deviations in Forgejo rather than weakening the rules with permanent exceptions.

An intentional departure must be explained in the relevant change. Update this guide when the departure should become the new standard. Add an ADR only when the decision is consequential, surprising, difficult to reverse, and the result of a real trade-off.