Branch for hardware verification on a host that meets v0.24.1's peer range.
Base: upstream `dsh-better-sidebar` v0.24.1 (tag v0.24.1 = a2751cf), MIT.
Measured delta carried over from the v0.19.1 line: 78 rename-only files, 11 real
edits, 10 additions. Ported with the same measurement method as the v0.19.1
re-base: build a normalized base (upstream tree + the fork rename, excluding
README.md / package.json / docs/plans/* which deliberately keep the upstream
name), extract the delta against it, then three-way apply onto the renamed
v0.24.1 tree (`git apply -3`). 13 conflicts resolved by hand.
Identity:
- version 1.3.0, SIDEBAR_SERVICE_VERSION 1.3.0 (package.json ↔ service.ts pair
is asserted by tests/service.spec.ts), forkedFrom -> v0.24.1/a2751cf.
- package.json keeps the union of both sides' metadata: upstream's new
`zustand` + `icon`/`locale` files entries, plus the fork's `scripts/switch.sh`
entry and its `forkedFrom` block.
- README.md takes upstream v0.24.1 wholesale (its own v0.24.1 changelog and all
16 sections) and keeps the fork's two deltas: the fork-notice block at the top
(now documenting the 0.2.0-rc.1+ requirement) and the fork package name.
- peerDependencies stay upstream's `^0.2.0-rc.1` — this line does NOT load on
DSH 0.1.x: the host's semver pre-check rejects a caret range across a minor
and silently disables the row (no error, no load).
Carried features, all re-verified against the new base:
- apes:* information-layer tabs (src/client/apes/) — the subtree's session feed
changed shape in 0.1.7 and the port follows the host's own migration notes in
context-types.ts: the per-session catalog is now
`projectionsBySession[id].values.subagentCatalog` (a flat entry list) instead
of 0.1.6's `subagentsByParent` catalog with `entries`/`kind`/`activity`, and
the removed `list.current` is answered by `props.scope.sessionId`. Rows are
normalized to { id, label, mode } with activity read from the durable
summaries, so catalog and byId sources render alike.
- HTML preview delivery through srcdoc (sandbox kept intact).
- Panel host mounted inside the shell's framed content viewport.
Gate: typecheck clean; build clean; 1515 passed / 9 skipped (141 files).
NOT applied to ~/.dsh/profiles/desktop — that profile runs DSH 2.0.10 with
@deepseek-ai/dsh 0.1.5-rc.2, where this line is silently disabled by design.
123 lines
4.6 KiB
TypeScript
123 lines
4.6 KiB
TypeScript
/**
|
|
* apes:* tab registration tests (Phase 1/1b, C6: 3 tabs). The fork registers
|
|
* its three information-layer tabs (agents / apps / profile) through the same
|
|
* BetterSidebarService the upstream builtins use (dogfood). `apes:tasks` was
|
|
* removed in C6 — it overlaps the official DSH session tree in the native
|
|
* sidebar. The spec pins the registration contract (id / single / order /
|
|
* component presence) and the isolated i18n contract (the apes namespace
|
|
* resolves zh vs en through the attached locale service, and every apesZh
|
|
* key has an apesEn entry).
|
|
*
|
|
* Runs in the node vitest environment like builtins.spec.ts — no DOM, no
|
|
* real DSH host (a live cordis scratch mount is Phase 2+).
|
|
*/
|
|
import { describe, expect, it } from 'vitest'
|
|
// Browser globals before the client graph loads (mirror of builtins.spec.ts).
|
|
import './browser-globals.ts'
|
|
|
|
import type { Context } from '../src/context-types.ts'
|
|
import { createBetterSidebarService } from '../src/client/service.ts'
|
|
import { createSidebarStore } from '../src/client/state.ts'
|
|
import { registerApesTabs } from '../src/client/apes/index.ts'
|
|
import { apesZh, apesEn, attachApesLocale, t, type ApesCopyKey } from '../src/client/apes/locales.ts'
|
|
|
|
function setup(): {
|
|
service: ReturnType<typeof createBetterSidebarService>
|
|
store: ReturnType<typeof createSidebarStore>
|
|
dispose: () => void
|
|
} {
|
|
const store = createSidebarStore()
|
|
const service = createBetterSidebarService(store)
|
|
const dispose = registerApesTabs({} as Context, service)
|
|
return { service, store, dispose }
|
|
}
|
|
|
|
describe('apes:* tab registrations', () => {
|
|
it('registers the 3 apes tabs with the apes: namespace', () => {
|
|
const { service, dispose } = setup()
|
|
try {
|
|
expect(service.getTabs().map(t => t.id).sort()).toEqual(
|
|
['apes:agents', 'apes:apps', 'apes:profile'],
|
|
)
|
|
} finally {
|
|
dispose()
|
|
}
|
|
})
|
|
|
|
it('every apes tab is single-instance, visible, and component-carrying', () => {
|
|
const { service, dispose } = setup()
|
|
try {
|
|
for (const id of ['apes:agents', 'apes:apps', 'apes:profile']) {
|
|
const tab = service.getTab(id)
|
|
expect(tab, `${id} registered`).toBeDefined()
|
|
expect(tab?.single, `${id} single`).toBe(true)
|
|
expect(tab?.hidden, `${id} hidden`).not.toBe(true)
|
|
expect(tab?.component, `${id} component`).toBeDefined()
|
|
expect(tab?.icon, `${id} icon`).toBeDefined()
|
|
}
|
|
} finally {
|
|
dispose()
|
|
}
|
|
})
|
|
|
|
it('apes tabs sort after the upstream builtins (order 40+)', () => {
|
|
const { service, dispose } = setup()
|
|
try {
|
|
const orders = {
|
|
agents: service.getTab('apes:agents')?.order,
|
|
apps: service.getTab('apes:apps')?.order,
|
|
profile: service.getTab('apes:profile')?.order,
|
|
}
|
|
// C8: order 40-50 places apes tabs immediately after the native builtins
|
|
// (10-35), not in a far-away 900+ band — visual unity in the tab strip.
|
|
expect(orders.agents).toBeGreaterThanOrEqual(40)
|
|
expect(orders.apps).toBeGreaterThanOrEqual(40)
|
|
expect(orders.profile).toBeGreaterThanOrEqual(40)
|
|
// Strictly ordered: agents < apps < profile.
|
|
expect(orders.agents!).toBeLessThan(orders.apps!)
|
|
expect(orders.apps!).toBeLessThan(orders.profile!)
|
|
} finally {
|
|
dispose()
|
|
}
|
|
})
|
|
|
|
it('the disposer unregisters all apes tabs (HMR-safe)', () => {
|
|
const store = createSidebarStore()
|
|
const service = createBetterSidebarService(store)
|
|
const dispose = registerApesTabs({} as Context, service)
|
|
expect(service.getTabs()).toHaveLength(3)
|
|
dispose()
|
|
expect(service.getTabs()).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('apes i18n contract', () => {
|
|
it('every apesZh key has an apesEn entry (no missing translations)', () => {
|
|
const zhKeys = Object.keys(apesZh) as ApesCopyKey[]
|
|
for (const key of zhKeys) {
|
|
expect(apesEn[key], `en entry for ${key}`).toBeDefined()
|
|
expect(typeof apesEn[key]).toBe('string')
|
|
expect((apesEn[key] ?? '').length).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
it('resolves zh when the DSH preference is zh', () => {
|
|
attachApesLocale({ getSnapshot: () => ({ active: 'zh-CN' }) })
|
|
expect(t('apesAgentsTitle')).toBe('智能体')
|
|
expect(t('apesAppsTitle')).toBe('应用')
|
|
expect(t('apesProfileTitle')).toBe('画像')
|
|
})
|
|
|
|
it('resolves en when the DSH preference is en', () => {
|
|
attachApesLocale({ getSnapshot: () => ({ active: 'en' }) })
|
|
expect(t('apesAgentsTitle')).toBe('Agents')
|
|
expect(t('apesAppsTitle')).toBe('Apps')
|
|
expect(t('apesProfileTitle')).toBe('Profile')
|
|
})
|
|
|
|
it('falls back to zh when no locale service is attached', () => {
|
|
attachApesLocale(undefined)
|
|
expect(t('apesAgentsTitle')).toBe('智能体')
|
|
})
|
|
})
|