Skip to content

feat(ui): consolidate the desktop design system - #138

Merged
kitlangton merged 14 commits into
mainfrom
feat/design-system
Oct 18, 2026
Merged

kitlangton merged 14 commits into
mainfrom
feat/design-system

Conversation

@kitlangton

@kitlangton kitlangton commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Why

The GPUI app had grown four visual dialects. Text came in 11 explicit sizes plus GPUI's inherited 16 px, so inputs and transcript bubbles looked like a different app. There were 7 button looks and 35 raw colour literals sitting beside 18 tokens. Section labels were in all caps, the product was called "HEX", and list cards overhung the header rule by 12 px. A .truncate() row could clip mid-glyph or collapse to "…". Every new pane added another helper because nothing said which one to use.

What Changes

  • Tokens: src/design_tokens.rs is now the only place a raw colour, type size, line height, radius, or control height is written. The V2 surface colours are unchanged. The old FAINT (#6e6e6e) text was below 4.5:1 on cards, so it merges into TEXT_TERTIARY (#969696) and survives only as TEXT_DISABLED.

  • Type scale: five steps, applied with .text_caption() … .text_title(). Each step sets its own line height, and the window frame sets BODY, so unsized text and inputs are 13 px.

    Step Size / line Use
    Caption 11 / 14 metadata, tags, section labels
    Footnote 12 / 16 descriptions, control labels
    Body 13 / 18 window default, values, inputs, row titles
    Headline 15 / 20 semibold card, detail, and dialog titles
    Title 20 / 26 semibold pane titles
  • Components: src/desktop_ui.rs now has one button system: button / small_button / icon_button × Primary, Secondary, Ghost, Danger. Primary is the single accent fill, used for at most one action per screen, so the white meeting buttons are gone. There is also one non-interactive tag, plus menu / menu_item, pane_list / pane_detail, error_panel, empty_state, and truncated_line. Every superseded helper is deleted: header_button, canvas_button, compact_*, meeting_action_button, pane_list_card, and others.

  • Geometry: one PANE_GUTTER is shared by the header and the body, so cards line up with the header rule. Controls are 30 px (24 px small).

  • Truncation: truncated_line shapes its text at paint time against its final width, so lines end in "…" instead of clipping or keeping a provisional width.

  • Copy: sentence case everywhere ("Stop & transcribe", "Permissions needed", "Open settings"). The product is "Hex" in window titles, menus, and copy. Duplicate state words beside toggles are gone. Native macOS menus keep title case.

  • Icons: one Icon set of line-weight SF Symbols at medium weight. No .fill navigation symbols, no "×" or "+" text glyphs, and a globe instead of letter monograms for web pages.

  • Meeting capsules: they use the shared tokens and buttons. The 2 px countdown bar and the "· 600s" suffix are removed. The 20 s timeout is unchanged.

  • Docs: docs/design-system.md documents the tokens, which component does which job, the copy and icon rules, verification, and the full before → after audit. In AGENTS.md, the two desktop-UI invariants are replaced by a short mandatory pointer to it. design_tokens::tests::desktop_sources_use_tokens_instead_of_raw_styles fails on raw colours, sizes, line heights, radii, or .truncate() in desktop sources.

Behaviour, entry points, and debug selectors are unchanged. The dictation HUD is untouched.

Meetings actions and accent colour

  • Overflow menu: the Meetings detail shows Copy transcript and Rename, plus a … button. Its menu holds Copy summary, Reveal Markdown/files, and Summarize/Regenerate summary ("Summarizing…" is shown disabled while it runs), then Delete… in red after a separator. It's built from new shared pieces: dropdown_menu, danger_menu_item, disabled_menu_item, menu_separator, and Icon::Ellipsis. Delete… keeps its two-step confirmation: the Trash button and Cancel replace the action row until you pick one. Copy summary keeps the menu open so "Copied" stays visible. Visibility and refusal rules haven't changed: nothing is offered while a meeting is recording or transcribing, and Delete is hidden while notes are being written. New preview flag: --open-meeting-actions.
  • Accent: "Stop & transcribe" is now a Secondary button with a red dot and the elapsed time, matching the Recording tag. The sidebar "Update" is Secondary with an accent dot. Primary blue is left on one action per screen at most: Start recording, Choose model, or a dialog's confirm. Capsules count as their own screen. The rule is written down in docs/design-system.md (Rules 6–7, Colour) and in the AGENTS.md pointer.

Polish pass

  • Shared detail header: Meetings, History, and Commands details all use one detail_header(detail_title, meta, actions) over a detail_body. Each has the same insets and an edge-to-edge rule under the actions. History used an inset rule before, and Commands had none.
  • Section labels: they now line up with the text inside their cards (px_4) in Settings, Voice Action, the setup sheet, and Modes. Before, they sat 4 px in from the card edge, which matched nothing.
  • Baselines: the new baseline_offset(LARGER, SMALLER) token puts the History timestamps on the title baseline (they sat about 3 px high) and detail_row labels on the value baseline.
  • List group labels: a single list_section_label is shared by Meetings and Commands. The first group in Commands used to start 8 px lower than the first group in Meetings.
  • Empty states: pane_list centres its empty message vertically, like the detail placeholder beside it.
  • Commands: the "Current context" footer under the list is now a detail row. The list and detail cards now end at the same bottom edge.
  • Transcript speaker labels: they line up with the text inside the bubble, not 4 px in from its rounded edge.
  • Left / Either / Right: the segments are wider (40/50/40), and the control's width is calculated from them. Before, the selected pill ran into "Right".
  • Menus: there's one MENU_WIDTH and one MENU_OFFSET for the history retention, microphone, mode context, and meeting menus. Before they used 148, 160, or 220 px with a literal 4 px offset.
  • Icon optical size: Ellipsis is drawn one size up so its dots look as heavy as the other glyphs.
  • Copy: "..." becomes "…" in placeholders and empty states. One-phrase row descriptions no longer end with a period; two-sentence descriptions keep theirs.

Demo

Release-build previews (voice-control preview …) on a MacBook Pro. Before is main 1c3dacf. After is the head of this branch. The full contact sheet is at ~/Library/Caches/hex-design/index.html.

Meetings overflow menu

Meetings delete confirmation

Meetings recording

Meetings

Settings

Modes

History

Commands

Transcription picker

Setup sheet

Meeting capsules

Verification

cargo fmt --check
git diff --check
cargo clippy -j 2 --all-targets --all-features -- -D warnings
cargo test -j 2 --bin voice-control -- design_tokens desktop_ui app_window meeting_watcher
cargo build --release --bin voice-control -j 2   # MacBook Pro, then 19 preview captures
  • macOS fmt, clippy, and git diff --check pass, and 59 targeted tests pass. New tests: meeting_overflow_menu_opens_beside_the_visible_actions, history_detail_rows_keep_their_values_on_one_line, detail_rows_fill_a_detail_body, and baseline_offset_lowers_smaller_text_onto_the_larger_baseline. The existing tests still cover contrast, the raw-style guard, and truncation layout.
  • I compiled only for macOS, not linux_app.rs locally. Linux CI covers it.
  • Captures caught a regression in the History detail rows on the real text system that the test platform's metrics didn't show: values first collapsed, then overflowed. The fix keeps detail_body a block. It's verified by a fresh capture.

- Icon::Ellipsis, dropdown_menu, danger_menu_item, disabled_menu_item,
  and menu_separator for one reusable overflow menu
- detail_header / detail_title / detail_body: one fixed, edge-to-edge
  ruled header for every detail card
- list_section_label for group headings in list cards
- baseline_offset puts smaller text on a larger line's baseline
- MENU_WIDTH / MENU_OFFSET tokens; section labels inset to card text;
  pane_list centres its empty state
Meetings detail keeps Copy transcript and Rename visible and moves Copy
summary, Reveal, Summarize/Regenerate, and Delete… into a … menu. Delete
keeps its two-step confirmation in the action row; refusal rules are
unchanged. Stop & transcribe is a Secondary button with a red dot and
the elapsed time; the sidebar Update is Secondary with an accent dot.

Polish: shared detail headers in Meetings, History, and Commands;
timestamp and detail-row baselines; section labels on the card text
edge; wider side segments; transcript labels on the bubble text; one
menu width; Commands context moved into the detail; ... -> …; no
closing period on one-phrase descriptions.

Adds the --open-meeting-actions preview flag.
- History metadata rows sit directly in the detail body so values keep
  the full width (they collapsed to one character per line)
- An armed meeting delete replaces the action row; actions wrap
- Left / Either / Right selector width follows its segment widths
- Ellipsis draws one icon size up to match the other glyphs optically
@kitlangton
kitlangton marked this pull request as ready for review October 18, 2026 23:59
@kitlangton
kitlangton merged commit 75f859b into main Oct 18, 2026
2 checks passed
Hona added a commit that referenced this pull request Oct 19, 2026
…e, and x64 + ARM64 release (#141)

* feat: Windows beta with streaming dictation, Hold/Toggle shortcut mode, and release pipeline

Ports the windows-shell branch onto main, adds a GPU streaming finish for Parakeet Unified English with a whole-clip fallback, a cross-platform Hold/Toggle shortcut mode, x64 and ARM64 Windows CI and release feeds, and the website Windows download.

* copy: Enabled/Disabled status on Linux and in the shared Activity labels

* ci: install SPIRV-Headers for the x64 Vulkan build and gate the Windows-only device query

* refactor: one implementation per concept across macOS, Linux, and Windows

The Windows port carried its own copies of modules main already has. Each
concept now has one implementation:

- UI: Windows uses main's design system (design_tokens, desktop_ui,
  text_input, desktop_transcription_picker types) instead of a Fluent copy.
  Windows-only bits are small cfg additions: Segoe Fluent Icons for Icon,
  Windows text-editing key bindings, fixed-height and read-only text inputs,
  and the shared slider_track component. windows_ui keeps the window chrome
  and the anchored dropdowns, now drawn with menu/menu_item.
- Windows chrome: the sidebar runs the full window height as on macOS. The
  caption row is split: the brand sits over the sidebar, the window buttons
  over the content, and both halves stay OS drag regions.
- Commands: Windows uses main's commands, command_grammar, context, keys,
  and personal_commands. The SDK provisioner accepts the embedded TypeScript
  src layout; Windows execute() opens URLs and paths with ShellExecuteW.
- OpenCode: dictation_processor is the one client and ordered policy
  (corrections, rewrite, transformations) on macOS and Windows. Windows keeps
  its opencode2 api CLI transport in a cfg'd submodule; the ranked catalog,
  prompts, Voice Action generation, and the transformation stage are shared.
- desktop_activity is shared; the Windows Settings contract is
  windows_desktop_host, without #[path] aliases.
- self_update is the signed-feed core for both the Linux and Windows
  updaters.
- Windows copy follows the design system: sentence case and "Hex".

* chore(windows): flat app icon that stays crisp at taskbar sizes

* chore: mark the Windows-only screens to consolidate with app_window after #138

* fix(linux): wait for partial X11 grabs to be released before reporting a conflict
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant