@cosmicdrift/kumiko-types 0.327.0 → 0.328.1
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/action-icon.d.ts +3 -0
- package/dist/action-icon.js +51 -0
- package/{src/agent-exposure.ts → dist/agent-exposure.d.ts} +8 -15
- package/dist/agent-exposure.js +16 -0
- package/dist/concurrency-mode.d.ts +1 -0
- package/dist/config-scope.d.ts +1 -0
- package/dist/config-scope.js +1 -0
- package/dist/config.d.ts +234 -0
- package/dist/config.js +1 -0
- package/dist/cursor-types.d.ts +16 -0
- package/dist/cursor-types.js +1 -0
- package/dist/db-connection.d.ts +14 -0
- package/dist/db-connection.js +1 -0
- package/dist/define-handler.d.ts +58 -0
- package/dist/define-handler.js +1 -0
- package/dist/derivatives-types.d.ts +53 -0
- package/dist/derivatives-types.js +3 -0
- package/dist/entity-cache.d.ts +16 -0
- package/dist/entity-cache.js +1 -0
- package/dist/entity-handlers.d.ts +54 -0
- package/dist/entity-handlers.js +1 -0
- package/dist/entity-table-meta-types.d.ts +44 -0
- package/dist/entity-table-meta-types.js +6 -0
- package/dist/envelope-cipher-types.d.ts +5 -0
- package/dist/envelope-cipher-types.js +1 -0
- package/dist/event-store-executor-types.d.ts +86 -0
- package/dist/event-store-executor-types.js +1 -0
- package/dist/event-store-types.d.ts +37 -0
- package/dist/event-store-types.js +7 -0
- package/dist/event-type-map.d.ts +8 -0
- package/{src/event-type-map.ts → dist/event-type-map.js} +1 -16
- package/dist/executor-brand.d.ts +8 -0
- package/dist/executor-brand.js +1 -0
- package/dist/extension-options-map.d.ts +9 -0
- package/dist/extension-options-map.js +4 -0
- package/dist/feature.d.ts +518 -0
- package/dist/feature.js +1 -0
- package/dist/field-icon.d.ts +1 -0
- package/dist/field-icon.js +1 -0
- package/dist/fields.d.ts +729 -0
- package/dist/fields.js +50 -0
- package/dist/file-handle-types.d.ts +12 -0
- package/dist/file-handle-types.js +1 -0
- package/dist/file-provider-resolver-types.d.ts +3 -0
- package/dist/file-provider-resolver-types.js +1 -0
- package/dist/file-storage-provider-types.d.ts +17 -0
- package/dist/file-storage-provider-types.js +1 -0
- package/dist/geo-tz.d.ts +17 -0
- package/{src/geo-tz.ts → dist/geo-tz.js} +1 -20
- package/dist/handlers.d.ts +493 -0
- package/dist/handlers.js +49 -0
- package/dist/hook-phase.d.ts +1 -0
- package/dist/hook-phase.js +1 -0
- package/dist/hooks.d.ts +75 -0
- package/dist/hooks.js +1 -0
- package/dist/http-route.d.ts +52 -0
- package/dist/http-route.js +13 -0
- package/dist/identifiers.d.ts +8 -0
- package/{src/identifiers.ts → dist/identifiers.js} +7 -24
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/kms-adapter-types.d.ts +68 -0
- package/dist/kms-adapter-types.js +49 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +1 -0
- package/dist/multi-stream-apply-context-types.d.ts +18 -0
- package/dist/multi-stream-apply-context-types.js +1 -0
- package/dist/nav-icon.d.ts +3 -0
- package/dist/nav-icon.js +100 -0
- package/dist/nav.d.ts +39 -0
- package/dist/nav.js +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.js +2 -0
- package/dist/observability/metric.d.ts +34 -0
- package/dist/observability/metric.js +3 -0
- package/dist/observability/provider.d.ts +24 -0
- package/dist/observability/provider.js +4 -0
- package/dist/observability/span.d.ts +32 -0
- package/dist/observability/span.js +4 -0
- package/dist/ownership.d.ts +33 -0
- package/dist/ownership.js +5 -0
- package/dist/projection.d.ts +38 -0
- package/dist/projection.js +1 -0
- package/dist/rate-limit-types.d.ts +19 -0
- package/dist/rate-limit-types.js +1 -0
- package/dist/relations.d.ts +25 -0
- package/dist/relations.js +1 -0
- package/dist/schema-table-types.d.ts +15 -0
- package/{src/schema-table-types.ts → dist/schema-table-types.js} +0 -20
- package/dist/screen.d.ts +1265 -0
- package/dist/screen.js +3 -0
- package/dist/search-adapter.d.ts +32 -0
- package/dist/search-adapter.js +1 -0
- package/dist/secrets-types.d.ts +52 -0
- package/dist/secrets-types.js +30 -0
- package/dist/snapshot-types.d.ts +13 -0
- package/dist/snapshot-types.js +1 -0
- package/dist/step.d.ts +304 -0
- package/dist/step.js +11 -0
- package/dist/target-ref.d.ts +5 -0
- package/{src/target-ref.ts → dist/target-ref.js} +1 -6
- package/dist/tenancy-brand.d.ts +6 -0
- package/dist/tenancy-brand.js +1 -0
- package/dist/tenant-db-types.d.ts +67 -0
- package/dist/tenant-db-types.js +5 -0
- package/dist/tree-node.d.ts +26 -0
- package/dist/tree-node.js +22 -0
- package/dist/tz-context.d.ts +48 -0
- package/dist/tz-context.js +6 -0
- package/dist/websocket-route.d.ts +46 -0
- package/dist/websocket-route.js +10 -0
- package/dist/where-clause-types.d.ts +20 -0
- package/dist/where-clause-types.js +1 -0
- package/dist/workspace.d.ts +10 -0
- package/dist/workspace.js +1 -0
- package/dist/write-error-info-types.d.ts +12 -0
- package/dist/write-error-info-types.js +1 -0
- package/package.json +372 -124
- package/src/changes.json +18 -0
- package/src/__tests__/handlers.test.ts +0 -40
- package/src/__tests__/plural-forms.test.ts +0 -18
- package/src/action-icon.ts +0 -57
- package/src/concurrency-mode.ts +0 -5
- package/src/config-scope.ts +0 -5
- package/src/config.ts +0 -563
- package/src/cursor-types.ts +0 -18
- package/src/db-connection.ts +0 -25
- package/src/define-handler.ts +0 -138
- package/src/derivatives-types.ts +0 -106
- package/src/entity-cache.ts +0 -35
- package/src/entity-handlers.ts +0 -68
- package/src/entity-table-meta-types.ts +0 -101
- package/src/envelope-cipher-types.ts +0 -6
- package/src/event-store-executor-types.ts +0 -163
- package/src/event-store-types.ts +0 -85
- package/src/executor-brand.ts +0 -19
- package/src/extension-options-map.ts +0 -21
- package/src/feature.ts +0 -1141
- package/src/field-icon.ts +0 -23
- package/src/fields.ts +0 -1114
- package/src/file-handle-types.ts +0 -20
- package/src/file-provider-resolver-types.ts +0 -7
- package/src/file-storage-provider-types.ts +0 -54
- package/src/handlers.ts +0 -1246
- package/src/hook-phase.ts +0 -2
- package/src/hooks.ts +0 -170
- package/src/http-route.ts +0 -78
- package/src/kms-adapter-types.ts +0 -128
- package/src/logger.ts +0 -7
- package/src/multi-stream-apply-context-types.ts +0 -52
- package/src/nav-icon.ts +0 -105
- package/src/nav.ts +0 -139
- package/src/observability/index.ts +0 -28
- package/src/observability/metric.ts +0 -56
- package/src/observability/provider.ts +0 -32
- package/src/observability/span.ts +0 -58
- package/src/ownership.ts +0 -83
- package/src/projection.ts +0 -170
- package/src/rate-limit-types.ts +0 -32
- package/src/relations.ts +0 -52
- package/src/screen.ts +0 -1593
- package/src/search-adapter.ts +0 -45
- package/src/secrets-types.ts +0 -182
- package/src/snapshot-types.ts +0 -36
- package/src/step.ts +0 -390
- package/src/tenancy-brand.ts +0 -9
- package/src/tenant-db-types.ts +0 -116
- package/src/tree-node.ts +0 -134
- package/src/tz-context.ts +0 -63
- package/src/websocket-route.ts +0 -68
- package/src/where-clause-types.ts +0 -31
- package/src/workspace.ts +0 -42
- package/src/write-error-info-types.ts +0 -20
- /package/{src/index.ts → dist/concurrency-mode.js} +0 -0
package/src/screen.ts
DELETED
|
@@ -1,1593 +0,0 @@
|
|
|
1
|
-
import type { FieldIconKey } from "./field-icon";
|
|
2
|
-
import type { FieldDefinition, FormFieldDefinition } from "./fields";
|
|
3
|
-
import type { AccessRule, AgentHandlerHints } from "./handlers";
|
|
4
|
-
import type { IconKey, NavIconKey } from "./nav-icon";
|
|
5
|
-
|
|
6
|
-
export type { FieldIconKey } from "./field-icon";
|
|
7
|
-
|
|
8
|
-
// Screen definitions describe how a feature surfaces data to the user.
|
|
9
|
-
// Pure data — the engine stores these verbatim and ui-core / the renderer
|
|
10
|
-
// packages decide what to do with them. The framework must not import
|
|
11
|
-
// React / react-native from here; renderer components stay opaque so
|
|
12
|
-
// `engine/` imports don't pull the whole UI toolchain into every bundle.
|
|
13
|
-
//
|
|
14
|
-
// Note on `id`: feature authors write the short form ("product-list"); the
|
|
15
|
-
// registry overwrites `id` with the qualified name ("shop:screen:product-list")
|
|
16
|
-
// in its stored copies. Callers of `registry.getScreen(qn)` /
|
|
17
|
-
// `getAllScreens()` / `getScreensByEntity(...)` always see the qualified id.
|
|
18
|
-
// `feature.screens[shortId]` on the unregistered FeatureDefinition keeps
|
|
19
|
-
// the short form.
|
|
20
|
-
|
|
21
|
-
// A per-platform component pair — used anywhere a feature attaches a
|
|
22
|
-
// rendered component (screens, slots, routes, field renderers, future
|
|
23
|
-
// r.uiComponent). Both fields are `unknown` so the engine doesn't depend
|
|
24
|
-
// on React types; ui-core resolves the correct platform at mount-time.
|
|
25
|
-
// Framework code only checks structural presence.
|
|
26
|
-
export type PlatformComponent = {
|
|
27
|
-
readonly react?: unknown;
|
|
28
|
-
readonly native?: unknown;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Closed set of unit identifiers for `{ format: "unit" }` (no bare `string`
|
|
32
|
-
// per house convention). "m2" has no ECMA-402 sanctioned unit — Intl throws
|
|
33
|
-
// RangeError for "square-meter" — so kumiko-headless's applyFormatSpec
|
|
34
|
-
// formats it as a locale-formatted number with a literal "m²" suffix. The
|
|
35
|
-
// rest map to a CLDR-sanctioned Intl.NumberFormat unit id and get
|
|
36
|
-
// locale-correct pluralization/spacing straight from Intl.
|
|
37
|
-
export type UnitKey = "m2" | "km" | "m" | "kg" | "percent" | "mi";
|
|
38
|
-
|
|
39
|
-
// Built-in value formatters. Apps extend via module augmentation:
|
|
40
|
-
// declare module "@cosmicdrift/kumiko-framework/engine/types" {
|
|
41
|
-
// interface FieldFormatRegistry { myFormat: { myOption?: string } }
|
|
42
|
-
// }
|
|
43
|
-
// renderer-web handles all built-in keys; unknown app-specific keys fall back
|
|
44
|
-
// to String(value).
|
|
45
|
-
export interface FieldFormatRegistry {
|
|
46
|
-
timestamp: {
|
|
47
|
-
readonly locale?: string;
|
|
48
|
-
readonly dateStyle?: "full" | "long" | "medium" | "short";
|
|
49
|
-
readonly timeStyle?: "full" | "long" | "medium" | "short";
|
|
50
|
-
};
|
|
51
|
-
date: {
|
|
52
|
-
readonly locale?: string;
|
|
53
|
-
readonly dateStyle?: "full" | "long" | "medium" | "short";
|
|
54
|
-
};
|
|
55
|
-
boolean: { readonly trueLabel?: string; readonly falseLabel?: string };
|
|
56
|
-
currency: { readonly symbol?: string };
|
|
57
|
-
priority: { readonly emptyLabel?: string; readonly prefix?: string };
|
|
58
|
-
/** `grouping: false` renders without thousands-separators (e.g. a model
|
|
59
|
-
* year: "2021", not "2.021"). Default `true`. */
|
|
60
|
-
number: { readonly locale?: string; readonly grouping?: boolean };
|
|
61
|
-
decimal: { readonly locale?: string; readonly grouping?: boolean };
|
|
62
|
-
bigInt: { readonly locale?: string; readonly grouping?: boolean };
|
|
63
|
-
// Value is percent *points* (12 → "12 %"), not a 0..1 ratio — Intl
|
|
64
|
-
// style:"unit"/unit:"percent" does NOT multiply by 100 (unlike style:"percent").
|
|
65
|
-
unit: {
|
|
66
|
-
readonly unit: UnitKey;
|
|
67
|
-
readonly locale?: string;
|
|
68
|
-
readonly unitDisplay?: "long" | "short" | "narrow";
|
|
69
|
-
};
|
|
70
|
-
// Resolves an enum value through the i18n convention
|
|
71
|
-
// `<feature>:entity:<entity>:field:<field>:option:<value>` — same
|
|
72
|
-
// fallback-to-raw-value rule as buildOptionLabels: an untranslated key
|
|
73
|
-
// renders the raw enum value instead of the literal i18n key.
|
|
74
|
-
// Prefer fieldOptionLabelKeyPrefix() from @cosmicdrift/kumiko-headless
|
|
75
|
-
// over hand-typed prefix strings.
|
|
76
|
-
enumOption: { readonly keyPrefix: string };
|
|
77
|
-
/** JSON-safe structured value rendered as indented text. */
|
|
78
|
-
json: { readonly indent?: number };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Discriminated union derived from the registry — one variant per key.
|
|
82
|
-
// JSON-safe: no function members, survives buildAppSchema → GET /api/schema.
|
|
83
|
-
export type FormatSpec = {
|
|
84
|
-
[K in keyof FieldFormatRegistry]: { readonly format: K } & FieldFormatRegistry[K];
|
|
85
|
-
}[keyof FieldFormatRegistry];
|
|
86
|
-
|
|
87
|
-
// Level-2 field renderer (ui-architecture.md §Renderer Customization):
|
|
88
|
-
// - PlatformComponent → platform-specific component from the same feature
|
|
89
|
-
// - string → cross-feature QN reference (resolved at mount-time)
|
|
90
|
-
// - FormatSpec → declarative value formatter, JSON-safe ({ format: "timestamp" } etc.)
|
|
91
|
-
export type FieldRenderer = PlatformComponent | string | FormatSpec;
|
|
92
|
-
|
|
93
|
-
// Declarative field-state condition. Evaluated by the renderer against the
|
|
94
|
-
// current row/form values. Five forms:
|
|
95
|
-
// boolean — static on/off (e.g. readOnly: true)
|
|
96
|
-
// { field, eq } — true when row[field] === eq
|
|
97
|
-
// { field, ne } — true when row[field] !== ne
|
|
98
|
-
// { field, in } — true when row[field] is one of the given values
|
|
99
|
-
// { field, notIn } — true when row[field] is none of the given values
|
|
100
|
-
// JSON-safe: survives buildAppSchema → GET /api/schema stringify.
|
|
101
|
-
export type FieldCondition =
|
|
102
|
-
| boolean
|
|
103
|
-
| { readonly field: string; readonly eq: unknown }
|
|
104
|
-
| { readonly field: string; readonly ne: unknown }
|
|
105
|
-
| { readonly field: string; readonly in: readonly unknown[] }
|
|
106
|
-
| { readonly field: string; readonly notIn: readonly unknown[] };
|
|
107
|
-
|
|
108
|
-
// --- entityList ---
|
|
109
|
-
|
|
110
|
-
// `string` shorthand when the column only needs its field name; the object
|
|
111
|
-
// form carries renderer / display overrides. normalizeListColumn() below
|
|
112
|
-
// collapses both into the object form for downstream consumers.
|
|
113
|
-
export type ListColumnSpec =
|
|
114
|
-
| string
|
|
115
|
-
| {
|
|
116
|
-
readonly field: string;
|
|
117
|
-
readonly renderer?: FieldRenderer;
|
|
118
|
-
/** Optional header, overriding the default
|
|
119
|
-
* `<feature>:entity:<entity>:field:<field>` i18n convention. Resolved
|
|
120
|
-
* through `translate` like any header — an i18n key, or a plain literal
|
|
121
|
-
* shown verbatim if it isn't a key. Also the way to declare a *virtual*
|
|
122
|
-
* column whose `field` is NOT an entity field — a presentational column
|
|
123
|
-
* drawn entirely by a `columnRenderer` component that reads the `row`
|
|
124
|
-
* (e.g. a tag-chips cell), not a stored value. Such a column needs a
|
|
125
|
-
* label; without one it is rejected at boot as an unknown field. `field`
|
|
126
|
-
* is then just a stable column key (pick any unique slug). */
|
|
127
|
-
readonly label?: string;
|
|
128
|
-
/** Marks this column as a reference lookup instead of a plain value —
|
|
129
|
-
* for projectionList/relatedList columns, which have no
|
|
130
|
-
* `EntityDefinition` to carry a real `reference` field type. Same
|
|
131
|
-
* target convention as `parseRefTarget`: an entity name (same
|
|
132
|
-
* feature) or `feature:entity` (cross-feature). Ignored on
|
|
133
|
-
* `entityList` columns, where the field's own declared type always
|
|
134
|
-
* wins. */
|
|
135
|
-
readonly refEntity?: string;
|
|
136
|
-
/** Row field on the referenced entity shown as the label (default
|
|
137
|
-
* "id"). Only meaningful together with `refEntity`. */
|
|
138
|
-
readonly refLabelField?: string;
|
|
139
|
-
/** Marks this column as sortable via a clickable header — for
|
|
140
|
-
* relatedList columns, which have no `EntityDefinition` field to carry
|
|
141
|
-
* `sortable` on. Ignored on `entityList` columns, where the entity
|
|
142
|
-
* field's own `sortable` flag wins. */
|
|
143
|
-
readonly sortable?: boolean;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
// Pagination-Modi für entityList:
|
|
147
|
-
// - "pages": klassischer Pager (← 1 2 ... N →) — bookmarkable
|
|
148
|
-
// via ?page=N in der URL. Server liefert `total`
|
|
149
|
-
// damit der Pager "Page X of Y" rendern kann.
|
|
150
|
-
// Default für CRUD-/Admin-Listen.
|
|
151
|
-
// - "infinite": IntersectionObserver am Bottom — beim Sichtbar
|
|
152
|
-
// werden lädt cursor-basiert die nächste Page und
|
|
153
|
-
// appended an `rows`. Kein page-State in URL (Scroll-
|
|
154
|
-
// Position ist Browser-eigen).
|
|
155
|
-
// - false: Pagination aus — `executor.list()` lädt alles
|
|
156
|
-
// was zur Tenant-Sicht passt. Sinnvoll für kleine
|
|
157
|
-
// Lookup-/Master-Daten (≤ ~200 Rows).
|
|
158
|
-
export type ListPaginationMode = "pages" | "infinite" | false;
|
|
159
|
-
|
|
160
|
-
// Sort-State auf der Wire — Field-Name muss zur Entity-Definition
|
|
161
|
-
// passen UND als sortable: true markiert sein. Validator lehnt sonst
|
|
162
|
-
// beim Boot ab.
|
|
163
|
-
export type ListSortDir = "asc" | "desc";
|
|
164
|
-
export type ListSortSpec = {
|
|
165
|
-
readonly field: string;
|
|
166
|
-
readonly dir: ListSortDir;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Screen-Level Filter — Author deklariert pro List-Screen einen festen
|
|
170
|
-
// Filter der bei jedem Query angewendet wird. Use-case: drei Buckets
|
|
171
|
-
// derselben Entity ("Upcoming Maintenance" / "Active Maintenance" /
|
|
172
|
-
// "Past Maintenance") ohne drei Custom-Pages — Filter pro Screen
|
|
173
|
-
// unterscheidet sie.
|
|
174
|
-
//
|
|
175
|
-
// Operatoren (Drizzle-konsistent):
|
|
176
|
-
// eq/ne → field = value | field != value
|
|
177
|
-
// lt/gt → field < value | field > value (numerisch / temporal)
|
|
178
|
-
// lte/gte → field <= value | field >= value (numerisch / temporal)
|
|
179
|
-
// in → field IN (...values), value muss readonly array sein
|
|
180
|
-
//
|
|
181
|
-
// Field muss in der Entity existieren UND `filterable: true` haben
|
|
182
|
-
// (Boot-Validator pinned beides). `lt`/`gt`/`lte`/`gte` nur auf
|
|
183
|
-
// vergleichbaren Field-Types (number/money/date/timestamp/
|
|
184
|
-
// locatedTimestamp); auf text/boolean/select/multiSelect lehnt der
|
|
185
|
-
// Validator das ab.
|
|
186
|
-
//
|
|
187
|
-
// Security-Modell: Filter ist UX-Bucketing, KEINE Access-Boundary. Der
|
|
188
|
-
// Server appliziert den filter aus dem Payload — der Client kann ihn
|
|
189
|
-
// weglassen oder durch einen anderen ersetzen. Boundary bleiben
|
|
190
|
-
// access-rule + Tenant-Scope; Felder mit Sicherheits-Bias (encrypted,
|
|
191
|
-
// restricted) müssen dort geschützt werden, nicht über den Screen-Filter.
|
|
192
|
-
export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
|
|
193
|
-
export type ScreenFilter = {
|
|
194
|
-
readonly field: string;
|
|
195
|
-
readonly op: ScreenFilterOp;
|
|
196
|
-
readonly value: unknown;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
/** Declarative row-field extractor — JSON-safe (no function prop, survives
|
|
200
|
-
* GET /api/schema / buildAppSchema).
|
|
201
|
-
*
|
|
202
|
-
* `pick`: extracts fields 1:1. `{ pick: ["id", "version"] }` → `{ id: row.id, version: row.version }`
|
|
203
|
-
* `map`: renames fields. `{ map: { incidentId: "id" } }` → `{ incidentId: row.id }`
|
|
204
|
-
*
|
|
205
|
-
* Limitation: computed/templated values can't be expressed this way —
|
|
206
|
-
* that logic belongs server-side in the write handler. */
|
|
207
|
-
export type RowFieldExtractor =
|
|
208
|
-
| { readonly pick: readonly string[] }
|
|
209
|
-
| { readonly map: Readonly<Record<string, string>> };
|
|
210
|
-
|
|
211
|
-
// RowAction — per-Row Button/Dropdown-Item das einen Write-Handler
|
|
212
|
-
// triggert oder zu einem anderen Screen navigiert.
|
|
213
|
-
//
|
|
214
|
-
// Pattern: row-level Lifecycle-Operations (Maintenance start/cancel/
|
|
215
|
-
// complete, Incident resolve, Order ship etc.) — Sachen die in einem
|
|
216
|
-
// CRUD-update kein passendes Verb haben aber als WriteHandler existieren.
|
|
217
|
-
//
|
|
218
|
-
// Discriminated Union mit `kind`:
|
|
219
|
-
// - "writeHandler" (default): dispatched einen Write-Handler mit
|
|
220
|
-
// Payload pro Row.
|
|
221
|
-
// - "navigate" (Tier 2.7e): navigiert zu einem anderen Screen,
|
|
222
|
-
// optional mit URL-Search-Params aus `params`. Use-case: "Edit",
|
|
223
|
-
// "View Audit-Log", "Open in actionForm" etc.
|
|
224
|
-
// - "drawer" (fw#2710): mounts an actionForm in a Drawer without leaving
|
|
225
|
-
// the list/detail screen, prefilled from the clicked row via `params`.
|
|
226
|
-
// Row-level pendant to ToolbarAction's "drawer" variant (fw#2225).
|
|
227
|
-
export type RowAction = RowActionWriteHandler | RowActionNavigate | RowActionDrawer;
|
|
228
|
-
|
|
229
|
-
export type RowActionWriteHandler = {
|
|
230
|
-
/** Default für RowActions ohne explizit gesetzten `kind` —
|
|
231
|
-
* Backwards-kompatible Form. Kann auch explizit gesetzt werden. */
|
|
232
|
-
readonly kind?: "writeHandler";
|
|
233
|
-
/** Stable id pro Screen — kebab-case, eindeutig im Action-Set. */
|
|
234
|
-
readonly id: string;
|
|
235
|
-
/** Anzeige-Text (i18n-Key). */
|
|
236
|
-
readonly label: string;
|
|
237
|
-
/** Qualified-Name des Server-Handlers, z.B.
|
|
238
|
-
* "publicstatus:write:maintenance:start". Wird via useDispatcher
|
|
239
|
-
* dispatcht. */
|
|
240
|
-
readonly handler: string;
|
|
241
|
-
/** Deklarativer Payload pro Row. Default = `{ id: row.id }`.
|
|
242
|
-
* `pick` extrahiert Felder gleichen Namens; `map` benennt um. */
|
|
243
|
-
readonly payload?: RowFieldExtractor;
|
|
244
|
-
/** i18n-Key für die Confirm-Dialog-Description. Wenn gesetzt, öffnet
|
|
245
|
-
* ein Modal vor der Ausführung — der User muss explizit bestätigen.
|
|
246
|
-
* Zusammen mit `style: "danger"` ist das die Standard-Sicherheits-
|
|
247
|
-
* Garde für destruktive Aktionen. */
|
|
248
|
-
readonly confirm?: string;
|
|
249
|
-
/** i18n-Key für den Confirm-Button-Text im Dialog. Default = `label`
|
|
250
|
-
* (also "Delete" → "Delete"-Button im Confirm). Setzen wenn die
|
|
251
|
-
* Action einen langen Namen hat ("Mark Subscription as Cancelled")
|
|
252
|
-
* und der Button kürzer sein soll ("Cancel Subscription"). */
|
|
253
|
-
readonly confirmLabel?: string;
|
|
254
|
-
/** Conditional Visibility pro Row. */
|
|
255
|
-
readonly visible?: FieldCondition;
|
|
256
|
-
/** Visual-Style. "danger" rendert rot + erzwingt einen Confirm-
|
|
257
|
-
* Dialog (auch ohne expliziten `confirm`-Key). */
|
|
258
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
259
|
-
/** Overrides the id-based default icon (see ACTION_ICON_BY_ID in
|
|
260
|
-
* kumiko-renderer) — closed IconKey vocabulary into the ICONS registry
|
|
261
|
-
* (renderer-web), analogous to EditFieldSpec.icon. */
|
|
262
|
-
readonly icon?: IconKey;
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
export type RowActionNavigateBase = {
|
|
266
|
-
readonly kind: "navigate";
|
|
267
|
-
readonly id: string;
|
|
268
|
-
readonly label: string;
|
|
269
|
-
/** Feldname dessen Wert als entityId in den URL-Pfad eingebettet wird
|
|
270
|
-
* (`/<workspace>/<screen>/<entityId>`). entityEdit liest die Id
|
|
271
|
-
* AUSSCHLIESSLICH aus dem Pfad. Default: "id" wenn der Ziel-Screen ein
|
|
272
|
-
* entityEdit ist ODER wenn `entity` (statt `screen`) gesetzt ist. */
|
|
273
|
-
readonly entityId?: string;
|
|
274
|
-
/** Declarative URL search params extracted from the clicked row.
|
|
275
|
-
* Both actionForm and entityEdit-create targets read `params` as
|
|
276
|
-
* initial values (pre-filling the form). entityEdit-update does
|
|
277
|
-
* not — the existing record's values take precedence.
|
|
278
|
-
* `pick` extracts fields of the same name; `map` renames them. */
|
|
279
|
-
readonly params?: RowFieldExtractor;
|
|
280
|
-
/** Section id of the tab to activate on the target projectionDetail
|
|
281
|
-
* (layout.mode "tabs"); merged into the navigate search params next to
|
|
282
|
-
* returnTo. Boot validator checks the id exists on the target. */
|
|
283
|
-
readonly tab?: string;
|
|
284
|
-
/** Conditional Visibility pro Row. */
|
|
285
|
-
readonly visible?: FieldCondition;
|
|
286
|
-
/** Visual style only — unlike the writeHandler variants, "danger" does not
|
|
287
|
-
* force a confirm dialog here: the target form is the confirmation. */
|
|
288
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
289
|
-
/** Wenn true, löst ein Klick auf die ganze Zeile (nicht nur das Aktionsmenü)
|
|
290
|
-
* diese navigate-Action aus. Max. eine pro Liste (Boot-Validator prüft). Nur
|
|
291
|
-
* auf navigate — ein Row-Klick darf keinen (evtl. destruktiven, unbestätigten)
|
|
292
|
-
* Write auslösen, daher nicht auf writeHandler-Actions. */
|
|
293
|
-
readonly rowClick?: boolean;
|
|
294
|
-
/** Overrides the id-based default icon (see ACTION_ICON_BY_ID in
|
|
295
|
-
* kumiko-renderer) — closed IconKey vocabulary into the ICONS registry
|
|
296
|
-
* (renderer-web), analogous to EditFieldSpec.icon. */
|
|
297
|
-
readonly icon?: IconKey;
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
/** Exactly one of `screen` / `entity` — mutual exclusivity is type-enforced
|
|
301
|
-
* (boot validator still rejects both/neither for untyped schemas). */
|
|
302
|
-
export type RowActionNavigate = RowActionNavigateBase &
|
|
303
|
-
(
|
|
304
|
-
| {
|
|
305
|
-
/** Screen-id (kurz, unqualified). Boot-Validator prüft Existenz im
|
|
306
|
-
* selben Feature. */
|
|
307
|
-
readonly screen: string;
|
|
308
|
-
readonly entity?: never;
|
|
309
|
-
}
|
|
310
|
-
| {
|
|
311
|
-
/** Entity-Name statt Screen-Id (fw#2228) — löst zur Boot-Zeit auf den
|
|
312
|
-
* Screen auf, der `detailFor: "<entity>"` deklariert (siehe ObjectTarget/
|
|
313
|
-
* resolveTarget in @cosmicdrift/kumiko-renderer's nav.tsx). */
|
|
314
|
-
readonly entity: string;
|
|
315
|
-
readonly screen?: never;
|
|
316
|
-
}
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
export type RowActionDrawer = {
|
|
320
|
-
readonly kind: "drawer";
|
|
321
|
-
readonly id: string;
|
|
322
|
-
readonly label: string;
|
|
323
|
-
/** Short, unqualified id of an `actionForm` screen in the same feature.
|
|
324
|
-
* Boot validator rejects a missing screen and a screen whose `type`
|
|
325
|
-
* isn't `actionForm` — same check as ToolbarAction's drawer variant
|
|
326
|
-
* (see docs/plans/bundled-features-screen-standardisierung.md §2.6c). */
|
|
327
|
-
readonly screen: string;
|
|
328
|
-
/** Declarative prefill extracted from the clicked row. `pick` extracts
|
|
329
|
-
* fields of the same name; `map` renames them. */
|
|
330
|
-
readonly params?: RowFieldExtractor;
|
|
331
|
-
/** Per-row conditional visibility. */
|
|
332
|
-
readonly visible?: FieldCondition;
|
|
333
|
-
/** Visual style only — unlike the writeHandler variants, "danger" does not
|
|
334
|
-
* force a confirm dialog here: the target form is the confirmation. */
|
|
335
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
336
|
-
/** Overrides the id-based default icon (see ACTION_ICON_BY_ID in
|
|
337
|
-
* kumiko-renderer) — closed IconKey vocabulary into the ICONS registry
|
|
338
|
-
* (renderer-web), analogous to EditFieldSpec.icon. */
|
|
339
|
-
readonly icon?: IconKey;
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
// ToolbarAction — button in the list header. Three variants: navigate to
|
|
343
|
-
// another screen (e.g. a full-page actionForm), dispatch a handler directly
|
|
344
|
-
// (e.g. "Sync All" without a form), or mount an actionForm in a Drawer
|
|
345
|
-
// without leaving the list (fw#2225).
|
|
346
|
-
export type ToolbarAction =
|
|
347
|
-
| {
|
|
348
|
-
readonly kind: "navigate";
|
|
349
|
-
readonly id: string;
|
|
350
|
-
readonly label: string;
|
|
351
|
-
/** Screen-id (kurz, unqualified) zu dem navigiert wird. */
|
|
352
|
-
readonly screen: string;
|
|
353
|
-
/** Section id of the tab to activate on the target projectionDetail
|
|
354
|
-
* (layout.mode "tabs"); merged into the navigate search params next to
|
|
355
|
-
* returnTo. Boot validator checks the id exists on the target. */
|
|
356
|
-
readonly tab?: string;
|
|
357
|
-
/** Visual style only — unlike the writeHandler variant, "danger" does
|
|
358
|
-
* not force a confirm dialog here: the target form is the
|
|
359
|
-
* confirmation. */
|
|
360
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
361
|
-
}
|
|
362
|
-
| {
|
|
363
|
-
readonly kind: "writeHandler";
|
|
364
|
-
readonly id: string;
|
|
365
|
-
readonly label: string;
|
|
366
|
-
readonly handler: string;
|
|
367
|
-
/** Statischer Payload ohne row-Context. Default = `{}`. */
|
|
368
|
-
readonly payload?: Record<string, unknown>;
|
|
369
|
-
/** i18n-Key für Confirm-Dialog-Description. Wenn gesetzt UND/ODER
|
|
370
|
-
* style="danger": Modal vor der Ausführung. */
|
|
371
|
-
readonly confirm?: string;
|
|
372
|
-
/** i18n-Key für Confirm-Button-Text im Dialog. Default = `label`. */
|
|
373
|
-
readonly confirmLabel?: string;
|
|
374
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
375
|
-
}
|
|
376
|
-
| {
|
|
377
|
-
readonly kind: "drawer";
|
|
378
|
-
readonly id: string;
|
|
379
|
-
readonly label: string;
|
|
380
|
-
/** Short, unqualified id of an `actionForm` screen in the same
|
|
381
|
-
* feature. Boot validator rejects a missing screen and a screen
|
|
382
|
-
* whose `type` isn't `actionForm` — the caller decides full-page
|
|
383
|
-
* vs. drawer, not the form (see docs/plans/bundled-features-screen-
|
|
384
|
-
* standardisierung.md §2.6c). */
|
|
385
|
-
readonly screen: string;
|
|
386
|
-
/** Visual style only — unlike the writeHandler variant, "danger" does
|
|
387
|
-
* not force a confirm dialog here: the target form is the
|
|
388
|
-
* confirmation. */
|
|
389
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
// relatedList-only extension of ToolbarAction:
|
|
393
|
-
// a relatedList section's toolbar renders inside a projectionDetail, which
|
|
394
|
-
// has a record to evaluate against — a plain entityList/projectionList
|
|
395
|
-
// toolbar does not, so `visible`/`params` live here instead of on the base
|
|
396
|
-
// union. `visible` uses the same FieldCondition as header actions/
|
|
397
|
-
// RowAction; `params` reuses RowActionNavigate's RowFieldExtractor, applied
|
|
398
|
-
// to the enclosing record instead of a clicked row. Every field is optional,
|
|
399
|
-
// so a plain ToolbarAction is already a valid RelatedListToolbarAction —
|
|
400
|
-
// callers pass either type without a cast.
|
|
401
|
-
export type RelatedListToolbarAction =
|
|
402
|
-
| (Extract<ToolbarAction, { readonly kind: "navigate" }> & {
|
|
403
|
-
/** Conditional visibility, evaluated against the relatedList's parent
|
|
404
|
-
* record (the "Akte"). */
|
|
405
|
-
readonly visible?: FieldCondition;
|
|
406
|
-
/** Declarative URL search params extracted from the parent record,
|
|
407
|
-
* prefilling the target screen. Replaces the implicit
|
|
408
|
-
* `{ [parentParam]: parentId }` default when set. */
|
|
409
|
-
readonly params?: RowFieldExtractor;
|
|
410
|
-
})
|
|
411
|
-
| (Extract<ToolbarAction, { readonly kind: "writeHandler" }> & {
|
|
412
|
-
readonly visible?: FieldCondition;
|
|
413
|
-
})
|
|
414
|
-
| (Extract<ToolbarAction, { readonly kind: "drawer" }> & {
|
|
415
|
-
readonly visible?: FieldCondition;
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
export type EntityListScreenDefinition = {
|
|
419
|
-
readonly id: string;
|
|
420
|
-
readonly type: "entityList";
|
|
421
|
-
readonly nav?: ScreenNavSugar;
|
|
422
|
-
readonly detailFor?: string;
|
|
423
|
-
readonly description?: string;
|
|
424
|
-
readonly agent?: AgentHandlerHints;
|
|
425
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
426
|
-
* app); exempts it from the nav-area boot check. */
|
|
427
|
-
readonly dormant?: boolean;
|
|
428
|
-
readonly entity: string;
|
|
429
|
-
readonly columns: readonly ListColumnSpec[];
|
|
430
|
-
// Row renderer (Desktop) — when omitted, renderer draws the default table
|
|
431
|
-
// from `columns`. cardRenderer fills the same role on compact layouts.
|
|
432
|
-
readonly rowRenderer?: PlatformComponent;
|
|
433
|
-
readonly cardRenderer?: PlatformComponent;
|
|
434
|
-
/** Per-Row-Aktionen — rendert eine Actions-Spalte rechts in der Tabelle.
|
|
435
|
-
* Bis zu 2 actions als inline-Buttons; >2 als Kebab-Dropdown.
|
|
436
|
-
* Reihenfolge im Array = Reihenfolge in der UI. */
|
|
437
|
-
readonly rowActions?: readonly RowAction[];
|
|
438
|
-
/** Toolbar-Aktionen (List-Header). "Open Incident", "Schedule Maintenance"
|
|
439
|
-
* etc. — neben "+ Neu" wenn vorhanden. Reihenfolge im Array = UI-
|
|
440
|
-
* Reihenfolge, primary-style links. */
|
|
441
|
-
readonly toolbarActions?: readonly ToolbarAction[];
|
|
442
|
-
/** Server-side Filter, fest am Screen — drei Buckets derselben
|
|
443
|
-
* Entity ohne Custom-Pages (z.B. "Upcoming" / "Active" / "Past"
|
|
444
|
-
* Maintenance). User-side q-Search läuft AUF dem gefilterten Set
|
|
445
|
-
* oben drauf. Boot-Validator pinst dass field in der Entity existiert. */
|
|
446
|
-
readonly filter?: ScreenFilter;
|
|
447
|
-
// Pagination-Modus (Default "pages"). Bestimmt UI (Pager vs Scroll-
|
|
448
|
-
// Sentinel) und ob der Server `total` mitliefern muss.
|
|
449
|
-
readonly pagination?: ListPaginationMode;
|
|
450
|
-
// Page-Größe. Default 50 — guter Kompromiss zwischen "viel sichtbar"
|
|
451
|
-
// und "DB liefert schnell". Apps mit teurem Read (Joins, Computed-
|
|
452
|
-
// Fields) gehen runter; Power-User-Listen (z.B. internal Analytics)
|
|
453
|
-
// gehen hoch.
|
|
454
|
-
readonly pageSize?: number;
|
|
455
|
-
// Default-Sortierung beim Erst-Mount. Wenn URL-Param `?sort=…`
|
|
456
|
-
// gesetzt ist, gewinnt der; sonst nutzt RenderList diesen Default.
|
|
457
|
-
// `field` muss in der Entity sortable: true sein — Boot-Validator
|
|
458
|
-
// pinnt das.
|
|
459
|
-
readonly defaultSort?: ListSortSpec;
|
|
460
|
-
// Search-Toolbar im UI an/aus. Server-Search geht IMMER über den
|
|
461
|
-
// SearchAdapter (Meilisearch) — kein DB-ILIKE-Drift. Default true
|
|
462
|
-
// wenn die Entity searchable Felder hat, sonst false.
|
|
463
|
-
readonly searchable?: boolean;
|
|
464
|
-
readonly slots?: ScreenSlots;
|
|
465
|
-
readonly access?: AccessRule;
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
// --- projectionList ---
|
|
469
|
-
|
|
470
|
-
// Like entityList, but bound to an EXPLICIT query instead of an entity. The
|
|
471
|
-
// list-query is taken verbatim from `query` (a fully qualified QN like
|
|
472
|
-
// "ledger:query:schedule:list") — NOT derived from the screen's own feature —
|
|
473
|
-
// so a screen can render any read-projection, including one owned by a
|
|
474
|
-
// different feature (the entityList feature-local resolution can't). Columns
|
|
475
|
-
// carry their own labels (no entity to derive field-labels from); there is no
|
|
476
|
-
// auto create-navigation (a projection isn't an editable entity list). Row
|
|
477
|
-
// interaction is explicit via `rowActions`. The query must return the same
|
|
478
|
-
// paged envelope as an entity list-query: `{ rows, nextCursor, total? }`.
|
|
479
|
-
// User-toggleable facet dropdown on a projectionList screen (fw#2224).
|
|
480
|
-
// entityList derives the same UI from `filterable: true` entity fields plus
|
|
481
|
-
// the `<feature>:entity:<entity>:field:<field>:option:<value>` i18n
|
|
482
|
-
// convention. Labels here (`label`, `options[].label`, `trueLabel`/
|
|
483
|
-
// `falseLabel`) are i18n keys resolved via `translate()` with passthrough
|
|
484
|
-
// for unknown keys (fw#2373) — not raw display strings. Sent to the server
|
|
485
|
-
// as `{field, op:"in", value}` in `payload.filters`, ANDed with `filter`.
|
|
486
|
-
// Exception: "dateRange" sends its bounds as the two top-level payload keys
|
|
487
|
-
// named in `params`, not as a `filters` entry.
|
|
488
|
-
export type ListFacetSpec =
|
|
489
|
-
| {
|
|
490
|
-
readonly field: string;
|
|
491
|
-
readonly type: "select";
|
|
492
|
-
readonly label: string;
|
|
493
|
-
readonly options: readonly { readonly value: string; readonly label: string }[];
|
|
494
|
-
}
|
|
495
|
-
| {
|
|
496
|
-
readonly field: string;
|
|
497
|
-
readonly type: "boolean";
|
|
498
|
-
readonly label: string;
|
|
499
|
-
readonly trueLabel: string;
|
|
500
|
-
readonly falseLabel: string;
|
|
501
|
-
}
|
|
502
|
-
| {
|
|
503
|
-
readonly field: string;
|
|
504
|
-
readonly type: "reference";
|
|
505
|
-
readonly label: string;
|
|
506
|
-
/** Entity name (same feature) or `feature:entity` (cross-feature), same
|
|
507
|
-
* convention as `ListColumnSpec.refEntity`. Options load at render time. */
|
|
508
|
-
readonly entity: string;
|
|
509
|
-
/** Row field on the referenced entity shown as the option label
|
|
510
|
-
* (default "id"). */
|
|
511
|
-
readonly labelField?: string;
|
|
512
|
-
}
|
|
513
|
-
| {
|
|
514
|
-
/** Declarative only — the date field the range narrows. It is NOT sent
|
|
515
|
-
* to the server (the bounds go under `params`); it namespaces the
|
|
516
|
-
* facet's URL keys and tells a reading agent which column this list
|
|
517
|
-
* is time-rangeable on. */
|
|
518
|
-
readonly field: string;
|
|
519
|
-
readonly type: "dateRange";
|
|
520
|
-
readonly label: string;
|
|
521
|
-
/** Query-payload keys the chosen bounds are sent under as ISO instants.
|
|
522
|
-
* Explicit rather than a `from`/`to` convention — a query is free to
|
|
523
|
-
* name its time bounds anything, and the boot-validator checks these
|
|
524
|
-
* keys against the handler's Zod schema. */
|
|
525
|
-
readonly params: { readonly from: string; readonly to: string };
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
export type ProjectionListScreenDefinition = {
|
|
529
|
-
readonly id: string;
|
|
530
|
-
readonly type: "projectionList";
|
|
531
|
-
readonly nav?: ScreenNavSugar;
|
|
532
|
-
readonly detailFor?: string;
|
|
533
|
-
readonly description?: string;
|
|
534
|
-
readonly agent?: AgentHandlerHints;
|
|
535
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
536
|
-
* app); exempts it from the nav-area boot check. */
|
|
537
|
-
readonly dormant?: boolean;
|
|
538
|
-
readonly query: string;
|
|
539
|
-
readonly columns: readonly ListColumnSpec[];
|
|
540
|
-
readonly rowRenderer?: PlatformComponent;
|
|
541
|
-
readonly cardRenderer?: PlatformComponent;
|
|
542
|
-
readonly rowActions?: readonly RowAction[];
|
|
543
|
-
readonly toolbarActions?: readonly ToolbarAction[];
|
|
544
|
-
readonly pagination?: ListPaginationMode;
|
|
545
|
-
readonly pageSize?: number;
|
|
546
|
-
readonly defaultSort?: ListSortSpec;
|
|
547
|
-
readonly searchable?: boolean;
|
|
548
|
-
/** Derived by buildAppSchema from the query handler's Zod schema (`sort`
|
|
549
|
-
* param present). Same type serves author and wire schema (no separate
|
|
550
|
-
* client-facing screen type exists), so this stays structurally
|
|
551
|
-
* writable — validateProjectionListScreens rejects any hand-authored
|
|
552
|
-
* value at boot (fw#2165). */
|
|
553
|
-
readonly sortable?: boolean;
|
|
554
|
-
/** Derived by buildAppSchema from the query handler's Zod schema (`cursor`
|
|
555
|
-
* or `offset` param present). See `sortable` doc for why this is
|
|
556
|
-
* boot-enforced rather than type-enforced. */
|
|
557
|
-
readonly paginated?: boolean;
|
|
558
|
-
/** Server-side filter, fixed on the screen — see `ScreenFilter` doc above
|
|
559
|
-
* (entityList). Field existence can't be checked without an entity; the
|
|
560
|
-
* boot-validator only checks structure (`op:"in"` ⇒ array value). */
|
|
561
|
-
readonly filter?: ScreenFilter;
|
|
562
|
-
/** User-toggleable facet dropdowns — see `ListFacetSpec` doc. `field`
|
|
563
|
-
* must be a declared column; the bound query handler must accept
|
|
564
|
-
* `filters` in its Zod schema — the boot-validator checks both. A
|
|
565
|
-
* "dateRange" facet instead needs its two `params` keys in that schema;
|
|
566
|
-
* it sends no `filters` entry. */
|
|
567
|
-
readonly facets?: readonly ListFacetSpec[];
|
|
568
|
-
readonly slots?: ScreenSlots;
|
|
569
|
-
readonly access?: AccessRule;
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
// --- projectionDetail ---
|
|
573
|
-
|
|
574
|
-
// Read-only counterpart to projectionList — a single-row inspector bound to
|
|
575
|
-
// an EXPLICIT query instead of an entity (`entityEdit` requires `r.entity`,
|
|
576
|
-
// which a direct-write/projection read-model like `jobs`/`sessions` doesn't
|
|
577
|
-
// have — see #255). There is no write path: the renderer forces every field
|
|
578
|
-
// readOnly structurally (not just by convention — see
|
|
579
|
-
// renderer/projection-detail-shim.ts), so no `<entity>:write:...:update`
|
|
580
|
-
// command is ever constructed. `idParam` names the query-payload key the
|
|
581
|
-
// route's row-id is passed under; defaults to "id", but a query handler
|
|
582
|
-
// owned by a different feature may already use a domain-specific param name
|
|
583
|
-
// (e.g. jobs' `detailQuery` expects `runId`) — this lets the primitive bind
|
|
584
|
-
// to it without forcing a handler rename. Extension sections aren't
|
|
585
|
-
// supported (no entity for them to persist against); the boot-validator
|
|
586
|
-
// rejects them. `relatedList` sections ARE supported — they run their own
|
|
587
|
-
// query against the displayed record's id, independent of the entity gap
|
|
588
|
-
// above (fw#2166).
|
|
589
|
-
export type RecordHeaderSpec = {
|
|
590
|
-
readonly title: string;
|
|
591
|
-
readonly subtitle?: string;
|
|
592
|
-
readonly status?: string;
|
|
593
|
-
/** Record field holding an absolute http(s) URL. When present and the
|
|
594
|
-
* field's value is such a URL, the subtitle renders as an external link
|
|
595
|
-
* (`target="_blank"`) instead of plain text. */
|
|
596
|
-
readonly subtitleHref?: string;
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
// Same shape as `RowActionNavigate`'s screen/entity choice, minus the fields
|
|
600
|
-
// a metric click has no use for (id, confirm, style).
|
|
601
|
-
export type MetricNavigate = {
|
|
602
|
-
readonly screen?: string;
|
|
603
|
-
readonly entity?: string;
|
|
604
|
-
readonly entityId?: string;
|
|
605
|
-
readonly params?: RowFieldExtractor;
|
|
606
|
-
/** Section id of the tab to activate at the destination. Alone (no
|
|
607
|
-
* `screen`/`entity`), it activates that tab on the current record
|
|
608
|
-
* instead of navigating away. */
|
|
609
|
-
readonly tab?: string;
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
// A metric can navigate on click, so the plain string shorthand (field name,
|
|
613
|
-
// same as before) sits alongside an object form carrying an explicit i18n
|
|
614
|
-
// `label` and a `navigate` target.
|
|
615
|
-
export type MetricSpec =
|
|
616
|
-
| string
|
|
617
|
-
| {
|
|
618
|
-
readonly field: string;
|
|
619
|
-
readonly label?: string;
|
|
620
|
-
readonly navigate?: MetricNavigate;
|
|
621
|
-
};
|
|
622
|
-
|
|
623
|
-
export function metricField(metric: MetricSpec): string {
|
|
624
|
-
return typeof metric === "string" ? metric : metric.field;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
export type ProjectionDetailScreenDefinition = {
|
|
628
|
-
readonly id: string;
|
|
629
|
-
readonly type: "projectionDetail";
|
|
630
|
-
readonly nav?: ScreenNavSugar;
|
|
631
|
-
readonly detailFor?: string;
|
|
632
|
-
readonly description?: string;
|
|
633
|
-
readonly agent?: AgentHandlerHints;
|
|
634
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
635
|
-
* app); exempts it from the nav-area boot check. */
|
|
636
|
-
readonly dormant?: boolean;
|
|
637
|
-
readonly query: string;
|
|
638
|
-
/** Query-payload key for the row-id. Default "id". */
|
|
639
|
-
readonly idParam?: string;
|
|
640
|
-
/** The server determines the shown row from the caller's session/context
|
|
641
|
-
* instead of a row id in the path — a self-service screen ("my profile",
|
|
642
|
-
* "my data") that has no row to link to (unlike `EntityEditScreenDefinition.
|
|
643
|
-
* singleton`, which still resolves via `<entity>:list` limit 1; here the
|
|
644
|
-
* query itself owns the row selection, e.g. `user:query:user:me`). The
|
|
645
|
-
* query is called WITHOUT the `idParam` key — there is no id to send, so
|
|
646
|
-
* the boot-validator rejects declaring `idParam` alongside this flag
|
|
647
|
-
* instead of letting one silently win. Stricter than an id-addressed
|
|
648
|
-
* screen, not looser: the server picks the row, so no client-supplied id
|
|
649
|
-
* can reach the query — a stray id on the path is ignored, never
|
|
650
|
-
* forwarded. */
|
|
651
|
-
readonly singleton?: boolean;
|
|
652
|
-
readonly layout: EditLayout;
|
|
653
|
-
/** Optionaler per-Field-Label-i18n-Key (Field-Name → Key), analog zu
|
|
654
|
-
* entityEdit.fieldLabels. Die Pseudo-Entity `__projection-detail__` hat
|
|
655
|
-
* keinen natürlichen Field-Namespace — fehlt ein Eintrag, gilt die
|
|
656
|
-
* Konvention `<feature>:entity:__projection-detail__:field:<name>`. */
|
|
657
|
-
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
658
|
-
/** Record header above the layout — title/subtitle/status name columns
|
|
659
|
-
* in the query row (not literals). Rendered only when set. */
|
|
660
|
-
readonly header?: RecordHeaderSpec;
|
|
661
|
-
/** Metric band above the layout — column names in the query row, labeled
|
|
662
|
-
* via `fieldLabels` (string shorthand) or the object form's own `label`.
|
|
663
|
-
* Rendered only when set. */
|
|
664
|
-
readonly metrics?: readonly MetricSpec[];
|
|
665
|
-
/** Parent list screen (kurze id) für eine "Zurück"-Navigation. */
|
|
666
|
-
readonly listScreenId?: string;
|
|
667
|
-
readonly slots?: ScreenSlots;
|
|
668
|
-
readonly access?: AccessRule;
|
|
669
|
-
/** Header action buttons. Reuses `RowAction` — the screen's single
|
|
670
|
-
* displayed record stands in for the "row", so `pick`/`map`, `visible`,
|
|
671
|
-
* `confirm`/`confirmLabel` and `style` keep their meaning unchanged.
|
|
672
|
-
* `rowClick` has no target here (there is no row to click) and is
|
|
673
|
-
* rejected by the boot-validator. */
|
|
674
|
-
readonly actions?: readonly RowAction[];
|
|
675
|
-
/** How head fields render. Default "text" — plain text instead of a
|
|
676
|
-
* disabled Input, since every field on this screen type is forced
|
|
677
|
-
* readOnly anyway (there is no write path, see the shim doc above).
|
|
678
|
-
* Set "form" to opt back into the disabled-Input look (fw#2245). */
|
|
679
|
-
readonly valueDisplay?: "form" | "text";
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
// --- dashboard ---
|
|
683
|
-
|
|
684
|
-
// Deklaratives Panel-Grid — Kennzahlen, Verläufe und Kurzlisten ohne
|
|
685
|
-
// Custom-JSX. Jedes Panel zieht seine Daten aus einer eigenen Query
|
|
686
|
-
// (fully-qualified QN, cross-feature erlaubt wie projectionList).
|
|
687
|
-
// Formatierung ist Sache des Query-Handlers: Stat-Werte kommen als
|
|
688
|
-
// anzeigefertige Strings/Zahlen aus der Read-Projection (ES-Read-Models
|
|
689
|
-
// shapen ihre Daten selbst; der Renderer formatiert nicht nach).
|
|
690
|
-
|
|
691
|
-
// Query-Result-Contract: flaches Record; `valueField` zeigt auf den
|
|
692
|
-
// anzeigefertigen Wert, `subField` optional auf eine Sub-Zeile,
|
|
693
|
-
// `toneField` optional auf "default" | "positive" | "warn".
|
|
694
|
-
export type DashboardStatPanel = {
|
|
695
|
-
readonly kind: "stat";
|
|
696
|
-
/** Stable id — kebab-case, eindeutig im Panel-Set. */
|
|
697
|
-
readonly id: string;
|
|
698
|
-
/** Anzeige-Text (i18n-Key). */
|
|
699
|
-
readonly label: string;
|
|
700
|
-
readonly query: string;
|
|
701
|
-
/** Static, author-set query parameters merged on top of the screen's
|
|
702
|
-
* dynamic filterParams — lets a panel pin a value (e.g. a status facet)
|
|
703
|
-
* the screen-wide filter doesn't cover, without needing its own query. */
|
|
704
|
-
readonly params?: Readonly<Record<string, unknown>>;
|
|
705
|
-
readonly valueField: string;
|
|
706
|
-
readonly subField?: string;
|
|
707
|
-
readonly toneField?: string;
|
|
708
|
-
/** Optionaler Delta-Chip (z.B. "↓23 %") neben dem Label. Nur wenn BEIDE
|
|
709
|
-
* Felder gesetzt sind UND der Query-Handler sie liefert, rendert der Chip
|
|
710
|
-
* — sonst bleibt die Kachel wie ohne Delta. `deltaToneField` fällt auf
|
|
711
|
-
* `toneField`/"default" zurück, wenn ungesetzt. */
|
|
712
|
-
readonly deltaField?: string;
|
|
713
|
-
readonly deltaDirectionField?: string;
|
|
714
|
-
readonly deltaToneField?: string;
|
|
715
|
-
/** Statisches Icon neben dem Label — anders als value/sub/delta variiert
|
|
716
|
-
* das Icon nicht pro Query-Result, sondern ist eine Author-Entscheidung
|
|
717
|
-
* wie das Panel selbst. Aufgelöst über dieselbe extensionSectionComponents-
|
|
718
|
-
* Registry wie custom-Panels; die registrierte Komponente ignoriert
|
|
719
|
-
* typischerweise entityName/entityId/filterParams (kein Entity-Kontext
|
|
720
|
-
* für ein reines Icon). */
|
|
721
|
-
readonly icon?: PlatformComponent;
|
|
722
|
-
/** Statischer CSS-Farbwert (z.B. "var(--color-debt)") für den Icon-Chip —
|
|
723
|
-
* Passthrough an die Kachel, keine Registry, kein Lookup. Wirkt NUR wenn
|
|
724
|
-
* `icon` gesetzt ist (StatCard rendert den Chip nur zusammen mit einem
|
|
725
|
-
* Icon) — ohne icon wird der Wert still verworfen. */
|
|
726
|
-
readonly accentColor?: string;
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
// Query-Result-Contract: `{ points: { atMs, value | null }[],
|
|
730
|
-
// windowStartMs, windowEndMs }` — value=null zeichnet einen Einbruch.
|
|
731
|
-
export type DashboardChartPanel = {
|
|
732
|
-
readonly kind: "chart";
|
|
733
|
-
readonly id: string;
|
|
734
|
-
readonly label: string;
|
|
735
|
-
/** v1: geglättete Zeitreihe. Weitere Chart-Formen additiv. */
|
|
736
|
-
readonly chart: "timeseries";
|
|
737
|
-
readonly query: string;
|
|
738
|
-
};
|
|
739
|
-
|
|
740
|
-
// Kurzliste im Dashboard — Query-Contract wie projectionList
|
|
741
|
-
// (`{ rows, nextCursor, total? }`), gerendert ohne Pager/Toolbar.
|
|
742
|
-
export type DashboardListPanel = {
|
|
743
|
-
readonly kind: "list";
|
|
744
|
-
readonly id: string;
|
|
745
|
-
readonly label: string;
|
|
746
|
-
readonly query: string;
|
|
747
|
-
readonly columns: readonly ListColumnSpec[];
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
// Betitelte Sektion aus mehreren Stat-Panels (z.B. "Net Worth": Assets/Debts/
|
|
751
|
-
// Net). Ein Nesting-Level, kein Group-of-Groups — jedes Kind bleibt ein
|
|
752
|
-
// vollwertiges DashboardStatPanel mit eigener Query/id/label, der Renderer
|
|
753
|
-
// zieht sie nur gemeinsam unter einen Sektions-Titel.
|
|
754
|
-
export type DashboardStatGroupPanel = {
|
|
755
|
-
readonly kind: "stat-group";
|
|
756
|
-
readonly id: string;
|
|
757
|
-
readonly label: string;
|
|
758
|
-
readonly stats: readonly DashboardStatPanel[];
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
// Nicht-tabellarische Kurzliste (z.B. "nächste Termine"). Query-Result-
|
|
762
|
-
// Contract: `{ rows: { primary: string; trailing?: string }[] }`.
|
|
763
|
-
export type DashboardFeedPanel = {
|
|
764
|
-
readonly kind: "feed";
|
|
765
|
-
readonly id: string;
|
|
766
|
-
readonly label: string;
|
|
767
|
-
readonly query: string;
|
|
768
|
-
readonly emptyLabel?: string;
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
// Liste aus Label/Wert/Fortschrittsbalken (z.B. Tilgungsfortschritt pro
|
|
772
|
-
// Kredit). Query-Result-Contract: `{ rows: { label: string; value: string;
|
|
773
|
-
// fraction: number }[] }` — fraction wird auf 0..1 geclampt.
|
|
774
|
-
export type DashboardProgressListPanel = {
|
|
775
|
-
readonly kind: "progress-list";
|
|
776
|
-
readonly id: string;
|
|
777
|
-
readonly label: string;
|
|
778
|
-
readonly query: string;
|
|
779
|
-
};
|
|
780
|
-
|
|
781
|
-
// Eingehängte App-Komponente, die ihre Daten/Titel selbst verwaltet (wie ein
|
|
782
|
-
// custom Screen, nur als Panel — bleibt an ihrer Array-Position statt in
|
|
783
|
-
// einen separaten Slot zu wandern). Kein `query`, keine `label`: der Renderer
|
|
784
|
-
// löst `component` über dieselbe extensionSectionComponents-Registry auf wie
|
|
785
|
-
// entityEdit-Extension-Sections und List-Header-Slots.
|
|
786
|
-
export type DashboardCustomPanel = {
|
|
787
|
-
readonly kind: "custom";
|
|
788
|
-
readonly id: string;
|
|
789
|
-
readonly component: PlatformComponent;
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
// Live query, so a write that flips `field` swaps panels without reload; hidden while loading.
|
|
793
|
-
export type DashboardPanelVisibility = {
|
|
794
|
-
readonly query: string;
|
|
795
|
-
readonly field: string;
|
|
796
|
-
readonly eq: string | number | boolean | null;
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
// Embedded redirect/cancelTarget navigate away from the dashboard — targets meant for tiles omit both.
|
|
800
|
-
export type DashboardScreenPanel = {
|
|
801
|
-
readonly kind: "screen";
|
|
802
|
-
readonly id: string;
|
|
803
|
-
/** Same-feature short id or cross-feature QN `<feature>:screen:<id>`. */
|
|
804
|
-
readonly screen: string;
|
|
805
|
-
readonly label?: string;
|
|
806
|
-
readonly visibleWhen?: DashboardPanelVisibility;
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
export type DashboardPanelDefinition =
|
|
810
|
-
| DashboardStatPanel
|
|
811
|
-
| DashboardStatGroupPanel
|
|
812
|
-
| DashboardChartPanel
|
|
813
|
-
| DashboardListPanel
|
|
814
|
-
| DashboardFeedPanel
|
|
815
|
-
| DashboardProgressListPanel
|
|
816
|
-
| DashboardCustomPanel
|
|
817
|
-
| DashboardScreenPanel;
|
|
818
|
-
|
|
819
|
-
// Screen-weiter Picker (Combobox), dessen gewählter Wert unter `id` in JEDE
|
|
820
|
-
// Panel-Query dieses Screens gemerged wird (Query-Handler validieren den Wert
|
|
821
|
-
// selbst gegen die Tenant-Sicht — dies ist UX-Scoping, keine Access-Boundary).
|
|
822
|
-
// Genau eins von `options`/`optionsQuery` ist gesetzt (Boot-Validator prüft).
|
|
823
|
-
export type DashboardFilterDefinition = {
|
|
824
|
-
readonly id: string;
|
|
825
|
-
readonly label: string;
|
|
826
|
-
readonly kind: "select";
|
|
827
|
-
readonly placeholder?: string;
|
|
828
|
-
/** i18n-Key für den "(alle)"-Eintrag. */
|
|
829
|
-
readonly allLabel?: string;
|
|
830
|
-
readonly options?: readonly { readonly value: string; readonly label: string }[];
|
|
831
|
-
/** Query-Result-Contract: `{ rows: { value: string; label: string }[] }`. */
|
|
832
|
-
readonly optionsQuery?: string;
|
|
833
|
-
};
|
|
834
|
-
|
|
835
|
-
export type DashboardScreenDefinition = {
|
|
836
|
-
readonly id: string;
|
|
837
|
-
readonly type: "dashboard";
|
|
838
|
-
readonly nav?: ScreenNavSugar;
|
|
839
|
-
readonly detailFor?: string;
|
|
840
|
-
readonly description?: string;
|
|
841
|
-
readonly agent?: AgentHandlerHints;
|
|
842
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
843
|
-
* app); exempts it from the nav-area boot check. */
|
|
844
|
-
readonly dormant?: boolean;
|
|
845
|
-
readonly panels: readonly DashboardPanelDefinition[];
|
|
846
|
-
readonly filter?: DashboardFilterDefinition;
|
|
847
|
-
readonly slots?: ScreenSlots;
|
|
848
|
-
readonly access?: AccessRule;
|
|
849
|
-
};
|
|
850
|
-
|
|
851
|
-
// --- entityEdit ---
|
|
852
|
-
|
|
853
|
-
// camelCase `readOnly` instead of the spec's lowercase `readonly`: TS's
|
|
854
|
-
// `readonly` modifier on the same line would make the declaration read
|
|
855
|
-
// `readonly readonly?: FieldCondition`, which is legal but a real lese-knick.
|
|
856
|
-
// Mirrors React's `readOnly` prop so the ergonomic cost of the divergence
|
|
857
|
-
// from ui-architecture.md is minimal.
|
|
858
|
-
export type EditFieldSpec =
|
|
859
|
-
| string
|
|
860
|
-
| {
|
|
861
|
-
readonly field: string;
|
|
862
|
-
readonly span?: number;
|
|
863
|
-
readonly visible?: FieldCondition;
|
|
864
|
-
readonly readOnly?: FieldCondition;
|
|
865
|
-
readonly required?: FieldCondition;
|
|
866
|
-
readonly renderer?: FieldRenderer;
|
|
867
|
-
/** Prefix icon on the input — closed FieldIconKey vocabulary into
|
|
868
|
-
* the FIELD_ICONS registry (renderer-web), analogous to
|
|
869
|
-
* `ScreenNavSugar.icon` / NavIconKey. Only takes effect for
|
|
870
|
-
* single-line `type: "text"` and `"number"` — a `text` field with
|
|
871
|
-
* `multiline` routes to a textarea (no icon slot), and other input
|
|
872
|
-
* kinds (select, combobox, date, …) silently ignore the key. */
|
|
873
|
-
readonly icon?: FieldIconKey;
|
|
874
|
-
/** Marks this field as a reference lookup instead of a plain value —
|
|
875
|
-
* for projectionDetail fields, which have no `EntityDefinition` to
|
|
876
|
-
* carry a real `reference` field type. Same target convention as
|
|
877
|
-
* `parseRefTarget` (and `ListColumnSpec.refEntity`): an entity name
|
|
878
|
-
* (same feature) or `feature:entity` (cross-feature). Ignored on
|
|
879
|
-
* `entityEdit` fields, where the field's own declared type always
|
|
880
|
-
* wins. */
|
|
881
|
-
readonly refEntity?: string;
|
|
882
|
-
/** Row field on the referenced entity shown as the label (default
|
|
883
|
-
* "id"). Only meaningful together with `refEntity`. */
|
|
884
|
-
readonly refLabelField?: string;
|
|
885
|
-
};
|
|
886
|
-
|
|
887
|
-
// A section is a normal field-grid (default — `kind` omitted keeps every
|
|
888
|
-
// existing screen-def working), an extension slot that mounts a feature-
|
|
889
|
-
// provided component, or a related-list of other records. The extension
|
|
890
|
-
// component is resolved client-side by name (same `__component` marker as
|
|
891
|
-
// custom screens / column renderers) and receives the host entity name +
|
|
892
|
-
// id, so a bundled feature (e.g. custom-fields) can load and persist its
|
|
893
|
-
// own data inside the form. `relatedList` runs its own query instead —
|
|
894
|
-
// see `EditRelatedListSection`.
|
|
895
|
-
export type EditSectionSpec =
|
|
896
|
-
| EditFieldsSection
|
|
897
|
-
| EditExtensionSection
|
|
898
|
-
| EditRelatedListSection
|
|
899
|
-
| EditWriteFormSection;
|
|
900
|
-
|
|
901
|
-
export type EditFieldsSection = {
|
|
902
|
-
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
903
|
-
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
904
|
-
readonly id?: string;
|
|
905
|
-
readonly kind?: "fields";
|
|
906
|
-
/** Optional. Ohne Titel rendert die Section nur ihre Felder (keine h3-
|
|
907
|
-
* Überschrift) — für flache Forms (Card-Titel + Felder direkt, ein
|
|
908
|
-
* einzelner Abschnitt) wie bei den meisten shadcn-Form-Mustern. */
|
|
909
|
-
readonly title?: string;
|
|
910
|
-
/** Optional help text under the block heading (i18n key or raw string,
|
|
911
|
-
* like `title`) — makes a form usable without training instead of
|
|
912
|
-
* falling back to custom JSX for help text. Renders through the same
|
|
913
|
-
* `subtitle` slot as `FormProps.subtitle`. Works without `title` too
|
|
914
|
-
* (subtitle-only section). */
|
|
915
|
-
readonly description?: string;
|
|
916
|
-
readonly columns?: number;
|
|
917
|
-
/** Mutually exclusive with `groups` — pass `[]` when using `groups`; the
|
|
918
|
-
* boot-validator rejects both non-empty or both empty. */
|
|
919
|
-
readonly fields: readonly EditFieldSpec[];
|
|
920
|
-
/** Splits the section into multiple titled cards instead of one flat grid.
|
|
921
|
-
* Mutually exclusive with `fields`; fields named here still need to exist. */
|
|
922
|
-
readonly groups?: readonly {
|
|
923
|
-
readonly title: string;
|
|
924
|
-
readonly fields: readonly EditFieldSpec[];
|
|
925
|
-
/** Default 2. */
|
|
926
|
-
readonly columns?: number;
|
|
927
|
-
}[];
|
|
928
|
-
/** Rendered left of the section title, `text-muted-foreground` — closed
|
|
929
|
-
* IconKey vocabulary into the ICONS registry (renderer-web), analogous
|
|
930
|
-
* to EditFieldSpec.icon. No title → no icon, and no heuristic derives
|
|
931
|
-
* one from the title (titles are free i18n strings). */
|
|
932
|
-
readonly icon?: IconKey;
|
|
933
|
-
/** Record field rendered as a count badge in the tab label when the
|
|
934
|
-
* enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
935
|
-
* Ignored outside tabs mode or when the field's value is not a finite
|
|
936
|
-
* number. */
|
|
937
|
-
readonly countField?: string;
|
|
938
|
-
/** Rendered top-right in the section's Card title row — same
|
|
939
|
-
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
940
|
-
* evaluated against the same record. Every action must resolve an icon
|
|
941
|
-
* (declared or id-derived); the boot-validator rejects one that doesn't. */
|
|
942
|
-
readonly actions?: readonly RowAction[];
|
|
943
|
-
};
|
|
944
|
-
|
|
945
|
-
export type EditExtensionSection = {
|
|
946
|
-
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
947
|
-
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
948
|
-
readonly id?: string;
|
|
949
|
-
readonly kind: "extension";
|
|
950
|
-
readonly title: string;
|
|
951
|
-
readonly component: PlatformComponent;
|
|
952
|
-
/** When true, the extension registers via `useExtensionFormSubmit` and the
|
|
953
|
-
* surrounding form's Save button should activate for its dirty state.
|
|
954
|
-
* Omit/false for extensions that persist through their own dispatcher
|
|
955
|
-
* writes (e.g. notes-history NotesSection) — otherwise a read-only form
|
|
956
|
-
* shows a Save button that does nothing (fw#2359). */
|
|
957
|
-
readonly contributesToFormSubmit?: boolean;
|
|
958
|
-
/** Overrides the host-derived entity name passed to the mounted component.
|
|
959
|
-
* Required on `projectionDetail` (which has no real entity — the host
|
|
960
|
-
* value there is an internal placeholder) so a self-persisting extension
|
|
961
|
-
* like NotesSection filters/writes against the right domain entity. Takes
|
|
962
|
-
* precedence over the host value on every screen type. */
|
|
963
|
-
readonly entityName?: string;
|
|
964
|
-
/** Record field rendered as a count badge in the tab label when the
|
|
965
|
-
* enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
966
|
-
* Ignored outside tabs mode or when the field's value is not a finite
|
|
967
|
-
* number. */
|
|
968
|
-
readonly countField?: string;
|
|
969
|
-
/** Rendered top-right in the section's Card title row — same
|
|
970
|
-
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
971
|
-
* evaluated against the same record. Every action must resolve an icon
|
|
972
|
-
* (declared or id-derived); the boot-validator rejects one that doesn't. */
|
|
973
|
-
readonly actions?: readonly RowAction[];
|
|
974
|
-
};
|
|
975
|
-
|
|
976
|
-
// Read-only list of related records, driven by its own query — for a
|
|
977
|
-
// projectionDetail screen showing e.g. a tenant's payments below the
|
|
978
|
-
// tenant's own fields. Only supported on projectionDetail (fw#2166); the
|
|
979
|
-
// boot-validator rejects it on entityEdit/configEdit/actionForm.
|
|
980
|
-
export type EditRelatedListSection = {
|
|
981
|
-
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
982
|
-
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
983
|
-
readonly id?: string;
|
|
984
|
-
readonly kind: "relatedList";
|
|
985
|
-
readonly title: string;
|
|
986
|
-
/** Fully qualified query QN. Same paged envelope as projectionList.query:
|
|
987
|
-
* `{ rows, nextCursor, total? }`. */
|
|
988
|
-
readonly query: string;
|
|
989
|
-
/** Query-payload key the parent record's id is passed under. Default "id". */
|
|
990
|
-
readonly parentParam?: string;
|
|
991
|
-
/** Server-side WHERE clause pinning this section to the parent record —
|
|
992
|
-
* sent as `payload.filter: { field, op: "eq", value: parentId }`, kept
|
|
993
|
-
* out of the user-facet `filters` array so it can't be cleared by facet
|
|
994
|
-
* interaction. Lets a tab reuse the generic `<entity>:list` query instead
|
|
995
|
-
* of a bespoke child-rows handler. Mutually exclusive with `parentParam`
|
|
996
|
-
* (the boot-validator rejects both). `field` must be a real field on the
|
|
997
|
-
* entity behind `query`, and that query's Zod schema must accept
|
|
998
|
-
* `filter` (same requirement `filter`/`facets` already have). */
|
|
999
|
-
readonly parentFilter?: { readonly field: string };
|
|
1000
|
-
readonly columns: readonly ListColumnSpec[];
|
|
1001
|
-
readonly pageSize?: number;
|
|
1002
|
-
/** Initial sort on mount, applied client-side over the already-loaded rows
|
|
1003
|
-
* (this section has no pager, so there is no "loaded subset" to mislead).
|
|
1004
|
-
* `field` must name a column with `sortable: true` — boot-validator pins
|
|
1005
|
-
* that, same as `entityList.defaultSort`. */
|
|
1006
|
-
readonly defaultSort?: ListSortSpec;
|
|
1007
|
-
/** Renders a search box whose debounced term rides along as
|
|
1008
|
-
* `payload.search` on this section's own query. Opt-in, unlike
|
|
1009
|
-
* `projectionList.searchable` (derived from the bound query's schema): a
|
|
1010
|
-
* relatedList query is an ordinary child-rows handler, so a `search`
|
|
1011
|
-
* parameter in its schema is not by itself a statement that this section
|
|
1012
|
-
* should offer search. The boot-validator rejects `true` when the query's
|
|
1013
|
-
* Zod schema has no `search` parameter. */
|
|
1014
|
-
readonly searchable?: boolean;
|
|
1015
|
-
/** User-toggleable facet dropdowns — same explicit-label `ListFacetSpec` as
|
|
1016
|
-
* `projectionList.facets` (a relatedList has no entity to derive filterable
|
|
1017
|
-
* fields from). `field` must be a declared column and the bound query
|
|
1018
|
-
* handler must accept `filters` in its Zod schema — the boot-validator
|
|
1019
|
-
* checks both. Selected values ride along as `payload.filters`. */
|
|
1020
|
-
readonly facets?: readonly ListFacetSpec[];
|
|
1021
|
-
/** Row click opens the target entity's detail screen via ObjectTarget —
|
|
1022
|
-
* the `detailFor` lookup owns the entity→screen mapping, so no screenId
|
|
1023
|
-
* is named here. `idColumn` names the row key holding that id (default
|
|
1024
|
-
* "id"). Omit `rowClick` for a non-interactive list. */
|
|
1025
|
-
readonly rowClick?: { readonly entity: string; readonly idColumn?: string };
|
|
1026
|
-
/** Per-row actions — same type and dispatch semantics as `entityList`/
|
|
1027
|
-
* `projectionList`'s `rowActions` (writeHandler dispatch with a
|
|
1028
|
-
* declarative `payload` extractor, or navigate). A successful
|
|
1029
|
-
* writeHandler action re-runs this section's own query, same as a
|
|
1030
|
-
* projectionList row action re-running its list query. */
|
|
1031
|
-
readonly rowActions?: readonly RowAction[];
|
|
1032
|
-
/** Toolbar actions above the table — same type and dispatch semantics as
|
|
1033
|
-
* `entityList`/`projectionList`'s `toolbarActions` ("+ Anlegen" etc.),
|
|
1034
|
-
* plus `visible`/`params` evaluated against the parent record (see
|
|
1035
|
-
* RelatedListToolbarAction). */
|
|
1036
|
-
readonly toolbarActions?: readonly RelatedListToolbarAction[];
|
|
1037
|
-
/** Record field rendered as a count badge in the tab label when the
|
|
1038
|
-
* enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
|
|
1039
|
-
* Ignored outside tabs mode or when the field's value is not a finite
|
|
1040
|
-
* number. */
|
|
1041
|
-
readonly countField?: string;
|
|
1042
|
-
/** Rendered top-right in the section's Card title row — same
|
|
1043
|
-
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
1044
|
-
* evaluated against the parent record. Every action must resolve an icon
|
|
1045
|
-
* (declared or id-derived); the boot-validator rejects one that doesn't.
|
|
1046
|
-
* Distinct from `toolbarActions` (rendered inside the DataTable toolbar) —
|
|
1047
|
-
* this is the Card-level title-row action. */
|
|
1048
|
-
readonly actions?: readonly RowAction[];
|
|
1049
|
-
/** Rendered inside the section's Card in place of the table when this
|
|
1050
|
-
* section's query returns zero rows. Omitted: the DataTable default
|
|
1051
|
-
* ("kumiko.list.no-entries") applies. `action`'s icon must resolve, same
|
|
1052
|
-
* as `actions` above. */
|
|
1053
|
-
readonly emptyState?: {
|
|
1054
|
-
readonly title: string;
|
|
1055
|
-
readonly description?: string;
|
|
1056
|
-
readonly action?: RowAction;
|
|
1057
|
-
};
|
|
1058
|
-
};
|
|
1059
|
-
|
|
1060
|
-
// A declarative, self-persisting form section for `projectionDetail`
|
|
1061
|
-
// (fw editable-detail-screens). Unlike the plain `fields` section — which
|
|
1062
|
-
// projectionDetail forces read-only, see the type's own doc — this section's
|
|
1063
|
-
// fields ARE editable and submit through their own write-handler, entirely
|
|
1064
|
-
// independent of the (nonexistent) outer form submit. Modeled on
|
|
1065
|
-
// `ActionFormScreenDefinition`: `fieldDefs` carries the field-type map (a
|
|
1066
|
-
// projectionDetail's synthesized display entity has no real field types to
|
|
1067
|
-
// draw on), `fields` the rendered order/overrides, same as
|
|
1068
|
-
// `EditFieldsSection.fields`. Only supported on projectionDetail — the
|
|
1069
|
-
// boot-validator rejects it on entityEdit/configEdit/actionForm, same as
|
|
1070
|
-
// `relatedList`.
|
|
1071
|
-
export type EditWriteFormSection = {
|
|
1072
|
-
/** Kebab-case tab id, used as the `?tab=` value. Required by the
|
|
1073
|
-
* boot-validator when the enclosing `EditLayout.mode` is "tabs". */
|
|
1074
|
-
readonly id?: string;
|
|
1075
|
-
readonly kind: "writeForm";
|
|
1076
|
-
readonly title?: string;
|
|
1077
|
-
readonly description?: string;
|
|
1078
|
-
readonly columns?: number;
|
|
1079
|
-
readonly icon?: IconKey;
|
|
1080
|
-
/** Field-type map for this section's own form — same shape as
|
|
1081
|
-
* `ActionFormScreenDefinition.fields`. */
|
|
1082
|
-
readonly fieldDefs: Readonly<Record<string, FieldDefinition>>;
|
|
1083
|
-
/** Rendered fields, in order — same shape as `EditFieldsSection.fields`.
|
|
1084
|
-
* Every referenced field must have an entry in `fieldDefs`. */
|
|
1085
|
-
readonly fields: readonly EditFieldSpec[];
|
|
1086
|
-
/** Write-handler QN dispatched on submit. The submitted values (not a
|
|
1087
|
-
* diff) land 1:1 as payload, same as `ActionFormScreenDefinition.handler`.
|
|
1088
|
-
* The payload carries only this section's own fields — no parent/record
|
|
1089
|
-
* id is injected automatically. To thread the host record's id through,
|
|
1090
|
-
* declare it in `fieldDefs` with `visible: false` on its `fields` entry:
|
|
1091
|
-
* it gets prefilled from the loaded record like any other field and rides
|
|
1092
|
-
* along in the submit payload without being shown in the form. */
|
|
1093
|
-
readonly handler: string;
|
|
1094
|
-
/** i18n-key for the submit button. Default: "kumiko.actions.save". */
|
|
1095
|
-
readonly submitLabel?: string;
|
|
1096
|
-
/** Rendered top-right in the section's Card title row — same
|
|
1097
|
-
* navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`,
|
|
1098
|
-
* evaluated against the same record. Every action must resolve an icon
|
|
1099
|
-
* (declared or id-derived); the boot-validator rejects one that doesn't. */
|
|
1100
|
-
readonly actions?: readonly RowAction[];
|
|
1101
|
-
};
|
|
1102
|
-
|
|
1103
|
-
// Max width of the form container (see FormScreenShell in renderer-web).
|
|
1104
|
-
export type FormWidth = "3xl" | "4xl" | "full";
|
|
1105
|
-
|
|
1106
|
-
export type EditLayout = {
|
|
1107
|
-
readonly sections: readonly EditSectionSpec[];
|
|
1108
|
-
/** Default "4xl" (a centered column, narrower than list chrome), unless
|
|
1109
|
-
* the app overrides it via `createKumikoApp({ screenWidth })`. Override
|
|
1110
|
-
* per-screen to "3xl" for a narrower column or "full" to match list
|
|
1111
|
-
* chrome. */
|
|
1112
|
-
readonly width?: FormWidth;
|
|
1113
|
-
/** Default "single". "wizard" renders one section per step (with
|
|
1114
|
-
* progress + per-step validation) instead of all sections at once —
|
|
1115
|
-
* boot-validator requires >= 2 sections, each with a title, when set.
|
|
1116
|
-
* "tabs" renders one section per tab (projectionDetail only — the
|
|
1117
|
-
* boot-validator rejects it on entityEdit, see the check for the reason);
|
|
1118
|
-
* requires >= 2 sections, each with a title and an `id`. */
|
|
1119
|
-
readonly mode?: "single" | "wizard" | "tabs";
|
|
1120
|
-
/** Persists in-progress wizard state as a resumable draft instead of
|
|
1121
|
-
* discarding it on navigation away. */
|
|
1122
|
-
readonly draft?: boolean;
|
|
1123
|
-
};
|
|
1124
|
-
|
|
1125
|
-
export type EntityEditScreenDefinition = {
|
|
1126
|
-
readonly id: string;
|
|
1127
|
-
readonly type: "entityEdit";
|
|
1128
|
-
readonly nav?: ScreenNavSugar;
|
|
1129
|
-
readonly detailFor?: string;
|
|
1130
|
-
readonly description?: string;
|
|
1131
|
-
readonly agent?: AgentHandlerHints;
|
|
1132
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1133
|
-
* app); exempts it from the nav-area boot check. */
|
|
1134
|
-
readonly dormant?: boolean;
|
|
1135
|
-
/** Derived by buildAppSchema from the navigate `params` targeting this
|
|
1136
|
-
* screen — the only URL query keys the create form prefills. An authored
|
|
1137
|
-
* value is overwritten. */
|
|
1138
|
-
readonly urlPrefillFields?: readonly string[];
|
|
1139
|
-
readonly entity: string;
|
|
1140
|
-
readonly layout: EditLayout;
|
|
1141
|
-
/** Optionaler i18n-Key (oder Roh-String) für den Submit-Button. Default
|
|
1142
|
-
* `kumiko.actions.save`. Lässt den Auto-Edit-Screen domain-spezifische
|
|
1143
|
-
* CTAs zeigen ("Save Address", "Create item") statt generisch "Speichern". */
|
|
1144
|
-
readonly submitLabel?: string;
|
|
1145
|
-
/** Default true. `false` für Entities deren Create über einen eigenen
|
|
1146
|
-
* Lifecycle-Write läuft (z.B. incident:open mit Event-Stream + Joins)
|
|
1147
|
-
* statt über `<entity>:create`: unterdrückt den automatischen
|
|
1148
|
-
* „+ Neu"-Button auf entityList-Screens dieser Entity und rendert den
|
|
1149
|
-
* Create-Branch (Aufruf ohne entityId) als Fehler statt eines Forms,
|
|
1150
|
-
* dessen Submit gegen einen nicht registrierten Handler liefe. */
|
|
1151
|
-
readonly allowCreate?: boolean;
|
|
1152
|
-
/** Default true. `false` wenn kein `<entity>:delete`-Handler existiert
|
|
1153
|
-
* (History-/Audit-Erhalt): unterdrückt den Löschen-Button im
|
|
1154
|
-
* Update-Form. */
|
|
1155
|
-
readonly allowDelete?: boolean;
|
|
1156
|
-
/** Default false. `true` for singleton entities (exactly one record per
|
|
1157
|
-
* tenant: organization, settings, tenant profile). A call without an
|
|
1158
|
-
* `entityId` first resolves the existing record via `<entity>:list`
|
|
1159
|
-
* (limit 1) and renders the update branch with prefill on a hit,
|
|
1160
|
-
* instead of blindly creating another one. The create branch only
|
|
1161
|
-
* fires when the table is empty (and `allowCreate: false` still
|
|
1162
|
-
* applies there). */
|
|
1163
|
-
readonly singleton?: boolean;
|
|
1164
|
-
/** Navigate to this screen ID after a successful save (create or update).
|
|
1165
|
-
* Either a same-feature short screen-ID, or a fully-qualified
|
|
1166
|
-
* cross-feature screen QN (`<feature>:screen:<id>`) — the nav-router
|
|
1167
|
-
* resolves both against the schema; boot-validator checks the target
|
|
1168
|
-
* resolves to a registered screen. Overrides the default post-save
|
|
1169
|
-
* target (the entity's list screen); delete is unaffected and still
|
|
1170
|
-
* always navigates to the list. A cross-feature QN hard-couples this
|
|
1171
|
-
* feature to the target at boot time — an app that doesn't mount the
|
|
1172
|
-
* target feature fails the boot-validator; only use this form in a
|
|
1173
|
-
* reusable feature when the target feature is guaranteed to be
|
|
1174
|
-
* mounted alongside it.
|
|
1175
|
-
*
|
|
1176
|
-
* The object form additionally names the success-payload field the
|
|
1177
|
-
* navigation id comes from (`ActionFormRedirect.idFrom`) — needed when
|
|
1178
|
-
* the edited record is a child and the target screen is the parent's
|
|
1179
|
-
* detail screen. Update falls back to the loaded record when the
|
|
1180
|
-
* success payload doesn't carry that field flatly; create falls back
|
|
1181
|
-
* to the submitted form values. */
|
|
1182
|
-
readonly redirect?: string | ActionFormRedirect;
|
|
1183
|
-
/** Parent list screen (short id) for breadcrumb + nav highlighting when
|
|
1184
|
-
* this screen has no nav entry of its own. Same field/semantics as on
|
|
1185
|
-
* `custom`/`projectionDetail`; an explicit value here wins over the
|
|
1186
|
-
* entity-list heuristic (rowAction target / same-entity entityList). */
|
|
1187
|
-
readonly listScreenId?: string;
|
|
1188
|
-
/** Optionaler per-Field-Label-i18n-Key (Field-Name → Key), überschreibt
|
|
1189
|
-
* die Default-Konvention `<feature>:entity:<entity>:field:<name>`.
|
|
1190
|
-
* Primär für configEdit: dessen Pseudo-Entity `__config-edit__` hat
|
|
1191
|
-
* keinen natürlichen Field-Namespace — der Settings-Hub injiziert hier
|
|
1192
|
-
* das `mask.title`-Label des Config-Keys. Fehlt ein Eintrag, gilt die
|
|
1193
|
-
* Konvention. */
|
|
1194
|
-
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
1195
|
-
/** Header action buttons. Reuses `RowAction` — analogous to
|
|
1196
|
-
* `ProjectionDetailScreenDefinition.actions` (the loaded record stands
|
|
1197
|
-
* in for the "row"). `rowClick` has no target here and is rejected by
|
|
1198
|
-
* the boot-validator, same as on projectionDetail. Not rendered in
|
|
1199
|
-
* create mode — every action targets an existing record, which the
|
|
1200
|
-
* create branch doesn't have yet. */
|
|
1201
|
-
readonly actions?: readonly RowAction[];
|
|
1202
|
-
readonly slots?: ScreenSlots;
|
|
1203
|
-
readonly access?: AccessRule;
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
// --- actionForm ---
|
|
1207
|
-
|
|
1208
|
-
// Form-Screen für non-CRUD Write-Handler. Wird gerendert wie ein
|
|
1209
|
-
// EntityEditScreen (sections + fields), aber:
|
|
1210
|
-
// - kein detail-fetch beim Mount (initial-state = field-defaults)
|
|
1211
|
-
// - kein CRUD-verb-mapping ("create"/"update") — Author gibt
|
|
1212
|
-
// explizit die Write-Handler-QN an
|
|
1213
|
-
// - Form-Object landet 1:1 als payload beim Handler; sein Zod-Schema
|
|
1214
|
-
// validiert weiter
|
|
1215
|
-
// - optional `redirect` zu einem anderen Screen nach Submit-Success
|
|
1216
|
-
//
|
|
1217
|
-
// Beispiele: "Send invitation" (mit recipient-email + role), "Approve
|
|
1218
|
-
// invoice" (mit notes), "Bulk-import" (mit CSV-string + mode).
|
|
1219
|
-
//
|
|
1220
|
-
// Field-Shape: inline am Screen statt entity-Reference. Author hat
|
|
1221
|
-
// explizite Kontrolle was die Form rendert ohne eine ganze Entity
|
|
1222
|
-
// dafür anzulegen. Die FieldDefinitions sind dieselben wie auf
|
|
1223
|
-
// Entities (text/select/number/...) — alle DefaultInput-Renderer
|
|
1224
|
-
// greifen unverändert.
|
|
1225
|
-
export type ActionFormScreenDefinition = {
|
|
1226
|
-
readonly id: string;
|
|
1227
|
-
readonly type: "actionForm";
|
|
1228
|
-
readonly nav?: ScreenNavSugar;
|
|
1229
|
-
readonly detailFor?: string;
|
|
1230
|
-
readonly description?: string;
|
|
1231
|
-
readonly agent?: AgentHandlerHints;
|
|
1232
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1233
|
-
* app); exempts it from the nav-area boot check. */
|
|
1234
|
-
readonly dormant?: boolean;
|
|
1235
|
-
/** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
|
|
1236
|
-
readonly urlPrefillFields?: readonly string[];
|
|
1237
|
-
/** Write-Handler-QN der bei Submit gerufen wird. Form-Object landet
|
|
1238
|
-
* 1:1 als payload — Handler-Schema (Zod) validiert weiter. */
|
|
1239
|
-
readonly handler: string;
|
|
1240
|
-
/** Form-Shape: Field-Map pro Name. Nutzt dieselben FieldDefinitions
|
|
1241
|
-
* wie Entity-Felder. Mindestens ein Feld erforderlich (Boot-
|
|
1242
|
-
* Validator).
|
|
1243
|
-
*
|
|
1244
|
-
* A `money` field here must name its own currency source: this screen
|
|
1245
|
-
* has no entity whose `defaultCurrency` it could inherit (fw#2839). */
|
|
1246
|
-
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1247
|
-
/** Layout analog zu EntityEditScreen: sections mit fields aus dem
|
|
1248
|
-
* fields-Map oben. */
|
|
1249
|
-
readonly layout: EditLayout;
|
|
1250
|
-
/** Per-field label i18n key override, same type/semantics as
|
|
1251
|
-
* `EntityEditScreenDefinition.fieldLabels`. Falls back to the convention when absent. */
|
|
1252
|
-
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
1253
|
-
/** i18n-key für den Submit-Button. Default: i18n-Default des
|
|
1254
|
-
* Renderers (typischerweise "actions.submit"). */
|
|
1255
|
-
readonly submitLabel?: string;
|
|
1256
|
-
/** Visual style of the submit button. "danger" renders it red — for a form
|
|
1257
|
-
* whose handler is destructive (terminate, revoke, delete), so the warning
|
|
1258
|
-
* isn't lost on the way from the calling screen to this one. Default "primary". */
|
|
1259
|
-
readonly submitStyle?: "primary" | "danger";
|
|
1260
|
-
/** Navigate to this screen ID after a successful submit: either a short
|
|
1261
|
-
* ID (e.g. "item-list" — same feature, the nav-router resolves to the
|
|
1262
|
-
* full path) or a fully-qualified cross-feature QN
|
|
1263
|
-
* (`<feature>:screen:<id>`). If not set, the user stays on the form
|
|
1264
|
-
* screen. Boot-validator checks that the target resolves to a
|
|
1265
|
-
* registered screen. A cross-feature QN hard-couples this feature to
|
|
1266
|
-
* the target at boot time — an app that doesn't mount the target
|
|
1267
|
-
* feature fails the boot-validator; only use this form in a reusable
|
|
1268
|
-
* feature when the target feature is guaranteed to be mounted
|
|
1269
|
-
* alongside it.
|
|
1270
|
-
*
|
|
1271
|
-
* The object form additionally names the success-payload field the
|
|
1272
|
-
* navigation id comes from (`ActionFormRedirect.idFrom`) — needed when
|
|
1273
|
-
* the handler creates a child record but the target screen is the
|
|
1274
|
-
* parent's detail screen (fw#2670). */
|
|
1275
|
-
readonly redirect?: string | ActionFormRedirect;
|
|
1276
|
-
/** Target of the Cancel button. Default: `redirect` (historical
|
|
1277
|
-
* behavior — Cancel and the submit-redirect then land in the same
|
|
1278
|
-
* place). `false` = no Cancel button; correct for single-action
|
|
1279
|
-
* screens with no discardable state (e.g. "Send test mail"), where
|
|
1280
|
-
* Cancel would just be a second path to the same target. Accepts the
|
|
1281
|
-
* same string targets as `redirect` (short ID or cross-feature QN);
|
|
1282
|
-
* boot-validator checks it. A cross-feature QN hard-couples this
|
|
1283
|
-
* feature to the target at boot time — an app that doesn't mount the
|
|
1284
|
-
* target feature fails the boot-validator; only use this form in a
|
|
1285
|
-
* reusable feature when the target feature is guaranteed to be
|
|
1286
|
-
* mounted alongside it. */
|
|
1287
|
-
readonly cancelTarget?: string | false;
|
|
1288
|
-
/** Parent list screen (short id) for breadcrumb + nav highlighting when
|
|
1289
|
-
* this screen has no nav entry of its own. Same field/semantics as on
|
|
1290
|
-
* `custom`/`projectionDetail`/`entityEdit`; an explicit value here wins
|
|
1291
|
-
* over the rowAction heuristic. */
|
|
1292
|
-
readonly listScreenId?: string;
|
|
1293
|
-
readonly slots?: ScreenSlots;
|
|
1294
|
-
readonly access?: AccessRule;
|
|
1295
|
-
};
|
|
1296
|
-
|
|
1297
|
-
/** Redirect target plus the success-payload field carrying the navigation
|
|
1298
|
-
* id. The write-handler reports the id of what it wrote (`data.id`); when
|
|
1299
|
-
* that record is a child and the target screen shows its parent, the
|
|
1300
|
-
* parent id has to be read from a different field instead of forcing the
|
|
1301
|
-
* handler to misreport its own result (fw#2670). */
|
|
1302
|
-
export type ActionFormRedirect = {
|
|
1303
|
-
/** Same target forms as the string `redirect`: short screen ID
|
|
1304
|
-
* (same-feature) or a fully-qualified cross-feature screen QN. */
|
|
1305
|
-
readonly screen: string;
|
|
1306
|
-
/** Flat field name in the handler's success payload, e.g. "leaseId".
|
|
1307
|
-
* Only used when the target screen type carries an id (`entityEdit`,
|
|
1308
|
-
* `projectionDetail`); a non-string or missing value navigates without
|
|
1309
|
-
* an id, same as a payload without `id` does today. */
|
|
1310
|
-
readonly idFrom: string;
|
|
1311
|
-
};
|
|
1312
|
-
|
|
1313
|
-
/** Author-declared field shown once after a mint-form submit — API token,
|
|
1314
|
-
* recovery codes, similar one-time secrets. */
|
|
1315
|
-
export type SecretRevealField = {
|
|
1316
|
-
/** Flat field name in the handler's success payload. */
|
|
1317
|
-
readonly field: string;
|
|
1318
|
-
/** i18n key for this value's label. */
|
|
1319
|
-
readonly label: string;
|
|
1320
|
-
/** "code" (default) renders one monospaced value; "list" renders a
|
|
1321
|
-
* string-array payload one value per line (recovery codes); "qr" renders
|
|
1322
|
-
* the value as a scannable QR code (otpauth://-style enrollment URI) —
|
|
1323
|
-
* platforms without a QR-capable SecretReveal primitive fall back to the
|
|
1324
|
-
* monospaced text display. */
|
|
1325
|
-
readonly display?: "code" | "list" | "qr";
|
|
1326
|
-
/** Default true — offer a copy-to-clipboard control next to the value. */
|
|
1327
|
-
readonly copyable?: boolean;
|
|
1328
|
-
};
|
|
1329
|
-
|
|
1330
|
-
/** Post-reveal proof-of-receipt step. A mint whose effect is only armed once
|
|
1331
|
-
* the user proves they received the secret (TOTP enrollment: scan the code,
|
|
1332
|
-
* then enter one) declares it here — the reveal card renders this form in
|
|
1333
|
-
* place of the bare acknowledge button. */
|
|
1334
|
-
export type SecretMintConfirmStep = {
|
|
1335
|
-
/** Write-handler QN dispatched when the confirm form is submitted. */
|
|
1336
|
-
readonly handler: string;
|
|
1337
|
-
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1338
|
-
readonly layout: EditLayout;
|
|
1339
|
-
/** Mint success-payload fields merged into the confirm payload (e.g. a
|
|
1340
|
-
* short-lived setup token). Held in component state only — never rendered,
|
|
1341
|
-
* never in the URL, a query cache, nav or a persisted draft. */
|
|
1342
|
-
readonly carry?: readonly string[];
|
|
1343
|
-
readonly submitLabel?: string;
|
|
1344
|
-
/** i18n key for the banner shown after a successful confirm when the screen
|
|
1345
|
-
* declares no `redirect`. Default "kumiko.secretMint.done". */
|
|
1346
|
-
readonly doneMessage?: string;
|
|
1347
|
-
};
|
|
1348
|
-
|
|
1349
|
-
export type SecretReveal = {
|
|
1350
|
-
readonly fields: readonly SecretRevealField[];
|
|
1351
|
-
/** i18n key. Default "kumiko.secretMint.title". */
|
|
1352
|
-
readonly title?: string;
|
|
1353
|
-
/** i18n key for the "this is the only time you see it" warning.
|
|
1354
|
-
* Default "kumiko.secretMint.warning". */
|
|
1355
|
-
readonly warning?: string;
|
|
1356
|
-
/** i18n key for the acknowledge button. Default "kumiko.secretMint.confirm". */
|
|
1357
|
-
readonly confirmLabel?: string;
|
|
1358
|
-
};
|
|
1359
|
-
|
|
1360
|
-
/** Mint form → one-time reveal → explicit confirm. The secret (API token,
|
|
1361
|
-
* recovery codes) lives only in the write-handler's own success payload —
|
|
1362
|
-
* no stored value backs it, so no query can redisplay it later. That gap
|
|
1363
|
-
* is why this needs its own screen type instead of a flag on `actionForm`,
|
|
1364
|
-
* which discards the success payload after extracting the navigation id. */
|
|
1365
|
-
export type SecretMintScreenDefinition = {
|
|
1366
|
-
readonly id: string;
|
|
1367
|
-
readonly type: "secretMint";
|
|
1368
|
-
readonly nav?: ScreenNavSugar;
|
|
1369
|
-
readonly detailFor?: string;
|
|
1370
|
-
readonly description?: string;
|
|
1371
|
-
readonly agent?: AgentHandlerHints;
|
|
1372
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1373
|
-
* app); exempts it from the nav-area boot check. */
|
|
1374
|
-
readonly dormant?: boolean;
|
|
1375
|
-
/** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
|
|
1376
|
-
readonly urlPrefillFields?: readonly string[];
|
|
1377
|
-
/** Write-handler QN dispatched on submit. */
|
|
1378
|
-
readonly handler: string;
|
|
1379
|
-
readonly fields: Readonly<Record<string, FormFieldDefinition>>;
|
|
1380
|
-
readonly layout: EditLayout;
|
|
1381
|
-
/** Navigate here after the reveal is confirmed. Short id (same-feature) or
|
|
1382
|
-
* a fully-qualified cross-feature screen QN — no result-id navigation
|
|
1383
|
-
* form exists here (unlike `ActionFormScreenDefinition.redirect`), since
|
|
1384
|
-
* the confirmed screen carries no entity id to thread through. */
|
|
1385
|
-
readonly redirect?: string;
|
|
1386
|
-
readonly cancelTarget?: string | false;
|
|
1387
|
-
readonly listScreenId?: string;
|
|
1388
|
-
readonly access?: AccessRule;
|
|
1389
|
-
readonly reveal: SecretReveal;
|
|
1390
|
-
/** i18n-key for the mint button. Default: renderer's own default. */
|
|
1391
|
-
readonly submitLabel?: string;
|
|
1392
|
-
/** Proof-of-receipt step rendered on the reveal card in place of the bare
|
|
1393
|
-
* acknowledge button — see `SecretMintConfirmStep` doc. */
|
|
1394
|
-
readonly confirm?: SecretMintConfirmStep;
|
|
1395
|
-
};
|
|
1396
|
-
|
|
1397
|
-
// --- custom ---
|
|
1398
|
-
|
|
1399
|
-
// Sub-route declared by a custom screen (Expo Router / URL-routing use).
|
|
1400
|
-
// `path` is the route-segment appended to the screen's own path; the
|
|
1401
|
-
// framework owns the outer routing. Components stay opaque.
|
|
1402
|
-
export type CustomScreenRoute = {
|
|
1403
|
-
readonly path: string;
|
|
1404
|
-
readonly component: PlatformComponent;
|
|
1405
|
-
};
|
|
1406
|
-
|
|
1407
|
-
export type CustomScreenDefinition = {
|
|
1408
|
-
readonly id: string;
|
|
1409
|
-
readonly type: "custom";
|
|
1410
|
-
readonly nav?: ScreenNavSugar;
|
|
1411
|
-
readonly detailFor?: string;
|
|
1412
|
-
readonly description?: string;
|
|
1413
|
-
/** Only `expose` is read for screens; `risk` ranks handler tool calls. */
|
|
1414
|
-
readonly agent?: AgentHandlerHints;
|
|
1415
|
-
readonly renderer: PlatformComponent;
|
|
1416
|
-
readonly routes?: readonly CustomScreenRoute[];
|
|
1417
|
-
/** Parent list screen for breadcrumb when this detail is not in nav. */
|
|
1418
|
-
readonly listScreenId?: string;
|
|
1419
|
-
readonly access?: AccessRule;
|
|
1420
|
-
/** This screen is registered without a self-owned `r.nav()` entry by
|
|
1421
|
-
* design — an app opts in by navving it explicitly (e.g. a settings-area
|
|
1422
|
-
* self-service screen). `createKumikoApp`'s boot diagnostic
|
|
1423
|
-
* (kumiko-framework#2025) skips dormant screens instead of flagging
|
|
1424
|
-
* every consumer that doesn't mount the client plugin as a false
|
|
1425
|
-
* positive (kumiko-framework#2034). Only set this on screens the
|
|
1426
|
-
* feature itself never navs — a screen the feature DOES nav still needs
|
|
1427
|
-
* its client plugin mounted by every consumer, and should keep
|
|
1428
|
-
* triggering the diagnostic if it's missing. Also gates the
|
|
1429
|
-
* boot-validator's nav-area check, same as `dormant` on other screens. */
|
|
1430
|
-
readonly dormant?: boolean;
|
|
1431
|
-
};
|
|
1432
|
-
|
|
1433
|
-
// --- configEdit ---
|
|
1434
|
-
|
|
1435
|
-
// Form-Screen der Tenant-/User-/System-Settings aus dem bundled
|
|
1436
|
-
// config-Feature liest und schreibt. Wird gerendert wie ein
|
|
1437
|
-
// EntityEditScreen (sections + fields), aber:
|
|
1438
|
-
// - Detail-Load via `config:query:values` (statt `<entity>:detail`)
|
|
1439
|
-
// - Pre-Fill nutzt `configKeys[shortName]` → qualifizierter Key, dann
|
|
1440
|
-
// `values[qualifiedKey].value`
|
|
1441
|
-
// - Submit feuert pro geändertem Feld einen `config:write:set` mit
|
|
1442
|
-
// {key, value, scope}; das config-feature behandelt jeden Key als
|
|
1443
|
-
// eigenes Aggregate (configValue.<keyHash>) und alle N Writes laufen
|
|
1444
|
-
// parallel (Promise.all). Per-Key idempotent → Retry safe.
|
|
1445
|
-
// - kein Singleton-Hack nötig: pro (key+tenantId) gibt's by-design
|
|
1446
|
-
// genau eine Row, der Bridge-Pattern aus dem Branding-MVP fällt weg
|
|
1447
|
-
//
|
|
1448
|
-
// Partial-Failure-Semantik: wenn von N parallelen Writes einer scheitert,
|
|
1449
|
-
// bleiben die anderen committed (pro-Aggregate, kein Multi-Stream-Rollback).
|
|
1450
|
-
// Das Form bleibt dirty bis der User retried — die schon erfolgreichen
|
|
1451
|
-
// Writes feuern dann nochmal mit demselben Wert. Für `text` / `number` /
|
|
1452
|
-
// `boolean` Keys ist das idempotent. Wer einen ConfigKey mit nicht-
|
|
1453
|
-
// idempotentem Setter baut (Counter, append-only-list o.ä.) muss die
|
|
1454
|
-
// Idempotenz im Setter sicherstellen.
|
|
1455
|
-
//
|
|
1456
|
-
// Field-Shape: inline am Screen wie bei `actionForm`. Author hat damit
|
|
1457
|
-
// explizite Kontrolle über Input-Type (text/number/select/...) ohne
|
|
1458
|
-
// Resolve-Ceremony — die FieldDefinitions sind dieselben wie auf
|
|
1459
|
-
// Entities, alle DefaultInput-Renderer greifen unverändert. Field-
|
|
1460
|
-
// Labels gehen über bestehende i18n-Konventionen (`<feature>:entity:
|
|
1461
|
-
// <namespace>:field:<name>` o.ä. — der Author wählt den Namespace).
|
|
1462
|
-
//
|
|
1463
|
-
// scope MUSS mit der `createTenantConfig`/`createSystemConfig`/
|
|
1464
|
-
// `createUserConfig`-Deklaration der referenzierten Keys
|
|
1465
|
-
// übereinstimmen — der Boot-Validator pinnt das.
|
|
1466
|
-
export type ConfigEditScreenDefinition = {
|
|
1467
|
-
readonly id: string;
|
|
1468
|
-
readonly type: "configEdit";
|
|
1469
|
-
readonly nav?: ScreenNavSugar;
|
|
1470
|
-
readonly detailFor?: string;
|
|
1471
|
-
readonly description?: string;
|
|
1472
|
-
readonly agent?: AgentHandlerHints;
|
|
1473
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1474
|
-
* app); exempts it from the nav-area boot check. */
|
|
1475
|
-
readonly dormant?: boolean;
|
|
1476
|
-
/** scope für config:write:set Calls. Muss zur Scope-Deklaration der
|
|
1477
|
-
* in `configKeys` referenzierten Keys passen — Boot-Validator
|
|
1478
|
-
* prüft das gegen die Registry. */
|
|
1479
|
-
readonly scope: "tenant" | "system" | "user";
|
|
1480
|
-
/** Map: form-field-name (kurz, wie im Layout referenziert) → voll-
|
|
1481
|
-
* qualifizierter Config-Key (`<feature>:config:<short>`). Boot-
|
|
1482
|
-
* Validator prüft dass jeder qualifizierte Key in der Registry
|
|
1483
|
-
* bekannt ist. */
|
|
1484
|
-
readonly configKeys: Readonly<Record<string, string>>;
|
|
1485
|
-
/** Form-Shape pro Field-Name. Selbe FieldDefinitions wie auf
|
|
1486
|
-
* Entities/ActionForm. Field-Names matchen die Keys in `configKeys`
|
|
1487
|
-
* — Boot-Validator pinnt das. */
|
|
1488
|
-
readonly fields: Readonly<Record<string, FieldDefinition>>;
|
|
1489
|
-
/** Layout: Sections mit Field-Refs. Identisch zu entityEdit/
|
|
1490
|
-
* actionForm. */
|
|
1491
|
-
readonly layout: EditLayout;
|
|
1492
|
-
/** Optionaler per-Field-Label-i18n-Key (Field-Name → Key). Der
|
|
1493
|
-
* Settings-Hub setzt hier `mask.title` des jeweiligen Config-Keys,
|
|
1494
|
-
* damit das am Key deklarierte Label am generierten Feld erscheint —
|
|
1495
|
-
* ohne es unter der `__config-edit__`-Konvention zu duplizieren.
|
|
1496
|
-
* Fehlt ein Eintrag, gilt die Konvention. */
|
|
1497
|
-
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
1498
|
-
/** i18n-key für den Submit-Button. Default: "kumiko.actions.save". */
|
|
1499
|
-
readonly submitLabel?: string;
|
|
1500
|
-
readonly slots?: ScreenSlots;
|
|
1501
|
-
readonly access?: AccessRule;
|
|
1502
|
-
};
|
|
1503
|
-
|
|
1504
|
-
// Self-populating counterpart of ConfigEditScreenDefinition for `r.secret(...)`
|
|
1505
|
-
// declarations. No EditLayout/FieldDefinition — secrets never round-trip a
|
|
1506
|
-
// stored value to the client, so the screen renders its own body (password
|
|
1507
|
-
// inputs + redaction display + delete) instead of going through RenderEdit.
|
|
1508
|
-
export type SecretsEditSection = {
|
|
1509
|
-
readonly title?: string;
|
|
1510
|
-
readonly fields: readonly string[];
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
export type SecretsEditScreenDefinition = {
|
|
1514
|
-
readonly id: string;
|
|
1515
|
-
readonly type: "secretsEdit";
|
|
1516
|
-
readonly nav?: ScreenNavSugar;
|
|
1517
|
-
readonly detailFor?: string;
|
|
1518
|
-
readonly description?: string;
|
|
1519
|
-
readonly agent?: AgentHandlerHints;
|
|
1520
|
-
/** Screen has no nav entry by design (opened via link or navved by the
|
|
1521
|
-
* app); exempts it from the nav-area boot check. */
|
|
1522
|
-
readonly dormant?: boolean;
|
|
1523
|
-
/** field id -> qualified secret name (`<feature>:secret:<kebab>`). */
|
|
1524
|
-
readonly secretKeys: Readonly<Record<string, string>>;
|
|
1525
|
-
/** field id -> i18n key for the label. */
|
|
1526
|
-
readonly fieldLabels: Readonly<Record<string, string>>;
|
|
1527
|
-
/** field id -> i18n key for the hint, only where the declaration carries one. */
|
|
1528
|
-
readonly fieldHints?: Readonly<Record<string, string>>;
|
|
1529
|
-
/** field ids whose declaration set `required: true`. */
|
|
1530
|
-
readonly requiredFields?: readonly string[];
|
|
1531
|
-
readonly sections: readonly SecretsEditSection[];
|
|
1532
|
-
readonly access?: AccessRule;
|
|
1533
|
-
};
|
|
1534
|
-
|
|
1535
|
-
// --- shared slots (Level 4 from ui-architecture.md) ---
|
|
1536
|
-
|
|
1537
|
-
export type ScreenSlots = {
|
|
1538
|
-
readonly header?: PlatformComponent;
|
|
1539
|
-
/** entityEdit / actionForm: rendered on the right of the form title, same row. */
|
|
1540
|
-
readonly titleAction?: PlatformComponent;
|
|
1541
|
-
readonly beforeForm?: PlatformComponent;
|
|
1542
|
-
readonly afterForm?: PlatformComponent;
|
|
1543
|
-
readonly sidebar?: PlatformComponent;
|
|
1544
|
-
readonly footer?: PlatformComponent;
|
|
1545
|
-
/** entityEdit wizard: marks `footer` as the sticky-footer primary action
|
|
1546
|
-
* (fw#1918's pinned `max-sm:fixed` group on mobile) instead of the
|
|
1547
|
-
* non-sticky secondary bucket it falls into by default alongside
|
|
1548
|
-
* Back/Cancel. Set this when `footer` renders the screen's actual
|
|
1549
|
-
* submit-type action (e.g. a custom "Publish" button standing in for, or
|
|
1550
|
-
* beside, the built-in Finish/Save) — the renderer can't infer that from
|
|
1551
|
-
* the slot's arbitrary rendered output, only from this declaration. */
|
|
1552
|
-
readonly footerPrimary?: boolean;
|
|
1553
|
-
readonly toolbar?: PlatformComponent;
|
|
1554
|
-
};
|
|
1555
|
-
|
|
1556
|
-
// --- discriminated union ---
|
|
1557
|
-
|
|
1558
|
-
// Inline nav-entry sugar for `r.screen({ ..., nav: {...} })` — covers the
|
|
1559
|
-
// common case of "one nav entry pointing at this screen". The nav entry's
|
|
1560
|
-
// `id`/`screen` are synthesized from the screen's own id; for anything
|
|
1561
|
-
// beyond label/icon/parent/order (access-gating, workspaces, actions),
|
|
1562
|
-
// declare a standalone `r.nav()` entry instead.
|
|
1563
|
-
export type ScreenNavSugar = {
|
|
1564
|
-
readonly label: string;
|
|
1565
|
-
readonly icon?: NavIconKey;
|
|
1566
|
-
readonly parent?: string;
|
|
1567
|
-
readonly order?: number;
|
|
1568
|
-
};
|
|
1569
|
-
|
|
1570
|
-
// `nav`/`detailFor`/`description`/`agent` live directly on every variant (not only via this
|
|
1571
|
-
// union) so a screen typed as its own concrete kind — e.g. `const screen:
|
|
1572
|
-
// CustomScreenDefinition = {...}` in a module split out of `feature.ts` —
|
|
1573
|
-
// still accepts all four; a union-only intersection drops them the
|
|
1574
|
-
// moment a caller narrows to one member.
|
|
1575
|
-
//
|
|
1576
|
-
// `detailFor` applies to any screen kind because any kind can be the
|
|
1577
|
-
// detail view for an entity — including `custom`: in app repos, detail
|
|
1578
|
-
// screens are overwhelmingly custom screens today (projectionDetail
|
|
1579
|
-
// doesn't carry entity semantics). Value is an entity name in the same
|
|
1580
|
-
// (unqualified, globally-unique) form as `entity` on
|
|
1581
|
-
// `EntityListScreenDefinition` — entities are never feature-prefixed, so
|
|
1582
|
-
// there's no separate qualification step.
|
|
1583
|
-
export type ScreenDefinition =
|
|
1584
|
-
| EntityListScreenDefinition
|
|
1585
|
-
| ProjectionListScreenDefinition
|
|
1586
|
-
| ProjectionDetailScreenDefinition
|
|
1587
|
-
| DashboardScreenDefinition
|
|
1588
|
-
| EntityEditScreenDefinition
|
|
1589
|
-
| ActionFormScreenDefinition
|
|
1590
|
-
| ConfigEditScreenDefinition
|
|
1591
|
-
| SecretsEditScreenDefinition
|
|
1592
|
-
| SecretMintScreenDefinition
|
|
1593
|
-
| CustomScreenDefinition;
|