@cosmicdrift/kumiko-types 0.406.0 → 0.410.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.
@@ -0,0 +1,4 @@
1
+ import type { AccessRule } from "./handlers.ts";
2
+ export declare function isOpenToAllGranted(rule: AccessRule): boolean;
3
+ export declare function isUiAccessGranted(access: AccessRule | undefined, userRoles: readonly string[] | undefined): boolean;
4
+ export declare function isAnySignedInGranted(rule: AccessRule): boolean;
@@ -0,0 +1,42 @@
1
+ // AccessRule can arrive from untyped sources (pattern-library JSON, Designer)
2
+ // where `openToAll` doesn't actually match the declared union — narrow via
3
+ // `unknown` instead of trusting the static type, deny on anything malformed.
4
+ export function isOpenToAllGranted(rule) {
5
+ if (!("openToAll" in rule))
6
+ return false;
7
+ const openToAll = rule.openToAll;
8
+ if (typeof openToAll !== "object" || openToAll === null)
9
+ return false;
10
+ if (!("reason" in openToAll) || typeof openToAll.reason !== "string")
11
+ return false;
12
+ return openToAll.reason.trim().length > 0;
13
+ }
14
+ const ANONYMOUS_ACCESS_ROLE = "anonymous";
15
+ // UI-visibility predicate — DEFAULT-VISIBLE, the opposite stance from
16
+ // `hasAccess` and the default-deny doc on AccessRule (handlers.ts): an unset `access`
17
+ // means every signed-in user may see the screen/nav/workspace, not nobody.
18
+ // The engine's dispatcher access check stays default-deny; this only
19
+ // decides what a client renders/lists, never what a handler accepts.
20
+ // Same `unknown`-narrowing caution as isOpenToAllGranted — `access` can
21
+ // arrive from untyped sources.
22
+ export function isUiAccessGranted(access, userRoles) {
23
+ if (access === undefined)
24
+ return true;
25
+ if ("openToAll" in access)
26
+ return isOpenToAllGranted(access);
27
+ if (userRoles === undefined)
28
+ return false;
29
+ if (!("roles" in access))
30
+ return false;
31
+ if (isAnySignedInGranted(access) && !userRoles.includes(ANONYMOUS_ACCESS_ROLE))
32
+ return true;
33
+ return access.roles.some((role) => userRoles.includes(role));
34
+ }
35
+ // Same `unknown`-narrowing caution as isOpenToAllGranted. Only granted next to the
36
+ // anonymous role, so a rule can never become "every signed-in user" through this flag.
37
+ export function isAnySignedInGranted(rule) {
38
+ if (!("roles" in rule) || !("anySignedIn" in rule))
39
+ return false;
40
+ const anySignedIn = rule.anySignedIn;
41
+ return (anySignedIn === true && Array.isArray(rule.roles) && rule.roles.includes(ANONYMOUS_ACCESS_ROLE));
42
+ }
@@ -2,9 +2,11 @@
2
2
  * unless it opts in explicitly. `kind` is a parameter because write and query
3
3
  * handler definitions are structurally indistinguishable at runtime. */
4
4
  export function resolveAgentExposure(def, kind) {
5
+ const effect = def.agent?.effect;
5
6
  return {
6
7
  expose: def.agent?.expose ?? def.description !== undefined,
7
8
  risk: def.agent?.risk ?? (kind === "query" ? "low" : "mid"),
9
+ ...(effect !== undefined && { effect }),
8
10
  };
9
11
  }
