Reviewed-on: #15 Co-authored-by: phg <mail@philip-henning.com> Co-committed-by: phg <mail@philip-henning.com>
This commit is contained in:
parent
9f8e9c3415
commit
0b7b52521f
19 changed files with 1507 additions and 723 deletions
404
docs/design-guide.md
Normal file
404
docs/design-guide.md
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
# 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:
|
||||
|
||||
```text
|
||||
┌──────────────────────────── 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:
|
||||
|
||||
```text
|
||||
┌──────────────────── 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:
|
||||
|
||||
```text
|
||||
╭─ Entry Details ─────────────────────╮
|
||||
│ IP address 192.0.2.10 │
|
||||
│ Hostnames api.local, api │
|
||||
│ Comment — │
|
||||
│ State ✓ Active │
|
||||
╰─────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Avoid:
|
||||
|
||||
```text
|
||||
╭─ 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.
|
||||
|
||||
### Footer and help
|
||||
|
||||
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.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue