@topolo/sdk 0.4.7 → 0.4.8

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/index.js CHANGED
@@ -58,32 +58,32 @@ var TopoloHttpError = class extends TopoloSdkError {
58
58
 
59
59
  // src/services.generated.ts
60
60
  var SERVICES = {
61
- agent: "https://agent-api.topolo.app",
61
+ agent: "https://agent.topolo.app",
62
62
  auth: "https://auth.topolo.app",
63
63
  backup: "https://backup.topolo.app",
64
64
  books: "https://books.topolo.app",
65
65
  bugfix: "https://bugfix.topolo.app",
66
- bytes: "https://bytes-api.topolo.app",
66
+ bytes: "https://bytes.topolo.app",
67
67
  calendar: "https://calendar.topolo.app",
68
68
  capacity: "https://capacity.topolo.app",
69
69
  chat: "https://chat.topolo.app",
70
- commerce: "https://commerce-api.topolo.app",
70
+ commerce: "https://commerce.topolo.app",
71
71
  compose: "https://compose.topolo.app",
72
72
  consent: "https://consent.topolo.app",
73
- crm: "https://crm-api.topolo.app",
73
+ crm: "https://crm.topolo.app",
74
74
  developers: "https://developers.topolo.app",
75
75
  director: "https://director.topolo.app",
76
- feed: "https://feed-api.topolo.app",
76
+ feed: "https://feed.topolo.app",
77
77
  flow: "https://flow.topolo.app",
78
- forecast: "https://forecast-api.topolo.app",
78
+ forecast: "https://forecast.topolo.app",
79
79
  forms: "https://forms.topolo.app",
80
80
  home: "https://home.topolo.app",
81
81
  insights: "https://insights.topolo.app",
82
82
  inventory: "https://inventory.topolo.app",
83
- learn: "https://learn-api.topolo.app",
83
+ learn: "https://learn.topolo.app",
84
84
  localize: "https://localize.topolo.app",
85
- mail: "https://mail-api.topolo.app",
86
- mdm: "https://mdm-api.topolo.app",
85
+ mail: "https://mail.topolo.app",
86
+ mdm: "https://mdm.topolo.app",
87
87
  messages: "https://messages.topolo.app",
88
88
  nexus: "https://nexus-api.topolo.app",
89
89
  notify: "https://notify.topolo.app",
@@ -92,20 +92,19 @@ var SERVICES = {
92
92
  p2p: "https://p2p.topolo.app",
93
93
  pay: "https://pay.topolo.app",
94
94
  people: "https://people.topolo.app",
95
- pulse: "https://pulse.topolo.app",
96
95
  "quro.api": "https://api.ol0.me",
97
96
  "quro.redirect": "https://ol0.me",
98
- roadmapper: "https://roadmapper-api.topolo.app",
97
+ roadmapper: "https://roadmapper.topolo.app",
99
98
  sign: "https://sign.topolo.app",
100
- social_studio: "https://studio-api.topolo.app",
101
- socialize: "https://socialize-api.topolo.app",
99
+ social_studio: "https://studio.topolo.app",
100
+ socialize: "https://socialize.topolo.app",
102
101
  spaces: "https://spaces.topolo.app",
103
102
  status: "https://status.topolo.app",
104
103
  success: "https://success.topolo.app",
105
104
  support: "https://support.topolo.app",
106
105
  survey: "https://survey.topolo.app",
107
106
  voice: "https://voice.topolo.app",
108
- web: "https://web-api.topolo.app"
107
+ web: "https://web.topolo.app"
109
108
  };
110
109
 
111
110
  // src/applications.generated.ts
@@ -156,17 +155,15 @@ var APPLICATIONS = {
156
155
  "agent.topolo.app"
157
156
  ],
158
157
  "routes": [],
159
- "serviceId": null
158
+ "serviceId": "agent"
160
159
  },
161
160
  {
162
161
  "name": "topolo-agent-api",
163
162
  "kind": "worker",
164
- "apiUrl": "https://agent-api.topolo.app",
165
- "domains": [
166
- "agent-api.topolo.app"
167
- ],
163
+ "apiUrl": null,
164
+ "domains": [],
168
165
  "routes": [],
169
- "serviceId": "agent"
166
+ "serviceId": null
170
167
  }
171
168
  ]
