Skip to content

Redesign the main window, Keys, Sounds and transcript views - #304

Merged
alexkroman merged 5 commits into
mainfrom
main-window-and-keys-redesign
Nov 5, 2026
Merged

alexkroman merged 5 commits into
mainfrom
main-window-and-keys-redesign

Conversation

@alexkroman

Copy link
Copy Markdown
Collaborator

Summary

Settings

  • Keys is now one row per action (Dictate, each style, Read Selection Aloud, Ask, Rewrite, Control Your Mac) with a key pop-up. Choosing a key that's in use moves it (TriggerKeyStyleStore.assign(_:to:), tested). One Activation mode applies to every key; per-key modes are removed (TriggerKeyActivationStore deleted).
  • New Sounds tab: a start/stop cue per key (TriggerKeySoundStore, a new DefaultsKey). A key with no voice of its own plays its action's existing setting, so untouched installs sound the same. CueSoundPlayer keeps a pair per key and plays the one whose press started the recording or read.
  • Removed the Read Aloud footer and the Sounds / Read Aloud section headings. General's Meetings section gets a header, a one-line subtitle and a footer.

Main window (Journal-style)

  • One window-level scroll on a pale brand-green backdrop, with no rule under the toolbar.
  • Keys shown as a keycap legend on a translucent panel. They aren't clickable, and nothing reacts to a key press.
  • Library entries are compact white cards with a ⋯ menu (Open, Play, Copy, Show in Finder, Delete…). A click opens one.
  • Toolbar: Record, Browse, Filter menu, Settings, and the native .searchable search (it doesn't take focus at launch; clicking the content unfocuses it).
  • Full key names ("Right Command") everywhere; the logo, "Library" heading and footer are removed.
  • Files come in four ways:
    • dropped on the window (an overlay appears only while dragging);
    • dropped on the Dock icon, or Finder's Open With (Alternate CFBundleDocumentTypes → application(_:open:));
    • File › Open… (⌘O);
    • Browse.

Transcripts

  • Opening an item slides the window over to TranscriptPage: a back button, a toolbar ✨ View menu, Play/Stop, Copy, Show in Finder, and Delete in its own group.
    • Play/Stop covers both the entry's audio and generated speech, with a spinner until speech starts (TextReader.read(onStart:), tested).
    • Dictations get no title.
    • The delete confirmation no longer shows the title.

Test plan

  • scripts/check.sh green locally: engine tests and coverage, swift-format, swiftlint, periphery, iPhone checks, docs linters, and invariants.
  • UI suite (BlurtUITests): updated for the new Keys rows, Sounds tab, Filter menu, cards and transcript page, but not run locally. CI is the first run.
  • Manually checked in Blurt Dev: the main window, keys legend, cards, Open With refusing a non-audio file, and toolbar icon states.

🤖 Generated with Claude Code

alexkroman-assembly and others added 3 commits November 4, 2026 14:52
Settings
- Keys: one row per action with a key pop-up (TriggerKeyStyleStore.assign),
  one activation mode for every key (per-key modes removed).
- Sounds: new tab with a start/stop cue per key (TriggerKeySoundStore;
  a key with no voice of its own plays its action's setting).
- Read Aloud and Sounds: section headings and the Read Aloud footer removed;
  General: Meetings gets a header, short subtitle and footer.

Main window
- Journal-style: one window-level scroll on a pale brand-green backdrop,
  no rule under the toolbar; keys as a keycap legend on a translucent
  panel; Library entries as compact white cards with a ⋯ menu.
- Toolbar: Record, Browse, Filter menu, Settings, native search.
- Full key names everywhere; nothing in the window reacts to a key press.
- Files come in by drop (overlay only while dragging), Dock icon / Open
  With (Alternate document types), File › Open… (⌘O), or Browse.

Transcripts
- Opening an item slides the window over to it: back button, toolbar
  View menu (✨), Play/Stop (recording or speech, with a spinner until
  speech starts — TextReader.read(onStart:)), Copy, Show in Finder,
  Delete in its own group. No title for dictations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lide

The window's path is bound to the transcripts model — a page while it has
something open, none when idle — so items, recordings and file uploads push
themselves, and popping (back or Esc) dismisses, abandoning an upload. The
system back button replaces the custom one and is hidden during a recording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Library is a stock List drawn as the same white cards (row backgrounds
  and insets), led by the keys legend: click selects, double-click or Return
  opens, ⌘C, Delete and arrows work again. The table's own highlight is
  turned off so a selected card takes a Journal-style accent ring; day
  headings are ordinary rows so they scroll instead of pinning.
- The open item scrolls as one page, title included.
- Dropping a file replaces the window's content with a single drop well
  instead of overlaying a label.
- Split LibraryCard.swift and AudioFileDropTarget.swift out for swiftlint's
  file-length limit; removed the unused card() helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +34 to +37
private static func table(in view: NSView) -> NSTableView? {
if let table = view as? NSTableView { return table }
for subview in view.subviews {
if let table = table(in: subview) { return table }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

table(in:) recursively traverses subviews without a depth bound, so a deeply nested view hierarchy can exhaust the stack.

Show fix
Suggested change
private static func table(in view: NSView) -> NSTableView? {
if let table = view as? NSTableView { return table }
for subview in view.subviews {
if let table = table(in: subview) { return table }
private static func table(in view: NSView, depth: Int = 0) -> NSTableView? {
if depth >= 1000 { return nil }
if let table = view as? NSTableView { return table }
for subview in view.subviews {
if let table = table(in: subview, depth: depth + 1) { return table }
Details

✨ AI Reasoning
​The helper searches descendant views recursively and has no bound on recursion depth. A sufficiently deep view hierarchy can cause excessive stack growth during highlight removal.

Reply on this comment to give feedback or ignore the issue.
More info

alexkroman-assembly and others added 2 commits November 4, 2026 15:55
…update menu hint test

onExitCommand needed the page focused and lost Esc to the View menu; a hidden
cancelAction button works wherever focus is. The menu bar hint now spells the
key as "Right Option", so the UI test matches that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tton in one wait

testControlsFollowTheKeys failed on CI with "Settings tab 'Read Aloud' not
found". Two things in the helpers made it fragile:

- openSettingsWindow() only sent ⌘, when Settings wasn't open, so after a
  test clicked the harness, Settings stayed behind it and its toolbar was
  driven from the background.
- selectSettingsTab waited 5s for a radio button that macOS 26 never
  exposes (its tabs are buttons), leaving the real button only 3s.

Now ⌘, is always sent (it raises an open Settings), and the tab is found
in the toolbar as either element type with one 10s wait. A miss dumps the
window's element tree, so a CI failure explains itself without the
.xcresult.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alexkroman
alexkroman enabled auto-merge (squash) November 5, 2026 01:03
@alexkroman
alexkroman merged commit cc07a1d into main Nov 5, 2026
13 checks passed
@alexkroman
alexkroman deleted the main-window-and-keys-redesign branch November 5, 2026 01:13
@alexkroman alexkroman mentioned this pull request Nov 5, 2026
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.

2 participants