Files
Termind/openwiki/concepts/ui-design-principles.md
T

23 KiB
Raw Blame History

type, title, description, tags, sources, generated, verified
type title description tags sources generated verified
design guide UI Design Principles and Shared Presentation Boundaries Source-backed UI principles for TFM and TFI: semantic visual systems, native interaction, shared forms and presentation values, stable rendering identity, accessibility, localization and safe extension boundaries.
ui
design-system
presentation
accessibility
localization
macos
ios
id resource
openwiki-source-4ae2a0061a7bd729d6e132b4 repo://Frameworks/TermindApplication/FeatureModels/TerminalSettingsModel.swift
id resource
openwiki-source-0ad8e888a4a6fb517fde6f7a repo://Frameworks/TermindDomain/Sessions/TerminalFontPreferences.swift
id resource
openwiki-source-400e4d9303a3f9d98f90660d repo://Frameworks/TermindPresentation/AssistantTranscriptEntry.swift
id resource
openwiki-source-eec1a2bc6dfc2c60b2bee55c repo://Frameworks/TermindPresentation/HostConnectionFields.swift
id resource
openwiki-source-db3eac4d3fbf63570c0e0d56 repo://Frameworks/TermindPresentation/SingleLineTextInput.swift
id resource
openwiki-source-8185a608f0b79f1af097efbd repo://Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift
id resource
openwiki-source-0689f4693840cd29f5ac352c repo://Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift
id resource
openwiki-source-98bdcb8d33d425fe144907e1 repo://Frameworks/TermindPresentation/Terminal/TerminalFontPicker.swift
id resource
openwiki-source-821c6451f936b3390d26080d repo://Frameworks/TermindPresentation/Terminal/TerminalFontSettings.swift
id resource
openwiki-source-07dbc9527b95676a55499eb8 repo://Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift
id resource
openwiki-source-906678adb91c2fce2d3fcaed repo://TFI/App/MobileAppModel.swift
id resource
openwiki-source-c8d832474c03f5ebad7b0578 repo://TFI/App/MobileRootView.swift
id resource
openwiki-source-99e98ad50ba8a7c6df018977 repo://TFI/Design/MobileDesignTokens.swift
id resource
openwiki-source-0368ecce931d0fb26fe8f35f repo://TFI/DesignSystem/MobileContentRegions.swift
id resource
openwiki-source-3dfad0e95bb7d66f292c20c9 repo://TFI/Features/AI/MobileAssistantConversation.swift
id resource
openwiki-source-fc448263e4e5ce330b8cbda1 repo://TFI/Features/HostEditor/MobileHostEditor.swift
id resource
openwiki-source-5503f969776cb509a7d69160 repo://TFI/Features/Ports/MobileForwardingView.swift
id resource
openwiki-source-02810e1f8eac4d0983f8cd44 repo://TFI/Features/Settings/MobileTerminalSettingsView.swift
id resource
openwiki-source-db402cad9d68dd8e66bdd7ee repo://TFI/Features/Snippets/MobileSnippetsView.swift
id resource
openwiki-source-e396438eb6a8a7ca888da5f4 repo://TFI/Platform/TerminalFontCatalog.swift
id resource
openwiki-source-a4f3a690a8395e2ac9b065b9 repo://TFI/UI/Components/MobileNativeList.swift
id resource
openwiki-source-c99ab42abad536ed4396933c repo://TFI/UI/Managed/MobileCards.swift
id resource
openwiki-source-fffc181d864b6caea779a6f5 repo://TFITests/MobileAdaptiveLayoutTests.swift
id resource
openwiki-source-8194e1eb3652c7ad61d75cfa repo://TFITests/MobileTerminalFontTests.swift
id resource
openwiki-source-873894299539cce9a737a860 repo://TFM/App/RootView.swift
id resource
openwiki-source-f490cb5bfbb8ce236a7f1bf4 repo://TFM/Design/DesignTokens.swift
id resource
openwiki-source-95f72232fb896aa95dd5f6c7 repo://TFM/Features/AI/TranscriptListView.swift
id resource
openwiki-source-d35453512ef488d0cacdc06e repo://TFM/Features/Assistant/AssistantView.swift
id resource
openwiki-source-1e0cf223ab8448c8eee3f345 repo://TFM/Features/HostEditor/HostEditorView.swift
id resource
openwiki-source-f1d91230931f6a1921af6d03 repo://TFM/Features/Ports/PortForwardingView.swift
id resource
openwiki-source-cfe2a6b22338023c37a5b503 repo://TFM/Features/Settings/SettingsPageView.swift
id resource
openwiki-source-af7fbbca079c2cb6201a5151 repo://TFM/Features/Settings/TerminalCustomConfigurationEditorSheet.swift
id resource
openwiki-source-0854be84b166268626a3cb08 repo://TFM/Features/Sidebar/WindowSidebarView.swift
id resource
openwiki-source-23281ca04cda9d7abc31173f repo://TFM/Features/Snippets/SnippetsPageView.swift
id resource
openwiki-source-ba160208435f3410138a3539 repo://TFM/Features/Workspaces/PersistentWorkspaceDeckLayout.swift
id resource
openwiki-source-385c953ef1c6bf095d1c1244 repo://TFM/Platform/TerminalFontCatalog.swift
id resource
openwiki-source-4a6780d3dff41f37f56a094f repo://TFM/UI/Components/SidebarRow.swift
id resource
openwiki-source-f6a967b4f14bf50d1af51b8b repo://TFM/UI/Managed/ManagedCardPage.swift
id resource
openwiki-source-d3d065330c730839f5cc6112 repo://TFMTests/ArchitectureBoundaryTests.swift
id resource
openwiki-source-dceb8d7e7c5bb09359e70052 repo://TFMTests/ResponsivePageLayoutTests.swift
id resource
openwiki-source-2891f940e682279163740c83 repo://TFMTests/TerminalFontRenderingTests.swift
id resource
openwiki-source-9442022b3dba1be62a55f0f0 repo://TFMTests/TerminalThemeTests.swift
by at
codex 2026-09-23T05:17:08.701Z
by at
openwiki/0.5.2 2026-09-23T05:17:08.701Z

UI Design Principles and Shared Presentation Boundaries

Termind aims for consistent meaning and behavior across platforms, not identical view trees. macOS uses a window-local Navigator, persistent workspace deck and pointer/keyboard-oriented interaction. iPhone/iPad uses registered feature stacks, scene-local routing, touch-native presentation and system-adaptive navigation.

This page separates implemented policies from extension guidance inferred from them. It is not a new visual redesign or a claim that every screen already enforces every principle uniformly. Start with TFM Desktop UI Architecture or TFI Mobile UI Architecture for the full component diagrams.

1. Share meaning; retain platform ownership

The useful sharing boundary is below the app shell but above repeated feature formatting and form fragments.

flowchart TD
    Domain[TermindDomain: value types and policies] --> Application[TermindApplication: state and use cases]
    Domain --> Presentation[TermindPresentation]
    Application --> Presentation
    Presentation --> Values[Stable rows / formatting / presentation state]
    Presentation --> Forms[Reusable SwiftUI fields and sections]
    Values --> Mac[TFM feature composition]
    Values --> Mobile[TFI feature composition]
    Forms --> Mac
    Forms --> Mobile
    Mac --> MacShell[Window routes and desktop interaction]
    Mac --> AppKit[AppKit terminal / list / focus adapters]
    Mobile --> MobileShell[Scene routes and mobile interaction]
    Mobile --> UIKit[UIKit terminal / list / input adapters]

These arrows show conceptual dependencies and consumption, not an exhaustive target dependency graph. See System Boundaries for the broader module structure.

TermindPresentation is allowed to contain SwiftUI views. It is not a transport-free name attached to an arbitrary bag of platform code: architecture tests forbid it from importing either app, AppKit, UIKit, SwiftData, GRDB, Persistence or RemoteInfrastructure. Native adapters remain app-owned. Shared non-presentation frameworks have their own boundaries; do not apply one blanket “no UI anywhere in Frameworks” rule.

Useful shared pieces include:

  • Binding-driven Host, identity, forwarding, snippet and settings form sections.
  • Stable Assistant transcript entries and presentation/disclosure state.
  • Formatting and presentation values for history, metrics, remote files and tool output.
  • Shared input-editing policies and environment hooks through which an app supplies native behavior.

