// Generated by scripts/sync-shared.mjs from shared/client/settings/settings-entry-form.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs". // Generated by scripts/sync-shared.mjs from shared/client/settings/settings-entry-form.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs". /** * Family settings form bound to the profile entry id a Host actually serves. * * Under the 0.1.7 settings model a form is addressed by PROFILE ENTRY ID, while * a family card knows only its own settings namespace. The family binder * (`ctx.get('webUiSettings')`, published by dsh-web-settings) resolves one onto * the other and is the preferred transport; this module is the fallback for a * page that serves no binder — the group plugin is not installed, or its client * half has not applied yet (the aggregate mounts its children in order, and a * child still waiting on its own injects publishes nothing). * * The shared describe mirror is the only local evidence of which row id this * profile serves, but it answers asynchronously: at plugin activation it usually * holds nothing yet. A one-shot guess therefore cannot be right for every * deployment, and the form is bound once per session: * * - binding the bare namespace made every save answer `No configurable plugin * entry ""` on an aggregate install (the namespace is not an entry * id there), which the card reported as "the deployment did not accept these * values"; * - binding the aggregate row id would break a standalone install, whose row id * is the namespace or the package's own `ui-*` row. * * So the fallback starts on the most likely row and REBINDS the moment the * mirror names one of this package's rows. Until the mirror answers, the form * reports the binding it holds (usually `loading`), which the card renders as an * unavailable form rather than a doomed one. */ import type { ConfigForm, ConfigFormSnapshot, ConfigForms } from '@deepseek-ai/dsh-client-ui-settings/client' /** Options of {@link createServedEntryForm}. */ export interface ServedEntryFormOptions { /** The shared configuration forms service (`ctx.configForms`). */ forms: ConfigForms /** * Profile entry ids this package's rows carry, most likely first: the * aggregate's generated row, the package's own standalone row, then the bare * namespace. The first is the fallback while the mirror has no answer; the * last is the answer when the mirror serves none of them. */ entryIds: readonly string[] } /** The snapshot a form reports before the Host has answered with this entry. */ function pendingSnapshot(): ConfigFormSnapshot { return { status: 'loading', value: undefined, base: undefined, user: undefined, revision: undefined, writable: false, } } /** * Create a settings form bound to the profile entry id this Host serves, and * rebound whenever the shared describe mirror names a different one of this * package's rows. * @param options - the shared forms service and this package's candidate row ids. * @returns a form delegating to the currently resolved entry's own form. */ export function createServedEntryForm(options: ServedEntryFormOptions): ConfigForm { const { forms, entryIds } = options const fallbackId = entryIds[0] const namespaceId = entryIds[entryIds.length - 1] const listeners = new Set<() => void>() let boundId: string | undefined let bound: ConfigForm | undefined let offBound: (() => void) | undefined let snapshot: ConfigFormSnapshot = pendingSnapshot() /** Republish: the delegated snapshot when one is bound, the pending one otherwise. */ const publish = (): void => { if (bound !== undefined) snapshot = bound.getSnapshot() for (const listener of [...listeners]) listener() } /** * The entry id the mirror currently justifies. An unanswered or empty mirror * is not evidence of absence, so it keeps the first candidate; a mirror that * answers without any of this package's rows leaves the namespace itself. * @returns the entry id to bind. */ const resolve = (): string => { let served: readonly string[] | undefined try { served = forms.describe().getSnapshot().view?.namespaces.map(row => row.ns) } catch { // A mirror that refuses the read is unanswered too, not absent. served = undefined } if (served === undefined || served.length === 0) return fallbackId return entryIds.find(id => served.includes(id)) ?? namespaceId } /** Bind (or rebind) the resolved entry's form; a no-op while it is unchanged. */ const bind = (): void => { const target = resolve() if (target === boundId) return offBound?.() offBound = undefined let form: ConfigForm try { form = forms.get(target) } catch { // A service that refuses the id stays unbound; the next mirror answer // retries rather than pinning a wrong entry for the session. boundId = undefined return } boundId = target bound = form offBound = form.subscribe(() => { publish() }) publish() } // The mirror subscription lives for the page: the card owns its form for the // session, and the ConfigForm contract carries no disposer to hang it on. The // callback is cheap (a resolve plus a no-op while the id is unchanged). A // mirror without a subscription seat keeps the initial binding. try { forms.describe().subscribe(() => { bind() }) } catch { // A describe face that refuses the subscription: the initial binding stands. } bind() return { getSnapshot: () => snapshot, subscribe: (listener) => { listeners.add(listener) return () => { listeners.delete(listener) } }, set: (field, value) => bound?.set(field, value) ?? Promise.resolve(false), unset: (field) => bound?.unset(field) ?? Promise.resolve(false), mutate: (ops, expectedRevision) => bound?.mutate(ops, expectedRevision) ?? Promise.resolve(false), } }