macOS client parity review
This commit is contained in:
+9
-6
@@ -58,7 +58,7 @@ The Swift files follow the TypeScript ones, so a change in one points at the sam
|
||||
| `terminal.ts` | `Terminal.swift` | The terminal view and its WebSocket |
|
||||
| `reviews.ts` | `Reviews.swift` | Review cards, finding diffs, Fix and Ignore |
|
||||
| `workflow.ts` | `Workflow.swift` | Stage graph and attempts |
|
||||
| `project.ts`, `todolink.ts` | `Project.swift`, `TodoLink.swift` | Project tab and TODO links |
|
||||
| `project.ts`, `todolink.ts` | `Project.swift`, `MarkdownView.swift`, `Store.swift` | Project tab and TODO links |
|
||||
| `status.ts`, `settings.ts` | `Status.swift`, `Settings.swift` | Left-pane tabs |
|
||||
| `switcher.ts` | `Switcher.swift` | ⌘K overlay, the shortcuts help and the Go actions (`runGo`) |
|
||||
| `drafts.ts` | `Drafts.swift` | Composer and answer drafts |
|
||||
@@ -82,7 +82,7 @@ Startup follows `main` in `app.ts`: `GET /api/admin/status`, then me, projects,
|
||||
|
||||
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.
|
||||
|
||||
There is no URL hash. The web client mirrors focus into `#/task/<id>`; the app keeps focus in `Store` only, and in-app task links call `focusTask` directly. Focus stays shared through the server, so the app and a browser follow each other as two browser tabs do.
|
||||
There is no URL hash. The web client mirrors focus into `#/task/<id>`; the app keeps focus in `Store` only. In-app `#/` links go through `Store.route`, a port of `route` in `app.ts`: `#/task/<id>/<tab>` opens that tab, `#/task/<id>` and `#/project/<id>` keep the current tab, and anything else clears focus. Focus stays shared through the server, so the app and a browser follow each other as two browser tabs do.
|
||||
|
||||
## Window and layout
|
||||
|
||||
@@ -107,11 +107,11 @@ Each view does what its TypeScript counterpart does, with the same text, refresh
|
||||
- **Changes, Files, Reviews, Workflow, Project, Status, Settings.** Ports of their files, including the pinned-snapshot stepper, open-diff memory, per-task place in Files, finding diffs with Fix and Ignore, nested subtasks, TODO Start and Discuss, rate-limit pace bars and lead notes. The Workflow tab draws its stage graph natively from the `WorkflowRun` (stages as boxes coloured by state, `after` as edges, laid out top to bottom by depth) with the layout and drawing code from "Diagrams", fed from the run's data rather than Mermaid text.
|
||||
- **Terminal.** A terminal view attached to `GET /api/tasks/{taskId}/terminal` with `URLSessionWebSocketTask`, following `terminal.ts`: binary input and output, `TerminalSize` JSON on resize, close code 4000 for `shell ended: <status>` with New shell, 1011 for a start failure, reconnect after the same delay while the task is active, one socket per open tab and selected task. Colours come from the palette as in the web client. Ctrl combinations typed in it go to the shell.
|
||||
- **Switcher.** The ⌘K overlay, also opened by clicking the task picker, centred below the picker: the same ordering (tasks with inbox items first, then least recently visited, the selected task last), the `name: title` project prefix, New task, Start in …, Start workflow… and its second step, projects, Escape stepping back, and the list scrolling to keep the highlighted row in view. Picking a task opens its Inbox when it has items, its unread summary when it has one, and its Transcript otherwise.
|
||||
- **Merge and Discard.** The transcript head's Merge button is enabled once `GET /api/tasks/{taskId}/unmerged` reports commits or files, refetched 1.5 s after a tool result or turn end, and labelled and disabled as in `taskHead`. `confirmMerge` (`MergeDialog.swift`) is ported whole: the editable commit title (at most 72 characters, kept across a warning link's detour), co-authors, warnings that refetch on the task's snapshot and review events and link to the Reviews or Inbox tab, the parent-or-default-branch choice for subtasks, and Merge now / After review while the latest snapshot's review is running. It sits just below the Merge button, or centred when ⌘S opens it with the button off screen. Discard confirms below its button, then clears the focused task.
|
||||
- **Merge and Discard.** The transcript head's Merge button is enabled once `GET /api/tasks/{taskId}/unmerged` reports commits or files, refetched 1.5 s after a tool result or turn end, and labelled and disabled as in `taskHead`. `confirmMerge` (`MergeDialog.swift`) is ported whole: the editable commit title (at most 72 characters, kept across a warning link's detour), co-authors, warnings that refetch on the task's snapshot and review events and link to the Reviews or Inbox tab, the parent-or-default-branch choice for subtasks, and Merge now / After review while the latest snapshot's review is running. It is centred in the right pane. Discard confirms below its button, then clears the focused task.
|
||||
|
||||
## Markdown
|
||||
|
||||
`Markdown.swift` ports `markdown.ts` line for line into a block tree (paragraph, heading, list, choice list, choice grid, quote, rule, table, code, diff, details, diagram, image), because byre's dialect (choice markers, `<details>` sections, `#/task/`, `#/todo/` and `#/suggest/` links, worktree image paths, `NO_REPLY`) matches no library. Inline content becomes an `AttributedString` with code, strong, emphasis and links. Links: `http` and `https` open in the default browser; `#/task/<id>` focuses the task; `#/todo/<project>/<title>` becomes the TODO link with its start and discuss actions or the task's state; `#/suggest/<project>/<title>/<brief>` becomes the same, its start creating the task with the brief and its discuss messaging the lead; anything else is plain text.
|
||||
`Markdown.swift` ports `markdown.ts` line for line into a block tree (paragraph, heading, list, choice list, choice grid, quote, rule, table, code, diff, details, diagram, image), because byre's dialect (choice markers, `<details>` sections, `#/task/`, `#/todo/` and `#/suggest/` links, worktree image paths, `NO_REPLY`) matches no library. Inline content becomes an `AttributedString` with code, strong, emphasis and links. Links: `http` and `https` open in the default browser; `#/todo/<project>/<title>` becomes the TODO link with its start and discuss actions or the task's state; `#/suggest/<project>/<title>/<brief>` becomes the same, its start creating the task with the brief and its discuss messaging the lead; any other `#/` link goes through `Store.route` (see "Store and events"); anything else is plain text. The TODO actions are link text in the selectable run, with the web's tooltips; while one runs both lose their link and the clicked one reads "starting…" (or "sending…" for a suggestion's discuss). The web draws them as small bordered buttons.
|
||||
|
||||
## Diagrams
|
||||
|
||||
@@ -153,7 +153,7 @@ Images load through the API: `GET /api/blobs/{id}`, `GET /api/tasks/{taskId}/ima
|
||||
|
||||
## Keyboard and menus
|
||||
|
||||
The app menu has About, Change Server…, Hide, Hide Others and Quit; Edit has the standard items, with Cut, Copy, Paste and Select All being the app's own commands so Paste can attach images (see "Gaps in SwiftUI"); a **Go** menu has Switch Task ⌘K, Project ⌘P, Lead ⌘L, Merge ⌘S and Keyboard Shortcuts ⌘/; View has Enter Full Screen; Window has the standard items. There is no File menu. The Go commands are disabled while an overlay is open, as `bindShortcuts` ignores them while a dialog is open. The Ctrl variants the web client also accepts (Ctrl-K, P, L, S and /) run the same actions from the key monitor in `Overlays`, except while the terminal's `TerminalView` is the first responder (`terminalFocused`), where they go to the shell. ⌘ combinations always reach the menu, the terminal included. Escape closes the open overlay.
|
||||
The app menu has About, Change Server…, Hide, Hide Others and Quit; Edit has the standard items, with Cut, Copy, Paste and Select All being the app's own commands so Paste can attach images (see "Gaps in SwiftUI"); a **Go** menu has Switch Task ⌘K, Project ⌘P, Lead ⌘L, Merge ⌘S and Keyboard Shortcuts ⌘/; View has Enter Full Screen; Window has the standard items. There is no File menu. The Go commands are disabled while an alert or modal is open (`dialogOpen`), as `bindShortcuts` ignores them while a dialog is open; the switcher and the More menu don't block them, and ⌘L closes the switcher so the Lead composer gets the keys. The Ctrl variants the web client also accepts (Ctrl-K, P, L, S and /) run the same actions from the key monitor in `Overlays`, except while the terminal's `TerminalView` is the first responder (`terminalFocused`), where they go to the shell. ⌘ combinations always reach the menu, the terminal included. Escape closes the open overlay.
|
||||
|
||||
## Connection
|
||||
|
||||
@@ -161,7 +161,7 @@ On first launch the window shows one field, Server URL (an `https` origin with n
|
||||
|
||||
## Persistence
|
||||
|
||||
`Persistence.swift` holds the app's state in SwiftData, synced through CloudKit to the private database of container `iCloud.run.fc.byre.mac`, so it follows the iCloud account to every Mac: `ServerRecord` (the server origin), `DraftRecord` (drafts, see "Drafts") and `VisitRecord` (one per task: when it was last selected, read by the task switcher). CloudKit has no unique constraints, so two Macs can each create a record for the same thing; readers take the newest `ServerRecord` and `DraftRecord` and the latest `VisitRecord` per task, and the next write keeps one and deletes the rest. The store opens at launch and the app exits if it can't, which is what an unsigned build without the iCloud entitlement does. 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`).
|
||||
`Persistence.swift` holds the app's state in SwiftData, synced through CloudKit to the private database of container `iCloud.run.fc.byre.mac`, so it follows the iCloud account to every Mac: `ServerRecord` (the server origin), `DraftRecord` (drafts, see "Drafts") and `VisitRecord` (one per task: when it was last selected, read by the task switcher). CloudKit has no unique constraints, so two Macs can each create a record for the same thing; readers take the newest `ServerRecord` and `DraftRecord` and the latest `VisitRecord` per task, and the next write keeps one and deletes the rest. The store is `byre.store` in `run.fc.byre.mac` under Application Support (the app isn't sandboxed, so SwiftData's default `default.store` would be shared with every other unsandboxed app). It opens at launch and the app exits if it can't, which is what an unsigned build without the iCloud entitlement does. 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
|
||||
|
||||
@@ -192,6 +192,9 @@ Platform differences the copy can't avoid, each with what the web client does an
|
||||
| Clicking a transcript image | Opens the image in a new browser tab | Opens it in an in-window overlay |
|
||||
| Preview links (`expose_port`) | Open in a new browser tab | Open in the default browser |
|
||||
| Window frame | Browser chrome and tabs | One macOS window with a standard title bar |
|
||||
| Merge dialog | Just below the Merge button | Centred in the right pane |
|
||||
| TODO and suggestion link actions | Small bordered **start** and **discuss** buttons | Accent link text, so the line stays selectable |
|
||||
| Pasted or dropped images | PNG, JPEG, GIF and WebP; anything else is refused | The same four, and any other image `NSBitmapImageRep` decodes, such as TIFF, is converted to PNG, its size checked after conversion |
|
||||
| Selecting transcript text | A drag selects across any number of messages | A drag selects within one run of text in one message, ending at a code block, table, quote, diagram or `<details>`; each message is copied on its own |
|
||||
|
||||
## Building and checking
|
||||
|
||||
Reference in New Issue
Block a user