Not suitable for moving into a generic shared view are a window's selected pane, a scene's navigation stack, a native terminal owner, credential resolution, connection teardown or repository mutation. Sharing a form does not make its save, authorization or lifetime policy shared automatically.

2. Use semantic visual roles, not per-feature themes

Desktop vocabulary

Metrics centralizes desktop spacing, radii, minimum sizes and feature geometry as plain constants. They are not a runtime theme object; terminal palettes are a separate user-controlled concern. The spacing vocabulary includes 2/4/6/8/12/16/24-point values, and related controls share rhythm rather than choosing unrelated literals.

Examples of deliberate geometry:

Role Implemented constraint Design purpose
Shared sidebar 260 minimum, 320 ideal, 520 maximum before window-width constraints. The same column must work as Navigator or compact Assistant without resetting its width.
Navigator rows 44-point row rhythm and fixed glyph column. Align labels despite different SF Symbol widths.
Split divider 1-point visual line, 8-point hit band. Thin appearance without an impractically thin pointer target.
Desktop cards 240–380 width range, 80-point minimum height. Readable metadata and aligned rows.
Full Assistant Shared 840-point reading-column maximum. Transcript, decisions and composer read as one conversation.

TermindColor uses semantic AppKit colors for neutral surfaces, foregrounds, separators, accent and success/warning/danger roles. User-authored Host colors and terminal ANSI palettes are deliberate exceptions. When a native layer needs a concrete color, resolvedNative resolves it against the owning appearance rather than assuming a dynamic NSColor will remain correct after conversion to CGColor.

Selection is contextual, not one universal tint formula. Generic Highlight uses a quiet accent wash for selection and a neutral hover wash. Sidebar rows instead use a neutral Color.primary fill at 10% opacity in the key window and 6% otherwise. Selected key-window text stays primary rather than reversed, leading row icons remain accent-colored, and inactive row text/icons become secondary. Inline rename fields follow the same active/inactive readability rule. The native context-menu outline is still accent-colored; it is not the selection fill. Read the implementation rather than assuming an older Highlight comment describes every surface.

Mobile vocabulary

Mobile surfaces use system/grouped UIKit colors through SwiftUI and a smaller set of mobile layout policies. MobileCardLayout supplies a 106-point minimum height, at most two visible tags and a column calculation based on measured width with 12-point spacing. Accessibility text sizes force one column. These are mobile choices, not desktop token values copied into an iPhone layout.

For example, desktop card density and mobile card density deliberately differ. The invariant is recognizable hierarchy and usable content, not identical point dimensions.

3. Prefer real system materials and explicit fallbacks

TFM's explicit glass controls use system Liquid Glass on macOS 26 and native material backgrounds on older systems. The Sidebar column itself is different: on macOS 26 and later, NavigationSplitView supplies its background and RootView adds no second glass surface. Before macOS 26, LegacySidebarMaterialBackground supplies an NSVisualEffectView using .windowBackground, .withinWindow blending and window-active-state following. Do not restore an extra full-column glass layer merely because custom native rows sit inside the split view.

The mode rail shares one glass capsule; selection is a neutral moving capsule behind primary symbols, with a separate keyboard-focus outline only in the key window. Its selection animation respects Reduce Motion, and selected accessibility traits remain explicit. Assistant's footer keeps New Chat as an interactive glass capsule and Archive as an interactive glass circle, grouped in GlassEffectContainer on macOS 26+. Archive selection uses a neutral fill and semibold primary symbol rather than accent fill with reversed content. These controls and row selections serve different roles even though both avoid an emphasized accent background.

TFI's card surface similarly uses system glass where available, but keeps a nearly opaque grouped background behind dense text. Reduce Transparency or increased contrast selects an opaque fallback. Increased contrast also strengthens the border. The clipping, interaction shape and context-menu-preview shape are coordinated, so the visible card and the tappable/previewed object agree.

Extension guidance: add visual roles at the platform's existing semantic surface boundary. Do not give each feature its own arbitrary gradient, selection color or radius, and do not remove readability fallbacks to make screenshots look more translucent.