172
169
  },
@@ -286,17 +283,15 @@ var APPLICATIONS = {
286
283
  "bytes.topolo.app"
287
284
  ],
288
285
  "routes": [],
289
- "serviceId": null
286
+ "serviceId": "bytes"
290
287
  },
291
288
  {
292
289
  "name": "topolo-bytes-api",
293
290
  "kind": "worker",
294
- "apiUrl": "https://bytes-api.topolo.app",
295
- "domains": [
296
- "bytes-api.topolo.app"
297
- ],
291
+ "apiUrl": null,
292
+ "domains": [],
298
293
  "routes": [],
299
- "serviceId": "bytes"
294
+ "serviceId": null
300
295
  }
301
296
  ]
302
297
  },
@@ -395,12 +390,10 @@ var APPLICATIONS = {
395
390
  {
396
391
  "name": "topolo-commerce-api",
397
392
  "kind": "worker",
398
- "apiUrl": "https://commerce-api.topolo.app",
399
- "domains": [
400
- "commerce-api.topolo.app"
401
- ],
393
+ "apiUrl": null,
394
+ "domains": [],
402
395
  "routes": [],
403
- "serviceId": "commerce"
396
+ "serviceId": null
404
397
  },
405
398
  {
406
399
  "name": "topolo-commerce-guest-web",
@@ -420,7 +413,7 @@ var APPLICATIONS = {
420
413
  "commerce.topolo.app"
421
414
  ],
422
415
  "routes": [],
423
- "serviceId": null
416
+ "serviceId": "commerce"
424
417
  },
425
418
  {
426
419
  "name": "topolo-commerce-station-web",
@@ -507,12 +500,10 @@ var APPLICATIONS = {
507
500
  {
508
501
  "name": "topolo-crm-api",
509
502
  "kind": "worker",
510
- "apiUrl": "https://crm-api.topolo.app",
511
- "domains": [
512
- "crm-api.topolo.app"
513
- ],
503
+ "apiUrl": null,
504
+ "domains": [],
514
505
  "routes": [],
515
- "serviceId": "crm"
506
+ "serviceId": null
516
507
  },
517
508
  {
518
509
  "name": "topolo-crm-app",
@@ -522,7 +513,7 @@ var APPLICATIONS = {
522
513
  "crm.topolo.app"
523
514
  ],
524
515
  "routes": [],
525
- "serviceId": null
516
+ "serviceId": "crm"
526
517
  }
527
518
  ]
528
519
  },
@@ -645,12 +636,10 @@ var APPLICATIONS = {
645
636
  {
646
637
  "name": "topolo-feed-api",
647
638
  "kind": "worker",
648
- "apiUrl": "https://feed-api.topolo.app",
649
- "domains": [
650
- "feed-api.topolo.app"
651
- ],
639
+ "apiUrl": null,
640
+ "domains": [],
652
641
  "routes": [],
653
- "serviceId": "feed"
642
+ "serviceId": null
654
643
  },
655
644
  {
656
645
  "name": "topolo-feed-web",
@@ -660,7 +649,7 @@ var APPLICATIONS = {
660
649
  "feed.topolo.app"
661
650
  ],
662
651
  "routes": [],
663
- "serviceId": null
652
+ "serviceId": "feed"
664
653
  }
665
654
  ]
666
655
  },
@@ -712,17 +701,15 @@ var APPLICATIONS = {
712
701
  "forecast.topolo.app"
713
702
  ],
714
703
  "routes": [],
