new draft for workflow tasks
This commit is contained in:
+1
-1
@@ -183,7 +183,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 in place of its raw session state, the first that applies (`taskState` in `web/src/state.ts`), with a dot and a tooltip saying why: **Needs your answer** (an open `question` inbox item), **Merging** (`merging` or `merging_into_parent`), **Merge held** (`merge_queued_at`), **Error** (an open `session_error` or `rate_limited` inbox item), **Working** (the task's `state` is `running`, `review_pending`, `reviewing` or `discarding`; see "Task state in the store"), **Idle** (anything else, including no session). The Project tab, in its task list and TODO items, draws a spinner in place of the dot for **Working** and **Merging**. The label follows the tasks and inbox state the page already holds, so it needs no extra fetch.
|
**Task state.** The switcher and the Project tab show one label per task in place of its raw session state, the first that applies (`taskState` in `web/src/state.ts`), with a dot and a tooltip saying why: **Needs your answer** (an open `question` inbox item), **Merging** (`merging` or `merging_into_parent`), **Merge held** (`merge_queued_at`), **Error** (an open `session_error` or `rate_limited` inbox item), **Working** (the task's `state` is `running`, `review_pending`, `reviewing` or `discarding`; see "Task state in the store"), **Idle** (anything else, including no session). The Project tab, in its task list and TODO items, draws a spinner in place of the dot for **Working** and **Merging**. The label follows the tasks and inbox state the page 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 page knows of unread summaries without loading transcripts; a `turn.ended` with `summary: true` on the global stream raises it in the page. The switcher's and the Project tab's task rows show a **new summary** state label while `summary_seq` is above the seq the user has read (`unreadSummary` in `web/src/state.ts`). Picking such a row (in the switcher, only when the task has no open inbox items) points the right pane at the task's Transcript and scrolls it to that turn's final message (`state.jump`, applied by the transcript once the event is drawn). 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 page the user has open clears the label. A summary counts as read only when the summary turn's end line (`.turn-end[data-seq]`) is inside a mounted transcript's visible area while the page is visible (an `IntersectionObserver` in `mountChat`), 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 page knows of unread summaries without loading transcripts; a `turn.ended` with `summary: true` on the global stream raises it in the page. The switcher's and the Project tab's task rows show a **new summary** state label while `summary_seq` is above the seq the user has read (`unreadSummary` in `web/src/state.ts`), except on tasks of a workflow run, which close too soon for their summaries to be read. Picking such a row (in the switcher, only when the task has no open inbox items) points the right pane at the task's Transcript and scrolls it to that turn's final message (`state.jump`, applied by the transcript once the event is drawn). 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 page the user has open clears the label. A summary counts as read only when the summary turn's end line (`.turn-end[data-seq]`) is inside a mounted transcript's visible area while the page is visible (an `IntersectionObserver` in `mountChat`), 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 the palette of `--task-0` … `--task-N` custom properties at the top of `web/style.css`, one set for light and one for dark, 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 palette entries. An element styled with `taskStyle(id)` (`web/src/taskcolor.ts`) sets `--task` for itself and its children from the task's `color`; `--task` defaults to the accent, which is also what a task the page doesn't know gets. The right pane sets it for the selected task, 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 (`.task-name`), and task links (`a.task-link`), including any Markdown link to `#/task/<id>` or `#/task/<id>/<tab>`. The lead's guidance tells it to link every task it names that way, using `#/task/<id>/inbox` when it points the human at the task's inbox, and clicking one points the right pane at the task, selecting the named tab if there is one.
|
**Task colours.** Each task has a colour, `tasks.color` (`color` in the API), an index into the palette of `--task-0` … `--task-N` custom properties at the top of `web/style.css`, one set for light and one for dark, 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 palette entries. An element styled with `taskStyle(id)` (`web/src/taskcolor.ts`) sets `--task` for itself and its children from the task's `color`; `--task` defaults to the accent, which is also what a task the page doesn't know gets. The right pane sets it for the selected task, 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 (`.task-name`), and task links (`a.task-link`), including any Markdown link to `#/task/<id>` or `#/task/<id>/<tab>`. The lead's guidance tells it to link every task it names that way, using `#/task/<id>/inbox` when it points the human at the task's inbox, and clicking one points the right pane at the task, selecting the named tab if there is one.
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -305,7 +305,7 @@ export async function loadSummaryReads() {
|
|||||||
notify("summaries");
|
notify("summaries");
|
||||||
}
|
}
|
||||||
export function unreadSummary(task) {
|
export function unreadSummary(task) {
|
||||||
return task.summary_seq > (state.summaryReads[task.id] ?? 0);
|
return task.workflow === undefined && task.summary_seq > (state.summaryReads[task.id] ?? 0);
|
||||||
}
|
}
|
||||||
function noteSummaryRead(taskId, seq) {
|
function noteSummaryRead(taskId, seq) {
|
||||||
if ((state.summaryReads[taskId] ?? 0) >= seq) {
|
if ((state.summaryReads[taskId] ?? 0) >= seq) {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -373,7 +373,7 @@ export async function loadSummaryReads(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function unreadSummary(task: Task): boolean {
|
export function unreadSummary(task: Task): boolean {
|
||||||
return task.summary_seq > (state.summaryReads[task.id] ?? 0);
|
return task.workflow === undefined && task.summary_seq > (state.summaryReads[task.id] ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteSummaryRead(taskId: string, seq: number): boolean {
|
function noteSummaryRead(taskId: string, seq: number): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user