macOS client panes and transcript
This commit is contained in:
+7
-6
@@ -1,6 +1,6 @@
|
||||
# byre for macOS
|
||||
|
||||
This is a design. What exists so far under `mac/` is the layer beneath the views: the generated API client, the SSE client, `Store` and `Feed`, the startup sequence, the connection screen with Change Server…, and the generated palette. Once started, the window shows the spike harness from the "Gaps in SwiftUI" checks (composer, transcript, terminal, font, SVG tabs), now driven by `Store`: its task picker lists the active tasks and sets focus, and its transcript tab reads the selected task's `Feed`. The panes replace it. The rest of the work is under "M8 — macOS client" in `TODO.md`.
|
||||
This is a design, partly built. What exists under `mac/`: the generated API client, the SSE client, `Store` and `Feed`, the startup sequence, the connection screen with Change Server…, the generated palette, the window with its two panes and tab strips (More included), the Lead and task transcripts through `TranscriptBuilder`, the composer with drafts and images, the Markdown port, native flowchart diagrams, pane-focus reporting and the identity banner. Overlays so far are alerts, the More menu and the full-size image view. The other tabs (Changes, Files, Terminal, Inbox, Reviews, Workflow, Project, Status, Settings) show `<tab> isn't in the macOS app yet.`. Until the switcher exists, the task picker is a plain menu of the active tasks, and the transcript head has Compact and Sync but not yet Merge or Discard. The rest of the work is under "M8 — macOS client" in `TODO.md`.
|
||||
|
||||
## Decision
|
||||
|
||||
@@ -59,7 +59,8 @@ The Swift files follow the TypeScript ones, so a change in one points at the sam
|
||||
| `switcher.ts` | `Switcher.swift` | ⌘K overlay and the shortcuts help |
|
||||
| `drafts.ts` | `Drafts.swift` | Composer and answer drafts |
|
||||
| `notifications.ts` | `Notifications.swift` | `UNUserNotificationCenter` delivery |
|
||||
| `dom.ts` | `Dialogs.swift`, `Format.swift` | Alert and confirm overlays, `ago`, byte sizes, Copy |
|
||||
| `dom.ts` | `Dialogs.swift`, `Format.swift` | Overlays (alerts, the More menu, the image view; confirm to come), `ago`, Copy, shared button and layout styles |
|
||||
| `images.ts` | `Images.swift` | Blob, worktree and `data:` images, their cache and the full-size overlay |
|
||||
|
||||
## Store and events
|
||||
|
||||
@@ -73,7 +74,7 @@ API calls go through the generated `Client` over `URLSessionTransport`. A middle
|
||||
|
||||
`EventStream` is a hand-written SSE client over `URLSession.bytes(for:)`. It splits lines itself, because `AsyncBytes.lines` drops the blank lines that end SSE events, and parses off the main actor, handing each frame to it. It handles `id`, `event: ping`, `event: ready` and `data`, and follows `subscribe` in `api.ts`: it tracks the last seq, reconnects from it after the same stale threshold (40 s, checked every 5 s) and retry delay (2 s), when the network comes back (`NWPathMonitor`), and when the app becomes active with a stale stream. An event whose data doesn't decode is logged and skipped. As in the web client there are two streams, global (after the bootstrap `last_event_seq`) and lead (`?lead=true`, after the last event of the lead's first page), and only the global one drives the connection dot.
|
||||
|
||||
Startup follows `main` in `app.ts`: `GET /api/admin/status`, then me, projects, inbox, lead sessions and summary reads in parallel, then drafts, focus, the lead stream, the window, the global stream. A failure before the window shows replaces it with `byre failed to start: <error>`, as the web page does; Change Server… leaves that screen. Drafts are not loaded yet; they come with the composer. Likewise `handleEvent` doesn't call `notifyInbox` or `notifyTurn` until `Notifications.swift` exists.
|
||||
Startup follows `main` in `app.ts`: `GET /api/admin/status`, then me, projects, inbox, lead sessions and summary reads in parallel, then drafts, focus, the lead stream, the window, the global stream. A failure before the window shows replaces it with `byre failed to start: <error>`, as the web page does; Change Server… leaves that screen. `handleEvent` doesn't call `notifyInbox` or `notifyTurn` until `Notifications.swift` exists.
|
||||
|
||||
The app logs through `os.Logger` with subsystem `run.fc.byre.mac`, as the web client logs to the console: `log show --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` prints it.
|
||||
|
||||
@@ -150,11 +151,11 @@ The app menu has About, Change Server…, Hide, Hide Others and Quit; Edit has t
|
||||
|
||||
## Connection
|
||||
|
||||
On first launch the window shows one field, Server URL (an `https` origin with no path), and Connect. Connect calls `GET /api/admin/status` and explains a failure: an untrusted certificate, an unreachable server ("Is Tailscale connected?"), 401 (no Tailscale identity: use the `ts.net` HTTPS address), 403 (the request didn't come through `tailscale serve`); anything else shows the error's own text. Certificate validation is never relaxed. On success the origin, normalized to a lower-case host with any port, is kept in the `byre.server` default and startup runs; later launches start against it directly. Change Server… (in the app menu) flushes drafts, reports pane focus false, drops the store and streams, and shows the connection screen again, its field holding the current origin. Drafts and pane focus don't exist yet, so for now it only drops the store and streams.
|
||||
On first launch the window shows one field, Server URL (an `https` origin with no path), and Connect. Connect calls `GET /api/admin/status` and explains a failure: an untrusted certificate, an unreachable server ("Is Tailscale connected?"), 401 (no Tailscale identity: use the `ts.net` HTTPS address), 403 (the request didn't come through `tailscale serve`); anything else shows the error's own text. Certificate validation is never relaxed. On success the origin, normalized to a lower-case host with any port, is kept in the `byre.server` default and startup runs; later launches start against it directly. Change Server… (in the app menu) flushes drafts, reports pane focus false, drops the store and streams, and shows the connection screen again, its field holding the current origin. Quitting does the same flush and report, waiting up to 2 s for the report before exiting (`applicationShouldTerminate` in `ByreApp.swift`).
|
||||
|
||||
## Gaps in SwiftUI
|
||||
|
||||
Places where SwiftUI alone may not match the web client. Each is checked in the spike before the rest is built, and the fallback is used only if the check fails. The spike's tabs put the SwiftUI way and the fallback side by side and log each key press, paste, drop and upload they see:
|
||||
Places where SwiftUI alone may not match the web client, each checked side by side against its fallback; the fallback is used only where the check failed:
|
||||
|
||||
| Need | SwiftUI | Fallback | Spike result |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -166,7 +167,7 @@ Places where SwiftUI alone may not match the web client. Each is checked in the
|
||||
| Font from `web/fonts/` | Core Text | Convert the files to TTF in the build script if Core Text refuses them | No fallback: Core Text reads the woff2 files and their `wght` axis (200–800) |
|
||||
| SVG images from the worktree image endpoint | `NSImage(data:)` | Show the "image not available" text | No fallback: `NSImage(data:)` decodes the SVG from the worktree image endpoint (`_NSSVGImageRep`) and draws it sharp at any size |
|
||||
|
||||
The font files need one thing beyond loading. The latin and latin-ext files are subsets of one variable font with the same PostScript names, so registering both and looking the font up by name finds only the latin file, and characters such as `ő` and `Ł` fall back to a system font. The app instead makes a descriptor from each file's URL, sets the weight with `CTFontDescriptorCreateCopyWithVariation`, and puts the latin-ext descriptor in the latin one's `kCTFontCascadeListAttribute`; characters in neither file then fall back to the system cascade (`→` to Menlo). `Fonts.swift` in the spike does this. SwiftUI gets the result through `Font(CTFont)`.
|
||||
The font files need one thing beyond loading. The latin and latin-ext files are subsets of one variable font with the same PostScript names, so registering both and looking the font up by name finds only the latin file, and characters such as `ő` and `Ł` fall back to a system font. The app instead makes a descriptor from each file's URL, sets the weight with `CTFontDescriptorCreateCopyWithVariation`, and puts the latin-ext descriptor in the latin one's `kCTFontCascadeListAttribute`; characters in neither file then fall back to the system cascade (`→` to Menlo). `Fonts.swift` does this. SwiftUI gets the result through `Font(CTFont)`.
|
||||
|
||||
## Differences from the web client
|
||||
|
||||
|
||||
Reference in New Issue
Block a user