away button placement
This commit is contained in:
+2
-2
@@ -167,7 +167,7 @@ Clients reach `tailscale serve` over HTTP/2; it proxies to byre over HTTP/1.1 an
|
||||
|
||||
**Building.** `mac/bundle.sh`, from any directory, builds the release configuration with `swift build` in `~/Library/Caches/byre/build`, one build directory every worktree shares and takes turns in (`docs/macos.md`, "Stack", says why), and assembles `mac/.build/Byre.app`: the `Byre` executable in `Contents/MacOS`; the server, built with `go build ./cmd/byre`, `byre-hands`, built from `hands/`, and `byre-update`, the `mac/` package's second executable, in `Contents/Helpers` (not beside `Byre`: the default APFS volume ignores case, so `byre` and `Byre` would be one file); Sparkle's framework in `Contents/Frameworks`; `mac/Info.plist` with a `ByreCommit` key added holding the worktree's `git rev-parse HEAD`, `CFBundleShortVersionString` its `git describe --tags --always` without the leading `v` (`0.1.0` on a release tag, `0.1.0-12-gabc1234` after it) and `CFBundleVersion` its `git rev-list --count HEAD` (the About panel shows them as "Version <version> (<count>)", and Sparkle compares the count); the `mac/fonts/` woff2 files, an icon rendered from `mac/icon.svg` with `rsvg-convert` and `iconutil` (cached as `mac/.build/byre.icns` until the SVG changes) and `mac/byre.provisionprofile`. It signs each piece with the Developer ID Application certificate, a secure timestamp and the hardened runtime, innermost first: `byre-hands` as `run.fc.byre.hands` and the server as `run.fc.byre`, both with `mac/automation.entitlements` (Apple Events, which the hardened runtime otherwise refuses), Sparkle's `Autoupdate`, `Updater.app` and framework (its XPC services, only needed by sandboxed apps, are removed), `byre-update` as `run.fc.byre.update`, then the bundle with `mac/byre.entitlements`. It prints the bundle's path. It needs Xcode selected (`xcode-select --print-path` prints `/Applications/Xcode.app/Contents/Developer`), Go, `rsvg-convert` (Homebrew's `librsvg`), the team's Developer ID Application certificate and network access for the timestamp, and it stops first unless `mac/byre.provisionprofile` has `ProvisionsAllDevices` (a Developer ID profile; a development profile can't authorize the Developer ID signature); `docs/macos.md`, "Signing", covers the certificate and profile. Debug builds are refused by an `#error` in `ByreApp.swift`. The first build on a Mac fetches the Swift packages pinned in `mac/Package.resolved` and compiles them and the generated API client; later builds, from any worktree, recompile only the targets whose sources differ from the last build's, usually just `Byre`. The API client is generated from `api/openapi.yaml` at build time. The palette is `mac/Sources/Byre/Palette.swift`, one light and one dark value per colour.
|
||||
|
||||
**Installing.** A release is installed from its disk image into `/Applications` (see "Releases") and updates itself (`docs/macos.md`, "Updates"). A deploy installs its build there the same way (see "Releases"). A build from a checkout can also run from where it was built, `open mac/.build/Byre.app` in the worktree that built it, after quitting the running app (`pkill -x Byre` quits every running copy). Every build shares the bundle identifier `run.fc.byre.mac` and the same store, so any build reconnects to the servers the last one saved, switched between with the window's server picker in its compact toolbar, which ends with the Away button (see "Away"). Closing the window leaves the app running for notifications; ⌘Q quits it.
|
||||
**Installing.** A release is installed from its disk image into `/Applications` (see "Releases") and updates itself (`docs/macos.md`, "Updates"). A deploy installs its build there the same way (see "Releases"). A build from a checkout can also run from where it was built, `open mac/.build/Byre.app` in the worktree that built it, after quitting the running app (`pkill -x Byre` quits every running copy). Every build shares the bundle identifier `run.fc.byre.mac` and the same store, so any build reconnects to the servers the last one saved, switched between with the window's server picker in its compact toolbar, which has the Away button at its right end (see "Away"). Closing the window leaves the app running for notifications; ⌘Q quits it.
|
||||
|
||||
**Storage.** The app keeps three kinds of record in SwiftData (`mac/Sources/Byre/Persistence.swift`): each saved server's origin and window colour, when each task was last selected, and composer and answer drafts as JSON, one record per server and login (`docs/macos.md`, "Persistence" and "Drafts"). The store is `~/Library/Application Support/run.fc.byre.mac/byre.store`, with its `-shm` and `-wal` files and CloudKit's working files beside it in that directory; the app isn't sandboxed, so it names its own file rather than SwiftData's default `default.store`, which every unsandboxed app shares. The store syncs through the iCloud private database of container `iCloud.run.fc.byre.mac`, in CloudKit's Production environment (`com.apple.developer.icloud-container-environment` in `mac/byre.entitlements`). Production never creates fields itself: a record carrying an attribute its schema lacks is rejected, and that one rejection stalls every later upload from that Mac (its `ANSCKEVENT` table shows exports failing with `CKErrorDomain` 2), until the app relaunches after the schema is fixed. The app never writes to Development either, so a change to the SwiftData models must be added there by hand before it is deployed or released: `xcrun cktool export-schema --team-id <team> --container-id iCloud.run.fc.byre.mac --environment development --output-file <file>` (`<team>` is `TeamIdentifier` in `security cms -D -i mac/byre.provisionprofile`), add each new attribute to its `CD_<Model>` record type as `CD_<attribute>` (`Int` is `INT64 QUERYABLE SORTABLE`), `xcrun cktool import-schema` the file back to Development, then Deploy Schema Changes in the CloudKit console; `export-schema --environment production` shows what Production has, and `mac/package.sh` refuses to build while it lacks a model attribute. `cktool` needs a management token saved with `xcrun cktool save-token --type management --method file` (the console's Tokens & Keys page makes one). The CloudKit console (icloud.developer.apple.com, the container, Production, Private Database, zone `com.apple.coredata.cloudkit.zone`) shows the records. The app watches every finished CloudKit setup, import and export (`NSPersistentCloudKitContainer.eventChangedNotification`, in `Servers` in `mac/Sources/Byre/Startup.swift`) and keeps the latest of each kind: succeeded, or failed with the error (for a partial failure, the first record's error and how many records failed), and when. Its sync state is the newest failed one while any kind's latest failed, so a successful import doesn't hide exports that still fail, and otherwise the newest. A failure is logged with the error in the clear, which CoreData's own log lines redact. The Settings tab shows the state under iCloud sync, a failure in the danger colour, and the app reports it to every server it is connected to with `PUT /api/clients/sync` (`ClientSync`: `ok`, `error`, `at`), on each change and each time its `/api/clients/connect` WebSocket connects or reconnects, so a report lost while the server was unreachable is sent again; the server keeps it in the client registry and shows it as the client's `sync` in the status (see "Status"), so the Status tab's Clients table and `list_clients` show a Mac whose sync is failing. Deleting the local files while the app isn't running makes the next launch download the records again from iCloud; deleting the records in the console as well starts from the connection screen. Pasted images stay in memory and are dropped on Forget Server and quit. The app logs through `os.Logger`: `log show --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` prints it.
|
||||
|
||||
@@ -344,7 +344,7 @@ An agent error (a turn that ends in error without a human interrupt, a process t
|
||||
|
||||
### Away
|
||||
|
||||
A user who is away has byre answer their agents' questions instead of waiting (`internal/agents/away.go`). `users.away`, `away` on `GET /api/me`, is set with `PUT /api/me/away` (`Manager.SetAway`), which records `user.updated`. While it is on, every question filed by `ask_human` in a task the user created is answered at once, as that user, with an `other` response of `awayText`: pick the most likely answer and carry on; fork with the `fork` tool (see "Forking") only when strongly convinced both answers are worth following; don't guess at what only the user can give (a test result, a password, a permission), and carry on with other work or end the turn instead; and no authorization is granted. The tool's result says so, and the answer is delivered like any other, usually mid-turn right after the `ask_human` call, so it gets an `answer` snapshot and the user can **Answer again** with their own answer when back. Turning it on answers the user's tasks' questions already open the same way; turning it off lets new questions wait again. The app's Away button, at the end of the server picker in the window's toolbar, shows on (accent) when the user is away on any connected server and sets all of them to the opposite on click, so a mixed state is always cleared by one click. `sqlite3 byre.db "SELECT login FROM users WHERE away = 1"` lists who is away.
|
||||
A user who is away has byre answer their agents' questions instead of waiting (`internal/agents/away.go`). `users.away`, `away` on `GET /api/me`, is set with `PUT /api/me/away` (`Manager.SetAway`), which records `user.updated`. While it is on, every question filed by `ask_human` in a task the user created is answered at once, as that user, with an `other` response of `awayText`: pick the most likely answer and carry on; fork with the `fork` tool (see "Forking") only when strongly convinced both answers are worth following; don't guess at what only the user can give (a test result, a password, a permission), and carry on with other work or end the turn instead; and no authorization is granted. The tool's result says so, and the answer is delivered like any other, usually mid-turn right after the `ask_human` call, so it gets an `answer` snapshot and the user can **Answer again** with their own answer when back. Turning it on answers the user's tasks' questions already open the same way; turning it off lets new questions wait again. The app's Away button, at the right end of the window's toolbar (a `primaryAction` toolbar item, apart from the server picker), shows on (accent) when the user is away on any connected server and sets all of them to the opposite on click, so a mixed state is always cleared by one click. `sqlite3 byre.db "SELECT login FROM users WHERE away = 1"` lists who is away.
|
||||
|
||||
## Notifications
|
||||
|
||||
|
||||
@@ -482,6 +482,12 @@ struct ServerWindow: View {
|
||||
ServerPicker()
|
||||
}
|
||||
.sharedBackgroundVisibility(.hidden)
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
AwayToggle()
|
||||
.font(Fonts.font(13))
|
||||
.fixedSize()
|
||||
}
|
||||
.sharedBackgroundVisibility(.hidden)
|
||||
}
|
||||
.toolbar(removing: .title)
|
||||
.toolbarBackgroundVisibility(.visible, for: .windowToolbar)
|
||||
@@ -532,7 +538,6 @@ struct ServerPicker: View {
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 3)
|
||||
.help("Add Server")
|
||||
AwayToggle()
|
||||
}
|
||||
.font(Fonts.font(13))
|
||||
.fixedSize()
|
||||
|
||||
Reference in New Issue
Block a user