@deepseek-ai/dsh-client-schema-form rehydrates the wire's serialized
schemastery envelope (new Schema(json)) and edits a draft user section
against it: presence-in-draft marks a field overridden with a per-field
reset, inherited values render as placeholders, role('secret') slots are
write-only with configured-state placeholders from the wire's secrets
list, dict adds take a union-typed sKey as their vocabulary, and any
node the renderer cannot faithfully edit falls back to a read-only view
instead of silently disappearing. renderField(context) is the role hook
the Models page will use for the credential-ref control; validateDraft
runs the same rehydrated validator the host uses, so the browser and
host judge one schema.
439 lines
16 KiB
TypeScript
439 lines
16 KiB
TypeScript
/**
|
|
* Schema-driven form renderer. One controlled component edits a draft user
|
|
* section against a rehydrated schemastery schema: every schema-declared
|
|
* field renders a control, a field's presence in the draft marks it
|
|
* overridden (with a per-field reset back to the inherited layer), secret
|
|
* slots render write-only, and nodes the renderer cannot faithfully edit
|
|
* fall back to a read-only view instead of disappearing.
|
|
* @module @deepseek-ai/dsh-client-schema-form/SchemaForm
|
|
*/
|
|
|
|
import { useMemo } from 'react'
|
|
import type { ReactNode } from 'react'
|
|
import {
|
|
deletePath, getPath, hasPath, nodeKind, rehydrateSchema, setPath, unionChoices,
|
|
} from './model.ts'
|
|
import type { SchemaNode } from './model.ts'
|
|
import styles from './SchemaForm.module.css'
|
|
|
|
/** One secret slot reported by the wire (`SettingsSecretView` shape). */
|
|
export interface SchemaFormSecret {
|
|
/** Path from the section root to the write-only field. */
|
|
path: readonly string[]
|
|
/** Whether a value is currently stored (it never rides the wire). */
|
|
set: boolean
|
|
}
|
|
|
|
/** User-visible strings; override to localize. */
|
|
export interface SchemaFormLabels {
|
|
reset: string
|
|
add: string
|
|
remove: string
|
|
secretSet: string
|
|
secretUnset: string
|
|
inherited: string
|
|
unsupported: string
|
|
}
|
|
|
|
const DEFAULT_LABELS: SchemaFormLabels = {
|
|
reset: 'Reset',
|
|
add: 'Add',
|
|
remove: 'Remove',
|
|
secretSet: 'Configured — enter a new value to replace',
|
|
secretUnset: 'Not configured',
|
|
inherited: 'Default',
|
|
unsupported: 'This field has no form control; edit the settings document directly.',
|
|
}
|
|
|
|
/** Everything a custom field renderer learns about one leaf position. */
|
|
export interface SchemaFieldContext {
|
|
/** Path from the section root. */
|
|
path: readonly string[]
|
|
/** Live schema node at this position. */
|
|
node: SchemaNode
|
|
/** `meta.role` of the node, when declared (`secret`, `credential-ref`, …). */
|
|
role: string | undefined
|
|
/** Current draft value at the path (`undefined` while inherited). */
|
|
draftValue: unknown
|
|
/** Inherited (resolved) value shown while the draft has no override. */
|
|
fallbackValue: unknown
|
|
/** Whether the draft carries this path. */
|
|
overridden: boolean
|
|
/** Whether the whole form is disabled. */
|
|
disabled: boolean
|
|
/** Store a draft value at this path. */
|
|
setValue: (value: unknown) => void
|
|
/** Remove this path from the draft (fall back to the inherited layer). */
|
|
clearValue: () => void
|
|
}
|
|
|
|
/** Props of {@link SchemaForm}. */
|
|
export interface SchemaFormProps {
|
|
/** Serialized schemastery envelope (`SettingsNamespaceView.schema`). */
|
|
schema: unknown
|
|
/** Draft user section being edited; never mutated. */
|
|
draft: Record<string, unknown>
|
|
/** Resolved value (defaults→base→user) used for inherited display. */
|
|
fallback?: unknown
|
|
/** Secret slots from the wire; matched by path for write-only placeholders. */
|
|
secrets?: readonly SchemaFormSecret[]
|
|
/** Disable every control (read-only provider or in-flight write). */
|
|
disabled?: boolean
|
|
/** Receives the complete next draft after each edit. */
|
|
onChange: (next: Record<string, unknown>) => void
|
|
/**
|
|
* Role-aware override hook: return a node to replace the default control
|
|
* for one leaf (the credential-ref control), or `undefined` to keep it.
|
|
*/
|
|
renderField?: (context: SchemaFieldContext) => ReactNode | undefined
|
|
/** String overrides for localization. */
|
|
labels?: Partial<SchemaFormLabels>
|
|
}
|
|
|
|
function pathKey(path: readonly string[]): string {
|
|
return path.join(' |