The remaining P1 from the #939 review, plus the P2 it shares a mechanism with. Nothing carried a version, so two tabs editing one namespace silently overwrote each other — reproduced as tab B's `reasoning` lost to tab A's older draft. The seam's per-namespace write queue orders writes; it cannot tell a fresh writer from one replaying a snapshot a predecessor superseded. Each namespace now carries a monotonic `revision` over its RAW section. A write may send `expectedRevision`, checked at the FRONT of the queue (not at call time, which would race the very predecessor it guards against); a mismatch rejects with `SettingsConflictError` → `settings-conflict` on the wire, carrying both revisions. The editor captures the revision it opened at and, on conflict, asks the user to reopen rather than replaying its snapshot. The same counter fixes the missing broadcast. `settings/updated` is gated on the resolved value — correct for consumers, wrong for configuration surfaces: storing an override equal to the composition base leaves the resolved value alone while changing what the document says (the field is now overridden, not inherited) and moving every open editor's revision. `settings/document-updated (ns, revision)` fires on any raw-section change, in-process or external, and `host/settings-changed` now rides it. That event also closes the stale model picker: editing a provider's `models` changes no route, so `llm/adapters-updated` never fired and an open picker kept serving the old catalog. A change to an exposed provider namespace now emits `host/models-changed` too — that namespace holds the catalog. Docs: both sides of the five touched README pairs, a type-equiv block for `SettingsPathOp`, and an Agent Note recording what the plane exposes and who may overwrite what. The deferred wire-redaction gaps (secrets behind union/intersection/transform, `.default(...)` in the served envelope, schema text in rejection messages, `new Function` rehydration, pi-ai's `headers`) are recorded as TODO(settings-wire-redaction) and in Known Limitations rather than half-fixed.
2.9 KiB
@deepseek-ai/dsh-client-schema-form
English | 中文
Schema/draft model layer for settings editors. The wire's settings.describe carries each namespace's serialized schemastery schema (schema.toJSON() ref envelope); rehydrateSchema turns it back into a live validator with new Schema(json) — the same schema object that validates a section on the host validates drafts in the browser, so client-side validation never drifts from the seam's. Editors render their own controls (the Models page hand-writes its card around the fields it probes here); this package owns no React and no rendering.
Contract
The unit of editing is a draft user section: a plain object edited immutably (setPath materializes intermediates, deletePath is the per-field reset — dropping the key falls the resolved value back to the composition base and schema defaults). A field's presence in the draft marks it overridden (hasPath) — presence semantics, not value comparison, exactly mirroring the settings seam's layering. nodeAtPath resolves the schema node addressed by a configurable-provider directory settingsPath (object properties by name, dict entries through inner), so an editor can probe which fields a provider's profile carries (and their meta.role) before deciding what to render; an unresolvable path returns undefined so the caller degrades loudly instead of rendering a wrong subtree. validateDraft(schema, draft) runs the rehydrated validator and returns its failure message, letting pages reject an invalid draft before writing.
Model Experience
None, as this package backs browser configuration editors; nothing here reaches a model request.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
- Rehydration executes the served envelope —
rehydrateSchemareconstructs a live schemastery validator, and schemastery revives serialized callbacks throughnew Function, so the schema envelope is executable content rather than inert data. That is acceptable only because the envelope comes from the same host that serves the page; a browser schema protocol should carry a description the client cannot execute, which is deferred with the settings seam's wire-boundary work. - Validation is draft-level, not per-field —
validateDraftreports schemastery's first failure message (which names the$.path); per-field error mapping is deferred until a consumer needs it. - No generic renderer — a schema-driven form component was built and then replaced by the hand-written Models editor (Agent Note); if a future page needs to edit arbitrary sections, it starts from these helpers, not from a resurrected generic renderer, unless the note's trade-off changes.