Add a Page tab that keeps the last shown page reachable
This commit is contained in:
@@ -17,6 +17,11 @@ A deploy is a release that isn't uploaded: the same notarized disk image, instal
|
|||||||
3. Once the service is confirmed healthy, update each client Mac whose app is running, through the client tools and without asking first: upload the disk image (`mac/.build/release/<version>/byre-<version>.dmg`) and `mac/install.sh`, quit the app, run `install.sh <dmg>`, and `open /Applications/Byre.app`. The app reads its server address from its iCloud-synced SwiftData store, so it reconnects to prod with no setup. Report each Mac's app commit (`install.sh` prints it).
|
3. Once the service is confirmed healthy, update each client Mac whose app is running, through the client tools and without asking first: upload the disk image (`mac/.build/release/<version>/byre-<version>.dmg`) and `mac/install.sh`, quit the app, run `install.sh <dmg>`, and `open /Applications/Byre.app`. The app reads its server address from its iCloud-synced SwiftData store, so it reconnects to prod with no setup. Report each Mac's app commit (`install.sh` prints it).
|
||||||
4. End the report with release notes for everything on main since the last release tag (`git describe --tags --abbrev=0` names it), written for a byre user in the "Releasing" shape (`## New` and `## Fixed`, each a bullet with a bold short name and what it does or what now works; no `## Install`). Read `git diff <tag>..HEAD`, not just the commit subjects, and leave out changes no user sees.
|
4. End the report with release notes for everything on main since the last release tag (`git describe --tags --abbrev=0` names it), written for a byre user in the "Releasing" shape (`## New` and `## Fixed`, each a bullet with a bold short name and what it does or what now works; no `## Install`). Read `git diff <tag>..HEAD`, not just the commit subjects, and leave out changes no user sees.
|
||||||
|
|
||||||
|
Besides iso, beast also runs its own byre server. Run `mac/package.sh` once, install beast first, and finish with iso's install:
|
||||||
|
|
||||||
|
- Upload the disk image to beast as `~/byre-deploy/byre.dmg` and `mac/install.sh` as `~/byre-deploy/install.sh` (`client_upload`), then run `~/byre-deploy/run.sh` there detached, through `client_bash`: `perl -MPOSIX -e 'fork and exit; POSIX::setsid(); open STDIN,"</dev/null"; open STDOUT,">>$ENV{HOME}/byre-deploy/run.log"; open STDERR,">&STDOUT"; exec "/bin/zsh","$ENV{HOME}/byre-deploy/run.sh"'`. It quits the app, installs, and reopens it; the beast client drops for about a minute and `list_clients` shows it back on the new commit. Do the same for every other running client Mac, and relaunch iso's app only if it is running.
|
||||||
|
- Check each server with the bearer token in `~/Library/Application Support/byre/loopback-token`: `GET https://byre-<host>.tailcecab.ts.net/api/tailnet` (`state` is `Running`) and `/api/admin/status` (report zombies, protocol errors, rate limits). The loopback address serves `/api/tailnet` but not `/api/admin/status`.
|
||||||
|
|
||||||
What to expect:
|
What to expect:
|
||||||
|
|
||||||
- The notarizations take a minute or two each; `notarytool` prints "In Progress" until "Accepted". A rejection stops the script before anything is installed; `xcrun notarytool log <id> --keychain-profile byre-notary` says why.
|
- The notarizations take a minute or two each; `notarytool` prints "In Progress" until "Accepted". A rejection stops the script before anything is installed; `xcrun notarytool log <id> --keychain-profile byre-notary` says why.
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ func isPage(_ path: String) -> Bool {
|
|||||||
|
|
||||||
extension Store {
|
extension Store {
|
||||||
func openPage(_ page: Page) {
|
func openPage(_ page: Page) {
|
||||||
sheet = Sheet(owner: .task(page.taskId), view: .page(page))
|
shownPages[page.taskId] = page
|
||||||
|
sheet = Sheet(owner: .task(page.taskId), view: .tab(.page))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ let tabLabels: [TaskTab: String] = [
|
|||||||
.board: "Board",
|
.board: "Board",
|
||||||
.changes: "Changes",
|
.changes: "Changes",
|
||||||
.files: "Files",
|
.files: "Files",
|
||||||
|
.page: "Page",
|
||||||
.terminal: "Terminal",
|
.terminal: "Terminal",
|
||||||
.workflow: "Workflow",
|
.workflow: "Workflow",
|
||||||
.project: "Project",
|
.project: "Project",
|
||||||
@@ -230,6 +231,10 @@ struct TaskTabBody: View {
|
|||||||
ChangesView()
|
ChangesView()
|
||||||
case .files:
|
case .files:
|
||||||
FilesView()
|
FilesView()
|
||||||
|
case .page:
|
||||||
|
if let page = store.selectedTaskId.flatMap({ store.shownPages[$0] }) {
|
||||||
|
PageView(page: page)
|
||||||
|
}
|
||||||
case .terminal:
|
case .terminal:
|
||||||
TerminalTab()
|
TerminalTab()
|
||||||
case .workflow:
|
case .workflow:
|
||||||
@@ -385,6 +390,7 @@ private struct TabStrip: View {
|
|||||||
}
|
}
|
||||||
switch t {
|
switch t {
|
||||||
case .board: return team
|
case .board: return team
|
||||||
|
case .page: return store.shownPages[taskId] != nil
|
||||||
case .workflow: return false
|
case .workflow: return false
|
||||||
default: return true
|
default: return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ enum SheetView {
|
|||||||
case tab(TaskTab)
|
case tab(TaskTab)
|
||||||
case image(NSImage)
|
case image(NSImage)
|
||||||
case diagram(BoxLayout)
|
case diagram(BoxLayout)
|
||||||
case page(Page)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Sheet {
|
struct Sheet {
|
||||||
@@ -82,7 +81,7 @@ private struct SheetPanel: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
if case .page(let page) = sheet.view, let url = page.url(store) {
|
if sheet.tab == .page, let page = store.selectedTaskId.flatMap({ store.shownPages[$0] }), let url = page.url(store) {
|
||||||
Button {
|
Button {
|
||||||
NSWorkspace.shared.open(url)
|
NSWorkspace.shared.open(url)
|
||||||
} label: {
|
} label: {
|
||||||
@@ -129,7 +128,6 @@ private struct SheetPanel: View {
|
|||||||
case .tab(let t): tabLabels[t]!
|
case .tab(let t): tabLabels[t]!
|
||||||
case .image: "Image"
|
case .image: "Image"
|
||||||
case .diagram: "Diagram"
|
case .diagram: "Diagram"
|
||||||
case .page(let page): page.title(store)
|
|
||||||
}
|
}
|
||||||
if !overLeft {
|
if !overLeft {
|
||||||
return "Lead · \(label)"
|
return "Lead · \(label)"
|
||||||
@@ -159,8 +157,6 @@ private struct SheetPanel: View {
|
|||||||
DiagramViewer(layout: layout)
|
DiagramViewer(layout: layout)
|
||||||
.id(layout.id)
|
.id(layout.id)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
case .page(let page):
|
|
||||||
PageView(page: page)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import Observation
|
|||||||
import SwiftData
|
import SwiftData
|
||||||
|
|
||||||
enum TaskTab: String {
|
enum TaskTab: String {
|
||||||
case transcript, board, changes, files, terminal, workflow, project
|
case transcript, board, changes, files, page, terminal, workflow, project
|
||||||
}
|
}
|
||||||
|
|
||||||
let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .terminal, .workflow, .project]
|
let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .page, .terminal, .workflow, .project]
|
||||||
|
|
||||||
enum JumpTarget: Equatable {
|
enum JumpTarget: Equatable {
|
||||||
case question(String)
|
case question(String)
|
||||||
@@ -71,6 +71,7 @@ final class Store {
|
|||||||
var connected = false
|
var connected = false
|
||||||
private(set) var focus = Focus()
|
private(set) var focus = Focus()
|
||||||
var sheet: Sheet?
|
var sheet: Sheet?
|
||||||
|
var shownPages: [String: Page] = [:]
|
||||||
var leadSessions: [AgentSession] = []
|
var leadSessions: [AgentSession] = []
|
||||||
let leadFeed: Feed
|
let leadFeed: Feed
|
||||||
var jump: Jump?
|
var jump: Jump?
|
||||||
|
|||||||
Reference in New Issue
Block a user