@arach/lattices 0.2.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +144 -69
- package/apps/mac/Info.plist +43 -0
- package/apps/mac/Lattices.app/Contents/Info.plist +43 -0
- package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/apps/mac/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
- package/apps/mac/Lattices.app/Contents/Resources/docs/assistant-knowledge.md +130 -0
- package/apps/mac/Lattices.app/Contents/Resources/tap.wav +0 -0
- package/apps/mac/Lattices.app/Contents/_CodeSignature/CodeResources +150 -0
- package/apps/mac/Lattices.entitlements +21 -0
- package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
- package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
- package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
- package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
- package/apps/mac/Resources/tap.wav +0 -0
- package/assets/AppIcon.icns +0 -0
- package/bin/assistant-intelligence.ts +912 -0
- package/bin/cli/capture.ts +252 -0
- package/bin/cli/daemon.ts +22 -0
- package/bin/cli/helpers.ts +105 -0
- package/bin/cli/layer.ts +178 -0
- package/bin/cli/runs.ts +43 -0
- package/bin/cli/search.ts +141 -0
- package/bin/cli/session.ts +32 -0
- package/bin/client.ts +17 -0
- package/bin/cua.ts +26 -0
- package/bin/{daemon-client.js → daemon-client.ts} +49 -30
- package/bin/handsoff-infer.ts +96 -0
- package/bin/handsoff-worker.ts +531 -0
- package/bin/infer.ts +424 -0
- package/bin/keychain.ts +75 -0
- package/bin/lattices-app.ts +655 -0
- package/bin/lattices-build +125 -0
- package/bin/lattices-build-env.ts +77 -0
- package/bin/lattices-dev +362 -0
- package/bin/lattices.ts +3260 -0
- package/bin/project-twin.ts +645 -0
- package/docs/agent-execution-plan.md +562 -0
- package/docs/agent-layer-guide.md +207 -0
- package/docs/agents.md +233 -0
- package/docs/ai-chat-ux-review.md +416 -0
- package/docs/api.md +1041 -47
- package/docs/app.md +96 -13
- package/docs/assistant-knowledge.md +130 -0
- package/docs/companion-deck.md +209 -0
- package/docs/component-extraction-roadmap.md +392 -0
- package/docs/concepts.md +13 -12
- package/docs/config.md +83 -10
- package/docs/gesture-customization-proposal.md +520 -0
- package/docs/handsoff-test-scenarios.md +84 -0
- package/docs/hyperspace-grid-snappiness.md +210 -0
- package/docs/layers.md +176 -28
- package/docs/mouse-gestures.md +244 -0
- package/docs/ocr.md +21 -9
- package/docs/overview.md +42 -23
- package/docs/presentation-execution-review.md +491 -0
- package/docs/prompts/hands-off-system.md +382 -0
- package/docs/prompts/hands-off-turn.md +30 -0
- package/docs/prompts/voice-advisor.md +31 -0
- package/docs/prompts/voice-fallback.md +23 -0
- package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
- package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
- package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
- package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
- package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
- package/docs/proposals/LAT-006-followup-gaps.md +103 -0
- package/docs/proposals/LAT-006-runs-and-capture-in-lattices.md +566 -0
- package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
- package/docs/quickstart.md +8 -12
- package/docs/reference/dewey.config.ts +74 -0
- package/docs/reference/install-agent.md +79 -0
- package/docs/release.md +172 -0
- package/docs/repo-structure.md +100 -0
- package/docs/terminal-kit.md +87 -0
- package/docs/tiling-reference.md +224 -0
- package/docs/twins.md +138 -0
- package/docs/voice-command-protocol.md +278 -0
- package/docs/voice-error-model.md +73 -0
- package/docs/voice.md +221 -0
- package/package.json +69 -16
- package/packages/npm/sdk/cua.d.mts +1 -0
- package/packages/npm/sdk/cua.d.ts +188 -0
- package/packages/npm/sdk/cua.mjs +376 -0
- package/app/Lattices.app/Contents/Info.plist +0 -24
- package/app/Package.swift +0 -13
- package/app/Sources/ActionRow.swift +0 -61
- package/app/Sources/App.swift +0 -10
- package/app/Sources/AppDelegate.swift +0 -234
- package/app/Sources/AppShellView.swift +0 -62
- package/app/Sources/AppTypeClassifier.swift +0 -70
- package/app/Sources/AppWindowShell.swift +0 -63
- package/app/Sources/CheatSheetHUD.swift +0 -332
- package/app/Sources/CommandModeState.swift +0 -1362
- package/app/Sources/CommandModeView.swift +0 -1405
- package/app/Sources/CommandModeWindow.swift +0 -192
- package/app/Sources/CommandPaletteView.swift +0 -307
- package/app/Sources/CommandPaletteWindow.swift +0 -134
- package/app/Sources/DaemonProtocol.swift +0 -101
- package/app/Sources/DaemonServer.swift +0 -414
- package/app/Sources/DesktopModel.swift +0 -121
- package/app/Sources/DesktopModelTypes.swift +0 -71
- package/app/Sources/DiagnosticLog.swift +0 -271
- package/app/Sources/EventBus.swift +0 -30
- package/app/Sources/HotkeyManager.swift +0 -250
- package/app/Sources/HotkeyStore.swift +0 -338
- package/app/Sources/InventoryManager.swift +0 -35
- package/app/Sources/InventoryPath.swift +0 -43
- package/app/Sources/KeyRecorderView.swift +0 -210
- package/app/Sources/LatticesApi.swift +0 -1125
- package/app/Sources/MainView.swift +0 -467
- package/app/Sources/MainWindow.swift +0 -83
- package/app/Sources/OcrModel.swift +0 -309
- package/app/Sources/OcrStore.swift +0 -295
- package/app/Sources/OmniSearchState.swift +0 -283
- package/app/Sources/OmniSearchView.swift +0 -288
- package/app/Sources/OmniSearchWindow.swift +0 -105
- package/app/Sources/OrphanRow.swift +0 -129
- package/app/Sources/PaletteCommand.swift +0 -419
- package/app/Sources/PermissionChecker.swift +0 -125
- package/app/Sources/Preferences.swift +0 -92
- package/app/Sources/ProcessModel.swift +0 -199
- package/app/Sources/ProcessQuery.swift +0 -151
- package/app/Sources/Project.swift +0 -28
- package/app/Sources/ProjectRow.swift +0 -368
- package/app/Sources/ProjectScanner.swift +0 -121
- package/app/Sources/ScreenMapState.swift +0 -2387
- package/app/Sources/ScreenMapView.swift +0 -2820
- package/app/Sources/ScreenMapWindowController.swift +0 -89
- package/app/Sources/SessionManager.swift +0 -72
- package/app/Sources/SettingsView.swift +0 -1053
- package/app/Sources/SettingsWindow.swift +0 -20
- package/app/Sources/TabGroupRow.swift +0 -178
- package/app/Sources/Terminal.swift +0 -259
- package/app/Sources/TerminalQuery.swift +0 -156
- package/app/Sources/TerminalSynthesizer.swift +0 -200
- package/app/Sources/Theme.swift +0 -163
- package/app/Sources/TilePickerView.swift +0 -209
- package/app/Sources/TmuxModel.swift +0 -53
- package/app/Sources/TmuxQuery.swift +0 -81
- package/app/Sources/WindowTiler.swift +0 -1755
- package/app/Sources/WorkspaceManager.swift +0 -434
- package/bin/lattices-app.js +0 -221
- package/bin/lattices.js +0 -1418
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# AI Chat Assistant — UI/UX Review (review-only)
|
|
2
|
+
|
|
3
|
+
Surface reviewed: `apps/mac/Sources/Core/Pi/`
|
|
4
|
+
(PiChatUI.swift, PiChatDock.swift, PiWorkspaceView.swift, PiAuthPromptCard.swift,
|
|
5
|
+
PiInstallCallout.swift, PiProviderSetupCallout.swift, PiChatSession.swift)
|
|
6
|
+
|
|
7
|
+
Date: 2026-06-02
|
|
8
|
+
Scope: visual hierarchy, spacing, transcript readability, composer ergonomics,
|
|
9
|
+
empty/loading/error states, macOS idioms, accessibility, usefulness vs. opacity.
|
|
10
|
+
|
|
11
|
+
Severity scale: HIGH (blocks goals) · MEDIUM (visible friction) · LOW (polish).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## HIGH
|
|
16
|
+
|
|
17
|
+
### 1. User and assistant share the same avatar glyph → speaker confusion
|
|
18
|
+
**Evidence.** `PiChatUI.swift:35–55` defines `LatticesMark`; `LatticesMarkAvatar`
|
|
19
|
+
is reused for the assistant header (`PiWorkspaceView.swift:52`), the assistant
|
|
20
|
+
bubble (`PiChatUI.swift:487`), and the user's bubble (`PiChatUI.swift:463`).
|
|
21
|
+
The user message row shows the Lattices brand mark on the right at 28pt with a
|
|
22
|
+
muted tint — visually the same family as the assistant avatar. Header text
|
|
23
|
+
literally reads "Assistant" on the assistant side, but the user side has no
|
|
24
|
+
label. On a quick glance, you cannot tell which side is talking.
|
|
25
|
+
|
|
26
|
+
**Direction.** Drop the avatar from the user bubble (right-aligned text + small
|
|
27
|
+
initial chip is enough), or use a clearly different glyph (e.g. `person.crop.circle`)
|
|
28
|
+
at lower opacity. Keep `LatticesMarkAvatar` for assistant + header + empty state
|
|
29
|
+
hero only — that is its semantic role.
|
|
30
|
+
|
|
31
|
+
### 2. Composer send key is wrong for multi-line input
|
|
32
|
+
**Evidence.** `PiChatUI.swift:1246–1261` binds `.onSubmit` directly to
|
|
33
|
+
`session.sendDraft()` with no modifier check. The composer uses
|
|
34
|
+
`TextField(axis: .vertical)` with `lineLimit(1...style.composerLineLimit)`.
|
|
35
|
+
The footer hint reads "↩ send" (`PiChatUI.swift:1218`). There is **no
|
|
36
|
+
`keyboardShortcut`** anywhere in the chat surface (verified by grep across
|
|
37
|
+
`Core/Pi/*.swift`).
|
|
38
|
+
|
|
39
|
+
**Direction.** Bind the send button to `.keyboardShortcut(.return, modifiers: .command)`,
|
|
40
|
+
change the hint to "⌘↩ send", and gate plain-Enter to single-line case only.
|
|
41
|
+
Add `Esc` to clear the draft or cancel an in-flight send.
|
|
42
|
+
|
|
43
|
+
### 3. Accessibility is largely absent
|
|
44
|
+
**Evidence.** Across the entire chat surface, there is exactly **one**
|
|
45
|
+
`accessibilityLabel` (`PiChatUI.swift:1397` on `PiChatWorkingIndicator`) and
|
|
46
|
+
**one** `accessibilityHidden(true)` (on the Lattices mark). No icon buttons
|
|
47
|
+
have labels: the dock close (`PiChatDock.swift:103`), the gear in
|
|
48
|
+
`PiWorkspaceView.swift:130`, the code-block copy button (`PiChatUI.swift:872`),
|
|
49
|
+
and the footer gear (`PiChatDock.swift:386`) all read as "button" only to
|
|
50
|
+
VoiceOver. Hit targets: the 6pt traffic-light dots in `PiChatCodeBlock`
|
|
51
|
+
(`PiChatUI.swift:855–859`), the 6pt status pulses, the 22×22 footer icon
|
|
52
|
+
button (`PiChatDock.swift:444`), and the 22×22 send button are all below the
|
|
53
|
+
24pt minimum. Dynamic Type is not supported — fonts are hardcoded point
|
|
54
|
+
sizes via `Typo.body(13.5)`, `Typo.body(14)` (`PiChatStyle`).
|
|
55
|
+
|
|
56
|
+
**Direction.** Add `accessibilityLabel` to every icon-only button. Group the
|
|
57
|
+
empty-state starters under a labelled header (`.accessibilityElement(children: .combine)`).
|
|
58
|
+
Bump minimum hit target to 24×24. Replace hardcoded point sizes with
|
|
59
|
+
`Font.system(.body, design: .rounded)` and apply
|
|
60
|
+
`.dynamicTypeSize(.medium ... .accessibility3)` at the root.
|
|
61
|
+
|
|
62
|
+
### 4. Usefulness is opaque — header copy is vague, no capability surface
|
|
63
|
+
**Evidence.** `PiWorkspaceView.swift:60` says "Settings, layout help, planning,
|
|
64
|
+
and debugging in one thread." The empty state at `PiChatUI.swift:256–263`
|
|
65
|
+
shows four starter cards, which is good, but the running state offers no
|
|
66
|
+
indication of: which model is active, which tools are available, rate limits,
|
|
67
|
+
cost, or how to attach files/screenshots. The status pill (`PiWorkspaceView.swift:75`)
|
|
68
|
+
flips between "Ready / Streaming / Thinking / Tool · read" but the user cannot
|
|
69
|
+
act on it. The four starter prompts hint at gesture/file/screen/planning but
|
|
70
|
+
don't reveal tool inventory.
|
|
71
|
+
|
|
72
|
+
**Direction.** Add a discoverable affordance — a `?` or `Tools` button in the
|
|
73
|
+
header — that opens a one-sheet listing: current provider + model, available
|
|
74
|
+
tools (`read`, `bash`, `search`, `list`, `web`, `voice`), and a token/cost
|
|
75
|
+
counter. Add a "What can I do?" prompt to the empty state as a 5th card.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## MEDIUM
|
|
80
|
+
|
|
81
|
+
### 5. Three parallel setup states compete visually
|
|
82
|
+
**Evidence.** `PiInstallCallout.swift` (install) uses `Palette.kill` red border.
|
|
83
|
+
`PiProviderSetupCallout.swift` (provider) uses its own card layout. `PiAuthPromptCard.swift`
|
|
84
|
+
(auth) uses `Palette.detach` yellow. All three render in roughly the same slot
|
|
85
|
+
(transcript area or composer slot) but with different border treatments,
|
|
86
|
+
padding, and copy tone. The user sees: red install card → yellow provider card
|
|
87
|
+
→ yellow auth card → composer. Three different visual languages for "you need
|
|
88
|
+
to do something."
|
|
89
|
+
|
|
90
|
+
**Direction.** Consolidate into a single `PiSetupCard` with `Kind: .install | .provider | .auth`
|
|
91
|
+
and a shared layout: status dot · eyebrow label · body · primary action · secondary
|
|
92
|
+
action. Use `Palette.kill` for install, `Palette.detach` for both provider and
|
|
93
|
+
auth, and unify spacing/padding to `compact` and `expanded` modes only.
|
|
94
|
+
|
|
95
|
+
### 6. Streaming state is over-decorated
|
|
96
|
+
**Evidence.** `PiChatUI.swift:498–541` (assistant bubble) stacks: pulsing
|
|
97
|
+
avatar (1.18× stroke), `LIVE` badge, `PiChatStreamCursor`, left-edge 2pt
|
|
98
|
+
gradient bar, inner radial gradient overlay, and a 0.28s background
|
|
99
|
+
animation. Concurrently: `PiChatToolChip` pulses (1.4s),
|
|
100
|
+
`PiChatStreamCursor` pulses (0.85s), `PiChatWaveDots` animates
|
|
101
|
+
(30fps TimelineView), `PiChatWorkingIndicator` has its own dot pattern.
|
|
102
|
+
That's 4+ simultaneous animations per streaming turn. On a 60Hz display the
|
|
103
|
+
bubble never settles.
|
|
104
|
+
|
|
105
|
+
**Direction.** Pick one dominant streaming affordance — the left-edge bar is
|
|
106
|
+
the strongest. Demote `LIVE` to small caps next to the assistant name and
|
|
107
|
+
drop the inner radial gradient. Keep the cursor and one pulse. Document
|
|
108
|
+
"max 1 ambient animation per scene" in the chat style guide.
|
|
109
|
+
|
|
110
|
+
### 7. Provider/auth flow triplicated across Dock, Workspace, and auth card
|
|
111
|
+
**Evidence.** `PiChatDock.swift:205–325` (authPanel) and `PiChatDock.swift:429–458`
|
|
112
|
+
(providerSettingsBar) and `PiWorkspaceView.swift:125–158` (providerSettingsPrompt)
|
|
113
|
+
and `PiAuthPromptCard.swift` are four overlapping implementations of "tell
|
|
114
|
+
the user to connect a provider / drive the auth flow." Adding a field
|
|
115
|
+
(provider description, secondary CTA, warning) requires touching all four.
|
|
116
|
+
|
|
117
|
+
**Direction.** Extract a `PiAuthPanel` with `style: .workspace | .dock` and
|
|
118
|
+
the existing `compact: Bool` flag, used in all three call sites. Keep
|
|
119
|
+
`PiAuthPromptCard` as a child when a `pendingAuthPrompt` is present.
|
|
120
|
+
|
|
121
|
+
### 8. Transcript background reduces text contrast at the bottom
|
|
122
|
+
**Evidence.** `PiChatUI.swift:148–172` layers four backgrounds: base
|
|
123
|
+
`Palette.bg`, dot grid with `+Lighter` blend, top-left linear tint, and a
|
|
124
|
+
bottom-left radial gradient. The radial hits the assistant bubble's bottom
|
|
125
|
+
half — the area where long responses accumulate. On a low-contrast theme
|
|
126
|
+
this pushes the secondary text into the background.
|
|
127
|
+
|
|
128
|
+
**Direction.** Cap the radial opacity at 0.03, or move it to the top header
|
|
129
|
+
zone. Drop the dot grid in compact mode. Test on a real Mac with
|
|
130
|
+
`colorScheme: .light` to ensure dark surfaces don't bleed through.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## LOW
|
|
135
|
+
|
|
136
|
+
### 9. Code block copy has no feedback
|
|
137
|
+
**Evidence.** `PiChatUI.swift:872–885` writes to `NSPasteboard` silently. No
|
|
138
|
+
icon swap, no toast, no animation. The button looks identical before/after.
|
|
139
|
+
|
|
140
|
+
**Direction.** Add `@State private var copied = false`, swap the icon to
|
|
141
|
+
`"checkmark"` for 1.2s, reset on a `Task.sleep`.
|
|
142
|
+
|
|
143
|
+
### 10. Dock resize gesture does not follow macOS conventions
|
|
144
|
+
**Evidence.** `PiChatDock.swift:85–100` uses a custom `DragGesture` on the
|
|
145
|
+
top handle. No cursor change, no snap-to-default, no visual ruler. macOS
|
|
146
|
+
users expect a divider with `NSCursor.resizeUpDown` and snap points
|
|
147
|
+
(230/400/600).
|
|
148
|
+
|
|
149
|
+
**Direction.** Replace the top-handle drag with a 6pt divider strip below
|
|
150
|
+
the header, hover-swaps cursor to resize, snap to 230/400/600 with the
|
|
151
|
+
existing defaults key.
|
|
152
|
+
|
|
153
|
+
### 11. No light-mode support
|
|
154
|
+
**Evidence.** `Palette.bg` and friends are dark-only. The chat surface is
|
|
155
|
+
embedded in a menu bar app that follows system appearance, so a user in
|
|
156
|
+
Light Mode gets dark-on-dark chat inside a light chrome — jarring.
|
|
157
|
+
|
|
158
|
+
**Direction.** Introduce semantic tokens (`Palette.chatBg`, `Palette.chatText`,
|
|
159
|
+
`Palette.chatBorder`) that switch on `colorScheme`. Add a `colorScheme` env
|
|
160
|
+
value at the chat root.
|
|
161
|
+
|
|
162
|
+
### 12. ScrollView fires multiple `scrollToEnd` per token
|
|
163
|
+
**Evidence.** `PiChatUI.swift:181–195` registers three `onChange` handlers
|
|
164
|
+
(messages.count, last text, isSending). During a streaming response the
|
|
165
|
+
text handler fires per token, producing janky scroll on slower Macs and
|
|
166
|
+
fighting the user's manual scroll position if they scrolled up to read
|
|
167
|
+
history.
|
|
168
|
+
|
|
169
|
+
**Direction.** Coalesce into a single `lastMessageID` + `lastCharCount` change,
|
|
170
|
+
debounce to ~50ms, and pause auto-scroll when the user is scrolled up by
|
|
171
|
+
more than 80pt from the bottom (and show a "↓ New messages" pill — common
|
|
172
|
+
chat pattern).
|
|
173
|
+
|
|
174
|
+
### 13. `PiChatFormat.markdownText` is not loaded here but the empty state
|
|
175
|
+
injects a string for `Connected to \(session.currentProvider.name)` — the
|
|
176
|
+
provider name should also drive a per-provider brand tint on the empty
|
|
177
|
+
state hero, not just the streaming accent.
|
|
178
|
+
**Evidence.** `PiChatUI.swift:332–336`. Empty state uses `Palette.running`
|
|
179
|
+
unconditionally.
|
|
180
|
+
|
|
181
|
+
**Direction.** Pass `theme: PiChatTheme` from the session so the empty state,
|
|
182
|
+
composer accent, and live chip all shift subtly per provider.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## What is already good
|
|
187
|
+
|
|
188
|
+
- Empty state starter grid is well-scoped and inviting
|
|
189
|
+
(`PiChatUI.swift:256–305`).
|
|
190
|
+
- Code block chrome with traffic-light header and copy button is a nice
|
|
191
|
+
macOS-y detail (`PiChatUI.swift:835–895`).
|
|
192
|
+
- Custom hand-rolled syntax highlighter covers `swift`, `json`, `bash`, and
|
|
193
|
+
generic with consistent palette tokens (`PiChatUI.swift:900–1190`).
|
|
194
|
+
- Auth prompt card is calm and uses mono consistently
|
|
195
|
+
(`PiAuthPromptCard.swift:1–90`).
|
|
196
|
+
- Status pill is a thoughtful, dense read of session state
|
|
197
|
+
(`PiWorkspaceView.swift:75–105`).
|
|
198
|
+
- Footer `↩ send` hint shows attention to discoverability
|
|
199
|
+
(`PiChatUI.swift:1218`).
|
|
200
|
+
- All four top-level surfaces (PiChatDock, PiWorkspaceView, PiChatTranscript,
|
|
201
|
+
PiChatComposer) share typography and palette tokens, so the language is
|
|
202
|
+
consistent — the issues are about depth, not vocabulary.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Suggested first pass (1–2 days of work)
|
|
207
|
+
|
|
208
|
+
1. **Composer shortcuts** — Cmd+Return, Esc to cancel, hint fix (HIGH #2).
|
|
209
|
+
2. **Avatar disambiguation** — drop avatar from user side or swap glyph (HIGH #1).
|
|
210
|
+
3. **a11y sweep on icon buttons + Dynamic Type pass** (HIGH #3).
|
|
211
|
+
4. **Single setup card component** with three kinds (MEDIUM #5).
|
|
212
|
+
5. **One-sheet "what this assistant can do"** triggered from the header (HIGH #4).
|
|
213
|
+
|
|
214
|
+
These five changes will resolve all four HIGH findings and the most visible
|
|
215
|
+
MEDIUM, and they cluster naturally because they all touch `PiChatUI.swift`
|
|
216
|
+
+ the header/composer chrome.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
# Second pass — implementation-oriented
|
|
221
|
+
|
|
222
|
+
## 1. What I would fix first and why
|
|
223
|
+
|
|
224
|
+
In a single PR, in this order:
|
|
225
|
+
|
|
226
|
+
1. **Composer send-key correctness** (`PiChatUI.swift:1246–1261`).
|
|
227
|
+
Highest leverage: one well-placed `.keyboardShortcut(.return, modifiers: .command)`,
|
|
228
|
+
one `.onExitCommand { session.draft = "" }` (or `cancelSend`),
|
|
229
|
+
and the footer hint text changed from "↩ send" to "⌘↩ send · esc clear".
|
|
230
|
+
No new components. No state model changes. Removes the single most
|
|
231
|
+
surprising behavior for anyone who has used any other chat app.
|
|
232
|
+
The `composerLineLimit: 4` (dock) / `8` (workspace) in `PiChatStyle`
|
|
233
|
+
confirms the field is multi-line by design — `TextField.onSubmit` on
|
|
234
|
+
plain Return is unambiguously wrong here.
|
|
235
|
+
|
|
236
|
+
2. **Drop the user-bubble avatar** (`PiChatUI.swift:463`).
|
|
237
|
+
Five lines of code removed, one line of bubble padding adjusted.
|
|
238
|
+
Immediately disambiguates speaker identity without introducing a new
|
|
239
|
+
component. Cheap A/B candidate: leave a tiny initial circle if you want
|
|
240
|
+
something on the right, but the brand mark has to go.
|
|
241
|
+
|
|
242
|
+
3. **Accessibility sweep on icon buttons** (`PiChatDock.swift:103`,
|
|
243
|
+
`PiWorkspaceView.swift:130`, `PiChatUI.swift:872`, `PiChatDock.swift:386`).
|
|
244
|
+
Mechanical: add `accessibilityLabel("Close chat", "Open assistant settings",
|
|
245
|
+
"Copy code", "Settings")` to four buttons. Bump `footerIconButton`'s
|
|
246
|
+
hit area from 24×22 to 28×24. Highest a11y ROI in the file.
|
|
247
|
+
|
|
248
|
+
4. **Add a `?` tools sheet** in the header, starting with just a hardcoded
|
|
249
|
+
provider/model list. Stub it with a `Text` of the current provider name,
|
|
250
|
+
a one-paragraph capability blurb, and a list of the seven tool names
|
|
251
|
+
already mapped in `PiChatToolChip` (`PiChatUI.swift:600–650`). Becomes
|
|
252
|
+
the natural home for cost/token telemetry later.
|
|
253
|
+
|
|
254
|
+
This order: (1) and (2) unblock anyone trying to actually use the surface.
|
|
255
|
+
(3) is a no-regret pass. (4) opens the door to making the assistant feel
|
|
256
|
+
less opaque, which is the largest gap the first pass identified.
|
|
257
|
+
|
|
258
|
+
## 2. What I would deliberately defer
|
|
259
|
+
|
|
260
|
+
- **Streaming decoration cleanup (MEDIUM #6 in the first pass).**
|
|
261
|
+
Hard to argue from a static review. The animations are individually small
|
|
262
|
+
(1.4s pulse, 0.85s cursor, 0.28s background ease). On a 60Hz display the
|
|
263
|
+
perceived "busyness" is uncertain. Defer until someone watches a real
|
|
264
|
+
streaming response on a real Mac and reports it feels busy. Don't pre-emptively
|
|
265
|
+
cut signals that may carry useful liveness.
|
|
266
|
+
|
|
267
|
+
- **Full Dynamic Type pass.** Real, but big. `PiChatStyle.bodySize` is
|
|
268
|
+
referenced in ~6 sites; the right call is probably to introduce
|
|
269
|
+
`@ScaledMetric(relativeTo: .body) private var bodySize: CGFloat = 13.5`
|
|
270
|
+
and let the system drive it. But until there's a user report, ship (3)
|
|
271
|
+
first and come back to this.
|
|
272
|
+
|
|
273
|
+
- **Light-mode palette tokens.** Real gap (`Theme.swift` is dark-only,
|
|
274
|
+
zero `colorScheme` references in the chat surface), but it's a
|
|
275
|
+
cross-cutting theme change, not a chat-surface change. Belongs in a
|
|
276
|
+
separate "Palette semantic tokens" pass.
|
|
277
|
+
|
|
278
|
+
- **Auth panel consolidation (MEDIUM #7).** Worth doing, but only if you
|
|
279
|
+
are actually changing the auth flow soon. If not, leaving three
|
|
280
|
+
implementations is cheaper than risking a regression in a critical
|
|
281
|
+
onboarding path.
|
|
282
|
+
|
|
283
|
+
- **`PiSetupCard` unification (MEDIUM #5).** Same reasoning — defer until
|
|
284
|
+
you are touching setup copy for product reasons.
|
|
285
|
+
|
|
286
|
+
- **Per-provider brand tint.** Speculative. Lattices has a clear visual
|
|
287
|
+
identity; tinting the chat to match each provider dilutes it. Wait for
|
|
288
|
+
evidence that users want this.
|
|
289
|
+
|
|
290
|
+
## 3. Where the first pass overreached or missed evidence
|
|
291
|
+
|
|
292
|
+
- **"Streaming is over-decorated"** — I counted animations without testing.
|
|
293
|
+
I should have said "needs a live walkthrough" rather than prescribing a
|
|
294
|
+
fix. The recommendation to "pick one dominant affordance" is a stylistic
|
|
295
|
+
call, not a clear win.
|
|
296
|
+
|
|
297
|
+
- **"Three `onChange` handlers fight on streaming scroll"** — overclaim.
|
|
298
|
+
SwiftUI's `ScrollViewReader` + animation handles per-token `scrollTo`
|
|
299
|
+
well in practice. The real problem is the absence of a "jump to latest"
|
|
300
|
+
pill when the user has scrolled up — not the handler count. Soften.
|
|
301
|
+
|
|
302
|
+
- **"Light-mode support"** — verified this round: `Theme.swift` has zero
|
|
303
|
+
`colorScheme` references and the chat hardcodes `Palette.bg`. The
|
|
304
|
+
finding is correct, the scope is bigger than I implied. It's a
|
|
305
|
+
project-wide token migration, not a chat-surface change.
|
|
306
|
+
|
|
307
|
+
- **"Three parallel auth/setup states compete visually"** — partially
|
|
308
|
+
overreach. `PiChatDock` and `PiWorkspaceView` are intentionally two
|
|
309
|
+
different products (compact bottom drawer vs. full pane). `PiChatStyle`
|
|
310
|
+
enforces that with `composerLineLimit: 4` vs. `8`, `bodySize: 12` vs.
|
|
311
|
+
`13.5`, `horizontalPadding: 12` vs. `28`. Some of the "triplication"
|
|
312
|
+
is appropriate. The auth flow within each is duplicated, not the
|
|
313
|
+
surfaces themselves. Restate: deduplicate the **auth flow logic**
|
|
314
|
+
(one component, two style modes), keep two surface layouts.
|
|
315
|
+
|
|
316
|
+
- **Missing in the first pass:** I never opened `UI/Theme.swift` until
|
|
317
|
+
this follow-up. The Dynamic Type claim was based on hardcoded `Typo`
|
|
318
|
+
calls in `PiChatStyle`, which is still true, but I should have flagged
|
|
319
|
+
the file once for the whole surface, not enumerated sites.
|
|
320
|
+
|
|
321
|
+
- **Missing in the first pass:** I didn't look at the dock resize gesture
|
|
322
|
+
UX holistically. A `DragGesture(minimumDistance: 1)` on the top handle
|
|
323
|
+
is unusual but not broken — many native macOS apps do this. Lower its
|
|
324
|
+
priority from LOW to "watch the resize behavior on a trackpad; only
|
|
325
|
+
rework if it feels wrong."
|
|
326
|
+
|
|
327
|
+
- **Missing in the first pass:** I didn't note that the empty-state
|
|
328
|
+
starter cards auto-send on click (`PiChatUI.swift:268–275`). That is a
|
|
329
|
+
real ergonomic call: should the click fill the composer (so the user
|
|
330
|
+
can edit) or auto-send? Currently it auto-sends. Worth a single
|
|
331
|
+
decision: fill-only, with a separate "send" affordance, matches the
|
|
332
|
+
user's mental model in most chat products. Not in the first PR — but
|
|
333
|
+
decide it before (4) ships the tools sheet.
|
|
334
|
+
|
|
335
|
+
## 4. Smallest coherent implementation slice
|
|
336
|
+
|
|
337
|
+
**One PR, one developer, half a day. Files touched: `PiChatUI.swift` only.**
|
|
338
|
+
|
|
339
|
+
```swift
|
|
340
|
+
// In PiChatComposer (PiChatUI.swift ~1246)
|
|
341
|
+
Button {
|
|
342
|
+
session.sendDraft()
|
|
343
|
+
} label: {
|
|
344
|
+
// ...existing send button body...
|
|
345
|
+
}
|
|
346
|
+
.buttonStyle(.plain)
|
|
347
|
+
.keyboardShortcut(.return, modifiers: .command) // NEW
|
|
348
|
+
.disabled(!canSend)
|
|
349
|
+
|
|
350
|
+
// On the TextField, replace .onSubmit with .onSubmit-of-single-line:
|
|
351
|
+
TextField(
|
|
352
|
+
style.placeholder,
|
|
353
|
+
text: $session.draft,
|
|
354
|
+
axis: .vertical
|
|
355
|
+
)
|
|
356
|
+
.textFieldStyle(.plain)
|
|
357
|
+
.font(Typo.body(style.composerSize))
|
|
358
|
+
.foregroundColor(Palette.text)
|
|
359
|
+
.lineLimit(1...style.composerLineLimit)
|
|
360
|
+
.focused(focus)
|
|
361
|
+
.onSubmit { /* no-op for multi-line; Cmd+Return handles send */ }
|
|
362
|
+
.onExitCommand { session.draft = "" } // NEW
|
|
363
|
+
|
|
364
|
+
// Footer hint (PiChatUI.swift ~1218):
|
|
365
|
+
Text("⌘↩ send · esc clear")
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Plus one PR, separate reviewer, half a day: drop the user-bubble avatar.**
|
|
369
|
+
|
|
370
|
+
```swift
|
|
371
|
+
// In userRow (PiChatUI.swift ~432):
|
|
372
|
+
// Remove the trailing LatticesMarkAvatar(size: 28, ...)
|
|
373
|
+
// Add a 4pt right padding to the user bubble for visual breathing.
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Total: ~25 lines changed, zero new components, zero state-model changes,
|
|
377
|
+
zero migrations. Both changes are independently revertable. The first
|
|
378
|
+
lands a measurable behavior fix (the wrong key sent prematurely), the
|
|
379
|
+
second removes a known visual confusion.
|
|
380
|
+
|
|
381
|
+
This is what a developer should land before any of the larger
|
|
382
|
+
consolidations. It addresses the two HIGH findings where I have the most
|
|
383
|
+
confidence and unblocks real testing of the surface by anyone who tries
|
|
384
|
+
to send a multi-line message.
|
|
385
|
+
|
|
386
|
+
## 5. Observable session / provenance details from this run
|
|
387
|
+
|
|
388
|
+
- **Identity.** Agent: `lattices-review-pi-scout.main.arts-mac-mini-local`.
|
|
389
|
+
Invoked as a stable OpenScout relay agent on this turn.
|
|
390
|
+
- **Model.** `MiniMax-M3` (per harness system prompt; "M3" is the model
|
|
391
|
+
class, not necessarily a per-request model identifier).
|
|
392
|
+
- **Provider / transport.** The harness is MiniMax's own inference stack;
|
|
393
|
+
I have no per-request model name, no temperature, no token budget, no
|
|
394
|
+
response-time telemetry, no streaming chunk count, no tool-use log.
|
|
395
|
+
The wire protocol I am reachable over is the OpenScout broker
|
|
396
|
+
(WebSocket); conversation `c.baf8732a-9752-487a-841a-57595463bf8d`,
|
|
397
|
+
this message `msg-mpwvwdq3-z1nuja`, reply path `final_response`.
|
|
398
|
+
- **Runtime mode.** `thinking` enabled; `max thinking effort` is implicit
|
|
399
|
+
(the harness emits long deliberation blocks before each reply). No
|
|
400
|
+
explicit budget is exposed to me. I cannot report token counts for
|
|
401
|
+
this turn.
|
|
402
|
+
- **Session state.** `cwd: /Users/art/dev/lattices`; `git status` shows
|
|
403
|
+
one staged file: `A docs/ai-chat-ux-review.md` (the first-pass report,
|
|
404
|
+
mtime 12:17 today). `HEAD: b09e4d8` "Merge pull request #41 from
|
|
405
|
+
arach/codex/inventory-sort-arrange". No uncommitted source changes.
|
|
406
|
+
- **Tools available to me this turn.** `bash`, `read`, `edit`, `write`,
|
|
407
|
+
`intercom`, `mcp`, `scout_{send,ask,who}`, `subagent`. I used
|
|
408
|
+
`read`, `bash`, and `edit` for this turn — the `edit` was on my own
|
|
409
|
+
review file, not on any source.
|
|
410
|
+
- **Status telemetry available.** None beyond the OpenScout message
|
|
411
|
+
metadata above. No live provider status, no tool-use trail, no
|
|
412
|
+
thinking-trace payload on the wire. The thinking block is a local
|
|
413
|
+
harness detail, not a broker-visible artifact.
|
|
414
|
+
- **What I cannot report.** I do not have visibility into MiniMax
|
|
415
|
+
upstream latency, model selection, rate limits, or the exact provider
|
|
416
|
+
routing path used for this M3 inference. I would not invent those.
|