4. Reuse interaction machinery, not business ownership

Desktop ManagedCardPage is a protocol-oriented page composition seam. It supplies typed sections, externally owned selection, capabilities, card/header/ghost views and presentation modifiers to ManagedCardPageRenderer. The renderer handles geometry, selection and drag mechanics, then reports stable IDs back to the page's actions.

Mobile uses a simpler MobileCardPage / MobileCardGrid / MobileActionCard composition. A card's primary action fills the card, while secondary actions live in its context menu. Other mobile lists use swipe actions and navigation links. There is no requirement to reproduce the desktop marquee-selection renderer on touch devices merely because both display Hosts.

The intended separation is:

flowchart LR
    Data[Feature-owned items and selection] --> Policy[Capabilities and presentation values]
    Policy --> Renderer[Platform renderer]
    Renderer --> Intent[Stable-ID action intent]
    Intent --> Owner[Feature or application owner]
    Owner --> Update[Validated state change]
    Update --> Data

Use capabilities to say what a renderer may offer; keep the operation's actual validity check with its owner. Disabled UI is useful feedback, not a replacement for business validation.

Native menu structure can differ without changing the operation. A single desktop Port Forwarding card groups Address and SSH Command under Copy, before the Edit/Duplicate group. Mobile keeps flat Copy Address and Copy SSH Command actions in the equivalent position. Both write through their native pasteboard adapter and disable SSH-command copying when the library cannot generate a command; copying never starts forwarding.

Snippet library cards expose one Run action instead of separate Execute and Execute on Hosts actions; the mobile detail and host-selection surfaces use the same Run vocabulary. Target selection and destructive confirmation remain in the existing feature flow: desktop fills missing saved targets through its chooser, while mobile asks when no valid targets remain. Renaming the action and removing the duplicate override affordance does not remove the host validation or destructive-script confirmation boundary.

5. Share form fields; keep draft and route lifetimes explicit

Both Host editors reuse HostOrganizationSection, HostProtocolProfileControls and HostConnectionFields, but their containers are intentionally different:

  • TFM edits a local Host draft in the Vault work column. Nested editing stays in that column; introducing another NavigationStack there can promote its destination into the enclosing split detail and cover the collection.
  • TFI edits a local Host draft in MobileEditorSheet, with mobile routes, dirty-state checking and save eligibility. Shared fields add platform-appropriate keyboard/autocorrection behavior where needed.
  • Group/tag controls are injected by each platform into the shared organization section rather than forcing one platform's picker into the other.

The data path is draft binding → shared fields → platform validation/save action. It is not shared fields → database or transport.

Terminal preferences: shared fields, native adapters

TerminalFontFields receives a TerminalFontPreferences binding and an app-supplied family list. Both apps reuse its family and Follow Theme/Custom controls and the 8–36 size-editing range; macOS uses a native Slider, while iOS uses a Stepper. Font discovery remains app-owned: AppKit/UIKit catalogs include a family when any loadable member is monospaced, sort with localizedStandardCompare, and refresh on settings appearance and active-scene transitions. The picker previews available families and retains an unavailable family name; resolving a renderable copy does not rewrite the saved choice.

Typography is device-local through TerminalFontSettings/@AppStorage, separate from synchronized TerminalPreferences. Theme edits go through TerminalSettingsModel, which persists before publishing new preferences and exposes storage errors on failure. Sharing these fields does not move persistence or native font enumeration into the shared view.

TerminalThemePicker uses the shared appearance-filtered, name-sorted catalog. Missing, empty, or wrong-appearance selections remain selected values, with a localized fallback hint; the renderer chooses the matching default without saving a replacement merely because Settings appeared. Custom configuration overlays the selected base theme, and explicit local font overrides apply last. The base picker remains editable while the overlay is enabled. Editor workflows remain native: desktop configuration editing autosaves and supports file import; mobile uses a draft sheet with explicit Save.

Focused tests cover complete macOS monospaced-family membership, typography precedence and controller identity, appearance filtering and fallback, base-theme changes beneath an overlay, and opening the actual desktop settings page without a preference write. Mobile rendering cases exercise the font controls in English, Simplified Chinese and Traditional Chinese.

