@growthub/cli 0.12.1 → 0.13.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.
Files changed (17) hide show
  1. package/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/package-lock.json +46 -110
  2. package/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/package.json +4 -1
  3. package/assets/worker-kits/growthub-creative-video-pipeline-v1/apps/creative-video-pipeline/package-lock.json +898 -3
  4. package/assets/worker-kits/growthub-creative-video-pipeline-v1/apps/creative-video-pipeline/package.json +4 -1
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +34 -213
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +8 -2
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +911 -8
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/views/[viewId]/page.jsx +206 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +11 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +1275 -4
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +11 -4
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +96 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +1592 -77
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +4 -1
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +1 -0
  17. package/package.json +1 -1
@@ -289,12 +289,19 @@ function deriveManualObjectTable(object) {
289
289
  }
290
290
 
291
291
  // Helper-owned hidden objects — system-managed, never surfaced in the
292
- // user-facing Data Model picker / object list / dynamic title. The
293
- // workspace-helper-sandbox row backs the helper's local-intelligence
294
- // sandbox primitive (helper-tuned instructions live there); users
295
- // interact with it only through the helper Setup tab.
292
+ // user-facing Data Model picker / object list / dynamic title.
293
+ //
294
+ // - `workspace-helper-sandbox` backs the helper's local-intelligence
295
+ // sandbox primitive (helper-tuned instructions live there); users
296
+ // interact with it only through the helper Setup tab.
297
+ // - `nav-folders` backs the Custom Folders Navigation module rendered
298
+ // in the workspace rail (between the tab toggles and the Home / Chat
299
+ // body). Users create, rename, drag, and add items entirely from the
300
+ // rail; the row-level structure is never exposed in the Data Model
301
+ // admin surface so the core governed business objects stay clean.
296
302
  const HIDDEN_HELPER_OBJECT_IDS = new Set([
297
303
  "workspace-helper-sandbox",
304
+ "nav-folders",
298
305
  ]);
299
306
 