10
12
  /** Screens are the inverse of handlers: the manifest lists every role-visible
@@ -0,0 +1,18 @@
1
+ export type AgentRisk = "low" | "mid" | "high";
2
+ /** "proposal": the write only changes a suggestion that a person still approves elsewhere, so the
3
+ * agent may run it without its own approval step. Boot accepts it only with `risk: "low"`. */
4
+ export type AgentEffect = "proposal";
5
+ /** Per-handler hints for the AI-agent manifest. `expose` overrides the
6
+ * default derived from `description`; `risk` overrides the per-kind default. */
7
+ export type AgentHandlerHints = {
8
+ readonly expose?: boolean;
9
+ readonly risk?: AgentRisk;
10
+ readonly effect?: AgentEffect;
11
+ };
12
+ /** Screens only read `expose`; `risk` ranks handler tool calls, so it is not offered here. */
13
+ export type AgentScreenHints = Pick<AgentHandlerHints, "expose">;
14
+ export type AgentExposure = {
15
+ readonly expose: boolean;
16
+ readonly risk: AgentRisk;
17
+ readonly effect?: AgentEffect;
18
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -31,6 +31,7 @@ export type LockGuardOption = {
31
31
  export type ListRuntimeOptions = {
32
32
  readonly searchAdapter?: SearchAdapter;
33
33
  readonly includeDeleted?: boolean;
34
+ readonly onlyDeleted?: boolean;
34
35
  readonly referenceSearch?: {
35
36
  readonly fields: ReadonlyArray<{
36
37
  readonly fieldName: string;
package/dist/fields.d.ts CHANGED
@@ -7,6 +7,7 @@ import type { DerivedFieldsMap } from "./fields-derived.ts";
7
7
  import type { EventUpcastCtx } from "./handlers.ts";
8
8
  import type { NamedListFilterDef } from "./named-list-filters.ts";
9
9
  import type { OwnershipMap } from "./ownership.ts";
10
+ import type { EntityPiiAbsentSubject } from "./personal-annotations.ts";
10
11
  import type { EntityTenancy } from "./tenancy-brand.ts";
11
12
  export type { ClientDerivedFieldDef, DeriveContext, DerivedCondition, DerivedConditionValue, DerivedFieldDef, DerivedFieldsMap, DerivedRelatedMeasure, DerivedSqlSpec, DerivedValueType, JsDerivedFieldDef, SqlDerivedFieldDef, } from "./fields-derived.ts";
12
13
  export { isSqlDerivedField } from "./fields-derived.ts";
@@ -36,51 +37,7 @@ export type ResolvedPiiFlags = {
36
37
  readonly subjectRef?: true;
37
38
  };
38
39
  type PiiAndFieldState = ResolvedPiiFlags & FieldStateConditions;
39
- export type EntityPiiAbsentSubject = "tenant" | "self";
40
- export type PersonalSubject = "self" | {
41
- readonly of: string;
42
- readonly whenAbsent?: EntityPiiAbsentSubject;
43
- } | "tenant";
44
- export type Findability = "exact" | "fuzzy" | "none" | "secret";
45
- type NoPersonalAnnotation = {
46
- readonly personal?: never;
47
- readonly find?: never;
48
- readonly reason?: never;
49
- readonly anonymize?: never;
50
- };
51
- export type PersonalAnnotations = {
52
- readonly personal: PersonalSubject;
53
- readonly find: Findability;
54
- readonly anonymize?: () => unknown | Promise<unknown>;
55
- } | {
56
- readonly personal: "ref";
57
- } | {
58
- readonly personal: false;
59
- readonly reason: string;
60
- readonly anonymize?: () => unknown | Promise<unknown>;
61
- };
62
- export type LongTextFindability = "none" | "secret";
63
- export type PersonalAnnotationsLongText = {
64
- readonly personal: PersonalSubject;
65
- readonly find: LongTextFindability;
66
- readonly anonymize?: () => unknown | Promise<unknown>;
67
- } | {
68
- readonly personal: "ref";
69
- } | {
70
- readonly personal: false;
71
- readonly reason: string;
72
- readonly anonymize?: () => unknown | Promise<unknown>;
73
- };
74
- export type PersonalAnnotationsNoFind = {
75
- readonly personal: PersonalSubject;
76
- readonly anonymize?: () => unknown | Promise<unknown>;
77
- } | {
78
- readonly personal: "ref";
79
- } | {
80
- readonly personal: false;
81
- readonly reason: string;
82
- readonly anonymize?: () => unknown | Promise<unknown>;
83
- } | NoPersonalAnnotation;
40
+ export type { EntityPiiAbsentSubject, Findability, LongTextFindability, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, } from "./personal-annotations.ts";
84
41
  export type RetentionDef = {
85
42
  readonly keepFor: string;
86
43
  readonly strategy: "hardDelete" | "softDelete" | "anonymize" | "blockDelete";
@@ -151,9 +108,9 @@ export type TextFieldDef = {
151
108
  *
152
109
  * - **No `sortable`**: ordering 100 KB strings costs I/O without useful UX;
153
110
  * lexicographic ordering of code is not meaningful.
154
- * - **No `searchable`**: ILIKE/substring searches do not scale on long text.
155
- * Full-text search should use the SearchAdapter (Meilisearch), which has
156
- * its own tokenizer and index rather than this field flag.
111
+ * - **`searchable` only through `find: "fuzzy"`**: ILIKE/substring searches
112
+ * do not scale on long text, so the value is full-text indexed by the
113
+ * SearchAdapter (Meilisearch, chunked) and never searched in SQL.
157
114
  * - **No `filterable`**: filtering long strings has the same issue as sorting.
158
115
  * - **No `format`**: email/url/phone are short, structured inputs, whereas
159
116
  * longText is unstructured by definition.
@@ -177,6 +134,8 @@ export type LongTextFieldDef = {
177
134
  * "high" on the directly-dispatched entry handler; a revert doesn't undo a
178
135
  * run that already read the compromised value. See irreversible-operation-gate. */
179
136
  readonly readAsInstruction?: boolean;
137
+ /** Set by `find: "fuzzy"`: the value goes into the SearchAdapter document. */
138
+ readonly searchable?: boolean;
180
139
  readonly default?: string;
181
140
  readonly access?: FieldAccess;
182
141
  readonly multiline?: boolean | {
@@ -20,6 +20,7 @@ import type { CrossTenantReads, TenantDb } from "./tenant-db-types.ts";
20
20
  import type { Clock, TzContext } from "./tz-context.ts";
21
21
  export type { BeforeTransactionContext, HandlerAuth, HandlerDb, HandlerDispatch, HandlerEvents, HandlerObservability, JobDispatch, JobEvents, JobObservability, ReservationContext, } from "./handler-context-facets.ts";
22
22
  export type { ContextServices, HandlerLifecycle, HandlerTenants, JobLifecycle, } from "./handler-context-services.ts";
23
+ import type { AgentHandlerHints } from "./agent-hints.ts";
23
24
  export type OpenToAllPersonalData = "tenant-members";
24
25
  export type OpenToAllDeclaration = {
25
26
  readonly reason: string;
@@ -32,6 +33,7 @@ export type RoleAccessPersonalData = "public-intake";
32
33
  export type RoleAccessRule = {
33
34
  readonly roles: readonly string[];
34
35
  readonly personalData?: RoleAccessPersonalData;
36
+ readonly anySignedIn?: true;
35
37
  };
36
38
  export type AccessRule = RoleAccessRule | OpenToAllAccessRule;
37
39
  export declare const ESCAPE_HATCH_GRANTS: readonly ["systemIdentity", "globalWrites", "unsafeRaw", "crossTenantReads"];
@@ -47,8 +49,7 @@ export declare function crossTenantReadsGrantFor(declaration: EscapeHatchDeclara
47
49
  export declare function escapeHatchGrantsProblem(declaration: {
48
50
  readonly grants?: unknown;
49
51
  }): string | undefined;
50
- export declare function isOpenToAllGranted(rule: AccessRule): boolean;
51
- export declare function isUiAccessGranted(access: AccessRule | undefined, userRoles: readonly string[] | undefined): boolean;
52
+ export { isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.ts";
52
53
  export type SessionUserOrigin = "member-resolution";
53
54
  export type SessionUser = {
54
55
  readonly id: string;
@@ -153,6 +154,7 @@ export type NotifyOptions = {
153
154
  readonly tenant: TenantId;
154
155
  };
155
156
  readonly route?: Readonly<Record<string, string>>;
157
+ readonly channels?: readonly string[];
156
158
  readonly data?: Readonly<Record<string, unknown>>;
157
159
  readonly priority?: NotifyPriority;
158
160
  readonly idempotencyKey?: string;
@@ -307,6 +309,7 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = {
307
309
  * dispatcher payload-bucket digest. Absent when the app wires no digest key. */
308
310
  readonly rateLimitDigest?: (value: string) => string;
309
311
  readonly includeDeleted?: boolean;
312
+ readonly onlyDeleted?: boolean;
310
313
  readonly dispatch: HandlerDispatch;
311
314
  readonly hasFeature: (featureName: string) => Promise<boolean>;
312
315
  readonly events: HandlerEvents<TMap>;
@@ -514,19 +517,7 @@ export type RateLimitDisabled = {
514
517
  export type RateLimitDeclaration = RateLimitOption | RateLimitOptions | RateLimitDisabled;
515
518
  export declare function declaredRateLimitOptions(declaration: RateLimitDeclaration | undefined): readonly RateLimitOption[];
516
519
  export declare function isRateLimitDisabled(v: RateLimitDeclaration | undefined): v is RateLimitDisabled;
517
- export type AgentRisk = "low" | "mid" | "high";
518
- /** Per-handler hints for the AI-agent manifest. `expose` overrides the
519
- * default derived from `description`; `risk` overrides the per-kind default. */
520
- export type AgentHandlerHints = {
521
- readonly expose?: boolean;
522
- readonly risk?: AgentRisk;
523
- };
524
- /** Screens only read `expose`; `risk` ranks handler tool calls, so it is not offered here. */
525
- export type AgentScreenHints = Pick<AgentHandlerHints, "expose">;
526
- export type AgentExposure = {
527
- readonly expose: boolean;
528
- readonly risk: AgentRisk;
529
- };
520
+ export type { AgentEffect, AgentExposure, AgentHandlerHints, AgentRisk, AgentScreenHints, } from "./agent-hints.ts";
530
521
  /** Undoes a reservation made before the handler transaction. */
531
522
  export type ReservationRelease = () => Promise<void>;
532
523
  /** The dispatcher's own open handler transaction: a reservation must settle on it, not on
package/dist/handlers.js CHANGED
@@ -39,37 +39,7 @@ export function escapeHatchGrantsProblem(declaration) {
39
39
  }
40
40
  return undefined;
41
41
  }
42
- // AccessRule can arrive from untyped sources (pattern-library JSON, Designer)
43
- // where `openToAll` doesn't actually match the declared union — narrow via
44
- // `unknown` instead of trusting the static type, deny on anything malformed.
45
- export function isOpenToAllGranted(rule) {
46
- if (!("openToAll" in rule))
47
- return false;
48
- const openToAll = rule.openToAll;
49
- if (typeof openToAll !== "object" || openToAll === null)
50
- return false;
51
- if (!("reason" in openToAll) || typeof openToAll.reason !== "string")
52
- return false;
53
- return openToAll.reason.trim().length > 0;
54
- }
55
- // UI-visibility predicate — DEFAULT-VISIBLE, the opposite stance from
56
- // `hasAccess`/AccessRule's own default-deny doc above: an unset `access`
57
- // means every signed-in user may see the screen/nav/workspace, not nobody.
58
- // The engine's dispatcher access check stays default-deny; this only
59
- // decides what a client renders/lists, never what a handler accepts.
60
- // Same `unknown`-narrowing caution as isOpenToAllGranted — `access` can
61
- // arrive from untyped sources.
62
- export function isUiAccessGranted(access, userRoles) {
63
- if (access === undefined)
64
- return true;
65
- if ("openToAll" in access)
66
- return isOpenToAllGranted(access);
67
- if (userRoles === undefined)
68
- return false;
69
- if (!("roles" in access))
70
- return false;
71
- return access.roles.some((role) => userRoles.includes(role));
72
- }
42
+ export { isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.js";
73
43
  // Closed vocabulary for delivery failures and skips. Only these values (plus
74
44
  // `http_<status>`) are ever stored or returned as a delivery error, never a raw
75
45
  // error message: provider errors can carry URLs, tokens and addresses.
@@ -2,6 +2,7 @@ export declare const IN_APP_NOTIFICATION_FEATURE: "channel-in-app";
2
2
  export declare const IN_APP_NOTIFICATION_QUERIES: {
3
3
  readonly inbox: "channel-in-app:query:inbox";
4
4
  readonly unreadCount: "channel-in-app:query:unread-count";
5
+ readonly readStateByKeys: "channel-in-app:query:read-state-by-keys";
5
6
  };
6
7
  export declare const IN_APP_NOTIFICATION_WRITES: {
7
8
  readonly markRead: "channel-in-app:write:mark-read";
@@ -4,6 +4,7 @@ export const IN_APP_NOTIFICATION_FEATURE = "channel-in-app";
4
4
  export const IN_APP_NOTIFICATION_QUERIES = {
5
5
  inbox: "channel-in-app:query:inbox",
6
6
  unreadCount: "channel-in-app:query:unread-count",
7
+ readStateByKeys: "channel-in-app:query:read-state-by-keys",
7
8
  };
8
9
  export const IN_APP_NOTIFICATION_WRITES = {
9
10
  markRead: "channel-in-app:write:mark-read",
@@ -1,3 +1,3 @@
1
- export declare const NAV_ICON_KEYS: readonly ["dashboard", "layout-grid", "book-open", "clipboard-list", "package", "gauge", "list", "table", "layers", "building", "calculator", "wallet", "coins", "credit-card", "piggy-bank", "receipt", "chart", "bar-chart", "trending", "sparkles", "wand", "calendar", "file", "folder", "folder-open", "home", "bell", "shield", "shield-check", "send", "settings", "users", "user", "search", "tag", "key", "link", "palette", "share", "server", "mail", "lock", "hash", "download", "upload", "rocket", "plus", "languages", "trash", "x", "check", "arrow-left", "arrow-right", "copy", "pencil", "eye", "eye-off", "filter", "refresh", "more-horizontal", "more-vertical", "external-link", "chevron-down", "chevron-up", "chevron-left", "chevron-right", "save", "undo", "archive", "star", "flag", "clock", "map-pin", "phone", "printer", "alert-triangle", "info", "check-circle", "x-circle", "loader", "mic", "circle-stop", "image", "camera", "headphones"];
1
+ export declare const NAV_ICON_KEYS: readonly ["dashboard", "layout-grid", "book-open", "clipboard-list", "package", "gauge", "list", "table", "layers", "building", "warehouse", "calculator", "wallet", "euro", "coins", "credit-card", "piggy-bank", "receipt", "chart", "bar-chart", "trending", "sparkles", "wand", "calendar", "file", "folder", "folder-open", "home", "bell", "shield", "shield-check", "send", "settings", "users", "user", "search", "tag", "key", "link", "palette", "share", "server", "mail", "lock", "hash", "download", "upload", "rocket", "plus", "languages", "trash", "x", "check", "arrow-left", "arrow-right", "copy", "pencil", "eye", "eye-off", "filter", "refresh", "more-horizontal", "more-vertical", "external-link", "chevron-down", "chevron-up", "chevron-left", "chevron-right", "save", "undo", "archive", "star", "flag", "clock", "map-pin", "phone", "printer", "alert-triangle", "info", "check-circle", "x-circle", "loader", "mic", "circle-stop", "image", "camera", "headphones"];
2
2
  export type NavIconKey = (typeof NAV_ICON_KEYS)[number];
3
3
  export type IconKey = NavIconKey;
package/dist/nav-icon.js CHANGED
@@ -25,8 +25,10 @@ export const NAV_ICON_KEYS = [
25
25
  "table",
26
26
  "layers",
27
27
  "building",
28
+ "warehouse",
28
29
  "calculator",
29
30
  "wallet",
31
+ "euro",
30
32
  "coins",
31
33
  "credit-card",
32
34
  "piggy-bank",
@@ -0,0 +1,46 @@
1
+ export type EntityPiiAbsentSubject = "tenant" | "self";
2
+ export type PersonalSubject = "self" | {
3
+ readonly of: string;
4
+ readonly whenAbsent?: EntityPiiAbsentSubject;
5
+ } | "tenant";
6
+ export type Findability = "exact" | "fuzzy" | "none" | "secret";
7
+ type NoPersonalAnnotation = {
8
+ readonly personal?: never;
9
+ readonly find?: never;
10
+ readonly reason?: never;
11
+ readonly anonymize?: never;
12
+ };
13
+ export type PersonalAnnotations = {
14
+ readonly personal: PersonalSubject;
15
+ readonly find: Findability;
16
+ readonly anonymize?: () => unknown | Promise<unknown>;
17
+ } | {
18
+ readonly personal: "ref";
19
+ } | {
20
+ readonly personal: false;
21
+ readonly reason: string;
22
+ readonly anonymize?: () => unknown | Promise<unknown>;
23
+ };
24
+ export type LongTextFindability = "none" | "fuzzy" | "secret";
25
+ export type PersonalAnnotationsLongText = {
26
+ readonly personal: PersonalSubject;
27
+ readonly find: LongTextFindability;
28
+ readonly anonymize?: () => unknown | Promise<unknown>;
29
+ } | {
30
+ readonly personal: "ref";
31
+ } | {
32
+ readonly personal: false;
33
+ readonly reason: string;
34
+ readonly anonymize?: () => unknown | Promise<unknown>;
35
+ };
36
+ export type PersonalAnnotationsNoFind = {
37
+ readonly personal: PersonalSubject;
38
+ readonly anonymize?: () => unknown | Promise<unknown>;
39
+ } | {
40
+ readonly personal: "ref";
41
+ } | {
42
+ readonly personal: false;
43
+ readonly reason: string;
44
+ readonly anonymize?: () => unknown | Promise<unknown>;
45
+ } | NoPersonalAnnotation;
46
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -81,10 +81,12 @@ export type ActionFormScreenDefinition = {
81
81
  * Order in the array = order in the footer, before the submit button. */
82
82
  readonly footerActions?: readonly ActionFormFooterAction[];
83
83
  /** Context box above the fields (i18n keys). `{name}` placeholders resolve from the
84
- * drawer prefill (`params` of the opening row action), formatted for display. */
84
+ * drawer prefill (`params` of the opening row action), formatted for display, and from
85
+ * the fixed `params` (e.g. the app name a feature option sets), which win on a name clash. */
85
86
  readonly summary?: {
86
87
  readonly title: string;
87
88
  readonly subtitle?: string;
89
+ readonly params?: Readonly<Record<string, string>>;
88
90
  };
89
91
  /** Visual style of the submit button. "danger" renders it red — for a form
90
92
  * whose handler is destructive (terminate, revoke, delete), so the warning
@@ -121,6 +123,11 @@ export type ActionFormScreenDefinition = {
121
123
  * while the screen stays put (e.g. charged/skipped/failed of a batch run).
122
124
  * Mutually exclusive with `redirect`. */
123
125
  readonly successFacts?: readonly ActionFormSuccessFact[];
126
+ /** One hint per entry of a success-payload array, each with its own link
127
+ * (e.g. "this file is already in the inbox" with "Open" per uploaded file).
128
+ * While at least one entry is shown, the hints replace the generic
129
+ * confirmation text. Mutually exclusive with `redirect`. */
130
+ readonly successItems?: ActionFormSuccessItems;
124
131
  /** Reset the form to its initial values after a successful submit (prefill
125
132
  * kept, typed values cleared). Only applies while the screen stays put,
126
133
  * i.e. without `redirect`. */
@@ -200,12 +207,25 @@ export type ActionFormSuccessFact = {
200
207
  /** Label (i18n key). */
201
208
  readonly label: string;
202
209
  };
210
+ /** Per-entry hints of the success box, read from an array in the handler's success payload. */
211
+ export type ActionFormSuccessItems = {
212
+ /** Flat field name of an array in the success payload. An entry is shown
213
+ * when it is an object with a string `label` (the sentence of the hint). */
214
+ readonly field: string;
215
+ /** Link of each hint. `idFrom` and `params` read from the entry, not from
216
+ * the whole payload; an entry whose link does not resolve shows without one. */
217
+ readonly link?: ActionFormSuccessLink;
218
+ };
203
219
  /** A redirect target rendered as a link in the success box. */
204
220
  export type ActionFormSuccessLink = Omit<ActionFormRedirect, "idFrom"> & {
205
221
  /** Success-payload field carrying the navigation id. Required for a record
206
222
  * target (`entityEdit`, `projectionDetail`); omit it for a list, dashboard
207
223
  * or form target, which opens without an id. */
208
224
  readonly idFrom?: string;
225
+ /** URL search params of the target, each read from a success-payload field
226
+ * (`{ cmd: "duplicateOfId" }` opens `?cmd=<duplicateOfId>`). The link is
227
+ * hidden while one of these fields is not a string. */
228
+ readonly params?: Readonly<Record<string, string>>;
209
229
  /** Link text (i18n key). */
210
230
  readonly label: string;
211
231
  };
@@ -35,6 +35,22 @@ export type CustomScreenDefinition = {
35
35
  /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
36
36
  readonly fallback?: ScreenFallback;
37
37
  };
38
+ /** A button row on a configEdit form that calls a write handler with `{}` (for example "send a test").
39
+ * It never saves the form: the button is disabled while the form has unsaved changes, so the
40
+ * handler always works on what is stored. All text fields are i18n keys. */
41
+ export type ConfigEditAction = {
42
+ /** Unique within the screen; used for the test id. */
43
+ readonly id: string;
44
+ /** Qualified name of a write handler (`<feature>:write:<name>`). */
45
+ readonly handler: string;
46
+ readonly label: string;
47
+ readonly description?: string;
48
+ readonly buttonLabel: string;
49
+ /** Field (from the layout) after which the row is drawn. */
50
+ readonly afterField: string;
51
+ /** Shown after a successful call; without it a success stays silent. */
52
+ readonly successMessage?: string;
53
+ };
38
54
  export type ConfigEditScreenDefinition = {
39
55
  readonly id: string;
40
56
  readonly type: "configEdit";
@@ -60,7 +76,8 @@ export type ConfigEditScreenDefinition = {
60
76
  * qualified config key (`<feature>:config:<short>`). The boot validator
61
77
  * checks that every qualified key is known in the registry. */
62
78
  readonly configKeys: Readonly<Record<string, string>>;
63
- /** Field name → qualified secret key (`<feature>:secret:<short>`), tenant scope only.
79
+ /** Field name → qualified secret key (`<feature>:secret:<short>`), tenant or system scope
80
+ * (system: platform-wide, SystemAdmin only), never user scope.
64
81
  * Such a field is a writeOnly text field with no `configKeys` entry; the same save
65
82
  * writes it through secrets:write:set (or secrets:write:delete on removal). */
66
83
  readonly secretKeys?: Readonly<Record<string, string>>;
@@ -84,6 +101,8 @@ export type ConfigEditScreenDefinition = {
84
101
  readonly requiredFields?: readonly string[];
85
102
  /** i18n key for the submit button. Default: "kumiko.actions.save". */
86
103
  readonly submitLabel?: string;
104
+ /** Handler buttons drawn as their own row after `afterField` (settings-list layout). */
105
+ readonly actions?: readonly ConfigEditAction[];
87
106
  readonly slots?: ScreenSlots;
88
107
  readonly access?: AccessRule;
89
108
  };
@@ -55,6 +55,10 @@ export type EditFieldSpec = string | {
55
55
  /** Row field on the referenced entity shown as the label (default
56
56
  * "id"). Only meaningful together with `refEntity`. */
57
57
  readonly refLabelField?: string;
58
+ /** Text format for projectionDetail fields, which have no `EntityDefinition`
59
+ * to carry it: a read-only value renders as a `mailto:`/`tel:` link.
60
+ * Ignored on `entityEdit` fields, where the field's own `format` wins. */
61
+ readonly format?: "email" | "phone";
58
62
  /** Embedded-list fields only (`createEmbeddedListField`). */
59
63
  readonly embeddedList?: EmbeddedListEditSpec;
60
64
  /** Fills an empty value when the create form opens; the update form and
@@ -122,6 +122,8 @@ export type ProjectionListScreenDefinition = {
122
122
  export type RecordHeaderSubtitlePart = string | {
123
123
  readonly field: string;
124
124
  readonly navigate?: MetricNavigate;
125
+ /** Formats the raw field value (e.g. `timestamp`); an empty value is still dropped. */
126
+ readonly format?: FormatSpec;
125
127
  };
126
128
  export type RecordHeaderSpec = {
127
129
  readonly title: string;
package/dist/screen.d.ts CHANGED
@@ -115,8 +115,8 @@ export type ScreenNavSugar = {
115
115
  readonly order?: number;
116
116
  };
117
117
  export type ScreenDefinition = EntityListScreenDefinition | ProjectionListScreenDefinition | ProjectionDetailScreenDefinition | DashboardScreenDefinition | EntityEditScreenDefinition | ActionFormScreenDefinition | ConfigEditScreenDefinition | SecretsEditScreenDefinition | SecretMintScreenDefinition | CustomScreenDefinition;
118
- export type { ActionFormFooterAction, ActionFormLoad, ActionFormRedirect, ActionFormScreenDefinition, ActionFormSuccessFact, ActionFormSuccessLink, FormFieldDerivation, FormFieldDerivationWithoutList, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, } from "./screen-action-form.ts";
119
- export type { ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, SecretsEditScreenDefinition, SecretsEditSection, } from "./screen-custom-config.ts";
118
+ export type { ActionFormFooterAction, ActionFormLoad, ActionFormRedirect, ActionFormScreenDefinition, ActionFormSuccessFact, ActionFormSuccessItems, ActionFormSuccessLink, FormFieldDerivation, FormFieldDerivationWithoutList, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, } from "./screen-action-form.ts";
119
+ export type { ConfigEditAction, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, SecretsEditScreenDefinition, SecretsEditSection, } from "./screen-custom-config.ts";
120
120
  export type { DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardStatTone, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, ScreenVisibility, } from "./screen-dashboard.ts";
121
121
  export { DASHBOARD_MIN_REFRESH_SECONDS, DASHBOARD_STAT_TONES } from "./screen-dashboard.ts";
122
122
  export type { EditExtensionSection, EditFieldGroup, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityWriteExcludedFields, FieldAffix, FieldFillAction, FormWidth, ListEmptyStateSpec, RelatedListEmptyStateSpec, RelatedListEmptyStateVariant, RelatedListRowTone, } from "./screen-entity-edit.ts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.406.0",
3
+ "version": "0.410.0",
4
4
  "description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
5
5
  "license": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",