@keemakr/ui-sdk 1.4.0-dev-20261012081437 → 1.4.0-dev-20261012082811

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/README.md CHANGED
@@ -54,7 +54,50 @@ export default defineScreen({
54
54
  Folder names are screen identities. For example, `ui/campaigns/campaign/screen.ts` is the
55
55
  `campaign` screen. Screen folder names must be unique across the module.
56
56
 
57
- ## 4. Generate before Eve runs
57
+ ## 4. Optional workspace presentation
58
+
59
+ Screens use the document layout unless they opt into a platform-owned workspace. A workspace can
60
+ place page actions in a pinned footer and promote one keyed root section into a responsive right
61
+ rail. The module declares meaning; each Keemakr client owns widths, breakpoints and styling.
62
+
63
+ ```ts
64
+ load: async () => ({
65
+ version: 1,
66
+ title: 'Account review',
67
+ actions: [
68
+ {
69
+ type: 'button',
70
+ label: 'Save review',
71
+ onPress: { kind: 'action', action: 'save_review' },
72
+ },
73
+ ],
74
+ presentation: {
75
+ layout: 'workspace',
76
+ actionPlacement: 'footer',
77
+ rightRail: {
78
+ sectionKey: 'history',
79
+ label: 'Review history',
80
+ collapsible: true,
81
+ },
82
+ },
83
+ children: [
84
+ { type: 'text', value: 'Current review' },
85
+ {
86
+ type: 'section',
87
+ key: 'history',
88
+ title: 'History',
89
+ layout: 'stack',
90
+ columns: 1,
91
+ children: [{ type: 'text', value: 'Version 1' }],
92
+ },
93
+ ],
94
+ }),
95
+ ```
96
+
97
+ `rightRail.sectionKey` must identify exactly one keyed section in the screen's root `children`.
98
+ Older screens require no changes and keep header actions plus the normal document flow.
99
+
100
+ ## 5. Generate before Eve runs
58
101
 
59
102
  Use the SDK wrapper in the module scripts:
60
103
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { UI_SCHEMA_VERSION, pressSchema, buttonSchema, cellSchema, nodeSchema, screenTreeSchema, actionResultSchema, screenLoadRequestSchema, screenLoadResponseSchema, screenActionRequestSchema, interfaceFamilySchema, surfaceDefinitionSchema, surfaceSchema, screenRegistrationSchema, uiManifestSchema, } from './schema.js';
2
- export type { Press, ScreenNode, ScreenRootNode, ScreenTree, ActionResult, InterfaceFamily, SurfaceDefinition, Surface, ScreenRegistration, UiManifest, ScreenTreeInput, ActionResultInput, } from './schema.js';
1
+ export { UI_SCHEMA_VERSION, pressSchema, buttonSchema, cellSchema, nodeSchema, screenPresentationSchema, screenTreeSchema, actionResultSchema, screenLoadRequestSchema, screenLoadResponseSchema, screenActionRequestSchema, interfaceFamilySchema, surfaceDefinitionSchema, surfaceSchema, screenRegistrationSchema, uiManifestSchema, } from './schema.js';
2
+ export type { Press, ScreenPresentation, ScreenNode, ScreenRootNode, ScreenTree, ActionResult, InterfaceFamily, SurfaceDefinition, Surface, ScreenRegistration, UiManifest, ScreenTreeInput, ActionResultInput, } from './schema.js';
3
3
  export { childNodesOf, KNOWN_CONTAINERS, KNOWN_NODE_TYPES } from './nodes.js';
4
4
  export type { ContainerChild, ContainerNodeType, KnownNodeType } from './nodes.js';
5
5
  export { pageRangeLabel, pageWindow, pressWithValue } from './pager.js';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  // platform components, proxies load/action with a grant
7
7
  //
8
8
  // Module code never ships to the browser. UI is data, not code.
9
- export { UI_SCHEMA_VERSION, pressSchema, buttonSchema, cellSchema, nodeSchema, screenTreeSchema, actionResultSchema, screenLoadRequestSchema, screenLoadResponseSchema, screenActionRequestSchema, interfaceFamilySchema, surfaceDefinitionSchema, surfaceSchema, screenRegistrationSchema, uiManifestSchema, } from './schema.js';
9
+ export { UI_SCHEMA_VERSION, pressSchema, buttonSchema, cellSchema, nodeSchema, screenPresentationSchema, screenTreeSchema, actionResultSchema, screenLoadRequestSchema, screenLoadResponseSchema, screenActionRequestSchema, interfaceFamilySchema, surfaceDefinitionSchema, surfaceSchema, screenRegistrationSchema, uiManifestSchema, } from './schema.js';
10
10
  export { childNodesOf, KNOWN_CONTAINERS, KNOWN_NODE_TYPES } from './nodes.js';
11
11
  export { pageRangeLabel, pageWindow, pressWithValue } from './pager.js';
12
12
  export { defineScreen, defineSurface } from './define.js';
package/dist/nodes.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ScreenRootNode } from './schema.js';
2
2
  /** Every node type the root union admits (KEE-574 item 3). */
3
- export declare const KNOWN_NODE_TYPES: readonly ["heading", "text", "markdown", "stat", "button", "form", "table", "timeline", "pending_request", "pending_requests", "choice_cards", "progress", "chart", "pagination", "select", "alert", "details", "timestamp", "divider", "section", "tabs"];
3
+ export declare const KNOWN_NODE_TYPES: readonly ["heading", "text", "markdown", "stat", "button", "form", "table", "timeline", "pending_request", "pending_requests", "choice_cards", "progress", "chart", "pagination", "select", "alert", "details", "timestamp", "divider", "board", "section", "tabs"];
4
4
  export type KnownNodeType = (typeof KNOWN_NODE_TYPES)[number];
5
5
  /**
6
6
  * The node types that hold child nodes. `section` mounts its children
package/dist/nodes.js CHANGED
@@ -29,6 +29,7 @@ export const KNOWN_NODE_TYPES = [
29
29
  'details',
30
30
  'timestamp',
31
31
  'divider',
32
+ 'board',
32
33
  'section',
33
34
  'tabs',
34
35
  ];
package/dist/schema.d.ts CHANGED
@@ -365,11 +365,56 @@ declare const leafNodeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
365
365
  label: z.ZodOptional<z.ZodString>;
366
366
  }, z.core.$strip>, z.ZodObject<{
367
367
  type: z.ZodLiteral<"divider">;
368
+ }, z.core.$strip>, z.ZodObject<{
369
+ type: z.ZodLiteral<"board">;
370
+ columns: z.ZodArray<z.ZodObject<{
371
+ key: z.ZodString;
372
+ label: z.ZodString;
373
+ tone: z.ZodDefault<z.ZodEnum<{
374
+ success: "success";
375
+ default: "default";
376
+ warning: "warning";
377
+ destructive: "destructive";
378
+ }>>;
379
+ cards: z.ZodArray<z.ZodObject<{
380
+ key: z.ZodString;
381
+ title: z.ZodString;
382
+ subtitle: z.ZodOptional<z.ZodString>;
383
+ values: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
384
+ kind: z.ZodLiteral<"text">;
385
+ value: z.ZodString;
386
+ }, z.core.$strip>, z.ZodObject<{
387
+ kind: z.ZodLiteral<"badge">;
388
+ value: z.ZodString;
389
+ tone: z.ZodDefault<z.ZodEnum<{
390
+ success: "success";
391
+ default: "default";
392
+ warning: "warning";
393
+ destructive: "destructive";
394
+ }>>;
395
+ }, z.core.$strip>, z.ZodObject<{
396
+ kind: z.ZodLiteral<"date">;
397
+ value: z.ZodISODateTime;
398
+ }, z.core.$strip>], "kind">>>;
399
+ onPress: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
400
+ kind: z.ZodLiteral<"action">;
401
+ action: z.ZodString;
402
+ input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
403
+ confirm: z.ZodOptional<z.ZodString>;
404
+ }, z.core.$strip>, z.ZodObject<{
405
+ kind: z.ZodLiteral<"navigate">;
406
+ screen: z.ZodString;
407
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
408
+ }, z.core.$strip>], "kind">>;
409
+ }, z.core.$strip>>;
410
+ }, z.core.$strip>>;
411
+ empty: z.ZodDefault<z.ZodString>;
368
412
  }, z.core.$strip>], "type">;
369
413
  type LeafScreenNode = z.infer<typeof leafNodeSchema>;
370
414
  type LeafScreenNodeInput = z.input<typeof leafNodeSchema>;
371
415
  export type ScreenNode = LeafScreenNode | {
372
416
  type: 'section';
417
+ key?: string;
373
418
  title?: string;
374
419
  description?: string;
375
420
  layout: 'stack' | 'grid' | 'row';
@@ -379,6 +424,7 @@ export type ScreenNode = LeafScreenNode | {
379
424
  };
380
425
  type ScreenNodeInput = LeafScreenNodeInput | {
381
426
  type: 'section';
427
+ key?: string;
382
428
  title?: string;
383
429
  description?: string;
384
430
  layout: 'stack' | 'grid' | 'row';
@@ -424,6 +470,19 @@ type ScreenRootNodeInput = ScreenNodeInput | TabsScreenNodeInput;
424
470
  * MAX_SECTION_DEPTH exactly as it was before `tabs` existed.
425
471
  */
426
472
  export declare const nodeSchema: z.ZodType<ScreenRootNode, ScreenRootNodeInput>;
473
+ export declare const screenPresentationSchema: z.ZodObject<{
474
+ layout: z.ZodLiteral<"workspace">;
475
+ actionPlacement: z.ZodDefault<z.ZodEnum<{
476
+ header: "header";
477
+ footer: "footer";
478
+ }>>;
479
+ rightRail: z.ZodOptional<z.ZodObject<{
480
+ sectionKey: z.ZodString;
481
+ label: z.ZodString;
482
+ collapsible: z.ZodDefault<z.ZodBoolean>;
483
+ defaultCollapsed: z.ZodDefault<z.ZodBoolean>;
484
+ }, z.core.$strip>>;
485
+ }, z.core.$strip>;
427
486
  export declare const screenTreeSchema: z.ZodObject<{
428
487
  version: z.ZodLiteral<1>;
429
488
  title: z.ZodString;
@@ -459,6 +518,19 @@ export declare const screenTreeSchema: z.ZodObject<{
459
518
  disabledReason: z.ZodOptional<z.ZodString>;
460
519
  }, z.core.$strip>>>;
461
520
  children: z.ZodArray<z.ZodType<ScreenRootNode, ScreenRootNodeInput, z.core.$ZodTypeInternals<ScreenRootNode, ScreenRootNodeInput>>>;
521
+ presentation: z.ZodOptional<z.ZodObject<{
522
+ layout: z.ZodLiteral<"workspace">;
523
+ actionPlacement: z.ZodDefault<z.ZodEnum<{
524
+ header: "header";
525
+ footer: "footer";
526
+ }>>;
527
+ rightRail: z.ZodOptional<z.ZodObject<{
528
+ sectionKey: z.ZodString;
529
+ label: z.ZodString;
530
+ collapsible: z.ZodDefault<z.ZodBoolean>;
531
+ defaultCollapsed: z.ZodDefault<z.ZodBoolean>;
532
+ }, z.core.$strip>>;
533
+ }, z.core.$strip>>;
462
534
  refreshSeconds: z.ZodOptional<z.ZodNumber>;
463
535
  }, z.core.$strip>;
464
536
  /** What an action handler returns. `refresh` re-runs `load` after the action. */
@@ -509,6 +581,19 @@ export declare const screenLoadResponseSchema: z.ZodObject<{
509
581
  disabledReason: z.ZodOptional<z.ZodString>;
510
582
  }, z.core.$strip>>>;
511
583
  children: z.ZodArray<z.ZodType<ScreenRootNode, ScreenRootNodeInput, z.core.$ZodTypeInternals<ScreenRootNode, ScreenRootNodeInput>>>;
584
+ presentation: z.ZodOptional<z.ZodObject<{
585
+ layout: z.ZodLiteral<"workspace">;
586
+ actionPlacement: z.ZodDefault<z.ZodEnum<{
587
+ header: "header";
588
+ footer: "footer";
589
+ }>>;
590
+ rightRail: z.ZodOptional<z.ZodObject<{
591
+ sectionKey: z.ZodString;
592
+ label: z.ZodString;
593
+ collapsible: z.ZodDefault<z.ZodBoolean>;
594
+ defaultCollapsed: z.ZodDefault<z.ZodBoolean>;
595
+ }, z.core.$strip>>;
596
+ }, z.core.$strip>>;
512
597
  refreshSeconds: z.ZodOptional<z.ZodNumber>;
513
598
  }, z.core.$strip>;
