feat(apes-sidebar): Phase 2 — per-tab state(tasks showInactive 持久化)

- useTasksPrefs:ctx.settings.register('apesSidebar.tasks') namespace,
  uSES 观察 + update 写回,applies:'live' 跨 reload 持久化
- tasks 面板加 showInactive 开关:OFF 时只渲染 running 会话
- filterTasksRows 纯函数导出(hook 需渲染,过滤决策单测锁定)
- locales:tasks.showInactive zh/en
- tests/tabs.spec.ts:+4 个 per-tab state 过滤测试(25→29)
- AGENTS.md:Phase 2 更新(per-tab state 落地,scratch 冒烟 +
  apps·msgs·profile feed 延后——better-sidebar 无对应数据源 + 无 dsh CLI)

门禁:typecheck rc=0 · build rc=0 · test 29/29
This commit is contained in:
figmar
2026-09-04 20:11:06 +08:00
parent 856d744139
commit 48e16c73c7
4 changed files with 145 additions and 27 deletions
+34 -1
View File
@@ -16,7 +16,7 @@
* `registerTab` (returns `() => void`, invoked by cordis on fiber teardown).
*/
import { describe, expect, it } from 'vitest'
import { apply } from '../src/client/index.tsx'
import { apply, filterTasksRows } from '../src/client/index.tsx'
/** The five tab ids this plugin owns (the + menu contract). */
const EXPECTED_IDS = ['apes:tasks', 'apes:agents', 'apes:apps', 'apes:messages', 'apes:profile']
@@ -232,3 +232,36 @@ describe('apes-sidebar tab badges (Phase 2)', () => {
}
})
})
/**
* Phase 2 per-tab state: the apes:tasks `showInactive` filter. When the user
* toggles the preference off, only running sessions render. This is a pure
* decision function (the hook's uSES↔settings wiring needs a render, but the
* filter itself is plain and unit-testable).
*/
describe('apes:tasks showInactive per-tab state filter (Phase 2)', () => {
const sessions = [
{ id: 'a', running: true },
{ id: 'b', running: false },
{ id: 'c', running: true },
{ id: 'd' }, // running undefined → treated as not running
]
it('showInactive=true renders every session', () => {
expect(filterTasksRows(sessions, true)).toHaveLength(4)
})
it('showInactive=false renders only running sessions (2 of 4)', () => {
const rows = filterTasksRows(sessions, false)
expect(rows).toHaveLength(2)
expect(rows.map((s) => s.id)).toEqual(['a', 'c'])
})
it('showInactive=false on an all-running list keeps everything', () => {
expect(filterTasksRows([{ running: true }, { running: true }], false)).toHaveLength(2)
})
it('showInactive=false on an all-inactive list yields empty', () => {
expect(filterTasksRows([{ running: false }, {}], false)).toHaveLength(0)
})
})