refactor(client): deferGroupRegistration owns the multi-hole flow semantics

The construction-rollback + late-conflict-rollback + loud-rethrow block was
about to be a verbatim clone across the two flow packages; ui-slots now owns
it as deferGroupRegistration (one occupant, several holes, as a unit), with
direct specs for all three arms, and the native flow consumes it.
This commit is contained in:
creatixchu
2026-07-29 03:17:01 +08:00
parent 62db16f81a
commit f56b9149e6
3 changed files with 110 additions and 24 deletions

View File

@@ -7,7 +7,7 @@
* both sides of the native interaction with one cordis.yml row; no client
* code branches on a capability kind.
*/
import { deferRegistration } from '@deepseek-ai/dsh-client-ui-slots'
import { deferGroupRegistration } from '@deepseek-ai/dsh-client-ui-slots'
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
// Type-only: pulls the SlotMap merge declaring the directory-flow holes.
import type {} from '@deepseek-ai/dsh-client-ui-workspace/client'
@@ -27,27 +27,15 @@ export const inject = ['slots', 'workspaces']
export function apply(ctx: ClientContext): void {
const injected = (): NativeFlowInjected => ({ pick: () => ctx.workspaces.pickDirectory() })
ctx.effect(() => {
// Constructing the pair can throw halfway (a declared hole already
// occupied registers synchronously): roll the earlier deferral back so
// no live subscription outlives the failed fiber. A conflict surfacing
// from a LATER ledger flush (holes declared after two flow providers
// activated) rolls the whole pair back the same way and re-raises on
// the global channel the boot's fail-loud handler owns — never a throw
// through the slot flush, never partial occupancy from this package.
const deferred: ReturnType<typeof deferRegistration>[] = []
const lateConflict = (error: unknown): void => {
for (const entry of deferred) entry.dispose()
queueMicrotask(() => { throw error instanceof Error ? error : new Error(String(error)) })
}
try {
deferred.push(deferRegistration(ctx.slots, 'conversation.hero.workspace.directoryFlow', NativeDirectoryFlow, () =>
ctx.slots.register({ name: 'conversation.hero.workspace.directoryFlow', inject: injected }, NativeDirectoryFlow), lateConflict))
deferred.push(deferRegistration(ctx.slots, 'sidebar.workspaces.directoryFlow', NativeDirectoryFlow, () =>
ctx.slots.register({ name: 'sidebar.workspaces.directoryFlow', inject: injected }, NativeDirectoryFlow), lateConflict))
} catch (error) {
for (const entry of deferred) entry.dispose()
throw error
}
return () => { for (const entry of deferred) entry.dispose() }
// One occupant, both holes, as a unit: construction or late conflicts
// (holes declared after rival providers activated) roll the whole pair
// back and fail loud — semantics owned by deferGroupRegistration.
const group = deferGroupRegistration(
ctx.slots,
['conversation.hero.workspace.directoryFlow', 'sidebar.workspaces.directoryFlow'] as const,
NativeDirectoryFlow,
name => ctx.slots.register({ name, inject: injected }, NativeDirectoryFlow),
)
return () => { group.dispose() }
}, 'directory-picker-native: flow registrations')
}