feat(apes-sidebar): Phase 2 — badge 计数 + auto-collapse(visible 门控暂停)
- apes:tasks badge = running 会话数;apes:agents badge = subagent 数 (纯 (ctx)=>number|null 函数,0→null 隐藏,host 封顶 99+) - useSessionList 接受 visible:非活动 tab 切 no-op subscribe, 停止 live session-list churn 重渲染隐藏面板(better-sidebar 'live views pause otherwise' 契约 = auto-collapse) - tests/tabs.spec.ts:新增 5 个 badge 契约测试(6→11),mock sessions feed 直测 badge 纯函数 - AGENTS.md:Phase 2 标 partial(per-tab state / scratch 冒烟 / apps·messages·profile feed 延后,better-sidebar 0.18.0 无相应 API/数据源) 门禁:typecheck rc=0 · build rc=0(client.js 15.74→20K)· test 25/25
This commit is contained in:
@@ -28,6 +28,7 @@ interface Registered {
|
||||
order?: number
|
||||
single?: boolean
|
||||
component: unknown
|
||||
badge?: (ctx: unknown, scope: unknown, state: unknown) => string | number | null | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,3 +155,80 @@ describe('apes-sidebar client tab registration (Phase 1)', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Build a mock `ctx.sessions.list.getSnapshot()` feed for badge assertions.
|
||||
* `byId` maps session id → a summary with optional `running` / `origin`.
|
||||
*/
|
||||
function makeMockSessionsCtx(byId: Record<string, { running?: boolean; origin?: string }>) {
|
||||
const snapshot = { current: undefined as string | undefined, byId }
|
||||
return {
|
||||
sessions: {
|
||||
list: {
|
||||
getSnapshot: () => snapshot,
|
||||
subscribe: () => () => undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 2 badge contract: `apes:tasks` shows the running-session count,
|
||||
* `apes:agents` shows the subagent count, and BOTH hide (return null) when
|
||||
* their count is 0. The badge is a pure `(ctx) => …` function, so it is
|
||||
* exercised directly against a mock sessions feed — no DOM, no rendering.
|
||||
*/
|
||||
describe('apes-sidebar tab badges (Phase 2)', () => {
|
||||
/** Register into a fresh mock registry and return the captured descriptors. */
|
||||
function registeredDescriptors() {
|
||||
const { registry, registered } = makeMockRegistry()
|
||||
const eff = makeEffectHarness()
|
||||
apply({ betterSidebar: registry, locale: makeMockLocale(), ...eff } as never)
|
||||
return registered
|
||||
}
|
||||
|
||||
function badgeOf(registered: Registered[], id: string) {
|
||||
return registered.find((d) => d.id === id)?.badge
|
||||
}
|
||||
|
||||
it('apes:tasks badge is the running-session count (2 running → 2)', () => {
|
||||
const badge = badgeOf(registeredDescriptors(), 'apes:tasks')
|
||||
expect(badge).toBeTypeOf('function')
|
||||
const ctx = makeMockSessionsCtx({
|
||||
a: { running: true },
|
||||
b: { running: true },
|
||||
c: { running: false },
|
||||
})
|
||||
expect(badge!(ctx as never, {} as never, {} as never)).toBe(2)
|
||||
})
|
||||
|
||||
it('apes:tasks badge is null when nothing is running (hidden pill)', () => {
|
||||
const badge = badgeOf(registeredDescriptors(), 'apes:tasks')
|
||||
const ctx = makeMockSessionsCtx({ a: { running: false }, b: {} })
|
||||
expect(badge!(ctx as never, {} as never, {} as never)).toBeNull()
|
||||
})
|
||||
|
||||
it('apes:agents badge is the subagent count (1 subagent → 1)', () => {
|
||||
const badge = badgeOf(registeredDescriptors(), 'apes:agents')
|
||||
expect(badge).toBeTypeOf('function')
|
||||
const ctx = makeMockSessionsCtx({
|
||||
parent: {},
|
||||
child: { origin: 'subagent' },
|
||||
other: { running: true }, // running but NOT a subagent → not counted
|
||||
})
|
||||
expect(badge!(ctx as never, {} as never, {} as never)).toBe(1)
|
||||
})
|
||||
|
||||
it('apes:agents badge is null when there are no subagents (hidden pill)', () => {
|
||||
const badge = badgeOf(registeredDescriptors(), 'apes:agents')
|
||||
const ctx = makeMockSessionsCtx({ a: {}, b: { running: true } })
|
||||
expect(badge!(ctx as never, {} as never, {} as never)).toBeNull()
|
||||
})
|
||||
|
||||
it('apps/messages/profile have NO badge (information placeholders, no count)', () => {
|
||||
const registered = registeredDescriptors()
|
||||
for (const id of ['apes:apps', 'apes:messages', 'apes:profile']) {
|
||||
expect(badgeOf(registered, id)).toBeUndefined()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user