Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) by UncertaintyDeterminesYou4ndMe · Pull Request #553 · apache/maka · GitHub
Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) by UncertaintyDeterminesYou4ndMe · Pull Request #553 · apache/maka · GitHub
Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) by UncertaintyDeterminesYou4ndMe · Pull Request #553 · apache/maka · GitHub
Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) by UncertaintyDeterminesYou4ndMe · Pull Request #553 · apache/maka · GitHub
Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) by UncertaintyDeterminesYou4ndMe · Pull Request #553 · apache/maka · GitHub
Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) by UncertaintyDeterminesYou4ndMe · Pull Request #553 · apache/maka · GitHub
Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) by UncertaintyDeterminesYou4ndMe · Pull Request #553 · apache/maka · GitHub
Skip to content

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI) - #553

Closed
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui
Closed

feat(desktop): task ledger panel — snapshot pull, honest cancel, race-hardened refresh (#15 P0-task UI)#553
UncertaintyDeterminesYou4ndMe wants to merge 5 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:feat/task-ledger-ui

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

#15 task-tracking 的 UI 切片,接 #537(model-facing 原语,已合并)。

做了什么

聊天界面上方新增会话任务面板:实时展示模型台账(状态徽标 + 标题 + 相对时间),用户唯一操作是取消任务(叫停某项工作)。台账归模型所有,面板刻意只读为主——#15 原文:「not a UI-only checklist」。

架构(全部锚在现有先例)

实现先例
信号SessionChangedReason + 'task-updated';wiring 的 onMutation 观察者把每次台账变更(模型工具与取消 IPC 共享同一 wired store)桥到 sessions:changed现有 sessions:changed 通道
IPCtasks:list / tasks:cancel(invoke);cancel 的 status 字面量钉死在 main 进程 handler,渲染层只传 idplan-reminders-ipc
数据流invoke 拉快照 + task-updated 触发刷新,不进逐 token 事件流plan-reminder-panel
面板packages/ui/src/task-ledger-panel.tsx 受控组件;空清单不挂载,零视觉负担plan-reminder-panel 受控范式
样式全 design token,过 #430/#448/#499/#527 全部 converge 契约plan-reminders.css

关键语义(三轮对抗审查驱动,共修复 9 项确认发现)

取消是不可逆的用户否决:cancelled 在 store 层冻结(模型不能翻回、不能改标题;想继续只能建新任务);completed 不可被取消(用户的陈旧快照与模型完成竞态时,不能摧毁完成记录);重开 completed 合法;cancelled→cancelled 幂等(无写、无错)。

取消对竞态诚实:tasks:cancel 返回 { outcome: 'cancelled' | 'already_terminal', tasks }——用户点取消恰逢模型改终态时,渲染层静默收敛到真实状态,不弹「请稍后重试」这种永远重试不通的误导 toast。

刷新无竞态:会话切换同步清空(旧会话任务不会在新会话下渲染、stale 取消按钮不会错配 id);同会话请求单调 seq 排序(旧响应不覆盖新快照);瞬时失败保留最后已知快照(不再静默清空面板)。

信号最小化:task-updated 不触发全量会话列表刷新(台账不改列表元数据),不重置会话事件流健康(它不伴随 transcript 重拉,重置会掩盖死掉的事件流)。

a11y:取消按钮 unmount 后焦点回收到面板根;aria-live 播报结果;过 check-a11y。

验证

  • @maka/core / @maka/storage / @maka/runtime / @maka/ui / @maka/desktop 五套件(数字见 PR 评论区补充)
  • 新契约测试:IPC 面(cancel 钉死 status、outcome 契约)、preload 命名空间、task-updated reason、wiring onMutation 行为级用例(观察者抛错不影响已提交变更)、幂等取消不写盘、失败保留快照
  • check-console / check-a11y / check-copy 门禁全过;npm run typecheck / git diff --check 干净

陆逊 added 3 commits July 5, 2026 18:51
…sh (apache#15 P0-task UI)
Read-mostly session task panel above the chat shell: status badge,
subject, relative time, and a single user affordance — cancel. The
ledger belongs to the model (TaskCreate/TaskUpdate); the renderer
pulls snapshots over tasks:list/tasks:cancel invoke IPC and refreshes
on sessions:changed reason 'task-updated', emitted by a notification
decorator around the shared store so model tools, turn-tail reads and
the cancel IPC all flow through one instance. The cancel status
literal is pinned in the main-process handler; renderer args carry
only ids. Empty ledger renders nothing.
…r-only refresh, fail-to-empty
Four fixes from adversarial review of the panel slice:
- cancelled records a user veto and is now frozen at the store layer
(no status flip or subject edit; model and cancel IPC alike), and a
completed task cannot be cancelled — a stale panel snapshot racing
the model's finish must not destroy the completion record. Reopening
completed work stays legal. TaskUpdate's description teaches the rule.
- task-updated no longer triggers the full session-list re-pull (the
ledger never changes list metadata), so a task-heavy run stops
churning the sidebar.
- task-updated no longer resets per-session event-stream health: it is
not paired with a transcript re-pull, so it would mask a dead event
stream while the model keeps updating tasks.
- a failed tasks:list now clears the panel (guarded by the active-
session check) instead of leaving the previous session's tasks
rendered under the new session.
Third adversarial-review round (18-agent, high effort):
- session switch clears the panel synchronously before fetching, so the
previous session's rows (and their cancel ids) can never render under
the new session.
- with that guarantee, a failed tasks:list keeps the last known
snapshot instead of blanking the panel (the earlier fail-to-empty fix
silently unmounted the panel on one transient IPC failure).
- ledger snapshots are ordered by a monotonic sequence; an older
response can no longer overwrite a newer one.
- tasks:cancel now returns { outcome: 'cancelled' | 'already_terminal',
tasks }: a user click racing the model's own terminal transition
converges silently to the truth instead of a misleading retry toast,
and the returned snapshot removes the follow-up list round-trip.
cancelled -> cancelled with a status-only patch is an idempotent no-op
at the store (no write, no error).
- a11y: focus returns to the panel root when the cancel button unmounts
under focus, and an aria-live status line announces the outcome.
- task-updated effect semantics moved from source-regex contracts to
behavior probes in the effect-stability harness; dead empty-state
branch and CSS removed; border width tokenized per the new
border-width converge contract.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

验证数字补充(最终 HEAD c09c7b6,本地 darwin/node 22):

套件结果
@maka/core720 pass / 0 fail
@maka/storage172 pass / 0 fail
@maka/ui44 pass / 0 fail
@maka/desktop2046 pass / 0 fail(含 console/a11y/copy 门禁,task-ledger-ui-contract 与 effect-stability 行为探针)
typecheck / git diff --check干净

@Astro-Han

Copy link
Copy Markdown
Contributor

能增加一些截图,展示一下显示效果吗~

Adds a 'task-ledger' visual-smoke fixture: seeds the turn session with a
tasks.json covering all four statuses (in_progress / pending / completed
/ cancelled) so `npm run screenshots:single -- task-ledger` renders the
session task panel above the chat — status badges, the relative-time
column, and the cancel affordance shown only on non-terminal rows.
Gives reviewers a reproducible screenshot and closes the panel's missing
visual baseline.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
dark-1280-motionlight-1280-motion

The ledger is persistent session state, unlike the transient tool /
reasoning / streaming surfaces — so it moves out of the main column
(where it competed with streaming output for vertical space) into its
own right-side rail, following the ArtifactPane collapsible-aside
precedent: 300px expanded, 32px collapsed strip with a 任务-count badge
and an in-progress dot, localStorage-remembered collapse, null render
for an empty ledger, and the artifact pane's bottom-drawer behavior at
the narrow breakpoint. The rail is only the collapse shell — list
rendering, cancel, focus recovery and aria-live stay in the controlled
TaskLedgerPanel, now imported via a @maka/ui subpath export to keep the
lazy-split discipline. ChatView drops its tasks/onCancelTask props.
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

布局更新(4005727):任务面板从聊天上方迁移到独立右侧可折叠任务栏

动机:任务台账是持续状态,不该和工具调用/推理/流式输出争抢主栏垂直空间;右栏让它常驻可见、聊天主栏完整归还给对话流。

实现完全复用 ArtifactPane 的可折叠 aside 先例:

  • 展开 300px / 折叠 32px 细条(竖排「任务」+ 计数 + 进行中小圆点);
  • 折叠态 localStorage 记忆,空台账 return null 不占空间;
  • 窄屏(990)走 ArtifactPane 同款断点方案:变全宽底部抽屉;
  • rail 只是折叠壳,列表/取消/焦点回收/aria-live 都留在受控的 TaskLedgerPanel。

复现截图(浅/深/窄三个变体):

npm --workspace @maka/desktop run screenshots:single -- task-ledger

新截图随后由作者贴上。验证:typecheck 干净,@maka/ui 44/0,@maka/desktop 2047/0(含新增 rail 契约:折叠持久化、空台账不渲染、细条计数)。

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor
light-1280-motiondark-1280-motionlight-990-motion

@jackwenerjackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against today's main. Two parts of this PR are genuinely strong and worth preserving through a rework: the race hardening is verified sound (shared monotonic snapshotSeq across refresh+cancel, applySnapshot gated on seq AND active-session identity — ordered, not debounced), and the CSS is fully governance-compliant (all tokens, sanctioned radius families, real @maka/ui primitives — zero violations found).

Why this needs rework rather than a rebase: main's ledger model has moved underneath the PR. packages/core/src/task-ledger.ts now defines SIX statuses (pending/in_progress/blocked/failed/completed/cancelled) with a transition state machine (canTransitionTaskStatus), evidence requirements, and reopen-via-explicitReopen — which explicitly allows cancelled→pending and completed→in_progress. The PR hard-codes the old 4-status world: task-ledger-panel.tsx status maps are missing blocked/failed (TS compile error after rebase), and the new explainTaskUpdateRejection pins 'cancelled is frozen; no resurrection' — mutually exclusive with main's reopen design. Please rebuild on main's semantics: drop explainTaskUpdateRejection (main owns transitions now), extend label/badge maps to all 6 statuses, decide badge treatment for blocked/failed. 7 files also conflict textually, concentrated in the ledger primitives; the SessionChangedReason/'task-updated' IPC seam itself is still clean.

Also address in the rework:

  • Cancel is cooperative, not a turn-abort — the model only sees it at the next turn boundary (ledger tail fragment rebuilt per turn). That's a defensible design but the UI should say so: show 取消中 (advisory) distinct from 已取消, or note it in the panel.
  • already_terminal is silently swallowed: user clicks 取消 on a just-completed task and the row flips to 已完成 with no explanation. Add a toast/aria-live note for that outcome.
  • Tests: most of task-ledger-ui-contract.test.ts is regex-against-source (pins strings, not behavior). Keep the two real behavioral tests (onMutation containment, task-updated effect routing) and add behavioral coverage for the already_terminal race and snapshotSeq ordering — those are the claims the PR title makes.

One nice surprise: the implementation is a right-side collapsible rail that renders nothing when empty — better than the 'panel above the chat' the description promises. Update the description to match.

Happy to re-review after the rework; the hard parts (races, governance) are already right.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
ContributorAuthor

关闭:本 PR 的目标已被 main 的后续演进完整吸收——#956 在 task-ledger 原语上建了分层任务账本与 TaskLedgerPanel,#1039 把任务/工件/浏览器统一进右侧 SessionWorkbar(正是当年讨论过的「合并单栏」方案的完整版)。本 PR 的独立右栏实现不再有增量价值,感谢当时的评审意见。

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@UncertaintyDeterminesYou4ndMe@Astro-Han@jackwener