Skip to content

refactor(ios): add a BlurtDesign component library - #230

Merged
alexkroman merged 10 commits into
mainfrom
jhe/blurt-design
Oct 15, 2026
Merged

alexkroman merged 10 commits into
mainfrom
jhe/blurt-design

Conversation

@he-james

@he-james he-james commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

What & why

Pulls the iPhone's design system into BlurtDesign, a component library the app and the keyboard both link. Before, it was a loose Shared/ folder compiled into each target, plus AppChrome.swift. Screens now import BlurtDesign for:

  • the generated DesignTokens, BlurtType and BlurtBrand
  • card(), page(), brandForm(), Eyebrow, BrandButtonStyle, Wordmark, FilmGrain

How it's built:

  • Static and extension-safe, with package access, like BlurtiOSCore. It uses SwiftUI only and depends on neither the engine nor the core.
  • No catalog dependency. The chrome colours adapt to light and dark from the app/* token pairs, instead of reading the app's asset catalog. A new test pins them to the catalog in both appearances.
  • One accent token. The CTA tokens now alias app/accent-*, so switching Blurt green to another accent is a two-line change in tokens.json. Generated colours are byte-identical. Styles' edit tint and checkmark now use the accent, so they lift to the light green in dark mode.
  • iPhone only. The Mac app and engine are untouched.

Also included:

  • The keyboard-only layout helpers moved into BlurtKeyboard/Sources.
  • The scripts (design-sync, ios-typecheck, check-invariants) and the docs now point at BlurtDesign/.

Stacked on #229.

How it was tested

  • scripts/ios-check.sh: 136 tests, BlurtiOSCore 96.69%, analyze, periphery and the probe build all pass.

  • design-sync.sh --check, check-invariants.sh (with --self-test), ios-typecheck.sh, swift-format, swiftlint and shellcheck are all clean.

  • Simulator: home screen in light and dark, and the keyboard gallery, all unchanged.

  • scripts/check.sh passes (or CI will, if I'm not on a Mac)

  • I read AGENTS.md and this doesn't reintroduce anything deliberately removed

  • Docs updated if behavior changed

🤖 Generated with Claude Code

he-james and others added 4 commits October 2, 2026 16:55
Gets the iPhone app ready for TestFlight.

- Bundle ids move from the dev.alex placeholders to
  com.assemblyai.blurt.ios (app, .keyboard, .core, .tests, .probe), the
  App Group to group.com.assemblyai.blurt.ios, and the term-pack type to
  com.assemblyai.blurt.terms. The Darwin notification names, log
  subsystem, queue labels and scripts follow. The Mac app keeps its
  dev.alex ids: changing them would orphan Sparkle updates and its
  granted Accessibility permission.
- API-key entry is no longer debug-only. Sign in with AssemblyAI
  doesn't exist yet, so a TestFlight build had no way to add a key. The
  setup card and Settings now offer "Paste your key" and "Get a free
  key" in every build, as the Mac app does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
App Review 5.1.2(i) requires clearly disclosing where personal data
goes, third-party AI included, and getting explicit permission first.

The listening window now won't open until the user agrees, in a sheet
that names AssemblyAI, lists what is sent (the voice while dictating,
the text before the cursor, recent dictations, key terms with contact
names) and links the privacy policy. Every path to the microphone goes
through startListening(), so one gate covers the button, the keyboard's
blurt://start and in-app dictation. Consent is versioned (AIConsent):
sending something new means a new version, and everyone is asked again.

Settings' About section links the privacy policy too. The debug
-BlurtStartListening flag grants consent, since scripted runs can't tap
through the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPhone project pulled the app icon and the wordmark straight out of
the Mac app's folder, so a Mac-side move or rename would have broken
the iPhone build. Both now live in a top-level Design/brand that each
project references directly; neither app's folder is a dependency of
the other.

The Mac project lists them explicitly (it used to get them by including
its whole Blurt folder) and its project.pbxproj is regenerated. Both
app bundles still carry the icon and logo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The package gains a second library, AssemblyAI, that BlurtEngine is
built on: the HTTPTransport seam and its streamed upload body, the
dictation wire types and multipart framing, and APIKeyValidator. It
holds no Blurt policy, so it can become a standalone Swift SDK; the
engine keeps everything that is a Blurt decision (the prompts, the
cleanup instruction and styles, key storage, logging, the User-Agent).

A pure move: no request, header or response handling changes. The
wire types un-nest from AssemblyAITranscriber and become public, the
SDK gets its own logger and URL helper, and the validator takes its
User-Agent as a parameter. APIKeyValidator.blurt() is where Blurt's
agent joins it, and both apps now use it, so their key checks still
send `Blurt/<version> (…)`. A new test pins that the SDK's validator
sends only the agent it's given.

SyncSTTLimits.pcmBytes(forSeconds:) goes: its one caller, the upload
buffer, now lives in the SDK and states its own size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@he-james
he-james marked this pull request as ready for review October 2, 2026 22:40
he-james and others added 2 commits October 2, 2026 18:11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the iPhone's design system out of the loose Shared/ folder and
AppChrome.swift into BlurtDesign, a static, extension-safe framework the
app and the keyboard both link: the generated DesignTokens, the type
roles, the brand colours, and card/page/brandForm/Eyebrow/
BrandButtonStyle/Wordmark/FilmGrain.

The chrome colours are built from the token pairs rather than read from
the app's asset catalog, so the library stands alone; a test pins them
to the catalog in light and dark. The CTA tokens now alias the accent,
so the accent (Blurt green) is one token pair to repoint.

iPhone only: the Mac app is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from jhe/assemblyai-sdk to main October 15, 2026 00:04
@alexkroman

Copy link
Copy Markdown
Collaborator

@claude resolve conflicts

@alexkroman
alexkroman enabled auto-merge (squash) October 15, 2026 00:15
he-james and others added 2 commits October 14, 2026 17:15
…SDK (#231)

* refactor(sdk): move key terms and text shortcuts into the AssemblyAI SDK

KeyTerms (parse a comma-separated list, fit it to keyterms_prompt's
caps; was KeytermsBoost plus KeyTermsStore.parse) and TextShortcut with
its on-device TextShortcutExpander now live in Sources/AssemblyAI, so
any app on the SDK gets them. BlurtEngine keeps Blurt's storage:
KeyTermsStore and TextShortcutStore, their UserDefaults keys and limits.

The iPhone's duplicate KeyTermList is replaced by KeyTerms. The Mac app
only gains an import; its behaviour and stored data are unchanged.

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

* style(sdk): keep KeyTerms.join's doc comment on its declaration

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

* feat(ios): add a Words tab for key terms and text shortcuts (#232)

* feat(ios): add a Words tab for key terms and text shortcuts

The app gets Dictate and Words tabs under a brand tab bar, as in the
blurt-ios prototype. MainView owns what works on either tab: Settings
from each tab's gear, the consent sheet and shared key-term lists.

Words, on the AssemblyAI SDK's KeyTerms and TextShortcut:
- Key terms as pills with an inline add field (commas add several,
  duplicates drop, capped at the request's 100), the contact-name
  count, and Share. They move here from Settings.
- Text shortcuts with an inline "When I say / Type this" form;
  re-adding a phrase replaces it. Expanded on the phone, never sent.

BlurtDesign gains BrandTabBar, FlowLayout, brandInput() and
brandFootnote(), and brand buttons fade while disabled.

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

* fix(ios): drop the Words tab pointer from Settings

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(sdk): move key terms and text shortcuts into the AssemblyAI SDK

KeyTerms (parse a comma-separated list, fit it to keyterms_prompt's
caps; was KeytermsBoost plus KeyTermsStore.parse) and TextShortcut with
its on-device TextShortcutExpander now live in Sources/AssemblyAI, so
any app on the SDK gets them. BlurtEngine keeps Blurt's storage:
KeyTermsStore and TextShortcutStore, their UserDefaults keys and limits.

The iPhone's duplicate KeyTermList is replaced by KeyTerms. The Mac app
only gains an import; its behaviour and stored data are unchanged.

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

* feat(ios): add a Words tab for key terms and text shortcuts

The app gets Dictate and Words tabs under a brand tab bar, as in the
blurt-ios prototype. MainView owns what works on either tab: Settings
from each tab's gear, the consent sheet and shared key-term lists.

Words, on the AssemblyAI SDK's KeyTerms and TextShortcut:
- Key terms as pills with an inline add field (commas add several,
  duplicates drop, capped at the request's 100), the contact-name
  count, and Share. They move here from Settings.
- Text shortcuts with an inline "When I say / Type this" form;
  re-adding a phrase replaces it. Expanded on the phone, never sent.

BlurtDesign gains BrandTabBar, FlowLayout, brandInput() and
brandFootnote(), and brand buttons fade while disabled.

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

* feat(ios): show lifetime dictation stats

A stats card at the top of the Dictate tab, as in the blurt-ios
prototype: dictations, words, and time saved against the 36 wpm phone
typing average, as serif numbers with mono units. Time saved shows in
full and moves to its own row past an hour; every value takes the
largest of four sizes that fits.

Every dictation counts, from the app or the keyboard. The coordinator
times the recording phase and records on the main actor when the
dictation's terminal phase renders, so the spoken time is the right
one and a reset can't race a record. Totals stay on the phone
(DictationStats, BlurtiOSCore); Settings › Data resets them.

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

* style(sdk): keep KeyTerms.join's doc comment on its declaration

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

* fix(ios): drop the Words tab pointer from Settings

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

* feat(ios): add the Easter Egg (#234)

* feat(ios): add the easter egg

A quiet "How fast can you type?" link at the foot of Settings opens a
30-second sprint over common words, ported from the blurt-ios
prototype: the clock starts on the first key, turns orange and thumps
through the last five seconds, and the result shows WPM, a rank,
accuracy, the best, and how talking at 150 wpm compares.

The latest result becomes the typing speed time saved is measured
against, in place of the 36 wpm phone average, with a "Reset to
average" link beside it. Scoring and the speed live in BlurtiOSCore
(TypingTest, TypingSpeed) and are tested.

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

* fix(ios): present the Easter Egg once from Settings

The cover and the reset confirmation were attached to their sections,
and a section's modifiers apply to each of its rows, so the About
section's five rows each tried to present the Easter Egg at once. Both
now hang off the form.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ios): add a Words tab for key terms and text shortcuts (#232)

* feat(ios): add a Words tab for key terms and text shortcuts

The app gets Dictate and Words tabs under a brand tab bar, as in the
blurt-ios prototype. MainView owns what works on either tab: Settings
from each tab's gear, the consent sheet and shared key-term lists.

Words, on the AssemblyAI SDK's KeyTerms and TextShortcut:
- Key terms as pills with an inline add field (commas add several,
  duplicates drop, capped at the request's 100), the contact-name
  count, and Share. They move here from Settings.
- Text shortcuts with an inline "When I say / Type this" form;
  re-adding a phrase replaces it. Expanded on the phone, never sent.

BlurtDesign gains BrandTabBar, FlowLayout, brandInput() and
brandFootnote(), and brand buttons fade while disabled.

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

* fix(ios): drop the Words tab pointer from Settings

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ios): open on a setup screen, then keep setup in Settings (#236)

* feat(ios): add the easter egg

A quiet "How fast can you type?" link at the foot of Settings opens a
30-second sprint over common words, ported from the blurt-ios
prototype: the clock starts on the first key, turns orange and thumps
through the last five seconds, and the result shows WPM, a rank,
accuracy, the best, and how talking at 150 wpm compares.

The latest result becomes the typing speed time saved is measured
against, in place of the 36 wpm phone average, with a "Reset to
average" link beside it. Scoring and the speed live in BlurtiOSCore
(TypingTest, TypingSpeed) and are tested.

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

* fix(ios): present the Easter Egg once from Settings

The cover and the reset confirmation were attached to their sections,
and a section's modifiers apply to each of its rows, so the About
section's five rows each tried to present the Easter Egg at once. Both
now hang off the form.

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

* feat(ios): open on a setup screen, then keep setup in Settings

As in the blurt-ios prototype, a first-run screen replaces the tabs
until Blurt is set up: the wordmark, a headline, and the numbered steps
in a card, with the API key pasted inline (Continue, Get a free key,
where the key and the audio go). Without a key it can't be left; with
one, Start dictating goes to the tabs and the microphone and keyboard
can wait. Whoever was already set up skips it.

The setup card leaves the Dictate tab. Settings › Setup keeps the
steps, hidden behind "Blurt is set up ✓ · Show steps" once everything
is done and open while anything isn't.

The rule is SetupProgress (BlurtiOSCore, tested); -BlurtNoKey shows a
fresh install's screen in debug builds without touching the Keychain.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@alexkroman alexkroman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👍

TextShortcut moved from BlurtEngine to the AssemblyAI module in #231;
this test (added on main in #243) still only imported BlurtEngine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBXt5MoXHUDypywez9xZR3
@alexkroman
alexkroman merged commit 27821b9 into main Oct 15, 2026
18 of 20 checks passed
@alexkroman
alexkroman deleted the jhe/blurt-design branch October 15, 2026 00:48
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.

4 participants