diff --git a/AGENTS.md b/AGENTS.md index c3da3b1..702677c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,5 +57,5 @@ When the owner tells the lead "deploy byre", or anything that means the same, th ## UI look - Two fonts, bundled from `mac/fonts/`: Atkinson Hyperlegible Next for everything, and Atkinson Hyperlegible Mono only for text whose columns matter: code (blocks and inline), diffs, file contents, the terminal, raw tool input and output, and shell commands. Next follows the text size setting and Mono the code size setting. -- High contrast means readable text, not harshness. Body text is at least 7:1 against its background and secondary text at least 4.5:1, on soft backgrounds (off-white, dark slate; never pure black or white). Keep the frame quiet: 1px borders in a subtle tone, no dashed or heavy borders, no inverted blocks for selection, colour used sparingly for state and accent. The palette lives in `mac/Sources/Byre/Palette.swift`; change it there. +- High contrast means readable text, not harshness. Body text is at least 7:1 against its background and secondary text at least 4.5:1, on soft backgrounds (off-white, dark slate; never pure black or white). Keep the frame quiet: 1px borders in a subtle tone, no dashed or heavy borders, no inverted blocks for selection, color used sparingly for state and accent. The palette lives in `mac/Sources/Byre/Palette.swift`; change it there. - Never show agent cost, turn duration, or turn counts anywhere in the UI. They stay in the event log for the API, not on screen. diff --git a/cmd/byre/check.go b/cmd/byre/check.go index d498ccc..f575dd7 100644 --- a/cmd/byre/check.go +++ b/cmd/byre/check.go @@ -34,7 +34,7 @@ func checks() []check { signedIn(command("claude", "claude", needAgent, "Claude models", "claude"), claudeSignedIn), withBrew(brew, signedIn(command("codex", "codex", needAgent, "GPT models", "codex"), codexSignedIn)), withBrew(brew, command("agy", "agy", needAgent, "Gemini models", "agy")), - withBrew(brew, command("npx", "npx", needOptional, "reviewers' Playwright browser tools", "node")), + withBrew(brew, command("npx", "npx", needOptional, "agents' Playwright browser tools", "node")), withBrew(brew, appCheck("Google Chrome", needOptional, "browser tools for agents and reviewers", "chrome")), } } diff --git a/cmd/byre/main.go b/cmd/byre/main.go index 17c6577..199e507 100644 --- a/cmd/byre/main.go +++ b/cmd/byre/main.go @@ -38,7 +38,7 @@ func main() { } func usage() { - fmt.Fprintln(os.Stderr, "usage: byre serve | byre install | byre check | byre install-tool NAME |byre run [--resume ID] | byre signing CHECKOUT | byre grep [--glob GLOB] [--ignore-case] [--files-only] PATTERN PATH | byre list PATH | byre client-exec [--client NAME] [--timeout SECS] -- CMD... | byre screen") + fmt.Fprintln(os.Stderr, "usage: byre serve | byre install | byre check | byre install-tool NAME | byre run [--resume ID] | byre signing CHECKOUT | byre grep [--glob GLOB] [--ignore-case] [--files-only] PATTERN PATH | byre list PATH | byre client-exec [--client NAME] [--timeout SECS] -- CMD... | byre screen") os.Exit(2) } diff --git a/internal/agents/afterreview.go b/internal/agents/afterreview.go index b0c1322..59adfe8 100644 --- a/internal/agents/afterreview.go +++ b/internal/agents/afterreview.go @@ -52,7 +52,7 @@ func (m *Manager) MergeAfterReview(ctx context.Context, task api.Task, user api. } } if reviewID == "" { - return api.Task{}, fmt.Errorf("%w: the review of snapshot %d of task %s isn't running", ErrConflict, latest.N, task.Id) + return api.Task{}, fmt.Errorf("%w: the review of snapshot #%d of task %s isn't running", ErrConflict, latest.N, task.Id) } if target == api.MergeTargetMain && title != task.MergeTitle { diff --git a/internal/agents/audit.go b/internal/agents/audit.go index 0681561..bdd96a0 100644 --- a/internal/agents/audit.go +++ b/internal/agents/audit.go @@ -108,6 +108,6 @@ func (m *Manager) auditError(ctx context.Context, task api.Task, msg string) { func auditBrief(branch, commit string) string { return fmt.Sprintf("Audit of %s at %s. This isn't a review of a change: check the whole codebase as it stands against the project's standards and expectations, and fix what falls short. "+ "byre runs this task in the background: the human doesn't see it until you ask a question, call ready_for_merge or hit an error. "+ - "Finish your stage with finish_stage as usual, even when you found nothing. Once your stage has finished and its review has come back, byre asks whether the task is ready to merge;"+ + "Finish your stage with finish_stage as usual, even when you found nothing. Once your stage has finished and its review has come back, byre asks whether the task is ready to merge; "+ "if nothing needed changing, answer with suggest_discard.", branch, commit) } diff --git a/internal/agents/fork.go b/internal/agents/fork.go index e32a7b6..fb5694e 100644 --- a/internal/agents/fork.go +++ b/internal/agents/fork.go @@ -24,7 +24,7 @@ func forkOrigin(t api.Task, from string) string { if t.ForkedAt == nil { return "continued from " + from } - return fmt.Sprintf("forked from %s at snapshot %d", from, *t.ForkedAt) + return fmt.Sprintf("forked from %s at snapshot #%d", from, *t.ForkedAt) } func forkTitle(src api.Task, n int) string { @@ -169,7 +169,7 @@ func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n return api.Task{}, "", err } if srcSess.Vendor == api.Antigravity { - return api.Task{}, "", fmt.Errorf("%w: snapshot %d of task %s is from an Antigravity session, and agy can't fork a conversation", ErrConflict, n, src.Id) + return api.Task{}, "", fmt.Errorf("%w: snapshot #%d of task %s is from an Antigravity session, and agy can't fork a conversation", ErrConflict, n, src.Id) } events, err := m.store.ForkEvents(ctx, src.Id, srcSess.Id) if err != nil { @@ -177,7 +177,7 @@ func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n } at := forkPoint(events, snap.AgentSessionId, snap.EventSeq) if at == "" { - return api.Task{}, "", fmt.Errorf("%w: snapshot %d of task %s has no recorded agent message at or before it to fork the conversation from", ErrConflict, n, src.Id) + return api.Task{}, "", fmt.Errorf("%w: snapshot #%d of task %s has no recorded agent message at or before it to fork the conversation from", ErrConflict, n, src.Id) } return m.forkSession(ctx, project, api.Task{ diff --git a/internal/agents/guidance.go b/internal/agents/guidance.go index 31a1269..14be827 100644 --- a/internal/agents/guidance.go +++ b/internal/agents/guidance.go @@ -154,7 +154,7 @@ func forkGuidance(task, src api.Task) string { "Everything before this point happened in %s, which no longer exists: from now on read and change files only in %s, and use paths under it.", src.Title, outcome, task.Branch, task.WorktreePath, src.WorktreePath, task.WorktreePath) } - return fmt.Sprintf("\n- This conversation was forked from byre task %q at its snapshot %d, into a task of its own: branch %s, worktree %s, which held that snapshot's files when the fork was made. "+ + return fmt.Sprintf("\n- This conversation was forked from byre task %q at its snapshot #%d, into a task of its own: branch %s, worktree %s, which held that snapshot's files when the fork was made. "+ "Everything before the fork happened in %s, which belongs to the other task: from now on read and change files only in %s, never in the other worktree, and use paths under it.", src.Title, *task.ForkedAt, task.Branch, task.WorktreePath, src.WorktreePath, task.WorktreePath) } diff --git a/internal/agents/kb/workflow-definitions.md b/internal/agents/kb/workflow-definitions.md index 1465d45..bd8270e 100644 --- a/internal/agents/kb/workflow-definitions.md +++ b/internal/agents/kb/workflow-definitions.md @@ -1,3 +1,5 @@ +The fields of a workflow's definition, the object save_workflow takes and read_workflow returns: steps (stage, order, parallel, random), auto_merge and auto_discard, and how stages pass outputs along and branch. + # Workflow definitions A workflow's definition is an object: an optional `description`, optional `auto_merge` and `auto_discard` booleans, then `steps`, which run in order. With `auto_merge`, when a stage task of a run calls `ready_for_merge`, byre merges it right away as the creator of the run, pressing the button the agent picked (`complete` or `continue`); with `auto_discard`, byre discards a stage task that calls `suggest_discard`. Without them the human decides, as for any task. The workflow's name is not in it. `save_workflow` takes it as its `definition` argument and `read_workflow` returns it, both as JSON. Unknown fields are refused. diff --git a/internal/agents/lead.go b/internal/agents/lead.go index af69f5a..05eeb75 100644 --- a/internal/agents/lead.go +++ b/internal/agents/lead.go @@ -380,7 +380,7 @@ func (m *Manager) Observe(events []api.Event) { go m.audit(*ev.ProjectId) } switch ev.Kind { - case "inbox.created", "inbox.updated", "task.created", "task.merge_queued", "task.merge_after_review", "task.merge_after_review_cancelled", "task.merge_after_review_dropped", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.started", "review.finished", "task.state", "task.ready", "task.discard_suggested": + case "inbox.created", "inbox.updated", "task.created", "task.merge_queued", "task.merge_after_review", "task.merge_after_review_canceled", "task.merge_after_review_dropped", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.started", "review.finished", "task.state", "task.ready", "task.discard_suggested": case "task.ready_cleared": if ev.Payload["reason"] == "merge" || ev.Payload["reason"] == "discard" { continue @@ -605,9 +605,9 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error) return fmt.Sprintf("%s's merge was requested by %s and is held until the merge in progress in its project is done.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Actor), nil case "task.merge_after_review": - return fmt.Sprintf("%s was set by %s to merge once the reviews of snapshot %v finish with no findings.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Actor, ev.Payload["snapshot_n"]), nil + return fmt.Sprintf("%s was set by %s to merge once the reviews of snapshot #%v finish with no findings.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Actor, ev.Payload["snapshot_n"]), nil - case "task.merge_after_review_cancelled": + case "task.merge_after_review_canceled": return fmt.Sprintf("%s's merge after review was canceled by %s.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Actor), nil case "task.merge_after_review_dropped": diff --git a/internal/agents/leadtools.go b/internal/agents/leadtools.go index 5040d7e..32a5dd1 100644 --- a/internal/agents/leadtools.go +++ b/internal/agents/leadtools.go @@ -1092,8 +1092,8 @@ func transcriptLine(ev api.Event, root string) string { case "task.merge_queued": return fmt.Sprintf("merge held, requested by %s", ev.Actor) case "task.merge_after_review": - return fmt.Sprintf("set by %s to merge after the review of snapshot %s", ev.Actor, text("snapshot_n")) - case "task.merge_after_review_cancelled": + return fmt.Sprintf("set by %s to merge after the review of snapshot #%s", ev.Actor, text("snapshot_n")) + case "task.merge_after_review_canceled": return fmt.Sprintf("merge after review canceled by %s", ev.Actor) case "task.merge_after_review_dropped": return "didn't merge after the review: " + clip(text("reason"), 300) diff --git a/internal/agents/review.go b/internal/agents/review.go index 94b083e..1038949 100644 --- a/internal/agents/review.go +++ b/internal/agents/review.go @@ -121,7 +121,7 @@ func (m *Manager) beginReviews(ctx context.Context, ref store.SnapshotRef) error } changes, err := git.Compare(ctx, project.RepoPath, project.DefaultBranch, snap.CommitSha, snap.TreeSha, "") if err != nil { - return finished(api.ReviewStateFailed, project.ReviewWorkflow, fmt.Sprintf("byre couldn't compare snapshot %d with %s: %v", snap.N, project.DefaultBranch, err)) + return finished(api.ReviewStateFailed, project.ReviewWorkflow, fmt.Sprintf("byre couldn't compare snapshot #%d with %s: %v", snap.N, project.DefaultBranch, err)) } if len(changes.Files) == 0 { slog.Info("review skipped, snapshot has no diff", "task", task.Id, "snapshot", snap.N) @@ -243,7 +243,7 @@ func (m *Manager) reviewPrompt(ctx context.Context, task api.Task, project api.P b := &strings.Builder{} fmt.Fprintf(b, "Review the work of the agent on the byre task %q, as reviewer %q of the project's review workflow %q.\n\n%s\n\n", task.Title, stage, project.ReviewWorkflow, strings.TrimSpace(workflow.Get(def, stage).Prompt)) - fmt.Fprintf(b, "It is snapshot %d of the task, commit %s, checked out in your working directory. The change is all of the task's work, `git diff %s %s` (from the merge-base with %s); %d files changed:\n", + fmt.Fprintf(b, "It is snapshot #%d of the task, commit %s, checked out in your working directory. The change is all of the task's work, `git diff %s %s` (from the merge-base with %s); %d files changed:\n", snap.N, snap.CommitSha, changes.Base, snap.CommitSha, project.DefaultBranch, len(changes.Files)) for i, f := range changes.Files { if i == maxReviewFiles { @@ -425,7 +425,7 @@ func findingsMessage(n int, reviews []api.Review) string { return "" } return fmt.Sprintf(findingsPreamble+"%d of your work has finished, and its reviewers raised the findings below. Reviewers can be wrong or miss context: check each one against the code and your brief. "+ - "Before you change anything, answer each finding with respond_to_finding (snapshot %d, the reviewer's name and the finding's number): fixing if it holds up and you'll act on it, ignoring with your reason if you're leaving it. "+ + "Before you change anything, answer each finding with respond_to_finding (snapshot #%d, the reviewer's name and the finding's number): fixing if it holds up and you'll act on it, ignoring with your reason if you're leaving it. "+ "The human sees your answers on the findings and can overrule an ignore.", n, n) + b.String() } @@ -480,16 +480,16 @@ func (m *Manager) respondToFindingTool(l *live) mcp.Tool { } } if reviewID == "" { - return "", fmt.Errorf("%w: snapshot %d has no finished review by reviewer %q", ErrInvalid, args.Snapshot, args.Reviewer) + return "", fmt.Errorf("%w: snapshot #%d has no finished review by reviewer %q", ErrInvalid, args.Snapshot, args.Reviewer) } err = m.store.SetFindingResponse(ctx, reviewID, args.Finding-1, api.FindingResponse{Response: args.Response, Reason: reason}, agentActor) if errors.Is(err, store.ErrNotFound) { - return "", fmt.Errorf("%w: reviewer %q's review of snapshot %d has no finding %d", ErrInvalid, args.Reviewer, args.Snapshot, args.Finding) + return "", fmt.Errorf("%w: reviewer %q's review of snapshot #%d has no finding %d", ErrInvalid, args.Reviewer, args.Snapshot, args.Finding) } if err != nil { return "", err } - return fmt.Sprintf("Recorded: %s finding %d of reviewer %q's review of snapshot %d.", args.Response, args.Finding, args.Reviewer, args.Snapshot), nil + return fmt.Sprintf("Recorded: %s finding %d of reviewer %q's review of snapshot #%d.", args.Response, args.Finding, args.Reviewer, args.Snapshot), nil }, } } @@ -671,9 +671,9 @@ func (m *Manager) stopStuckReviewers(ctx context.Context, h Health) { func reviewName(r api.Review) string { if r.Stage == "" { - return fmt.Sprintf("review of snapshot %d", r.SnapshotN) + return fmt.Sprintf("review of snapshot #%d", r.SnapshotN) } - return fmt.Sprintf("%q review of snapshot %d", r.Stage, r.SnapshotN) + return fmt.Sprintf("%q review of snapshot #%d", r.Stage, r.SnapshotN) } func reviewLine(r api.Review) string { diff --git a/internal/agents/snapshots.go b/internal/agents/snapshots.go index 2f4a8be..6a9e520 100644 --- a/internal/agents/snapshots.go +++ b/internal/agents/snapshots.go @@ -152,7 +152,7 @@ func (m *Manager) takeSnapshot(ctx context.Context, t snapTarget, trigger api.Sn } n := last.N + 1 - commit, err := git.CommitSnapshot(ctx, t.dir, tree, git.SnapshotRef(t.taskID, n), fmt.Sprintf("byre snapshot %d (%s)", n, trigger), git.IdentityEnv(t.gitName, t.gitEmail)) + commit, err := git.CommitSnapshot(ctx, t.dir, tree, git.SnapshotRef(t.taskID, n), fmt.Sprintf("byre snapshot #%d (%s)", n, trigger), git.IdentityEnv(t.gitName, t.gitEmail)) if err != nil { return nil, err } diff --git a/internal/agents/team.go b/internal/agents/team.go index e78c396..afa91b1 100644 --- a/internal/agents/team.go +++ b/internal/agents/team.go @@ -119,7 +119,7 @@ func teamGuidance(sessions []api.AgentSession, member string, stage bool) string lead := teamLead(sessions) g := fmt.Sprintf("\n- This task is worked by a team of agents in this one worktree: %s. You are %q. "+ "The team talks through a shared board: post_to_board sends a post to every other agent (a working agent gets it after its next tool call; an idle one starts a turn with it), and read_board reads the posts so far. "+ - "The human and the lead post there too. Post only when you add something: a finding, a proposal, an objection, a decision, a request. Don't post acknowledgements or thanks, since every post wakes every other agent. "+ + "The human and the lead post there too. Post only when you add something: a finding, a proposal, an objection, a decision, a request. Don't post acknowledgments or thanks, since every post wakes every other agent. "+ "Your final message goes to the human, not to the team. The other agents change the same files: re-read a file before you edit it, and say on the board when you take on a file or an area.", strings.Join(names, ", "), member) if !stage { diff --git a/internal/store/migrations/107-rename-merge-after-review-canceled.sql b/internal/store/migrations/107-rename-merge-after-review-canceled.sql new file mode 100644 index 0000000..f784fbd --- /dev/null +++ b/internal/store/migrations/107-rename-merge-after-review-canceled.sql @@ -0,0 +1 @@ +UPDATE events SET kind = 'task.merge_after_review_canceled' WHERE kind = 'task.merge_after_review_cancelled'; diff --git a/internal/store/readyprompt.go b/internal/store/readyprompt.go index 4cd9e98..444306f 100644 --- a/internal/store/readyprompt.go +++ b/internal/store/readyprompt.go @@ -121,7 +121,7 @@ func readyPrompts(ctx context.Context, tx *sql.Tx, emit emitFunc) ([]ReadyPrompt for _, r := range reviews { if r.State == api.ReviewStateFailed && c.background { err = emit(api.Event{Kind: "session.error", ProjectId: &c.projectID, TaskId: &c.TaskID, Actor: "byre", Payload: map[string]any{ - "error": fmt.Sprintf("Review %q of snapshot %d failed: %s. The audit can't go on without you.", r.Stage, c.Snapshot, r.Error), + "error": fmt.Sprintf("Review %q of snapshot #%d failed: %s. The audit can't go on without you.", r.Stage, c.Snapshot, r.Error), }}) if err != nil { return nil, err diff --git a/internal/store/review.go b/internal/store/review.go index 9a9906e..0364d0a 100644 --- a/internal/store/review.go +++ b/internal/store/review.go @@ -137,7 +137,7 @@ func endReviews(ctx context.Context, tx *sql.Tx, emit emitFunc) ([]EndedReviewer case !r.active: reason = "the task was closed" case r.n < r.current: - reason = fmt.Sprintf("superseded by snapshot %d", r.current) + reason = fmt.Sprintf("superseded by snapshot #%d", r.current) case r.state != string(api.AgentSessionStateRunning): reason = "the reviewer stopped before submitting a review" default: diff --git a/internal/store/store.go b/internal/store/store.go index 2808b0a..f494358 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -687,7 +687,7 @@ func (s *Store) CancelMergeAfterReview(ctx context.Context, id, actor string) er if err != nil { return err } - return emit(api.Event{Kind: "task.merge_after_review_cancelled", ProjectId: &projectID, TaskId: &id, Actor: actor, Payload: map[string]any{"id": id}}) + return emit(api.Event{Kind: "task.merge_after_review_canceled", ProjectId: &projectID, TaskId: &id, Actor: actor, Payload: map[string]any{"id": id}}) }) } diff --git a/mac/Sources/Byre/Changes.swift b/mac/Sources/Byre/Changes.swift index 5188012..539613b 100644 --- a/mac/Sources/Byre/Changes.swift +++ b/mac/Sources/Byre/Changes.swift @@ -369,7 +369,7 @@ private struct SnapWhere: View { let snaps = store.snapshots() let snap = snaps.first { $0.n == n } HStack(spacing: 0) { - Text("snapshot \(n)") + Text("snapshot #\(n)") if let snap { Text(" · \(snap.trigger.rawValue) · \(clockText(snap.createdAt))") } diff --git a/mac/Sources/Byre/Chat.swift b/mac/Sources/Byre/Chat.swift index 41d87fe..0621928 100644 --- a/mac/Sources/Byre/Chat.swift +++ b/mac/Sources/Byre/Chat.swift @@ -159,7 +159,7 @@ final class ChatModel { var placeholder: String { if closed { - return "Continue in a new task, started from main, whose agent picks up this conversation. Paste or drop images to attach them." + return "Continue in a new task, started from the default branch, whose agent picks up this conversation. Paste or drop images to attach them." } if team { return "Post to the team's board; every agent of the team gets it." @@ -1654,7 +1654,7 @@ private struct ReadyBubble: View { } return AttentionCard(open: current) { VStack(alignment: .leading, spacing: 4) { - AttentionHeader(label: discard ? "suggests discarding" : "ready for merge", when: when, open: current) + AttentionHeader(label: discard ? "suggests discarding" : "ready to merge", when: when, open: current) MarkdownView(blocks: blocks, ctx: ctx) if current, let task { let busy = store.taskBusy(task.id) @@ -1972,7 +1972,7 @@ private struct SnapshotRow: View { store.sheet = Sheet(owner: .task(store.selectedTaskId), view: .tab(.changes)) } .buttonStyle(SmallLinkButtonStyle()) - .help("Show snapshot \(snap.n) in the Changes tab") + .help("Show snapshot #\(snap.n) in the Changes tab") } Button("Fork") { guard let taskId else { diff --git a/mac/Sources/Byre/Composer.swift b/mac/Sources/Byre/Composer.swift index 8725378..64fb897 100644 --- a/mac/Sources/Byre/Composer.swift +++ b/mac/Sources/Byre/Composer.swift @@ -806,7 +806,7 @@ struct ComposerView: View { .buttonStyle(WebButtonStyle()) .disabled(!shown) .fixedSize() - .help(chat.closed ? "Start a new task from main whose agent continues this conversation with your message" : "Send the message") + .help(chat.closed ? "Start a new task from the default branch whose agent continues this conversation with your message" : "Send the message") } } diff --git a/mac/Sources/Byre/Files.swift b/mac/Sources/Byre/Files.swift index 8fc7a5b..6e2e75a 100644 --- a/mac/Sources/Byre/Files.swift +++ b/mac/Sources/Byre/Files.swift @@ -75,16 +75,6 @@ private func parent(_ path: String) -> String { return i == path.startIndex ? "/" : String(path[.. String { - if n < 1024 { - return "\(n) B" - } - if n < 1024 * 1024 { - return "\((Double(n) / 1024).formatted(.number.precision(.fractionLength(1)).grouping(.never))) KiB" - } - return "\((Double(n) / 1024 / 1024).formatted(.number.precision(.fractionLength(1)).grouping(.never))) MiB" -} - private let imageExts: Set = ["png", "jpg", "jpeg", "gif", "webp", "svg"] private func isImage(_ path: String) -> Bool { diff --git a/mac/Sources/Byre/Format.swift b/mac/Sources/Byre/Format.swift index d48738c..40f17e1 100644 --- a/mac/Sources/Byre/Format.swift +++ b/mac/Sources/Byre/Format.swift @@ -150,6 +150,17 @@ func ago(_ date: Date) -> String { return "\(seconds / 86400)d ago" } +func bytes(_ n: Int64) -> String { + let units = ["B", "KiB", "MiB", "GiB", "TiB"] + var i = 0 + var v = Double(n) + while v >= 1024 && i < units.count - 1 { + v /= 1024 + i += 1 + } + return "\(v.formatted(.number.precision(.fractionLength(i == 0 ? 0 : 1)).grouping(.never))) \(units[i])" +} + func isoText(_ date: Date) -> String { date.formatted(Date.ISO8601FormatStyle(includingFractionalSeconds: true)) } diff --git a/mac/Sources/Byre/Startup.swift b/mac/Sources/Byre/Startup.swift index a1ef0ea..e6e0b5c 100644 --- a/mac/Sources/Byre/Startup.swift +++ b/mac/Sources/Byre/Startup.swift @@ -703,7 +703,7 @@ struct ServerPicker: View { .foregroundStyle(servers.current == nil ? Palette.text : Palette.muted) .padding(.horizontal, 8) .padding(.vertical, 3) - .help("Add Server") + .help("Add server") } .font(Fonts.font(13)) .fixedSize() diff --git a/mac/Sources/Byre/Status.swift b/mac/Sources/Byre/Status.swift index bf40d09..9404326 100644 --- a/mac/Sources/Byre/Status.swift +++ b/mac/Sources/Byre/Status.swift @@ -25,17 +25,6 @@ private func since(_ date: Date) -> String { return ago(date) } -private func bytes(_ n: Int64) -> String { - let units = ["B", "KB", "MB", "GB", "TB"] - var i = 0 - var v = Double(n) - while v >= 1024 && i < units.count - 1 { - v /= 1024 - i += 1 - } - return "\(v.formatted(.number.precision(.fractionLength(i == 0 ? 0 : 1)).grouping(.never))) \(units[i])" -} - private func resetText(_ at: Date) -> String { let time = at.formatted(Date.FormatStyle().weekday(.abbreviated).hour().minute()) let minutes = Int((at.timeIntervalSinceNow / 60).rounded()) @@ -487,7 +476,7 @@ private struct ClientTable: View { Text("\(c.login) · build \(build(c))") .font(Fonts.font(11)) .foregroundStyle(Palette.muted) - Text("Connected \(ago(c.connectedAt)) ·last action \(c.lastActionAt.map(since) ?? "—")") + Text("Connected \(ago(c.connectedAt)) · last action \(c.lastActionAt.map(since) ?? "—")") .font(Fonts.font(11)) .foregroundStyle(Palette.muted) } diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index 9af24a8..ed0df3d 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -48,7 +48,7 @@ private let stateTitles: [ByreTask.StatePayload: TaskState] = [ .reviewing: TaskState(kind: .working, label: "Reviewing", title: "A review is running"), .discarding: TaskState(kind: .working, label: "Discarding", title: "The agent is writing the task summary, then byre removes the task"), .readyToMerge: TaskState(kind: .ready, label: "Ready to merge", title: "The agent says the task is ready to merge"), - .discardSuggested: TaskState(kind: .ready, label: "Ready to discard", title: "The agent suggests discarding the task"), + .discardSuggested: TaskState(kind: .ready, label: "Suggests discard", title: "The agent suggests discarding the task"), .idle: TaskState(kind: .idle, label: "Idle", title: "Waiting for a message"), .finished: TaskState(kind: .idle, label: "Finished", title: "The workflow stage finished"), .noAgent: TaskState(kind: .idle, label: "Idle", title: "Waiting for a message"), @@ -853,7 +853,7 @@ final class Store { } case "schedule.saved", "schedule.deleted": applySchedule(ev) - case "task.created", "task.title", "task.merge_title", "task.merge_queued", "task.merge_unqueued", "task.merge_after_review", "task.merge_after_review_ended", "task.merge_after_review_cancelled", "task.parent_merge_started", "task.parent_merge_stopped", "task.merge_started", "task.merge_ended", "task.discard_started", "task.merged", "task.discarded", "task.state", "task.ready", "task.discard_suggested", "task.ready_cleared", "snapshot.created": + case "task.created", "task.title", "task.merge_title", "task.merge_queued", "task.merge_unqueued", "task.merge_after_review", "task.merge_after_review_ended", "task.merge_after_review_canceled", "task.parent_merge_started", "task.parent_merge_stopped", "task.merge_started", "task.merge_ended", "task.discard_started", "task.merged", "task.discarded", "task.state", "task.ready", "task.discard_suggested", "task.ready_cleared", "snapshot.created": if let taskId = ev.taskId, ["task.merged", "task.discarded"].contains(ev.kind) || (ev.kind == "snapshot.created" && !["answer", "fork", "ask_human"].contains(str(ev.payload.value, "trigger"))) { clearTask(self, taskId) } diff --git a/mac/Sources/Byre/TranscriptBuilder.swift b/mac/Sources/Byre/TranscriptBuilder.swift index c104ae5..08c242e 100644 --- a/mac/Sources/Byre/TranscriptBuilder.swift +++ b/mac/Sources/Byre/TranscriptBuilder.swift @@ -948,7 +948,7 @@ final class TranscriptBuilder { if let task = rowTask, task != taskId { return note([.text("\(ev.actor) continued "), .task(from), .text(" as "), .task(task), .text(" here")]) } - return note([.text("\(ev.actor) continued "), .task(from), .text(" here in a new task, started from main; everything above happened in that task, and its agent continues that conversation")]) + return note([.text("\(ev.actor) continued "), .task(from), .text(" here in a new task, started from the default branch; everything above happened in that task, and its agent continues that conversation")]) } if let from = str(p, "forked_from"), let task = rowTask, task != taskId { return note([.text("\(ev.actor) forked "), .task(task), .text(" here from "), .task(from), .text(" at snapshot #\(jsString(p["forked_at"]))")]) @@ -984,7 +984,7 @@ final class TranscriptBuilder { n.cancelMergeAfterReview = jsString(p["id"]) return Row(.note(n), taskId: rowTask) - case "task.merge_after_review_cancelled": + case "task.merge_after_review_canceled": return note([.text("\(ev.actor) canceled the merge after review")]) case "task.merge_after_review_dropped": diff --git a/mac/Sources/Byre/Updater.swift b/mac/Sources/Byre/Updater.swift index 9c95a2f..79c7b6d 100644 --- a/mac/Sources/Byre/Updater.swift +++ b/mac/Sources/Byre/Updater.swift @@ -120,10 +120,6 @@ final class Updater: NSObject, SPUUserDriver, SPUUpdaterDelegate { panel = nil } - private func bytes(_ n: Double) -> String { - Int64(n).formatted(.byteCount(style: .file)) - } - func updater(_ updater: SPUUpdater, willInstallUpdateOnQuit item: SUAppcastItem, immediateInstallationBlock immediateInstallHandler: @escaping () -> Void) -> Bool { staged = (item, immediateInstallHandler) now = ask(item) @@ -184,9 +180,9 @@ final class Updater: NSObject, SPUUserDriver, SPUUpdaterDelegate { func showDownloadDidReceiveData(ofLength length: UInt64) { progress.done += Double(length) if let total = progress.total { - progress.detail = "\(bytes(progress.done)) of \(bytes(total))" + progress.detail = "\(bytes(Int64(progress.done))) of \(bytes(Int64(total)))" } else { - progress.detail = bytes(progress.done) + progress.detail = bytes(Int64(progress.done)) } }