715
- "serviceId": null
704
+ "serviceId": "forecast"
716
705
  },
717
706
  {
718
707
  "name": "topolo-forecast-api",
719
708
  "kind": "worker",
720
- "apiUrl": "https://forecast-api.topolo.app",
721
- "domains": [
722
- "forecast-api.topolo.app"
723
- ],
709
+ "apiUrl": null,
710
+ "domains": [],
724
711
  "routes": [],
725
- "serviceId": "forecast"
712
+ "serviceId": null
726
713
  }
727
714
  ]
728
715
  },
@@ -864,17 +851,15 @@ var APPLICATIONS = {
864
851
  "learn.topolo.app"
865
852
  ],
866
853
  "routes": [],
867
- "serviceId": null
854
+ "serviceId": "learn"
868
855
  },
869
856
  {
870
857
  "name": "topolo-learn-api",
871
858
  "kind": "worker",
872
- "apiUrl": "https://learn-api.topolo.app",
873
- "domains": [
874
- "learn-api.topolo.app"
875
- ],
859
+ "apiUrl": null,
860
+ "domains": [],
876
861
  "routes": [],
877
- "serviceId": "learn"
862
+ "serviceId": null
878
863
  }
879
864
  ]
880
865
  },
@@ -920,17 +905,15 @@ var APPLICATIONS = {
920
905
  "mail.topolo.app"
921
906
  ],
922
907
  "routes": [],
923
- "serviceId": null
908
+ "serviceId": "mail"
924
909
  },
925
910
  {
926
911
  "name": "topolo-mail-api",
927
912
  "kind": "worker",
928
- "apiUrl": "https://mail-api.topolo.app",
929
- "domains": [
930
- "mail-api.topolo.app"
931
- ],
913
+ "apiUrl": null,
914
+ "domains": [],
932
915
  "routes": [],
933
- "serviceId": "mail"
916
+ "serviceId": null
934
917
  }
935
918
  ]
936
919
  },
@@ -947,12 +930,10 @@ var APPLICATIONS = {
947
930
  {
948
931
  "name": "topolo-mdm-api",
949
932
  "kind": "worker",
950
- "apiUrl": "https://mdm-api.topolo.app",
951
- "domains": [
952
- "mdm-api.topolo.app"
953
- ],
933
+ "apiUrl": null,
934
+ "domains": [],
954
935
  "routes": [],
955
- "serviceId": "mdm"
936
+ "serviceId": null
956
937
  },
957
938
  {
958
939
  "name": "topolo-mdm-console",
@@ -962,7 +943,7 @@ var APPLICATIONS = {
962
943
  "mdm.topolo.app"
963
944
  ],
964
945
  "routes": [],
965
- "serviceId": null
946
+ "serviceId": "mdm"
966
947
  }
967
948
  ]
968
949
  },
@@ -1221,36 +1202,6 @@ var APPLICATIONS = {
1221
1202
  }
1222
1203
  ]
1223
1204
  },
1224
- "pulse": {
1225
- "id": "pulse",
1226
- "appDir": "TopoloPulse",
1227
- "name": "topolo-pulse",
1228
- "packageName": "topolo-pulse",
1229
- "productionUrl": "https://pulse.topolo.app",
1230
- "services": [
1231
- "pulse"
1232
- ],
1233
- "deployTargets": [
1234
- {
1235
- "name": "topolo-pulse",
1236
- "kind": "worker",
1237
- "apiUrl": "https://pulse.topolo.app",
1238
- "domains": [
1239
- "pulse.topolo.app"
1240
- ],
1241
- "routes": [],
1242
- "serviceId": "pulse"
1243
- },
1244
- {
1245
- "name": "topolo-pulse-api",
1246
- "kind": "worker",
1247
- "apiUrl": null,
1248
- "domains": [],
1249
- "routes": [],
1250
- "serviceId": null
1251
- }
1252
- ]
1253
- },
1254
1205
  "quro": {
1255
1206
  "id": "quro",
1256
1207
  "appDir": "TopoloQuro",
@@ -1316,17 +1267,15 @@ var APPLICATIONS = {
1316
1267
  "roadmapper.topolo.app"
1317
1268
  ],
1318
1269
  "routes": [],
1319
- "serviceId": null
1270
+ "serviceId": "roadmapper"
1320
1271
  },