514
599
  }, z.core.$strip>;
@@ -569,6 +654,7 @@ export declare const uiManifestSchema: z.ZodObject<{
569
654
  }, z.core.$strip>>;
570
655
  }, z.core.$strip>;
571
656
  export type Press = z.infer<typeof pressSchema>;
657
+ export type ScreenPresentation = z.infer<typeof screenPresentationSchema>;
572
658
  export type ScreenTree = z.infer<typeof screenTreeSchema>;
573
659
  export type ActionResult = z.infer<typeof actionResultSchema>;
574
660
  export type InterfaceFamily = z.infer<typeof interfaceFamilySchema>;
package/dist/schema.js CHANGED
@@ -233,6 +233,71 @@ const timestampSchema = z.object({
233
233
  /** Shown before the time, muted: "Last contacted". */
234
234
  label: z.string().max(80).optional(),
235
235
  });
236
+ /**
237
+ * Cards in columns by stage: a sales pipeline, installs by status, accounts by
238
+ * health. A card carries a title, an optional subtitle, and up to 3 values that
239
+ * reuse the table cell kinds (a `date` shows in the viewer's zone, a `badge`
240
+ * keeps its tone). A card with `onPress` opens a record or calls an action.
241
+ * Each column shows its card count. Moving a card between columns is not part
242
+ * of this block yet: call an action from the record instead.
243
+ */
244
+ const boardSchema = z
245
+ .object({
246
+ type: z.literal('board'),
247
+ columns: z
248
+ .array(z.object({
249
+ key: slug,
250
+ label: z.string().min(1).max(40),
251
+ /** Colours the column's count: an at-risk stage, a done stage. */
252
+ tone: tone.default('default'),
253
+ cards: z
254
+ .array(z.object({
255
+ key: z.string().min(1).max(128),
256
+ title: z.string().min(1).max(120),
257
+ subtitle: z.string().max(200).optional(),
258
+ values: z.array(cellSchema).max(3).optional(),
259
+ onPress: pressSchema.optional(),
260
+ }))
261
+ .max(100),
262
+ }))
263
+ .min(1)
264
+ .max(8),
265
+ /** Shown in a column that has no cards. */
266
+ empty: z.string().min(1).max(80).default('Nothing here'),
267
+ })
268
+ .superRefine((node, ctx) => {
269
+ const columnKeys = new Set();
270
+ const cardKeys = new Set();
271
+ let cards = 0;
272
+ for (const [c, column] of node.columns.entries()) {
273
+ if (columnKeys.has(column.key)) {
274
+ ctx.addIssue({
275
+ code: 'custom',
276
+ path: ['columns', c, 'key'],
277
+ message: `duplicate column key "${column.key}"`,
278
+ });
279
+ }
280
+ columnKeys.add(column.key);
281
+ for (const [i, card] of column.cards.entries()) {
282
+ if (cardKeys.has(card.key)) {
283
+ ctx.addIssue({
284
+ code: 'custom',
285
+ path: ['columns', c, 'cards', i, 'key'],
286
+ message: `duplicate card key "${card.key}"`,
287
+ });
288
+ }
289
+ cardKeys.add(card.key);
290
+ }
291
+ cards += column.cards.length;
292
+ }
293
+ if (cards > 500) {
294
+ ctx.addIssue({
295
+ code: 'custom',
296
+ path: ['columns'],
297
+ message: `a board holds at most 500 cards, got ${cards}`,
298
+ });
299
+ }
300
+ });
236
301
  /**
237
302
  * A horizontal line that says where one part of a screen ends and the next
238
303
  * begins. It carries no content. Use a `section` when the parts need a title.
@@ -362,12 +427,15 @@ const leafNodeSchema = z.discriminatedUnion('type', [
362
427
  detailsSchema,
363
428
  timestampSchema,
364
429
  dividerSchema,
430
+ boardSchema,
365
431
  ]);
366
432
  const MAX_SECTION_DEPTH = 8;
367
433
  const nodeSchemas = new Map();
368
434
  /** Shared by the chain and by the root union, so the two cannot drift apart. */
