M1-03: Design pass — static UI on sample data #3

Closed
opened 2026-08-06 10:53:40 -04:00 by agent · 2 comments
Member

First visual pass, in code, with default SwiftUI mechanics. No audio anywhere —
every value on screen is sample data.

Wes's instruction: "let's just go ahead and throw together an initial design pass in code
using default swiftui mechanics and then i can talk you through tweaks and take screenshots."

There is deliberately no Figma/Penpot step — SwiftUI has no drag-and-drop builder, Xcode
Previews render against real iOS primitives, and any intermediate mockup would be a
lower-fidelity picture of something Previews shows for real.

Screens

  • Memo list — the main view. Each row shows a memo and its sync status, using the three
    states Wes described: a cloud with a line through it (not synced), a cloud with circular
    arrows (syncing), a cloud with a checkmark (synced). In M1 everything is permanently "not
    synced" — the states exist so the design is right, not because anything syncs yet.
  • Empty state — what you see on first launch.
  • Active recording — the bottom bar/"toast" from the old app: audio levels, elapsed time,
    a stop button.
  • Settings — a stub. (The VTT-first/audio-first sync-order preference is deliberately
    not here; it controls something that does not exist until M3.)

Design constraints

  • System standard UI over custom UI, always. People know their platform.
  • Sparse but not obtusely sparse. It must be obvious how to use the app. Not fancy.
  • Controls contextual and obvious.
  • Amethyst purple and the existing iconography, pulled from ~/Projects/_archive/stash-saas
    — the archived v2 app is kept for exactly this.
  • Dark and light mode, mostly letting the system manage it.

Done when

Wes has it on his phone, takes screenshots, and directs tweaks from there. Expect iteration
on this issue rather than one clean pass.

Milestone 1 of the Stash v3 rebuild, scoped 2026-08-06. M1 is a functioning iOS app on Wes's
phone with no sync at all — the entire goal is recording voice memos reliably and getting
them off the device by hand. Sync arrives in M3.

Working agreement for every issue in this repo: feature branch, tested before the PR opens
(on-device where the label says so), then Wes reviews the PR and we walk the code together.
Docs update in the same commit. A PR that takes more than 20 minutes to review is too big —
say so and split it.

First visual pass, in code, with **default SwiftUI mechanics**. No audio anywhere — every value on screen is sample data. Wes's instruction: *"let's just go ahead and throw together an initial design pass in code using default swiftui mechanics and then i can talk you through tweaks and take screenshots."* There is deliberately no Figma/Penpot step — SwiftUI has no drag-and-drop builder, Xcode Previews render against real iOS primitives, and any intermediate mockup would be a lower-fidelity picture of something Previews shows for real. ## Screens - **Memo list** — the main view. Each row shows a memo and its sync status, using the three states Wes described: a cloud with a line through it (not synced), a cloud with circular arrows (syncing), a cloud with a checkmark (synced). In M1 everything is permanently "not synced" — the states exist so the design is right, not because anything syncs yet. - **Empty state** — what you see on first launch. - **Active recording** — the bottom bar/"toast" from the old app: audio levels, elapsed time, a stop button. - **Settings** — a stub. (The VTT-first/audio-first sync-order preference is deliberately **not** here; it controls something that does not exist until M3.) ## Design constraints - **System standard UI over custom UI, always.** People know their platform. - Sparse but not obtusely sparse. It must be obvious how to use the app. Not fancy. - Controls contextual and obvious. - Amethyst purple and the existing iconography, pulled from `~/Projects/_archive/stash-saas` — the archived v2 app is kept for exactly this. - Dark and light mode, mostly letting the system manage it. ## Done when Wes has it on his phone, takes screenshots, and directs tweaks from there. Expect iteration on this issue rather than one clean pass. --- *Milestone 1 of the Stash v3 rebuild, scoped 2026-08-06. M1 is a functioning iOS app on Wes's phone with **no sync at all** — the entire goal is recording voice memos reliably and getting them off the device by hand. Sync arrives in M3.* *Working agreement for every issue in this repo: feature branch, tested before the PR opens (on-device where the label says so), then Wes reviews the PR and we walk the code together. Docs update in the same commit. A PR that takes more than 20 minutes to review is too big — say so and split it.*
Author
Member

Branch m1-03-design-pass, one commit. Screenshots below — this is the pass to talk tweaks against.

What's there

Memo list is the root view. Each row is title, relative date, duration, and the sync icon. The three states use the symbols you described and they're all stock: icloud.slash (cloud with a line through it), arrow.triangle.2.circlepath.icloud (cloud with circular arrows), checkmark.icloud (cloud with a checkmark). Every sample memo is notSynced, which is the only honest state in M1 — the other two exist in the type so the row is designed against the states it will really have rather than redesigned when sync lands.