300
307
  function listWorkspaceDataModelTables(workspaceConfig) {
@@ -461,6 +461,92 @@ function nextThreadId() {
461
461
  return `thr_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
462
462
  }
463
463
 
464
+ /**
465
+ * Custom Folders Navigation — governed end-user organization layer.
466
+ *
467
+ * Same well-known custom-typed Data Model object pattern as
468
+ * `helper-threads`: one object (id = "nav-folders") whose `rows[]` carry
469
+ * the user's folder structure. Each row is a folder. Folder items
470
+ * (dashboard links, lightweight views of governed objects) are nested
471
+ * inside the row as a JSON-serialisable `items[]` array.
472
+ *
473
+ * Persists through the existing PATCH allowlist on `/api/workspace`
474
+ * (`dataModel`), is round-trip validated by `validateWorkspaceConfig`,
475
+ * is hidden from the user-facing Data Model picker via
476
+ * `HIDDEN_HELPER_OBJECT_IDS` in `lib/workspace-data-model.js`, and is
477
+ * fully backwards compatible — if the object is missing the rail simply
478
+ * shows zero folders and the user can add one with the `+` button.
479
+ *
480
+ * Row shape (validated in `lib/workspace-schema.js`):
481
+ *
482
+ * {
483
+ * id: "fld_…",
484
+ * name: "Sales Pipeline",
485
+ * order: 0,
486
+ * collapsed: false,
487
+ * items: [
488
+ * { id: "item_…", type: "dashboard", refId: "dash-abc",
489
+ * label: "Revenue Overview" },
490
+ * { id: "item_…", type: "view", objectId: "prospects",
491
+ * label: "Active Prospects",
492
+ * viewConfig: { columns: [...], filters: [...], sort: {...} } }
493
+ * ]
494
+ * }
495
+ */
496
+
497
+ const NAV_FOLDERS_OBJECT_ID = "nav-folders";
498
+ const NAV_FOLDERS_LABEL = "Custom Folders";
499
+ const NAV_FOLDER_NAME_MAX = 60;
500
+ const NAV_ITEM_LABEL_MAX = 80;
501
+ const NAV_ITEM_TYPES = new Set(["dashboard", "view"]);
502
+
503
+ function ensureNavFoldersObject(config) {
504
+ const dm = config?.dataModel && typeof config.dataModel === "object" ? config.dataModel : {};
505
+ const objects = Array.isArray(dm.objects) ? dm.objects.slice() : [];
506
+ const idx = objects.findIndex((o) => o?.id === NAV_FOLDERS_OBJECT_ID);
507
+ if (idx >= 0) {
508
+ const existing = objects[idx];
509
+ if (!Array.isArray(existing.rows)) {
510
+ objects[idx] = { ...existing, rows: [] };
511
+ }
512
+ return { ...config, dataModel: { ...dm, objects } };
513
+ }
514
+ const seeded = {
515
+ id: NAV_FOLDERS_OBJECT_ID,
516
+ label: NAV_FOLDERS_LABEL,
517
+ source: NAV_FOLDERS_LABEL,
518
+ objectType: "custom",
519
+ icon: "Folder",
520
+ columns: ["name", "order", "collapsed", "items"],
521
+ rows: [],
522
+ binding: { mode: "manual", source: NAV_FOLDERS_LABEL },
523
+ };
524
+ return { ...config, dataModel: { ...dm, objects: [...objects, seeded] } };
525
+ }
526
+
527
+ function nextNavFolderId() {
528
+ return `fld_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
529
+ }
530
+
531
+ function nextNavItemId() {
532
+ return `item_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
533
+ }
534
+
535
+ function getNavFolderRows(config) {
536
+ const obj = (config?.dataModel?.objects || []).find((o) => o?.id === NAV_FOLDERS_OBJECT_ID);
537
+ return Array.isArray(obj?.rows) ? obj.rows : [];
538
+ }
539
+
540
+ function writeNavFolderRows(config, rows) {
541
+ const withObject = ensureNavFoldersObject(config);
542
+ const dm = withObject.dataModel;
543
+ const objects = dm.objects.slice();
544
+ const idx = objects.findIndex((o) => o?.id === NAV_FOLDERS_OBJECT_ID);
545
+ if (idx === -1) return withObject;
546
+ objects[idx] = { ...objects[idx], rows: rows.map((row, i) => ({ ...row, order: i })) };
547
+ return { ...withObject, dataModel: { ...dm, objects } };
548
+ }
549
+
464
550
  export {
465
551
  applyProposalToConfig,
466
552
  validateProposalForApply,
@@ -470,4 +556,14 @@ export {
470
556
  nextThreadId,
471
557
  HELPER_THREADS_OBJECT_ID,
472
558
  ALLOWED_PATCH_FIELDS,
559
+ NAV_FOLDERS_OBJECT_ID,
560
+ NAV_FOLDERS_LABEL,
561
+ NAV_FOLDER_NAME_MAX,
562
+ NAV_ITEM_LABEL_MAX,
563
+ NAV_ITEM_TYPES,
564
+ ensureNavFoldersObject,
565
+ nextNavFolderId,
566
+ nextNavItemId,
567
+ getNavFolderRows,
568
+ writeNavFolderRows,
473
569
  };
@@ -1000,6 +1000,101 @@ function validateSandboxEnvironmentRow(row, path, errors) {
1000
1000
  }
1001
1001
  }
1002
1002
 
1003
+ const NAV_FOLDERS_OBJECT_ID = "nav-folders";
1004
+ const NAV_FOLDER_NAME_MAX = 60;
1005
+ const NAV_ITEM_LABEL_MAX = 80;
1006
+ const NAV_ITEM_TYPES = ["dashboard", "view"];
1007
+
1008
+ function validateNavFolderRow(row, path, errors) {
1009
+ if (!isPlainObject(row)) return;
1010
+ if (typeof row.id !== "string" || !row.id.trim()) {
1011
+ errors.push(`${path}.id must be a non-empty string`);
1012
+ }
1013
+ if (typeof row.name !== "string" || !row.name.trim()) {
1014
+ errors.push(`${path}.name must be a non-empty string`);
1015
+ } else if (row.name.length > NAV_FOLDER_NAME_MAX) {
1016
+ errors.push(`${path}.name must be ${NAV_FOLDER_NAME_MAX} characters or fewer`);
1017
+ }
1018
+ if (row.order !== undefined && !isFiniteInt(row.order)) {
1019
+ errors.push(`${path}.order must be a finite integer when present`);
1020
+ }
1021
+ if (row.collapsed !== undefined && typeof row.collapsed !== "boolean") {
1022
+ errors.push(`${path}.collapsed must be a boolean when present`);
1023
+ }
1024
+ if (row.icon !== undefined && typeof row.icon !== "string") {
1025
+ errors.push(`${path}.icon must be a string when present`);
1026
+ }
1027
+ if (row.color !== undefined && typeof row.color !== "string") {
1028
+ errors.push(`${path}.color must be a string when present`);
1029
+ }
1030
+ if (row.iconBg !== undefined && typeof row.iconBg !== "string") {
1031
+ errors.push(`${path}.iconBg must be a string when present`);
1032
+ }
1033
+ if (row.items === undefined) return;
1034
+ if (!Array.isArray(row.items)) {
1035
+ errors.push(`${path}.items must be an array`);
1036
+ return;
1037
+ }
1038
+ const seenItemIds = new Set();
1039
+ row.items.forEach((item, index) => {
1040
+ const ipfx = `${path}.items[${index}]`;
1041
+ if (!isPlainObject(item)) {
1042
+ errors.push(`${ipfx} must be a plain object`);
1043
+ return;
1044
+ }
1045
+ if (typeof item.id !== "string" || !item.id.trim()) {
1046
+ errors.push(`${ipfx}.id must be a non-empty string`);
1047
+ } else if (seenItemIds.has(item.id)) {
1048
+ errors.push(`${ipfx}.id duplicates an earlier item id in this folder`);
1049
+ } else {
1050
+ seenItemIds.add(item.id);
1051
+ }
1052
+ if (!NAV_ITEM_TYPES.includes(item.type)) {
1053
+ errors.push(`${ipfx}.type must be one of ${NAV_ITEM_TYPES.join(", ")}`);
1054
+ }
1055
+ if (item.label !== undefined) {
1056
+ if (typeof item.label !== "string") {
1057
+ errors.push(`${ipfx}.label must be a string when present`);
1058
+ } else if (item.label.length > NAV_ITEM_LABEL_MAX) {
1059
+ errors.push(`${ipfx}.label must be ${NAV_ITEM_LABEL_MAX} characters or fewer`);
1060
+ }
1061
+ }
1062
+ if (item.icon !== undefined && typeof item.icon !== "string") {
1063
+ errors.push(`${ipfx}.icon must be a string when present`);
1064
+ }
1065
+ if (item.color !== undefined && typeof item.color !== "string") {
1066
+ errors.push(`${ipfx}.color must be a string when present`);
1067
+ }
1068
+ if (item.iconBg !== undefined && typeof item.iconBg !== "string") {
1069
+ errors.push(`${ipfx}.iconBg must be a string when present`);
1070
+ }
1071
+ if (item.type === "dashboard") {
1072
+ if (typeof item.refId !== "string" || !item.refId.trim()) {
1073
+ errors.push(`${ipfx}.refId must be a non-empty string for dashboard items`);
1074
+ }
1075
+ } else if (item.type === "view") {
1076
+ if (typeof item.objectId !== "string" || !item.objectId.trim()) {
1077
+ errors.push(`${ipfx}.objectId must be a non-empty string for view items`);
1078
+ }
1079
+ if (item.viewConfig !== undefined) {
1080
+ if (!isPlainObject(item.viewConfig)) {
1081
+ errors.push(`${ipfx}.viewConfig must be a plain object when present`);
1082
+ } else {
1083
+ if (item.viewConfig.columns !== undefined) {
1084
+ validateStringArray(item.viewConfig.columns, `${ipfx}.viewConfig.columns`, errors);
1085
+ }
1086
+ if (item.viewConfig.filters !== undefined && !Array.isArray(item.viewConfig.filters)) {
1087
+ errors.push(`${ipfx}.viewConfig.filters must be an array when present`);
1088
+ }
1089
+ if (item.viewConfig.sort !== undefined && !isPlainObject(item.viewConfig.sort)) {
1090
+ errors.push(`${ipfx}.viewConfig.sort must be a plain object when present`);
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ });
1096
+ }
1097
+
1003
1098
  function validateDataModelConfig(dataModel, errors) {
1004
1099
  if (dataModel === undefined) return;
1005
1100
  if (!isPlainObject(dataModel)) {
@@ -1040,6 +1135,9 @@ function validateDataModelConfig(dataModel, errors) {
1040
1135
  if (object.objectType === "sandbox-environment") {
1041
1136
  validateSandboxEnvironmentRow(row, `${prefix}.rows[${rowIndex}]`, errors);
1042
1137
  }
1138
+ if (object.id === NAV_FOLDERS_OBJECT_ID) {
1139
+ validateNavFolderRow(row, `${prefix}.rows[${rowIndex}]`, errors);
1140
+ }
1043
1141
  });
1044
1142
  }
1045
1143
  validateStaticDataBinding(object.binding, `${prefix}.binding`, errors);
@@ -1316,6 +1414,10 @@ export {
1316
1414
  DEFAULT_SANDBOX_ADAPTER,
1317
1415
  SANDBOX_DEFAULT_TIMEOUT_MS,
1318
1416
  SANDBOX_MAX_TIMEOUT_MS,
1417
+ NAV_FOLDERS_OBJECT_ID,
1418
+ NAV_FOLDER_NAME_MAX,
1419
+ NAV_ITEM_LABEL_MAX,
1420
+ NAV_ITEM_TYPES,
1319
1421
  NORMALIZED_OBJECT_FIELD_IDS,
1320
1422
  SAMPLE_DATA_BINDINGS,
1321
1423
  SAMPLE_VIEW_ROWS,