@keemakr/ui-sdk 1.6.0-dev-20261027083656 → 1.6.0-dev-20261027085115

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.
@@ -71,8 +71,12 @@ import type { ChatPress, ScreenRootNode, ScreenTree } from './schema.js';
71
71
  * published release; core's own renderer draws it). An older parse strips it without a sign,
72
72
  * so without it the bar's legend ("Red 8 · Amber 13 · Green 29") is written into the tile's
73
73
  * `hint`, after any hint it had, and the bar is dropped.
74
+ * - `section.action`: a section's `action`, a press drawn at the right end of its title line
75
+ * (newer than any published release; core's own renderer draws it). An older parse strips it,
76
+ * so without it the action becomes a `button` with the same label and press, last in the
77
+ * section, and the reader can still go where it led. A full section drops it.
74
78
  */
75
- export type UiFeature = 'breadcrumbs' | 'button.disabled' | 'section.row' | 'form.field_kinds' | 'form.confirm' | 'press.chat' | 'form.radio' | 'table.cells' | 'header.meta' | 'tabs.many' | 'choice.action' | 'markdown.links' | 'form.disabled' | 'press.seek' | 'edit.inline' | 'timeline.time_tag' | 'stat.bar';
79
+ export type UiFeature = 'breadcrumbs' | 'button.disabled' | 'section.row' | 'form.field_kinds' | 'form.confirm' | 'press.chat' | 'form.radio' | 'table.cells' | 'header.meta' | 'tabs.many' | 'choice.action' | 'markdown.links' | 'form.disabled' | 'press.seek' | 'edit.inline' | 'timeline.time_tag' | 'stat.bar' | 'section.action';
76
80
  /** The node types and features one ui-sdk release, or one client, draws. */
77
81
  export interface NodeSet {
78
82
  /** For messages: "ui-sdk 1.4.0", "mobile 0.2.5". */
@@ -109,7 +113,7 @@ export declare const UI_SDK_SNAPSHOTS: {
109
113
  readonly current: {
110
114
  readonly name: "ui-sdk (this package)";
111
115
  readonly nodes: readonly ["heading", "text", "markdown", "stat", "button", "form", "table", "timeline", "pending_request", "pending_requests", "choice_cards", "progress", "chart", "section", "tabs", "pagination", "select", "alert", "details", "timestamp", "divider", "board", "link", "tags", "toggle", "segmented", "progress_bar", "conversation", "composer", "calendar", "image", "video", "stacked_bar", "heatmap", "audio"];
112
- readonly features: readonly ["breadcrumbs", "button.disabled", "section.row", "form.field_kinds", "form.confirm", "press.chat", "form.radio", "table.cells", "header.meta", "tabs.many", "choice.action", "markdown.links", "form.disabled", "press.seek", "edit.inline", "timeline.time_tag", "stat.bar"];
116
+ readonly features: readonly ["breadcrumbs", "button.disabled", "section.row", "form.field_kinds", "form.confirm", "press.chat", "form.radio", "table.cells", "header.meta", "tabs.many", "choice.action", "markdown.links", "form.disabled", "press.seek", "edit.inline", "timeline.time_tag", "stat.bar", "section.action"];
113
117
  };
114
118
  };
115
119
  export type UiSdkSnapshot = keyof typeof UI_SDK_SNAPSHOTS;
package/dist/negotiate.js CHANGED
@@ -51,6 +51,7 @@ const ALL_FEATURES = [
51
51
  'edit.inline',
52
52
  'timeline.time_tag',
53
53
  'stat.bar',
54
+ 'section.action',
54
55
  ];
55
56
  /** The nodes of ui-sdk 1.1.0 and 1.2.0 (one schema). */
56
57
  const NODES_1_1_0 = [
@@ -339,6 +340,9 @@ export function negotiateTree(tree, set) {
339
340
  fallbacks.push({ path, type: 'section', reason: 'section.row', as: 'folded' });
340
341
  next = { ...next, layout: 'stack' };
341
342
  }
343
+ if (next.action && !features.has('section.action')) {
344
+ next = foldSectionAction(next, path, fallbacks);
345
+ }
342
346
  return next;
343
347
  }
344
348
  case 'form': {
@@ -617,6 +621,38 @@ export function foldChoiceActions(node, path, fallbacks) {
617
621
  });
618
622
  return { ...node, choices };
619
623
  }
624
+ /**
625
+ * A section with its `action` moved into its body as a last `button`, for a client that does not
626
+ * draw the action on the title line (`section.action`). Dropped when the section is full.
627
+ */
628
+ function foldSectionAction(node, path, fallbacks) {
629
+ const { action, ...rest } = node;
630
+ if (!action)
631
+ return node;
632
+ if (rest.children.length >= CHILDREN_MAX) {
633
+ fallbacks.push({
634
+ path: `${path}.action`,
635
+ type: 'section',
636
+ reason: 'section.action',
637
+ as: 'dropped',
638
+ });
639
+ return rest;
640
+ }
641
+ fallbacks.push({
642
+ path: `${path}.action`,
643
+ type: 'section',
644
+ reason: 'section.action',
645
+ as: 'folded',
646
+ });
647
+ const button = {
648
+ type: 'button',
649
+ label: action.label,
650
+ onPress: action.onPress,
651
+ variant: 'secondary',
652
+ disabled: false,
653
+ };
654
+ return { ...rest, children: [...rest.children, button] };
655
+ }
620
656
  /**
621
657
  * The node as a client that draws no edit control should get it (`edit.inline`): the same block,
622
658
  * read-only. Drops `edit` from a `text`, `markdown` or `details` block and its items, and from each
@@ -687,6 +723,11 @@ function withoutPresses(node, path, fallbacks, matches, reason) {
687
723
  return node;
688
724
  dropped(`${path}.action`, 'button');
689
725
  return omit(node, 'action');
726
+ case 'section':
727
+ if (!node.action || !matches(node.action.onPress))
728
+ return node;
729
+ dropped(`${path}.action`, 'section');
730
+ return omit(node, 'action');
690
731
  case 'image':
691
732
  if (!matches(node.onPress))
692
733
  return node;
package/dist/schema.d.ts CHANGED
@@ -1919,6 +1919,11 @@ export type ScreenNode = LeafScreenNode | {
1919
1919
  badge?: string;
1920
1920
  badgeTone?: 'default' | 'success' | 'warning' | 'destructive' | 'info' | 'accent';
1921
1921
  icon?: UiIcon;
1922
+ action?: {
1923
+ label: string;
1924
+ onPress: Press;
1925
+ };
1926
+ tone?: 'default' | 'success' | 'warning' | 'destructive' | 'info' | 'accent';
1922
1927
  children: ScreenNode[];
1923
1928
  };
1924
1929
  type ScreenNodeInput = LeafScreenNodeInput | {
@@ -1939,6 +1944,11 @@ type ScreenNodeInput = LeafScreenNodeInput | {
1939
1944
  badge?: string;
1940
1945
  badgeTone?: 'default' | 'success' | 'warning' | 'destructive' | 'info' | 'accent';
1941
1946
  icon?: UiIcon;
1947
+ action?: {
1948
+ label: string;
1949
+ onPress: z.input<typeof pressSchema>;
1950
+ };
1951
+ tone?: 'default' | 'success' | 'warning' | 'destructive' | 'info' | 'accent';
1942
1952
  children: ScreenNodeInput[];
1943
1953
  };
1944
1954
  /**
@@ -2044,6 +2054,10 @@ export declare const screenTreeSchema: z.ZodObject<{
2044
2054
  dot: z.ZodOptional<z.ZodBoolean>;
2045
2055
  hint: z.ZodOptional<z.ZodString>;
2046
2056
  }, z.core.$strip>>>;
2057
+ avatar: z.ZodOptional<z.ZodObject<{
2058
+ initials: z.ZodString;
2059
+ src: z.ZodOptional<z.ZodString>;
2060
+ }, z.core.$strip>>;
2047
2061
  meta: z.ZodOptional<z.ZodArray<z.ZodObject<{
2048
2062
  label: z.ZodString;
2049
2063
  value: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -2268,6 +2282,10 @@ export declare const actionResultSchema: z.ZodObject<{
2268
2282
  dot: z.ZodOptional<z.ZodBoolean>;
2269
2283
  hint: z.ZodOptional<z.ZodString>;
2270
2284
  }, z.core.$strip>>>;
2285
+ avatar: z.ZodOptional<z.ZodObject<{
2286
+ initials: z.ZodString;
2287
+ src: z.ZodOptional<z.ZodString>;
2288
+ }, z.core.$strip>>;
2271
2289
  meta: z.ZodOptional<z.ZodArray<z.ZodObject<{
2272
2290
  label: z.ZodString;
2273
2291
  value: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -2485,6 +2503,10 @@ export declare const jobStatusSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2485
2503
  dot: z.ZodOptional<z.ZodBoolean>;
2486
2504
  hint: z.ZodOptional<z.ZodString>;
2487
2505
  }, z.core.$strip>>>;
2506
+ avatar: z.ZodOptional<z.ZodObject<{
2507
+ initials: z.ZodString;
2508
+ src: z.ZodOptional<z.ZodString>;
2509
+ }, z.core.$strip>>;
2488
2510
  meta: z.ZodOptional<z.ZodArray<z.ZodObject<{
2489
2511
  label: z.ZodString;
2490
2512
  value: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -2692,6 +2714,10 @@ export declare const screenLoadResponseSchema: z.ZodObject<{
2692
2714
  dot: z.ZodOptional<z.ZodBoolean>;
2693
2715
  hint: z.ZodOptional<z.ZodString>;
2694
2716
  }, z.core.$strip>>>;
2717
+ avatar: z.ZodOptional<z.ZodObject<{
2718
+ initials: z.ZodString;
2719
+ src: z.ZodOptional<z.ZodString>;
2720
+ }, z.core.$strip>>;
2695
2721
  meta: z.ZodOptional<z.ZodArray<z.ZodObject<{
2696
2722
  label: z.ZodString;
2697
2723
  value: z.ZodDiscriminatedUnion<[z.ZodObject<{
package/dist/schema.js CHANGED
@@ -2275,6 +2275,22 @@ const sectionShape = (children) => z.object({
2275
2275
  * apart at a glance. Decorative: the title says what the section is. Needs a `title`.
2276
2276
  */
2277
2277
  icon: z.enum(UI_ICONS).optional(),
2278
+ /**
2279
+ * A press at the right end of the title line: "All", "Open", "See all 13 amber". Drawn as a
2280
+ * quiet text button, so the label says where it goes. Needs a `title`.
2281
+ */
2282
+ action: z
2283
+ .object({
2284
+ label: z.string().min(1).max(40),
2285
+ onPress: pressSchema,
2286
+ })
2287
+ .optional(),
2288
+ /**
2289
+ * Tints a `card` section in a tone: its border and a faint wash behind it, so one card reads
2290
+ * as marked (a red diagnosis on an account). Colour is never the only signal: the title says
2291
+ * why ("Red · onboarding stalled"). Needs `surface: 'card'`. Default `default`, no tint.
2292
+ */
2293
+ tone: tone.optional(),
2278
2294
  children: z.array(children).max(100),
2279
2295
  });