1321
1272
  {
1322
1273
  "name": "topolo-roadmapper-api",
1323
1274
  "kind": "worker",
1324
- "apiUrl": "https://roadmapper-api.topolo.app",
1325
- "domains": [
1326
- "roadmapper-api.topolo.app"
1327
- ],
1275
+ "apiUrl": null,
1276
+ "domains": [],
1328
1277
  "routes": [],
1329
- "serviceId": "roadmapper"
1278
+ "serviceId": null
1330
1279
  }
1331
1280
  ]
1332
1281
  },
@@ -1378,17 +1327,15 @@ var APPLICATIONS = {
1378
1327
  "studio.topolo.app"
1379
1328
  ],
1380
1329
  "routes": [],
1381
- "serviceId": null
1330
+ "serviceId": "social_studio"
1382
1331
  },
1383
1332
  {
1384
1333
  "name": "topolo-social-studio-api",
1385
1334
  "kind": "worker",
1386
- "apiUrl": "https://studio-api.topolo.app",
1387
- "domains": [
1388
- "studio-api.topolo.app"
1389
- ],
1335
+ "apiUrl": null,
1336
+ "domains": [],
1390
1337
  "routes": [],
1391
- "serviceId": "social_studio"
1338
+ "serviceId": null
1392
1339
  },
1393
1340
  {
1394
1341
  "name": "topolo-social-studio-queue",
@@ -1418,17 +1365,15 @@ var APPLICATIONS = {
1418
1365
  "socialize.topolo.app"
1419
1366
  ],
1420
1367
  "routes": [],
1421
- "serviceId": null
1368
+ "serviceId": "socialize"
1422
1369
  },
1423
1370
  {
1424
1371
  "name": "topolo-socialize-api",
1425
1372
  "kind": "worker",
1426
- "apiUrl": "https://socialize-api.topolo.app",
1427
- "domains": [
1428
- "socialize-api.topolo.app"
1429
- ],
1373
+ "apiUrl": null,
1374
+ "domains": [],
1430
1375
  "routes": [],
1431
- "serviceId": "socialize"
1376
+ "serviceId": null
1432
1377
  },
1433
1378
  {
1434
1379
  "name": "topolo-socialize-scheduler",
@@ -1629,12 +1574,10 @@ var APPLICATIONS = {
1629
1574
  {
1630
1575
  "name": "topolo-web-api",
1631
1576
  "kind": "worker",
1632
- "apiUrl": "https://web-api.topolo.app",
1633
- "domains": [
1634
- "web-api.topolo.app"
1635
- ],
1577
+ "apiUrl": null,
1578
+ "domains": [],
1636
1579
  "routes": [],
1637
- "serviceId": "web"
1580
+ "serviceId": null
1638
1581
  },
1639
1582
  {
1640
1583
  "name": "topolo-web-runtime",
@@ -1657,7 +1600,7 @@ var APPLICATIONS = {
1657
1600
  "web.topolo.app"
1658
1601
  ],
1659
1602
  "routes": [],
1660
- "serviceId": null
1603
+ "serviceId": "web"
1661
1604
  }
1662
1605
  ]
1663
1606
  }