Empty state is ContentUnavailableView, the stock one, so it matches every other app on the phone. Reachable in the running app by swiping every row away.

Recording bar pins with safeAreaInset rather than overlaying, so the last row can still scroll clear of it. Level meter, live elapsed timer, red stop button. Tapping Record shows it; nothing is recorded and no audio session is touched.

Settings is a Form with the version and a footer. The VTT-first/audio-first preference is deliberately not there — it controls something that doesn't exist until M3, and a switch that changes nothing is worse than no switch.

Titles are the date-stamped default names the app will generate on its own, since M1 has no naming UI. All synthetic — this repo is public.

What I validated

Clean lint (--strict, 0 violations, 7 files) and BUILD SUCCEEDED on the iPhone 17 Pro Max simulator. Installed, launched, confirmed alive rather than launch-and-crash. Screenshotted all four screens plus dark mode; dark and light are entirely system-managed, no per-mode colours anywhere.

One layout bug found and fixed in the process: the recording bar's material was hugging its content instead of spanning the width, so the bar rendered as a floating box in the middle of the screen. The screenshot below is after the fix.

The three states that need a tap — recording, empty, settings — were captured by temporarily patching the initial state, building, and reverting. The reverted tree is what's committed and what built last.

What I did not validate

Not on your phone. No signing identity here, and none belongs in a public repo, so this is simulator-only. Getting it on the device is your step, and it's the part of "done when" I can't do.

Not checked: Dynamic Type at large sizes, landscape, iPad, or VoiceOver actually reading the rows — the accessibility labels are written but I have not run VoiceOver over them.

The level meter is the one hand-drawn control. It's driven by a sine wave, not a microphone, and TimelineView(.animation) redraws it at display refresh — fine for a stub, worth revisiting when it's showing real levels.

Leaving this open — you said to expect iteration here rather than one clean pass.


Memo list

Memo list

Empty state

Empty state

Active recording

Active recording

Settings stub

Settings stub

Memo list, dark

Memo list, dark

Branch `m1-03-design-pass`, one commit. Screenshots below — this is the pass to talk tweaks against. ## What's there **Memo list** is the root view. Each row is title, relative date, duration, and the sync icon. The three states use the symbols you described and they're all stock: `icloud.slash` (cloud with a line through it), `arrow.triangle.2.circlepath.icloud` (cloud with circular arrows), `checkmark.icloud` (cloud with a checkmark). Every sample memo is `notSynced`, which is the only honest state in M1 — the other two exist in the type so the row is designed against the states it will really have rather than redesigned when sync lands. **Empty state** is `ContentUnavailableView`, the stock one, so it matches every other app on the phone. Reachable in the running app by swiping every row away. **Recording bar** pins with `safeAreaInset` rather than overlaying, so the last row can still scroll clear of it. Level meter, live elapsed timer, red stop button. Tapping Record shows it; nothing is recorded and no audio session is touched. **Settings** is a `Form` with the version and a footer. The VTT-first/audio-first preference is deliberately not there — it controls something that doesn't exist until M3, and a switch that changes nothing is worse than no switch. Titles are the date-stamped default names the app will generate on its own, since M1 has no naming UI. All synthetic — this repo is public. ## What I validated Clean lint (`--strict`, 0 violations, 7 files) and **BUILD SUCCEEDED** on the iPhone 17 Pro Max simulator. Installed, launched, confirmed alive rather than launch-and-crash. Screenshotted all four screens plus dark mode; dark and light are entirely system-managed, no per-mode colours anywhere. One layout bug found and fixed in the process: the recording bar's material was hugging its content instead of spanning the width, so the bar rendered as a floating box in the middle of the screen. The screenshot below is after the fix. The three states that need a tap — recording, empty, settings — were captured by temporarily patching the initial state, building, and reverting. The reverted tree is what's committed and what built last. ## What I did not validate **Not on your phone.** No signing identity here, and none belongs in a public repo, so this is simulator-only. Getting it on the device is your step, and it's the part of "done when" I can't do. Not checked: Dynamic Type at large sizes, landscape, iPad, or VoiceOver actually reading the rows — the accessibility labels are written but I have not run VoiceOver over them. The level meter is the one hand-drawn control. It's driven by a sine wave, not a microphone, and `TimelineView(.animation)` redraws it at display refresh — fine for a stub, worth revisiting when it's showing real levels. Leaving this open — you said to expect iteration here rather than one clean pass. --- **Memo list** ![Memo list](https://git.wes.today/attachments/ceada0dc-432a-45fb-aa57-33a667e05361) **Empty state** ![Empty state](https://git.wes.today/attachments/b1d9923b-e28f-454c-b839-b5de68becc26) **Active recording** ![Active recording](https://git.wes.today/attachments/7d74d3e1-1377-46ea-8acd-28b5ef165cfe) **Settings stub** ![Settings stub](https://git.wes.today/attachments/7de7410b-35c7-4e80-af14-5ff6b4679019) **Memo list, dark** ![Memo list, dark](https://git.wes.today/attachments/44620679-5b2e-40a0-ad28-81d04f803629)
Author
Member

