diff --git a/docs/operations.md b/docs/operations.md index 28b721f..775ebf7 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -285,7 +285,7 @@ The task switcher (⌘K, or the project ▸ task picker centred in the right pan **Task state.** The switcher and the Project tab show one label per task for its `state` (see "Task state in the store"; `stateTitles` in `mac/Sources/Byre/Store.swift`), with a dot and a tooltip saying why: **Needs your answer** (`asking`), **Merging** (`merging`), **Merge held** (`merge_held`), **Merge after review** (`merge_after_review`), **Reviewing** (`review_pending` or `reviewing`), **Working** (`running`), **Discarding** (`discarding`), **Ready to merge** (`ready_to_merge`), **Ready to discard** (`discard_suggested`), **Idle** (`idle` or `no_agent`). The Project tab, in its task list and TODO items, draws a spinner in place of the dot for **Reviewing**, **Working**, **Discarding** and **Merging**. The label follows the tasks the app already holds, so it needs no extra fetch. -**Unread summaries.** A Task's `summary_seq` is the seq of its latest `turn.ended` event with `summary: true` (0 for none), read through the partial index `events_summary`, so the app knows of unread summaries without loading transcripts; a `turn.ended` with `summary: true` on the global stream raises it in the app. The switcher's and the Project tab's task rows show a **new summary** state label in place of Idle while the task is idle and `summary_seq` is above the seq the user has read (`unreadSummary` in `mac/Sources/Byre/Store.swift`), except on tasks of a workflow run, which close too soon for their summaries to be read. A summary isn't an attention item: picking such a row opens the Transcript at the end, like any task without open questions, findings or a ready bubble. Read state is per user on the server: `summary_reads` holds one highest-read seq per user and task. `GET /api/me/summary-reads` returns the caller's, for active tasks, as a map from task id to seq, and `PUT /api/tasks/{taskId}/summary-read` with `{seq}` raises it, emitting `summary.read` (`task_id`, `seq`) owned by the user only when it went up, so every Mac the user has byre open on clears the label. A summary counts as read only when the summary turn's end row is on screen in a transcript while that server's window is the key window of the active app (`summaryVisible` in `mac/Sources/Byre/Chat.swift`), so selecting the task on another tab leaves it unread. `sqlite3 byre.db 'SELECT * FROM summary_reads'` shows the read seqs. +**Unread summaries.** A Task's `summary_seq` is the seq of its latest `turn.ended` event with `summary: true` (0 for none), read through the partial index `events_summary`, so the app knows of unread summaries without loading transcripts; a `turn.ended` with `summary: true` on the global stream raises it in the app. The switcher's and the Project tab's task rows show a **new summary** state label in place of Idle while the task is idle and `summary_seq` is above the seq the user has read (`unreadSummary` in `mac/Sources/Byre/Store.swift`), except on tasks of a workflow run, which close too soon for their summaries to be read. A server's chip in the server bar shows how many rows carry that label (`unreadSummaries`). A summary isn't an attention item: picking such a row opens the Transcript at the end, like any task without open questions, findings or a ready bubble. Read state is per user on the server: `summary_reads` holds one highest-read seq per user and task. `GET /api/me/summary-reads` returns the caller's, for active tasks, as a map from task id to seq, and `PUT /api/tasks/{taskId}/summary-read` with `{seq}` raises it, emitting `summary.read` (`task_id`, `seq`) owned by the user only when it went up, so every Mac the user has byre open on clears the label. A summary counts as read only when the summary turn's end row is on screen in a transcript while that server's window is the key window of the active app (`summaryVisible` in `mac/Sources/Byre/Chat.swift`), so selecting the task on another tab leaves it unread. `sqlite3 byre.db 'SELECT * FROM summary_reads'` shows the read seqs. **Task colours.** Each task has a colour, `tasks.color` (`color` in the API), an index into `taskPalette` (`mac/Sources/Byre/Format.swift`), the `Palette.task0` … `Palette.taskN` colours in `Palette.swift`, each with a light and a dark value, all at least 4.5:1 against the backgrounds so they work as text. `CreateTask` assigns it in the same transaction as the task's row: of the `store.TaskColors` colours, one no active task holds, and among those the one given out longest ago (the lowest `max(rowid)` of tasks with that colour, never-used first), so a new task doesn't take the colour of one just closed. Active tasks have distinct colours until more than `TaskColors` are active at once. `TaskColors` must match the number of `taskPalette` entries. The right pane sets its accent (the `paneAccent` environment value, the palette's accent when no task is selected) to the selected task's colour, which tints the picker's row, colours the task picker's text, the selected tab's underline, the composer's border and the running border and scanner. Everywhere a task is named it shows in its colour: task titles in the switcher, the Project tab and the Status tab, and task links, including any Markdown link to `#/task/` or `#/task//`. The lead's guidance tells it to link every task it names that way, using `#/task//questions` when it points the human at a question, and clicking one points the right pane at the task, selecting the named tab if there is one. diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index aa3e64e..28a46cf 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -530,7 +530,7 @@ final class Store { } var unreadSummaries: Int { - tasks.values.joined().filter { $0.status == .active && unreadSummary($0) }.count + activeTasks().filter { taskState($0.task).kind == .idle && unreadSummary($0.task) }.count } private func noteSummaryRead(_ taskId: String, _ seq: Int64) -> Bool {