@cosmicdrift/kumiko-types 0.383.1 → 0.385.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,348 @@
1
+ import type { AccessRule, AgentScreenHints } from "./handlers.ts";
2
+ import type { PlatformComponent, ScreenNavSugar, ScreenSlots } from "./screen.ts";
3
+ import type { ListColumnSpec, RowAction } from "./screen-entity-list.ts";
4
+ /** Text a query handler can send untranslated: the renderer translates it. */
5
+ export type DashboardI18nText = {
6
+ readonly i18nKey: string;
7
+ readonly i18nParams?: Readonly<Record<string, DashboardTextParam>>;
8
+ };
9
+ /** Minor units (like Money fields), formatted with the user locale. */
10
+ export type DashboardMoneyParam = {
11
+ readonly kind: "money";
12
+ readonly amountMinor: number;
13
+ readonly currency: string;
14
+ };
15
+ /** Epoch ms, rendered as a medium date in the user time zone. */
16
+ export type DashboardDateParam = {
17
+ readonly kind: "date";
18
+ readonly atMs: number;
19
+ };
20
+ /** Epoch ms, rendered relative to now ("5 minutes ago", "in 30 seconds") in the
21
+ * user locale; the text keeps ticking while it is on screen. */
22
+ export type DashboardRelativeTimeParam = {
23
+ readonly kind: "relativeTime";
24
+ readonly atMs: number;
25
+ };
26
+ /** Nested DashboardI18nText params resolve first (e.g. a duration "{years} {months}"
27
+ * with one plural key each); numbers stay raw so plural rules can read `count`. */
28
+ export type DashboardTextParam = string | number | DashboardI18nText | DashboardMoneyParam | DashboardDateParam | DashboardRelativeTimeParam;
29
+ /** Plain string is displayed as-is; DashboardI18nText is translated by the renderer. */
30
+ export type DashboardText = string | DashboardI18nText;
31
+ /** Chart/stat values are minor units, formatted with the user locale. */
32
+ export type DashboardCurrencyFormat = {
33
+ readonly kind: "currency";
34
+ readonly currency: string;
35
+ /** Defaults to the currency's own decimals. */
36
+ readonly fractionDigits?: number;
37
+ };
38
+ /** The stat value is an epoch ms timestamp, shown relative to now and kept current. */
39
+ export type DashboardRelativeTimeFormat = {
40
+ readonly kind: "relativeTime";
41
+ };
42
+ /** Stat panels take both formats; chart panels only the currency one. */
43
+ export type DashboardValueFormat = DashboardCurrencyFormat | DashboardRelativeTimeFormat;
44
+ export type DashboardPanelQueryOptions = {
45
+ /** Static, author-set query parameters merged on top of the screen's
46
+ * dynamic filterParams — lets a panel pin a value (e.g. a status facet)
47
+ * the screen-wide filter doesn't cover, without needing its own query.
48
+ * The panel wins over the screen filter and the time range. */
49
+ readonly params?: Readonly<Record<string, unknown>>;
50
+ /** The screen `filter` value is NOT merged into this panel's query (a tenant
51
+ * picker must not hit sources without a tenant column). The screen
52
+ * `timeRange` still is. */
53
+ readonly ignoreScreenFilter?: boolean;
54
+ };
55
+ export type DashboardPanelGate = {
56
+ readonly visibleWhen?: DashboardPanelVisibility;
57
+ };
58
+ export type DashboardPanelSpan = "half" | "full";
59
+ export type DashboardPanelEmptyState = {
60
+ readonly span?: DashboardPanelSpan;
61
+ readonly emptyLabel?: string;
62
+ /** Second line under `emptyLabel`. */
63
+ readonly emptyHint?: string;
64
+ };
65
+ export declare const DASHBOARD_STAT_TONES: readonly ["default", "positive", "ok", "warn", "negative"];
66
+ export type DashboardStatTone = (typeof DASHBOARD_STAT_TONES)[number];
67
+ export type DashboardStatPanel = DashboardPanelQueryOptions & DashboardPanelGate & {
68
+ readonly kind: "stat";
69
+ /** Stable id: kebab-case, unique within the panel set. */
70
+ readonly id: string;
71
+ /** Display text (i18n key). */
72
+ readonly label: string;
73
+ readonly query: string;
74
+ readonly valueField: string;
75
+ readonly subField?: string;
76
+ readonly toneField?: string;
77
+ /** Formats a numeric value as currency (minor units) or, with `relativeTime`, as
78
+ * a ticking "5 minutes ago" from epoch ms; ignored for string values. The sparkline is not formatted. */
79
+ readonly valueFormat?: DashboardValueFormat;
80
+ /** Fixed tone when `toneField` is unset or yields none; not applied while the value is 0. */
81
+ readonly tone?: DashboardStatTone;
82
+ /** Record field holding `{ atMs, value | null }[]` (MetricResult.points),
83
+ * drawn as a sparkline next to the value. */
84
+ readonly sparklineField?: string;
85
+ /** Delta chip (e.g. "↓23 %") next to the label. It renders only when BOTH
86
+ * fields are set AND the query handler returns them; otherwise the tile
87
+ * looks as it does without a delta. `deltaToneField` falls back to
88
+ * `toneField`/"default" when unset. */
89
+ readonly deltaField?: string;
90
+ readonly deltaDirectionField?: string;
91
+ readonly deltaToneField?: string;
92
+ /** Static icon next to the label. Unlike value/sub/delta it does not vary
93
+ * per query result; it is an author decision like the panel itself.
94
+ * Resolved through the same extensionSectionComponents registry as custom
95
+ * panels; the registered component usually ignores
96
+ * entityName/entityId/filterParams (a plain icon has no entity context). */
97
+ readonly icon?: PlatformComponent;
98
+ /** Static CSS color value (e.g. "var(--color-debt)") for the icon chip,
99
+ * passed straight to the tile without a registry lookup. Applies ONLY when
100
+ * `icon` is set (StatCard renders the chip only together with an icon);
101
+ * without an icon the value is silently dropped. Without `accentColor` the
102
+ * chip follows the tone, including a `toneField` value delivered per result.
103
+ * An accent that should change with the result (e.g. by sign) goes through
104
+ * `toneField`; leave `accentColor` unset then. */
105
+ readonly accentColor?: string;
106
+ };
107
+ export type DashboardChartKind = "timeseries" | "stacked-bars" | "segment-bars" | "stacked-area";
108
+ export type DashboardChartTone = "positive" | "negative" | "active" | "neutral";
109
+ /** Look of the markers whose `kind` matches: a dashed guide line through the
110
+ * plot plus a colored pin. `color` (raw CSS color) wins over `tone`. */
111
+ export type DashboardChartMarkerKind = {
112
+ readonly tone?: DashboardChartTone;
113
+ readonly color?: string;
114
+ /** i18n key; with `markerLegend: "legend"` the chart legend lists the markers of this kind under it. */
115
+ readonly label?: string;
116
+ };
117
+ /** One option of a stacked-area range switch. Without `months` the option
118
+ * shows every bucket; with it, a window of that many months anchored at
119
+ * `todayMs` (or the last bucket). `label` is an i18n key. */
120
+ export type DashboardChartRangeOption = {
121
+ readonly value: string;
122
+ readonly label: string;
123
+ readonly months?: number;
124
+ };
125
+ export type DashboardChartRanges = {
126
+ readonly options: readonly DashboardChartRangeOption[];
127
+ readonly default: string;
128
+ };
129
+ export type DashboardChartPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
130
+ readonly kind: "chart";
131
+ readonly id: string;
132
+ readonly label: string;
133
+ readonly chart: DashboardChartKind;
134
+ readonly query: string;
135
+ /** i18n key under the title, e.g. "7 days, by creation day". */
136
+ readonly subtitle?: string;
137
+ /** Series/segment/line key -> tone; unmapped keys fall back to a palette. */
138
+ readonly seriesTones?: Readonly<Record<string, DashboardChartTone>>;
139
+ /** Series/segment/line key -> raw CSS color (e.g. "var(--color-debt)"); wins over `seriesTones`. */
140
+ readonly seriesColors?: Readonly<Record<string, string>>;
141
+ /** Formats values as currency (minor units): y ticks, legend totals and tooltips on
142
+ * stacked-bars/segment-bars/stacked-area, only the y ticks on timeseries. */
143
+ readonly valueFormat?: DashboardCurrencyFormat;
144
+ /** i18n key of a unit label. Only for chart "timeseries": shown once above the y ticks instead of on every tick. */
145
+ readonly unit?: string;
146
+ /** Only for chart "stacked-area": fixed width per bucket, plot scrolls horizontally. */
147
+ readonly scrollable?: boolean;
148
+ /** Only for chart "stacked-area": marker `kind` -> look. Markers without a
149
+ * declared kind keep the neutral numbered pin. */
150
+ readonly markerKinds?: Readonly<Record<string, DashboardChartMarkerKind>>;
151
+ /** Only for chart "stacked-area": false hides the per-series sums in the
152
+ * legend (they mean nothing for balances). Default true. */
153
+ readonly legendTotals?: boolean;
154
+ /** Only for chart "stacked-area": range switch in the panel header. */
155
+ readonly ranges?: DashboardChartRanges;
156
+ /** Only for chart "stacked-area": scrubber under the plot to drag the visible window; starts at today. */
157
+ readonly brush?: boolean;
158
+ /** Only for chart "stacked-area": date format of axis and marker labels.
159
+ * Default automatic: day and month below 18 visible months, month and year above. */
160
+ readonly dateFormat?: "day" | "month";
161
+ /** Only for chart "stacked-area": window the uncontrolled chart starts in.
162
+ * "from-today" starts at today even when `ranges.default` is set. Default "default-range". */
163
+ readonly initialWindow?: "default-range" | "from-today";
164
+ /** Only for chart "stacked-area": "legend" drops the numbered marker list; pins become
165
+ * plain dots with a tooltip and the legend lists marker kinds (see `markerKinds[].label`). Default "list". */
166
+ readonly markerLegend?: "list" | "legend";
167
+ /** Only for chart "stacked-area": true writes each marker label vertically next to its guide line on wide panels; narrow panels keep the pins. Default false. */
168
+ readonly markerLabels?: boolean;
169
+ };
170
+ export type DashboardListPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
171
+ readonly kind: "list";
172
+ readonly id: string;
173
+ readonly label: string;
174
+ readonly query: string;
175
+ readonly columns: readonly ListColumnSpec[];
176
+ };
177
+ export type DashboardStatGroupPanel = DashboardPanelGate & {
178
+ readonly kind: "stat-group";
179
+ readonly id: string;
180
+ readonly label?: string;
181
+ /** i18n key, a line under `label`; the boot validator requires `label`. */
182
+ readonly subtitle?: string;
183
+ readonly span?: DashboardPanelSpan;
184
+ readonly stats: readonly DashboardStatPanel[];
185
+ };
186
+ export type DashboardFeedPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
187
+ readonly kind: "feed";
188
+ readonly id: string;
189
+ readonly label: string;
190
+ readonly query: string;
191
+ };
192
+ export type DashboardProgressTone = "default" | "danger";
193
+ export type DashboardProgressListPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
194
+ readonly kind: "progress-list";
195
+ readonly id: string;
196
+ readonly label: string;
197
+ readonly query: string;
198
+ };
199
+ export type DashboardCustomPanel = {
200
+ readonly kind: "custom";
201
+ readonly id: string;
202
+ readonly component: PlatformComponent;
203
+ };
204
+ export type DashboardTextBlock = {
205
+ readonly kind: "heading";
206
+ readonly text: string;
207
+ } | {
208
+ readonly kind: "paragraph";
209
+ readonly text: string;
210
+ /** Default "muted", like body copy inside panels. */
211
+ readonly emphasis?: "muted" | "strong";
212
+ } | {
213
+ readonly kind: "list";
214
+ readonly items: readonly string[];
215
+ } | {
216
+ readonly kind: "code";
217
+ readonly code: string;
218
+ } | {
219
+ readonly kind: "hint";
220
+ readonly text: string;
221
+ } | {
222
+ readonly kind: "link";
223
+ /** Link label (i18n key). */
224
+ readonly text: string;
225
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>`. */
226
+ readonly screen: string;
227
+ /** Show the link only to users holding one of these roles. Display
228
+ * gate only: the target screen's own `access` stays the enforcement. */
229
+ readonly roles?: readonly string[];
230
+ };
231
+ export type DashboardTextPanel = DashboardPanelGate & {
232
+ readonly kind: "text";
233
+ readonly id: string;
234
+ /** Panel title (i18n key). */
235
+ readonly label?: string;
236
+ readonly span?: DashboardPanelSpan;
237
+ readonly blocks: readonly DashboardTextBlock[];
238
+ };
239
+ export type DashboardPanelVisibility = {
240
+ readonly query: string;
241
+ readonly field: string;
242
+ readonly eq: string | number | boolean | null;
243
+ };
244
+ export type DashboardScreenPanel = {
245
+ readonly kind: "screen";
246
+ readonly id: string;
247
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>`. */
248
+ readonly screen: string;
249
+ readonly label?: string;
250
+ readonly visibleWhen?: DashboardPanelVisibility;
251
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` embedded in this
252
+ * panel's place when `visibleWhen` is unmet or its query fails (nothing while it loads).
253
+ * Needs `visibleWhen`; the target must not have a `visibleWhen` of its own. */
254
+ readonly fallback?: string;
255
+ /** Render the embedded form without its own card frame and padding, aligned to
256
+ * the dashboard's page grid like a standalone settings screen. Default false
257
+ * (framed card); set for panels whose body is a settings-list form, where a
258
+ * card inside the page would double the inset. */
259
+ readonly chromeless?: boolean;
260
+ };
261
+ export type DashboardPanelDefinition = DashboardStatPanel | DashboardStatGroupPanel | DashboardChartPanel | DashboardListPanel | DashboardFeedPanel | DashboardProgressListPanel | DashboardTextPanel | DashboardCustomPanel | DashboardScreenPanel;
262
+ export type DashboardFilterDefinition = {
263
+ readonly id: string;
264
+ readonly label: string;
265
+ readonly kind: "select";
266
+ readonly placeholder?: string;
267
+ /** i18n key for the "(all)" entry. */
268
+ readonly allLabel?: string;
269
+ readonly options?: readonly {
270
+ readonly value: string;
271
+ readonly label: string;
272
+ }[];
273
+ /** Query-Result-Contract: `{ rows: { value: string; label: string }[] }`. */
274
+ readonly optionsQuery?: string;
275
+ };
276
+ export type DashboardTimeRangeDefinition = {
277
+ readonly id: string;
278
+ /** `label` is an i18n key. */
279
+ readonly options: readonly {
280
+ readonly value: string;
281
+ readonly label: string;
282
+ }[];
283
+ readonly default: string;
284
+ };
285
+ export type DashboardScopeDefinition = {
286
+ readonly badge: string;
287
+ readonly notice?: string;
288
+ };
289
+ /** Minimum polling interval of `DashboardScreenDefinition.refresh`; shorter values are rejected
290
+ * at boot (`seconds`) or raised at runtime (`field`). */
291
+ export declare const DASHBOARD_MIN_REFRESH_SECONDS = 5;
292
+ export type DashboardRecordBinding = {
293
+ /** URL search param carrying the record id. Without it the dashboard shows a
294
+ * hint instead of its panels and runs no query. */
295
+ readonly param: string;
296
+ /** Key of the id in each panel query payload. Default: `param`. */
297
+ readonly payloadKey?: string;
298
+ /** Optional query for the record row (QN). It receives `{ [payloadKey]: id }`
299
+ * and returns a flat row; `actions` take that row as their input and
300
+ * `refresh.field` and `titleField` read from it. */
301
+ readonly query?: string;
302
+ /** Field of the record row shown as the page title. Needs `query`. */
303
+ readonly titleField?: string;
304
+ };
305
+ /** Polling of the record and all panel queries, never faster than
306
+ * DASHBOARD_MIN_REFRESH_SECONDS. `field` reads the seconds from the record row
307
+ * (needs `record.query`), so a monitor can be polled at its own check interval. */
308
+ export type DashboardRefresh = {
309
+ readonly seconds: number;
310
+ } | {
311
+ readonly field: string;
312
+ };
313
+ export type DashboardScreenDefinition = {
314
+ readonly id: string;
315
+ readonly type: "dashboard";
316
+ readonly nav?: ScreenNavSugar;
317
+ readonly detailFor?: string;
318
+ readonly description?: string;
319
+ readonly agent?: AgentScreenHints;
320
+ /** Screen has no nav entry by design (opened via link or navved by the
321
+ * app); exempts it from the nav-area boot check. */
322
+ readonly dormant?: boolean;
323
+ /** UI-only gate, also on direct URL access; handlers still enforce access. Unmet or failed renders `fallback`. */
324
+ readonly visibleWhen?: DashboardPanelVisibility;
325
+ /** Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice). */
326
+ readonly fallback?: string;
327
+ readonly panels: readonly DashboardPanelDefinition[];
328
+ /** Binds the dashboard to one record via a URL search param, see DashboardRecordBinding. */
329
+ readonly record?: DashboardRecordBinding;
330
+ /** Header actions on the bound record (writeHandler, navigate, clientAction).
331
+ * The row is the `record.query` row, or `{ id, [payloadKey]: id }` without a
332
+ * query. After a success the record and all panels refetch. Needs `record`;
333
+ * no drawer, redirect or rowClick. */
334
+ readonly actions?: readonly RowAction[];
335
+ readonly refresh?: DashboardRefresh;
336
+ readonly filter?: DashboardFilterDefinition;
337
+ /** Segmented control in the toolbar; the value lives in URL search param
338
+ * `id` and is merged into EVERY panel query (also ignoreScreenFilter ones). */
339
+ readonly timeRange?: DashboardTimeRangeDefinition;
340
+ /** Marks the screen as spanning more than the caller's own scope. */
341
+ readonly scope?: DashboardScopeDefinition;
342
+ /** Show the "Updated at" timestamp in the toolbar. Default true; turn off for
343
+ * dashboards that host forms rather than live metrics, where a load time
344
+ * next to the form reads as stale data. */
345
+ readonly showUpdatedAt?: boolean;
346
+ readonly slots?: ScreenSlots;
347
+ readonly access?: AccessRule;
348
+ };
@@ -0,0 +1,10 @@
1
+ // Query result contract: flat record; `valueField` points at the
2
+ // value (string is display-ready, number is locale-formatted, DashboardI18nText
3
+ // is translated), `subField` optionally at a sub line (string | DashboardI18nText), `toneField` optionally at
4
+ // "default" | "positive" | "ok" | "warn" | "negative".
5
+ // `visibleWhen` only on a top-level stat; the boot validator rejects it on a
6
+ // stat-group child (gate the whole group instead).
7
+ export const DASHBOARD_STAT_TONES = ["default", "positive", "ok", "warn", "negative"];
8
+ /** Minimum polling interval of `DashboardScreenDefinition.refresh`; shorter values are rejected
9
+ * at boot (`seconds`) or raised at runtime (`field`). */
10
+ export const DASHBOARD_MIN_REFRESH_SECONDS = 5;