369
435
  const sectionBranch = (children) => z.object({
370
436
  type: z.literal('section'),
437
+ /** Stable identity for presentation regions that reference this root section. */
438
+ key: slug.optional(),
371
439
  title: z.string().max(120).optional(),
372
440
  description: z.string().max(300).optional(),
373
441
  /**
@@ -448,7 +516,26 @@ export const nodeSchema = z.discriminatedUnion('type', [
448
516
  sectionBranch(z.lazy(() => nodeSchemaAtDepth(1))),
449
517
  tabsBranch(z.lazy(() => nodeSchemaAtDepth(1))),
450
518
  ]);
451
- export const screenTreeSchema = z.object({
519
+ export const screenPresentationSchema = z.object({
520
+ /** A full-height working surface whose chrome stays outside its content scroller. */
521
+ layout: z.literal('workspace'),
522
+ /** Where the renderer places the screen's page-level actions. */
523
+ actionPlacement: z.enum(['header', 'footer']).default('header'),
524
+ /**
525
+ * Promote one keyed root section into a supporting right rail. Renderers own
526
+ * its responsive width and visual treatment; modules only declare meaning.
527
+ */
528
+ rightRail: z
529
+ .object({
530
+ sectionKey: slug,
531
+ label: z.string().min(1).max(80),
532
+ collapsible: z.boolean().default(false),
533
+ defaultCollapsed: z.boolean().default(false),
534
+ })
535
+ .optional(),
536
+ });
537
+ export const screenTreeSchema = z
538
+ .object({
452
539
  version: z.literal(UI_SCHEMA_VERSION),
453
540
  title: z.string().max(120),
454
541
  description: z.string().max(300).optional(),
@@ -471,12 +558,34 @@ export const screenTreeSchema = z.object({
471
558
  .min(1)
472
559
  .max(5)
473
560
  .optional(),
474
- /** Page-level actions, rendered in the header (e.g. "New draft"). */
561
+ /** Page-level actions. Presentation chooses header or pinned workspace footer. */
475
562
  actions: z.array(buttonSchema).max(4).optional(),
476
563
  children: z.array(nodeSchema).max(100),
564
+ /** Optional platform-owned layout semantics. Absence preserves the document layout. */
565
+ presentation: screenPresentationSchema.optional(),
477
566
  // ponytail: polling keeps live views (agent activity) simple; switch to the
478
567
  // existing run-event NDJSON stream when polling measurably falls short.
479
568
  refreshSeconds: z.number().int().min(2).max(300).optional(),
569
+ })
570
+ .superRefine((tree, ctx) => {
571
+ const rail = tree.presentation?.rightRail;
572
+ if (!rail)
573
+ return;
574
+ if (rail.defaultCollapsed && !rail.collapsible) {
575
+ ctx.addIssue({
576
+ code: 'custom',
577
+ path: ['presentation', 'rightRail', 'defaultCollapsed'],
578
+ message: 'requires collapsible to be true',
579
+ });
580
+ }
581
+ const matches = tree.children.filter((node) => node.type === 'section' && node.key === rail.sectionKey);
582
+ if (matches.length !== 1) {
583
+ ctx.addIssue({
584
+ code: 'custom',
585
+ path: ['presentation', 'rightRail', 'sectionKey'],
586
+ message: 'must identify exactly one keyed root section',
587
+ });
588
+ }
480
589
  });
481
590
  /** What an action handler returns. `refresh` re-runs `load` after the action. */
482
591
  export const actionResultSchema = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keemakr/ui-sdk",
3
- "version": "1.4.0-dev-20261012081437",
3
+ "version": "1.4.0-dev-20261012082811",
4
4
  "description": "Product screens for keemakr marketplace modules: a versioned, server-driven UI tree schema plus defineScreen() handlers. Module code never ships to the browser — keemakr-core validates the tree and renders it with platform components.",
5
5
  "license": "MIT",
6
6
  "type": "module",