@@ -1720,16 +1663,20 @@ var APPLICATION_REQUIREMENTS = [
1720
1663
  summary: "Browser applications should feel like one platform and should not create isolated navigation or account surfaces.",
1721
1664
  appliesTo: ["browser"],
1722
1665
  evidence: [
1723
- "@topolo/ui-kit is used for the platform shell where the app has authenticated UI",
1724
- "The shared launcher/app switcher is reachable from authenticated app chrome",
1725
- "Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu",
1666
+ "@topolo-io/app-shell is used for the platform shell where the app has authenticated UI",
1667
+ "@topolo-io/app-launcher keeps the shared launcher/app switcher reachable from authenticated app chrome",
1668
+ "Account menu and sign-out behavior use TopoloAppShell or the ui-kit TopoloAccountMenu primitive",
1669
+ "Repeated app body surfaces use shared TopoloAppLayout primitives instead of local stat cards, empty states, or status badges",
1670
+ "Local preview supports deterministic visual QA fixtures for overview, list, detail, settings, and empty/loading states",
1726
1671
  "Authenticated app body content stays inside the viewport on mobile and desktop widths"
1727
1672
  ],
1728
1673
  implementation: [
1729
- "Use TopoloAppShell for authenticated app layouts",
1730
- "Use TopoloAccountMenu when a partial-shell surface still needs account chrome",
1674
+ "Use @topolo-io/app-shell TopoloAppShell for authenticated app layouts",
1675
+ "Use the @topolo-io/ui-kit TopoloAccountMenu primitive when a partial-shell surface still needs account chrome",
1731
1676
  "Pass app-specific account actions as additive menu items instead of forking dropdowns",
1732
1677
  "Use shared design tokens and platform UI primitives before creating local equivalents",
1678
+ "Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
1679
+ "Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
1733
1680
  "Keep mobile and desktop shell behavior aligned with the platform UI kit",
1734
1681
  "Constrain app-owned workspace wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
1735
1682
  ]
@@ -2111,6 +2058,8 @@ function firstString(...values) {
2111
2058
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2112
2059
  var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
2113
2060
  var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
2061
+ var TOPOLO_RESOURCE_TYPE_HEADER = "X-Topolo-Resource-Type";
2062
+ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
2114
2063
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
2115
2064
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
2116
2065
  var CATALOG_CACHE_TTL_MS = 60 * 1e3;
@@ -2189,6 +2138,18 @@ var TopoloClient = class _TopoloClient {
2189
2138
  if (opts.headers) {
2190
2139
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
2191
2140
  }
2141
+ if (opts.resource) {
2142
+ const resourceType = opts.resource.resourceType.trim();
2143
+ const resourceId = opts.resource.resourceId.trim();
2144
+ if (!resourceType || !resourceId) {
2145
+ throw new TopoloSdkError(
2146
+ "invalid_resource_context",
2147
+ "Both resource.resourceType and resource.resourceId are required when resource context is provided."
2148
+ );
2149
+ }
2150
+ headers.set(TOPOLO_RESOURCE_TYPE_HEADER, resourceType);
2151
+ headers.set(TOPOLO_RESOURCE_ID_HEADER, resourceId);
2152
+ }
2192
2153
  const controller = new AbortController();
2193
2154
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
2194
2155
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
@@ -2371,6 +2332,11 @@ var TopoloClient = class _TopoloClient {
2371
2332
  const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2372
2333
  if (!serviceId) continue;
2373
2334
  const permissions = Array.isArray(s.granted_permissions) ? s.granted_permissions.filter((permission) => typeof permission === "string" && permission.trim().length > 0) : [];
2335
+ const catalogApiKeyResources = normalizeApiKeyResourceMetadataList(
2336
+ s.api_key_resources ?? s.apiKeyResources
2337
+ );
2338
+ const apiKeyResource = normalizeApiKeyResourceMetadata(s.api_key_resource ?? s.apiKeyResource) ?? catalogApiKeyResources[0] ?? null;
2339
+ const apiKeyResources = catalogApiKeyResources.length > 0 ? catalogApiKeyResources : apiKeyResource ? [apiKeyResource] : [];
2374
2340
  entries.push({
2375
2341
  serviceId,
2376
2342
  slug: typeof s.app_slug === "string" ? s.app_slug : null,
@@ -2381,7 +2347,9 @@ var TopoloClient = class _TopoloClient {
2381
2347
  status: typeof s.status === "string" ? s.status : null,
2382
2348
  surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2383
2349
  launchable: s.launchable === true,
2384
- permissions
2350
+ permissions,
2351
+ apiKeyResource,
2352
+ apiKeyResources
2385
2353
  });
2386
2354
  }
2387
2355
  this.serviceCatalogCache = {
@@ -2467,7 +2435,9 @@ var TopoloClient = class _TopoloClient {
2467
2435
  status: "bundled",
2468
2436
  surfaceKind: "application",
2469
2437
  launchable: Boolean(app.productionUrl),
2470
- permissions: []
2438
+ permissions: [],
2439
+ apiKeyResource: null,
2440
+ apiKeyResources: []
2471
2441
  }));
2472
2442
  }
2473
2443
  /**
@@ -2516,6 +2486,46 @@ function resolveConfiguredServiceId(slug, serviceIds) {
2516
2486
  }
2517
2487
  return null;
2518
2488
  }
2489
+ function normalizeApiKeyResourceMetadata(input) {
2490
+ if (!isRecord(input)) return null;
2491
+ const resourceType = readString(input.resourceType ?? input.resource_type);
2492
+ const label = readString(input.label);
2493
+ const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
2494
+ const resourceConcept = readString(input.resourceConcept ?? input.resource_concept);
2495
+ const canonicalResourceType = readString(input.canonicalResourceType ?? input.canonical_resource_type);
2496
+ const defaultEntitlement = readString(input.defaultEntitlement ?? input.default_entitlement);
2497
+ const exceptionPolicy = readString(input.exceptionPolicy ?? input.exception_policy);
2498
+ if (!resourceType || !label || !emptyLabel || resourceConcept !== "workspace" || canonicalResourceType !== "workspace" || defaultEntitlement !== "enterprise" || exceptionPolicy !== "app_or_organization") {
2499
+ return null;
2500
+ }
2501
+ return {
2502
+ resourceType,
2503
+ label,
2504
+ emptyLabel,
2505
+ resourceConcept,
2506
+ canonicalResourceType,
2507
+ defaultEntitlement,
2508
+ exceptionPolicy
2509
+ };
2510
+ }
2511
+ function normalizeApiKeyResourceMetadataList(input) {
2512
+ if (!Array.isArray(input)) return [];
2513
+ const resources = [];
2514
+ const seenTypes = /* @__PURE__ */ new Set();
2515
+ for (const item of input) {
2516
+ const resource = normalizeApiKeyResourceMetadata(item);
2517
+ if (!resource || seenTypes.has(resource.resourceType)) continue;
2518
+ seenTypes.add(resource.resourceType);
2519
+ resources.push(resource);
2520
+ }
2521
+ return resources;
2522
+ }
2523
+ function readString(value) {
2524
+ return typeof value === "string" ? value.trim() : "";
2525
+ }
2526
+ function isRecord(value) {
2527
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2528
+ }
2519
2529
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2520
2530
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2521
2531
  const configured = serviceUrls?.[serviceKey];
@@ -3083,7 +3093,7 @@ function createTopoloWidgetError(error) {
3083
3093
  }
3084
3094
  function validateTopoloWidgetResponse(response) {
3085
3095
  const errors = [];
3086
- if (!isRecord(response)) {
3096
+ if (!isRecord2(response)) {
3087
3097
  return { ok: false, errors: ["response must be an object"] };
3088
3098
  }
3089
3099
  if (typeof response.success !== "boolean") {
@@ -3102,7 +3112,7 @@ function validateTopoloWidgetResponse(response) {
3102
3112
  return { ok: errors.length === 0, errors };
3103
3113
  }
3104
3114
  function validateWidget(widget, path, errors) {
3105
- if (!isRecord(widget)) {
3115
+ if (!isRecord2(widget)) {
3106
3116
  errors.push(`${path} must be an object`);
3107
3117
  return;
3108
3118
  }
@@ -3129,7 +3139,7 @@ function validateWidget(widget, path, errors) {
3129
3139
  case "chart":
3130
3140
  validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
3131
3141
  validateNonEmptyString(widget.title, `${path}.title`, errors);
3132
- if (!isRecord(widget.data)) {
3142
+ if (!isRecord2(widget.data)) {
3133
3143
  errors.push(`${path}.data must be an object`);
3134
3144
  } else {
3135
3145
  validateArray(widget.data.labels, `${path}.data.labels`, errors);
@@ -3145,7 +3155,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
3145
3155
  errors.push(`${path} must be an object`);
3146
3156
  return;
3147
3157
  }
3148
- if (!isRecord(snapshot)) {
3158
+ if (!isRecord2(snapshot)) {
3149
3159
  errors.push(`${path} must be an object`);
3150
3160
  return;
3151
3161
  }
@@ -3165,7 +3175,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
3165
3175
  }
3166
3176
  snapshot.availableMetrics.forEach((metric, index) => {
3167
3177
  const metricPath = `${path}.availableMetrics[${index}]`;
3168
- if (!isRecord(metric)) {
3178
+ if (!isRecord2(metric)) {
3169
3179
  errors.push(`${metricPath} must be an object`);
3170
3180
  return;
3171
3181
  }
@@ -3186,7 +3196,7 @@ function validateArray(value, path, errors) {
3186
3196
  errors.push(`${path} must be an array`);
3187
3197
  }
3188
3198
  }
3189
- function isRecord(value) {
3199
+ function isRecord2(value) {
3190
3200
  return typeof value === "object" && value !== null && !Array.isArray(value);
3191
3201
  }
3192
3202
  function toIsoTimestamp(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/sdk",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
4
4
  "description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -125,16 +125,20 @@ export const APPLICATION_REQUIREMENTS = [
125
125
  'Browser applications should feel like one platform and should not create isolated navigation or account surfaces.',
126
126
  appliesTo: ['browser'],
127
127
  evidence: [
128
- '@topolo/ui-kit is used for the platform shell where the app has authenticated UI',
129
- 'The shared launcher/app switcher is reachable from authenticated app chrome',
130
- 'Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu',
128
+ '@topolo-io/app-shell is used for the platform shell where the app has authenticated UI',
129
+ '@topolo-io/app-launcher keeps the shared launcher/app switcher reachable from authenticated app chrome',
130
+ 'Account menu and sign-out behavior use TopoloAppShell or the ui-kit TopoloAccountMenu primitive',
131
+ 'Repeated app body surfaces use shared TopoloAppLayout primitives instead of local stat cards, empty states, or status badges',
132
+ 'Local preview supports deterministic visual QA fixtures for overview, list, detail, settings, and empty/loading states',
131
133
  'Authenticated app body content stays inside the viewport on mobile and desktop widths',
132
134
  ],
133
135
  implementation: [
134
- 'Use TopoloAppShell for authenticated app layouts',
135
- 'Use TopoloAccountMenu when a partial-shell surface still needs account chrome',
136
+ 'Use @topolo-io/app-shell TopoloAppShell for authenticated app layouts',
137
+ 'Use the @topolo-io/ui-kit TopoloAccountMenu primitive when a partial-shell surface still needs account chrome',
136
138
  'Pass app-specific account actions as additive menu items instead of forking dropdowns',
137
139
  'Use shared design tokens and platform UI primitives before creating local equivalents',
140
+ 'Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns',
141
+ 'Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state',
138
142
  'Keep mobile and desktop shell behavior aligned with the platform UI kit',
139
143
  'Constrain app-owned workspace wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow',
140
144
  ],