@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.
- package/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/package-lock.json +46 -110
- package/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/package.json +4 -1
- package/assets/worker-kits/growthub-creative-video-pipeline-v1/apps/creative-video-pipeline/package-lock.json +898 -3
- package/assets/worker-kits/growthub-creative-video-pipeline-v1/apps/creative-video-pipeline/package.json +4 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +34 -213
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +8 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +911 -8
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/views/[viewId]/page.jsx +206 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +11 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +1275 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +11 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +96 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +1592 -77
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +4 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +1 -0
- 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.
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
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,
|