@cosmicdrift/kumiko-types 0.383.1 → 0.385.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { ZodType } from "zod";
2
2
  import type { ConcurrencyMode } from "./concurrency-mode.ts";
3
3
  import type { ConfigScope } from "./config-scope.ts";
4
4
  import type { DbConnection } from "./db-connection.ts";
5
- import type { FieldDefinition, OptionsQueryPayload } from "./fields.ts";
5
+ import type { EntityFieldDefinition, OptionsQueryPayload } from "./fields.ts";
6
6
  import type { EscapeHatchDeclaration, JobContext, JobDeferral } from "./handlers.ts";
7
7
  import type { PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn } from "./hooks.ts";
8
8
  import type { TenantId } from "./identifiers.ts";
@@ -289,7 +289,7 @@ export type UiExtensionDef = {
289
289
  };
290
290
  export type RegistrarExtensionDef = {
291
291
  readonly onRegister?: (entityName: string, options?: Record<string, unknown>) => void;
292
- readonly extendSchema?: (entityName: string) => Record<string, FieldDefinition>;
292
+ readonly extendSchema?: (entityName: string) => Record<string, EntityFieldDefinition>;
293
293
  readonly hooks?: RegistrarExtensionHooks;
294
294
  readonly extendSearch?: (entityName: string) => Record<string, unknown>;
295
295
  readonly uiExtension?: UiExtensionDef;
package/dist/fields.d.ts CHANGED
@@ -739,7 +739,23 @@ export type EntityIndexDef = {
739
739
  */
740
740
  readonly where?: unknown;
741
741
  };
742
- export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
742
+ export type ResolvedPiiStance = {
743
+ readonly pii: true;
744
+ } | {
745
+ readonly userOwned: NonNullable<ResolvedPiiFlags["userOwned"]>;
746
+ } | {
747
+ readonly tenantOwned: true;
748
+ } | {
749
+ readonly recordOwned: true;
750
+ } | {
751
+ readonly allowPlaintext: string;
752
+ } | {
753
+ readonly subjectRef: true;
754
+ };
755
+ export type EntityTextFieldDef = TextFieldDef & ResolvedPiiStance;
756
+ export type EntityLongTextFieldDef = LongTextFieldDef & ResolvedPiiStance;
757
+ export type EntityFieldDefinition = Exclude<FieldDefinition, TextFieldDef | LongTextFieldDef> | EntityTextFieldDef | EntityLongTextFieldDef;
758
+ export type FieldsMap = Readonly<Record<string, EntityFieldDefinition>>;
743
759
  export type ParentRefDef = {
744
760
  /** Field holding the name of the host entity this row hangs off. */
745
761
  readonly entityTypeField: string;
@@ -0,0 +1,302 @@
1
+ import type { FormFieldDefinition } from "./fields.ts";
2
+ import type { AccessRule, AgentScreenHints } from "./handlers.ts";
3
+ import type { IconKey } from "./nav-icon.ts";
4
+ import type { ScreenNavSugar, ScreenSlots } from "./screen.ts";
5
+ import type { DashboardPanelVisibility } from "./screen-dashboard.ts";
6
+ import type { EditLayout } from "./screen-entity-edit.ts";
7
+ /** A form field computed from other fields of the same form. `listSum` adds one
8
+ * numeric cell over all rows of an embedded list field; its `whenEmpty`
9
+ * fallback applies while the list has no rows (it must not be a `listSum`). */
10
+ export type FormFieldDerivation = {
11
+ readonly op: "sum" | "subtract" | "multiply" | "percent";
12
+ readonly from: readonly string[];
13
+ } | {
14
+ readonly op: "listSum";
15
+ readonly list: string;
16
+ readonly cell: string;
17
+ readonly whenEmpty?: FormFieldDerivationWithoutList;
18
+ };
19
+ export type FormFieldDerivationWithoutList = Extract<FormFieldDerivation, {
20
+ from: unknown;
21
+ }>;
22
+ export type ActionFormScreenDefinition = {
23
+ readonly id: string;
24
+ readonly type: "actionForm";
25
+ /** Default true: the screen fills the shell content height, its body scrolls
26
+ * inside and the pager / action bar stays pinned. `false` restores page
27
+ * scroll (table grows with its rows, pager below the last row, form footer
28
+ * in the flow). */
29
+ readonly fillHeight?: boolean;
30
+ readonly nav?: ScreenNavSugar;
31
+ readonly detailFor?: string;
32
+ readonly description?: string;
33
+ readonly agent?: AgentScreenHints;
34
+ /** Screen has no nav entry by design (opened via link or navved by the
35
+ * app); exempts it from the nav-area boot check. */
36
+ readonly dormant?: boolean;
37
+ /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
38
+ readonly visibleWhen?: DashboardPanelVisibility;
39
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
40
+ readonly fallback?: string;
41
+ /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
42
+ readonly urlPrefillFields?: readonly string[];
43
+ /** Derived by buildAppSchema from the `excludedFields` of the called `handler`; the form
44
+ * shows these fields read-only, not required and never in the payload. An authored value is overwritten. */
45
+ readonly writeExcludedFields?: readonly string[];
46
+ /** Submit is enabled without edits: values seeded from navigate `params` count as the
47
+ * user's intent. Every required field the layout hides with `visible: false` must hold a
48
+ * value, otherwise submit stays disabled (boot-checked against navigate `params`). */
49
+ readonly submitPrefilled?: boolean;
50
+ /** Write-handler QN called on submit. The form object lands 1:1 as the
51
+ * payload — the handler schema (Zod) keeps validating. */
52
+ readonly handler: string;
53
+ /** Form shape: field map by name. Uses the same FieldDefinitions as
54
+ * entity fields. At least one field is required (boot
55
+ * validator).
56
+ *
57
+ * A `money` field here must name its own currency source: this screen
58
+ * has no entity whose `defaultCurrency` it could inherit (fw#2839). */
59
+ readonly fields: Readonly<Record<string, FormFieldDefinition>>;
60
+ /** Fields computed from other fields, shown read-only and recomputed on every
61
+ * change. A `listSum` over an empty list without `whenEmpty` leaves the field
62
+ * a normal input, and then the client value counts. Otherwise the client value
63
+ * is never trusted: the handler must wrap its schema with
64
+ * `withFormDerivations(schema, screen)`, which the boot validator enforces.
65
+ * A field read only as `whenEmpty` operand is read-only while every such list has rows. */
66
+ readonly derive?: Readonly<Record<string, FormFieldDerivation>>;
67
+ /** Layout analogous to EntityEditScreen: sections with fields from the
68
+ * fields map above. */
69
+ readonly layout: EditLayout;
70
+ /** Per-field label i18n key override, same type/semantics as
71
+ * `EntityEditScreenDefinition.fieldLabels`. Falls back to the convention when absent. */
72
+ readonly fieldLabels?: Readonly<Record<string, string>>;
73
+ /** i18n key for the submit button. Default: the renderer's i18n default
74
+ * (typically "actions.submit"). */
75
+ readonly submitLabel?: string;
76
+ /** Extra buttons in the form footer next to the submit button. A click
77
+ * applies `patch` to the form values, then submits the form (same
78
+ * validation, same handler) — e.g. a "Mark as posted" button that sets
79
+ * `posted: true`. The patched field need not be rendered by the layout.
80
+ * Order in the array = order in the footer, before the submit button. */
81
+ readonly footerActions?: readonly ActionFormFooterAction[];
82
+ /** Context box above the fields (i18n keys). `{name}` placeholders resolve from the
83
+ * drawer prefill (`params` of the opening row action), formatted for display. */
84
+ readonly summary?: {
85
+ readonly title: string;
86
+ readonly subtitle?: string;
87
+ };
88
+ /** Visual style of the submit button. "danger" renders it red — for a form
89
+ * whose handler is destructive (terminate, revoke, delete), so the warning
90
+ * isn't lost on the way from the calling screen to this one. Default "primary". */
91
+ readonly submitStyle?: "primary" | "danger";
92
+ /** i18n key of a confirmation shown above the form after a successful submit
93
+ * when the screen stays put (no `redirect`, not drawer-hosted). `{field}`
94
+ * placeholders resolve from the submitted values; a `reference` field shows
95
+ * the chosen record's label (its `labelField`), not the id — so a
96
+ * cross-tenant admin form can name the record it just changed. */
97
+ readonly successMessage?: string;
98
+ /** Navigate to this screen ID after a successful submit: either a short
99
+ * ID (e.g. "item-list" — same feature, the nav-router resolves to the
100
+ * full path) or a fully-qualified cross-feature QN
101
+ * (`<feature>:screen:<id>`). If not set, the user stays on the form
102
+ * screen. Boot-validator checks that the target resolves to a
103
+ * registered screen. A cross-feature QN hard-couples this feature to
104
+ * the target at boot time — an app that doesn't mount the target
105
+ * feature fails the boot-validator; only use this form in a reusable
106
+ * feature when the target feature is guaranteed to be mounted
107
+ * alongside it.
108
+ *
109
+ * The object form additionally names the success-payload field the
110
+ * navigation id comes from (`ActionFormRedirect.idFrom`) — needed when
111
+ * the handler creates a child record but the target screen is the
112
+ * parent's detail screen (fw#2670). */
113
+ readonly redirect?: string | ActionFormRedirect;
114
+ /** Links shown in the success box after a successful submit while the
115
+ * screen stays put. Targets resolve like `redirect` (the id comes from the
116
+ * handler's success payload); a link without a string id is hidden.
117
+ * Mutually exclusive with `redirect`. */
118
+ readonly successLinks?: readonly ActionFormSuccessLink[];
119
+ /** Key figures from the handler's success payload, shown in the success box
120
+ * while the screen stays put (e.g. charged/skipped/failed of a batch run).
121
+ * Mutually exclusive with `redirect`. */
122
+ readonly successFacts?: readonly ActionFormSuccessFact[];
123
+ /** Reset the form to its initial values after a successful submit (prefill
124
+ * kept, typed values cleared). Only applies while the screen stays put,
125
+ * i.e. without `redirect`. */
126
+ readonly resetOnSuccess?: boolean;
127
+ /** Load the initial values from a query. The record id comes from the route
128
+ * (or the drawer row id) and is sent as `{ [idParam]: id }`; the result,
129
+ * picked to `fields`, becomes the initial values. Turns URL prefill off:
130
+ * search params never reach the form. */
131
+ readonly load?: ActionFormLoad;
132
+ /** Target of the Cancel button. Default: `redirect` (historical
133
+ * behavior — Cancel and the submit-redirect then land in the same
134
+ * place). `false` = no Cancel button; correct for single-action
135
+ * screens with no discardable state (e.g. "Send test mail"), where
136
+ * Cancel would just be a second path to the same target. Accepts the
137
+ * same string targets as `redirect` (short ID or cross-feature QN);
138
+ * boot-validator checks it. A cross-feature QN hard-couples this
139
+ * feature to the target at boot time — an app that doesn't mount the
140
+ * target feature fails the boot-validator; only use this form in a
141
+ * reusable feature when the target feature is guaranteed to be
142
+ * mounted alongside it. */
143
+ readonly cancelTarget?: string | false;
144
+ /** Parent list screen (short id) for breadcrumb + nav highlighting when
145
+ * this screen has no nav entry of its own. Same field/semantics as on
146
+ * `custom`/`projectionDetail`/`entityEdit`; an explicit value here wins
147
+ * over the rowAction heuristic. */
148
+ readonly listScreenId?: string;
149
+ readonly slots?: ScreenSlots;
150
+ readonly access?: AccessRule;
151
+ };
152
+ export type ActionFormFooterAction = {
153
+ /** Stable id (kebab-case), used for the test id. */
154
+ readonly id: string;
155
+ /** i18n key. */
156
+ readonly label: string;
157
+ readonly icon?: IconKey;
158
+ /** Button style. Default "secondary". */
159
+ readonly variant?: "primary" | "secondary" | "danger";
160
+ /** Form values set before the submit. JSON-safe literals only. */
161
+ readonly patch: Readonly<Record<string, string | number | boolean | null>>;
162
+ };
163
+ /** Redirect target plus the success-payload field carrying the navigation
164
+ * id. The write-handler reports the id of what it wrote (`data.id`); when
165
+ * that record is a child and the target screen shows its parent, the
166
+ * parent id has to be read from a different field instead of forcing the
167
+ * handler to misreport its own result (fw#2670). */
168
+ export type ActionFormRedirect = {
169
+ /** Same target forms as the string `redirect`: short screen ID
170
+ * (same-feature) or a fully-qualified cross-feature screen QN. */
171
+ readonly screen: string;
172
+ /** Flat field name in the handler's success payload, e.g. "leaseId".
173
+ * Only used when the target screen type carries an id (`entityEdit`,
174
+ * `projectionDetail`); a non-string or missing value navigates without
175
+ * an id, same as a payload without `id` does today. */
176
+ readonly idFrom: string;
177
+ /** Section id to open on the target `projectionDetail` with `layout.mode:
178
+ * "tabs"` (the `tab` search param, same as `ToolbarAction` navigate `tab`).
179
+ * Boot rejects other targets and unknown section ids. */
180
+ readonly tab?: string;
181
+ };
182
+ export type ActionFormLoad = {
183
+ /** Query QN that returns the record. */
184
+ readonly query: string;
185
+ /** Payload key carrying the record id (default "id"). */
186
+ readonly idParam?: string;
187
+ /** The query picks the row from the caller's tenant/session context, so the
188
+ * form loads without a record id and the query is called WITHOUT `idParam`
189
+ * (same semantics as `ProjectionDetailScreenDefinition.singleton`). Boot
190
+ * rejects declaring `idParam` alongside it. */
191
+ readonly singleton?: boolean;
192
+ };
193
+ /** One key figure of the success box, read from the handler's success payload. */
194
+ export type ActionFormSuccessFact = {
195
+ /** Flat field name in the handler's success payload. A number or string is
196
+ * shown as one value, an array a comma-separated list (numbers, strings and
197
+ * `{ label }` objects). A missing field hides the fact. */
198
+ readonly field: string;
199
+ /** Label (i18n key). */
200
+ readonly label: string;
201
+ };
202
+ /** A redirect target rendered as a link in the success box. */
203
+ export type ActionFormSuccessLink = Omit<ActionFormRedirect, "idFrom"> & {
204
+ /** Success-payload field carrying the navigation id. Required for a record
205
+ * target (`entityEdit`, `projectionDetail`); omit it for a list, dashboard
206
+ * or form target, which opens without an id. */
207
+ readonly idFrom?: string;
208
+ /** Link text (i18n key). */
209
+ readonly label: string;
210
+ };
211
+ /** Author-declared field shown once after a mint-form submit — API token,
212
+ * recovery codes, similar one-time secrets. */
213
+ export type SecretRevealField = {
214
+ /** Flat field name in the handler's success payload. */
215
+ readonly field: string;
216
+ /** i18n key for this value's label. */
217
+ readonly label: string;
218
+ /** "code" (default) renders one monospaced value; "list" renders a
219
+ * string-array payload one value per line (recovery codes); "qr" renders
220
+ * the value as a scannable QR code (otpauth://-style enrollment URI) —
221
+ * platforms without a QR-capable SecretReveal primitive fall back to the
222
+ * monospaced text display. */
223
+ readonly display?: "code" | "list" | "qr";
224
+ /** Default true — offer a copy-to-clipboard control next to the value. */
225
+ readonly copyable?: boolean;
226
+ };
227
+ /** Post-reveal proof-of-receipt step. A mint whose effect is only armed once
228
+ * the user proves they received the secret (TOTP enrollment: scan the code,
229
+ * then enter one) declares it here — the reveal card renders this form in
230
+ * place of the bare acknowledge button. */
231
+ export type SecretMintConfirmStep = {
232
+ /** Write-handler QN dispatched when the confirm form is submitted. */
233
+ readonly handler: string;
234
+ readonly fields: Readonly<Record<string, FormFieldDefinition>>;
235
+ readonly layout: EditLayout;
236
+ /** Mint success-payload fields merged into the confirm payload (e.g. a
237
+ * short-lived setup token). Held in component state only — never rendered,
238
+ * never in the URL, a query cache, nav or a persisted draft. */
239
+ readonly carry?: readonly string[];
240
+ readonly submitLabel?: string;
241
+ /** i18n key for the banner shown after a successful confirm when the screen
242
+ * declares no `redirect`. Default "kumiko.secretMint.done". */
243
+ readonly doneMessage?: string;
244
+ };
245
+ export type SecretReveal = {
246
+ readonly fields: readonly SecretRevealField[];
247
+ /** i18n key. Default "kumiko.secretMint.title". */
248
+ readonly title?: string;
249
+ /** i18n key for the "this is the only time you see it" warning.
250
+ * Default "kumiko.secretMint.warning". */
251
+ readonly warning?: string;
252
+ /** i18n key for the acknowledge button. Default "kumiko.secretMint.confirm". */
253
+ readonly confirmLabel?: string;
254
+ /** i18n key for a checkbox on the reveal card ("I have saved my recovery
255
+ * codes"). While unchecked, the confirm step's submit or the acknowledge
256
+ * button stays disabled. The tick is UI-only and never part of a payload. */
257
+ readonly acknowledge?: string;
258
+ };
259
+ /** Mint form → one-time reveal → explicit confirm. The secret (API token,
260
+ * recovery codes) lives only in the write-handler's own success payload —
261
+ * no stored value backs it, so no query can redisplay it later. That gap
262
+ * is why this needs its own screen type instead of a flag on `actionForm`,
263
+ * which discards the success payload after extracting the navigation id. */
264
+ export type SecretMintScreenDefinition = {
265
+ readonly id: string;
266
+ readonly type: "secretMint";
267
+ readonly nav?: ScreenNavSugar;
268
+ readonly detailFor?: string;
269
+ readonly description?: string;
270
+ readonly agent?: AgentScreenHints;
271
+ /** Screen has no nav entry by design (opened via link or navved by the
272
+ * app); exempts it from the nav-area boot check. */
273
+ readonly dormant?: boolean;
274
+ /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
275
+ readonly visibleWhen?: DashboardPanelVisibility;
276
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
277
+ readonly fallback?: string;
278
+ /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
279
+ readonly urlPrefillFields?: readonly string[];
280
+ /** Submit is enabled without edits: values seeded from navigate `params` count as the
281
+ * user's intent. Every required field the layout hides with `visible: false` must hold a
282
+ * value, otherwise submit stays disabled (boot-checked against navigate `params`). */
283
+ readonly submitPrefilled?: boolean;
284
+ /** Write-handler QN dispatched on submit. */
285
+ readonly handler: string;
286
+ readonly fields: Readonly<Record<string, FormFieldDefinition>>;
287
+ readonly layout: EditLayout;
288
+ /** Navigate here after the reveal is confirmed. Short id (same-feature) or
289
+ * a fully-qualified cross-feature screen QN — no result-id navigation
290
+ * form exists here (unlike `ActionFormScreenDefinition.redirect`), since
291
+ * the confirmed screen carries no entity id to thread through. */
292
+ readonly redirect?: string;
293
+ readonly cancelTarget?: string | false;
294
+ readonly listScreenId?: string;
295
+ readonly access?: AccessRule;
296
+ readonly reveal: SecretReveal;
297
+ /** i18n-key for the mint button. Default: renderer's own default. */
298
+ readonly submitLabel?: string;
299
+ /** Proof-of-receipt step rendered on the reveal card in place of the bare
300
+ * acknowledge button — see `SecretMintConfirmStep` doc. */
301
+ readonly confirm?: SecretMintConfirmStep;
302
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,122 @@
1
+ import type { FieldDefinition } from "./fields.ts";
2
+ import type { AccessRule, AgentScreenHints } from "./handlers.ts";
3
+ import type { PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts";
4
+ import type { DashboardPanelVisibility } from "./screen-dashboard.ts";
5
+ import type { EditLayout } from "./screen-entity-edit.ts";
6
+ export type CustomScreenRoute = {
7
+ readonly path: string;
8
+ readonly component: PlatformComponent;
9
+ };
10
+ export type CustomScreenDefinition = {
11
+ readonly id: string;
12
+ readonly type: "custom";
13
+ readonly nav?: ScreenNavSugar;
14
+ readonly detailFor?: string;
15
+ readonly description?: string;
16
+ readonly agent?: AgentScreenHints;
17
+ readonly renderer: PlatformComponent;
18
+ readonly routes?: readonly CustomScreenRoute[];
19
+ /** Parent list screen for breadcrumb when this detail is not in nav. */
20
+ readonly listScreenId?: string;
21
+ readonly access?: AccessRule;
22
+ /** This screen is registered without a self-owned `r.nav()` entry by
23
+ * design — an app opts in by navving it explicitly (e.g. a settings-area
24
+ * self-service screen). `createKumikoApp`'s boot diagnostic
25
+ * (kumiko-framework#2025) skips dormant screens instead of flagging
26
+ * every consumer that doesn't mount the client plugin as a false
27
+ * positive (kumiko-framework#2034). Only set this on screens the
28
+ * feature itself never navs — a screen the feature DOES nav still needs
29
+ * its client plugin mounted by every consumer, and should keep
30
+ * triggering the diagnostic if it's missing. Also gates the
31
+ * boot-validator's nav-area check, same as `dormant` on other screens. */
32
+ readonly dormant?: boolean;
33
+ /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
34
+ readonly visibleWhen?: DashboardPanelVisibility;
35
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
36
+ readonly fallback?: string;
37
+ };
38
+ export type ConfigEditScreenDefinition = {
39
+ readonly id: string;
40
+ readonly type: "configEdit";
41
+ /** Default true: the form fills the shell content height with a pinned
42
+ * footer. `false` restores the card layout with the footer in the flow. */
43
+ readonly fillHeight?: boolean;
44
+ readonly nav?: ScreenNavSugar;
45
+ readonly detailFor?: string;
46
+ readonly description?: string;
47
+ readonly agent?: AgentScreenHints;
48
+ /** Screen has no nav entry by design (opened via link or navved by the
49
+ * app); exempts it from the nav-area boot check. */
50
+ readonly dormant?: boolean;
51
+ /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
52
+ readonly visibleWhen?: DashboardPanelVisibility;
53
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
54
+ readonly fallback?: string;
55
+ /** scope for config:write:set calls. Must match the scope declaration of
56
+ * the keys referenced in `configKeys` — the boot validator checks that
57
+ * against the registry. */
58
+ readonly scope: "tenant" | "system" | "user";
59
+ /** Map: form field name (short, as referenced in the layout) → fully
60
+ * qualified config key (`<feature>:config:<short>`). The boot validator
61
+ * checks that every qualified key is known in the registry. */
62
+ readonly configKeys: Readonly<Record<string, string>>;
63
+ /** Field name → qualified secret key (`<feature>:secret:<short>`), tenant scope only.
64
+ * Such a field is a writeOnly text field with no `configKeys` entry; the same save
65
+ * writes it through secrets:write:set (or secrets:write:delete on removal). */
66
+ readonly secretKeys?: Readonly<Record<string, string>>;
67
+ /** Form shape per field name. Same FieldDefinitions as on
68
+ * entities/ActionForm. Field names match the keys in `configKeys`
69
+ * — the boot validator pins that. */
70
+ readonly fields: Readonly<Record<string, FieldDefinition>>;
71
+ /** Layout: sections with field refs. Identical to entityEdit/
72
+ * actionForm. */
73
+ readonly layout: EditLayout;
74
+ /** Optional per-field label i18n key (field name → key). The settings
75
+ * hub sets the respective config key's `mask.title` here, so the label
76
+ * declared on the key shows up on the generated field — without
77
+ * duplicating it under the `__config-edit__` convention. A missing
78
+ * entry falls back to the convention. */
79
+ readonly fieldLabels?: Readonly<Record<string, string>>;
80
+ /** Per-field help text i18n key (Field-Name → Key), from `mask.description`. */
81
+ readonly fieldDescriptions?: Readonly<Record<string, string>>;
82
+ /** Field names whose config key declares `required: true` — drives the
83
+ * "Not set. Required." origin line, not form validation. */
84
+ readonly requiredFields?: readonly string[];
85
+ /** i18n key for the submit button. Default: "kumiko.actions.save". */
86
+ readonly submitLabel?: string;
87
+ readonly slots?: ScreenSlots;
88
+ readonly access?: AccessRule;
89
+ };
90
+ export type SecretsEditSection = {
91
+ readonly title?: string;
92
+ readonly fields: readonly string[];
93
+ };
94
+ export type SecretsEditScreenDefinition = {
95
+ readonly id: string;
96
+ readonly type: "secretsEdit";
97
+ readonly nav?: ScreenNavSugar;
98
+ readonly detailFor?: string;
99
+ readonly description?: string;
100
+ readonly agent?: AgentScreenHints;
101
+ /** Screen has no nav entry by design (opened via link or navved by the
102
+ * app); exempts it from the nav-area boot check. */
103
+ readonly dormant?: boolean;
104
+ /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
105
+ readonly visibleWhen?: DashboardPanelVisibility;
106
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
107
+ readonly fallback?: string;
108
+ /** field id -> qualified secret name (`<feature>:secret:<kebab>`). */
109
+ readonly secretKeys: Readonly<Record<string, string>>;
110
+ /** field id -> i18n key for the label. */
111
+ readonly fieldLabels: Readonly<Record<string, string>>;
112
+ /** field id -> i18n key for the hint, only where the declaration carries one. */
113
+ readonly fieldHints?: Readonly<Record<string, string>>;
114
+ /** field ids whose declaration set `required: true`. */
115
+ readonly requiredFields?: readonly string[];
116
+ /** field id -> write access derived from the key's `writeRoles`. The screen
117
+ * hides fields the viewer fails; UI only, the secrets write gate stays
118
+ * authoritative. */
119
+ readonly fieldAccess?: Readonly<Record<string, AccessRule>>;
120
+ readonly sections: readonly SecretsEditSection[];
121
+ readonly access?: AccessRule;
122
+ };
@@ -0,0 +1 @@
1
+ export {};