SingleLineTextInput illustrates a narrow shared policy. It replaces runs of newline scalars with one space, preserves attributed paste content, and normalizes only edits through the binding setter. Reading the original bound value does not rewrite stored data. This is not a general sanitizer for secrets, multiline commands, snippets or existing repository records. Native paste/input insertion is supplied by app-owned environment adapters so selection and input-method handling can stay platform-specific.

6. Native rendering is a deliberate workload boundary

SwiftUI composes pages, but terminal and high-update list workloads use native adapters. ArchitectureBoundaryTests restricts ListViewKit importers to a specific set of native-list workloads rather than permitting it throughout every feature.

Assistant is a useful example:

  • AssistantTranscriptEntry shares stable IDs and typed row content derived from the timeline. Edit/fork eligibility is passed in rather than inferred by a renderer.
  • Desktop mounts persistent AppKit transcript owners from a window-local, surface-aware cache.
  • Mobile uses MobileNativeList, supplying locale, Dynamic Type, appearance, motion/scene state, row revisions and viewport callbacks through the adapter.

Sharing transcript values does not justify sharing AppKit/UIKit view instances or their caches. Native views have platform and mounting constraints; their owners must be reviewed separately from the value model.

Extension guidance: first extract deterministic row construction, formatting or policy. Only then consider shared SwiftUI content. Leave platform focus, mounting, text selection, scroll retention and lifecycle in the platform adapter unless there is concrete evidence that their contracts match.

7. Accessibility and localization are layout inputs

The implementation already treats accessibility as behavior, not a final label pass:

  • Parked desktop workspaces and unusable mobile panes disable hit testing and hide from accessibility as well as becoming transparent.
  • Selected sidebar controls expose selected traits and accessible names; icon-only menus expose the current title as their accessible value.
  • Mobile card grids change density for accessibility text sizes; card materials respond to transparency/contrast preferences.
  • Motion helpers accept Reduce Motion, and native-list adapters propagate motion and scene activity into hosted rows.
  • Mobile geometry tests retain marked Chinese text and first responder status during region changes, not just the final draft string.

Localization likewise affects measurement and identity-sensitive presentation. The existing adaptive/rendering cases exercise English, Simplified Chinese and Traditional Chinese, including narrow desktop editors and the mobile tab set. A layout that works only with the shortest English label is not proven by a source-level logic test.

Do not claim all animation sites are already Reduce Motion-aware: the helpers make that policy available, but each changed call site still needs inspection. Likewise, an accessibility label does not prove that focus order, clipping, menu reachability or large-text layout is correct.

8. Continuity has a cost budget

The platform guides describe several different retention strategies:

Mechanism What is preserved How unnecessary work is limited
Desktop workspace deck Mounted session views and native terminal state. Hidden geometry is parked and invisible controls are disabled.
Desktop transcript owner cache Native list, measurements and presentation caches across remounts. LRU trimming of unmounted owners; separate keys for sidebar/full screen.
Mobile workspace registry Shared session and native terminal across navigation. Explicit close and app background-expiration policy, not unconditional foreground work.
Mobile region layout Native child identity during adaptive geometry changes. Only usable regions accept input/accessibility; covered children keep valid sizes.

“Keep state” does not mean “keep every view measuring, animating and accepting events forever.” Conversely, unmounting a view does not by itself authorize terminating the work it displays.

Practical extension checklist

Before adding or refactoring a UI feature:

  1. Name its app, window/scene, workspace and temporary-view owners.
  2. Choose the existing platform shell and presentation style; do not create a second navigation system for one feature.
  3. Reuse semantic surfaces and sizing policies, checking actual implementation rather than comments alone.
  4. Share values and field fragments where contracts match; preserve native adapter and runtime boundaries.
  5. Verify hidden/disabled/accessibility behavior together with geometry and focus.
  6. Check compact and spacious containers, large text, light/dark appearance, transparency/contrast, and relevant language variants.
  7. Add deterministic policy/identity tests first, then targeted native rendering or device verification where necessary.

This is engineering guidance derived from current patterns, not an additional runtime subsystem. Use the platform runtime guide, engineering invariants and build/test guidance when the change crosses ownership or verification boundaries.