Verified on hardware — wes-iphone, iPhone 17 Pro. Wes confirmed the screens look right on the device.

Nothing signing-related was added to the repository. The team identifier is passed on the command line, not written into project.yml, because this repo is public. git status was clean afterwards.

Team identifier, read off the existing certificate rather than guessed — it is the OU:

security find-identity -v -p codesigning
security find-certificate -c "Apple Development: Wes Kennedy (LW2SXU4UH5)" -p \
  | openssl x509 -noout -subject
# subject= /UID=.../CN=Apple Development: Wes Kennedy (LW2SXU4UH5)/OU=C924BFP24W/O=Wes Kennedy/C=US

Checked for a leftover install first. Wes had deleted the old app; this confirms the bundle ID was actually free rather than assuming it:

xcrun devicectl list devices
xcrun devicectl device info apps --device wes-iphone
# "Apps installed:" — nothing under today.wes.stash, so no uninstall was needed

Build, install, launch:

xcodegen generate
xcodebuild -project Stash.xcodeproj -scheme Stash \
  -destination 'platform=iOS,name=wes-iphone' \
  -derivedDataPath /tmp/stash-device \
  -allowProvisioningUpdates \
  DEVELOPMENT_TEAM=C924BFP24W \
  build

xcrun devicectl device install app --device wes-iphone \
  /tmp/stash-device/Build/Products/Debug-iphoneos/Stash.app

xcrun devicectl device process launch --device wes-iphone today.wes.stash

-allowProvisioningUpdates is what lets Xcode mint iOS Team Provisioning Profile: today.wes.stash without a visit to the UI. Signing identity resolved to Apple Development: Wes Kennedy (LW2SXU4UH5), BUILD SUCCEEDED.

Confirmed it was still running rather than launch-and-crash, which is the part a successful launch does not tell you:

xcrun devicectl device info processes --device wes-iphone | grep -i stash
# 71850   /private/var/containers/Bundle/Application/.../Stash.app/Stash

Worth knowing: this is a development build, so the profile expires and the app will stop launching after a while. Re-running the three commands above fixes it.

PR is open. Leaving this issue open — you said to expect iteration on the design rather than one clean pass, and the device pass has not changed that.

Verified on hardware — `wes-iphone`, iPhone 17 Pro. Wes confirmed the screens look right on the device. Nothing signing-related was added to the repository. The team identifier is passed on the command line, not written into `project.yml`, because this repo is public. `git status` was clean afterwards. **Team identifier**, read off the existing certificate rather than guessed — it is the OU: ```sh security find-identity -v -p codesigning security find-certificate -c "Apple Development: Wes Kennedy (LW2SXU4UH5)" -p \ | openssl x509 -noout -subject # subject= /UID=.../CN=Apple Development: Wes Kennedy (LW2SXU4UH5)/OU=C924BFP24W/O=Wes Kennedy/C=US ``` **Checked for a leftover install first.** Wes had deleted the old app; this confirms the bundle ID was actually free rather than assuming it: ```sh xcrun devicectl list devices xcrun devicectl device info apps --device wes-iphone # "Apps installed:" — nothing under today.wes.stash, so no uninstall was needed ``` **Build, install, launch:** ```sh xcodegen generate xcodebuild -project Stash.xcodeproj -scheme Stash \ -destination 'platform=iOS,name=wes-iphone' \ -derivedDataPath /tmp/stash-device \ -allowProvisioningUpdates \ DEVELOPMENT_TEAM=C924BFP24W \ build xcrun devicectl device install app --device wes-iphone \ /tmp/stash-device/Build/Products/Debug-iphoneos/Stash.app xcrun devicectl device process launch --device wes-iphone today.wes.stash ``` `-allowProvisioningUpdates` is what lets Xcode mint `iOS Team Provisioning Profile: today.wes.stash` without a visit to the UI. Signing identity resolved to `Apple Development: Wes Kennedy (LW2SXU4UH5)`, **BUILD SUCCEEDED**. **Confirmed it was still running rather than launch-and-crash**, which is the part a successful launch does not tell you: ```sh xcrun devicectl device info processes --device wes-iphone | grep -i stash # 71850 /private/var/containers/Bundle/Application/.../Stash.app/Stash ``` **Worth knowing:** this is a development build, so the profile expires and the app will stop launching after a while. Re-running the three commands above fixes it. PR is open. Leaving this issue open — you said to expect iteration on the design rather than one clean pass, and the device pass has not changed that.
wk closed this issue 2026-08-06 14:38:55 -04:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Stash/stash-ios#3
No description provided.