2280
2296
  /** The rules between a section's fields, which a field's own schema cannot state. */
@@ -2344,6 +2360,20 @@ function checkSection(node, ctx) {
2344
2360
  message: 'a section icon sits before its title, so it needs a title',
2345
2361
  });
2346
2362
  }
2363
+ if (node.action !== undefined && !node.title) {
2364
+ ctx.addIssue({
2365
+ code: 'custom',
2366
+ path: ['action'],
2367
+ message: 'a section action sits on its title line, so it needs a title',
2368
+ });
2369
+ }
2370
+ if (node.tone !== undefined && node.surface !== 'card') {
2371
+ ctx.addIssue({
2372
+ code: 'custom',
2373
+ path: ['tone'],
2374
+ message: "a section tone tints a card, so it needs surface: 'card'",
2375
+ });
2376
+ }
2347
2377
  }
2348
2378
  const sectionBranch = (children) => sectionShape(children).superRefine(checkSection);
2349
2379
  /**
@@ -2453,6 +2483,16 @@ export const screenTreeSchema = z
2453
2483
  * only signal, so each label says the status in words ("At risk").
2454
2484
  */
2455
2485
  badges: z.array(tagSchema).min(1).max(4).optional(),
2486
+ /**
2487
+ * A square before the title that says whose screen this is at a glance: an account's
2488
+ * initials, or its logo from the module's media. Decorative: the title names it. With an
2489
+ * avatar the header is drawn as one card (avatar, title and badges, facts, actions), the
2490
+ * top of a record's page. `initials` are drawn when there is no `src`, and while it loads
2491
+ * or when it fails.
2492
+ */
2493
+ avatar: z
2494
+ .object({ initials: z.string().min(1).max(3), src: mediaPathSchema.optional() })
2495
+ .optional(),
2456
2496
  /**
2457
2497
  * Up to eight facts drawn as a compact row under the title (and its
2458
2498
  * description): ARR, renewal date, reps, NPS. Each value is a cell, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keemakr/ui-sdk",
3
- "version": "1.6.0-dev-20261027083656",
3
+ "version": "1.6.0-dev-20261027085115",
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",