refactor(client): replace the per-field settings preference controller with a namespace settings scope
bindSettingsScope mirrors the Host-side settings owner seam in the browser: one scope per namespace publishes a snapshot store (status, section value, revision, writability, host/memory mode), validates sections against the namespace's serialized wire schema via dsh-client-schema-form, and keeps the controller's listener-before-read, revisioned serialized writes, latest-wins publication, conflict recovery, and disposal quiescence. Theme, locale, and busy-Enter services now take the scope as a constructor collaborator, which removes the bindPersistence/syncPreference two-phase callback pair and the defaulted no-op persist writers; hand-written wire guards fall away in favor of the registered schema. test-runtime gains a stubSettingsScope double.
This commit is contained in:
@@ -34,6 +34,8 @@ import type { Stabilizer } from './fixtures.ts'
|
||||
|
||||
export { domSnapshotSerializer, registerDomSnapshotSerializer } from './snapshot.ts'
|
||||
export { FixtureSession, TestSessions } from './sessions.ts'
|
||||
export { stubSettingsScope } from './settings-scope.ts'
|
||||
export type { StubSettingsScope } from './settings-scope.ts'
|
||||
export { TestWorkspaces } from './workspaces.ts'
|
||||
export { conversationSnapshot, workspaceListState } from './fixtures.ts'
|
||||
export type { SessionBehaviorOverrides, SessionFixture, Stabilizer } from './fixtures.ts'
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/** Test double for the client settings-scope seam. */
|
||||
import { vi } from 'vitest'
|
||||
import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
|
||||
/** Handle over one stubbed scope: the scope, its write spy, and publication controls. */
|
||||
export interface StubSettingsScope<T> {
|
||||
/** The scope face handed to the service under test. */
|
||||
scope: SettingsScope<T>
|
||||
/** Spy behind `scope.set`; resolves immediately. */
|
||||
set: ReturnType<typeof vi.fn>
|
||||
/** @returns how many listeners are currently subscribed (disposal assertions). */
|
||||
listenerCount(): number
|
||||
/**
|
||||
* Replace part of the snapshot and notify subscribers, as a Host
|
||||
* acceptance would.
|
||||
* @param next - snapshot fields to replace.
|
||||
*/
|
||||
publish(next: Partial<SettingsScopeSnapshot<T>>): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an in-memory settings scope for service specs: starts in the host
|
||||
* loading state, records writes, and lets the test publish Host acceptances.
|
||||
* @returns the stub handle.
|
||||
*/
|
||||
export function stubSettingsScope<T>(): StubSettingsScope<T> {
|
||||
let snapshot: SettingsScopeSnapshot<T> = {
|
||||
status: 'loading', value: undefined, revision: undefined, writable: false, mode: 'host',
|
||||
}
|
||||
const listeners = new Set<() => void>()
|
||||
const set = vi.fn(() => Promise.resolve())
|
||||
return {
|
||||
scope: {
|
||||
getSnapshot: () => snapshot,
|
||||
subscribe: (listener) => {
|
||||
listeners.add(listener)
|
||||
return () => { listeners.delete(listener) }
|
||||
},
|
||||
set,
|
||||
},
|
||||
set,
|
||||
listenerCount: () => listeners.size,
|
||||
publish: (next) => {
|
||||
snapshot = { ...snapshot, ...next }
|
||||
for (const listener of [...listeners]) listener()
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user