@growthub/cli 0.13.7 → 0.13.9
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-custom-workspace-starter-v1/apps/workspace/app/api/workspace/codex-sites/route.js +13 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +98 -34
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +106 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +17 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceContributionGraph.jsx +119 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +357 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +488 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensWalkthrough.jsx +69 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +105 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +37 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +382 -32
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/codex-sites-data-model-card.jsx +81 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/page.jsx +31 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/settings-accordion-section.jsx +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +192 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-lens/page.jsx +76 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +140 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/codex-sites-local-state.js +139 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/codex-sites-workspace-adapter.js +156 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +1025 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +24 -8
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +5 -0
- package/dist/index.js +5224 -5225
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ import { promises as fs } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { readWorkspaceConfig } from "@/lib/workspace-config";
|
|
5
5
|
import { AppsList } from "./apps-list.jsx";
|
|
6
|
+
import { CodexSitesDataModelCard } from "./codex-sites-data-model-card.jsx";
|
|
7
|
+
import { SettingsAccordionGroup, SettingsAccordionSection } from "./settings-accordion-section.jsx";
|
|
6
8
|
|
|
7
9
|
async function readForkMetadata() {
|
|
8
10
|
try {
|
|
@@ -82,24 +84,39 @@ async function AppsSettingsPage() {
|
|
|
82
84
|
<div className="workspace-settings-card-heading">
|
|
83
85
|
<div>
|
|
84
86
|
<h2>Apps</h2>
|
|
85
|
-
<p>
|
|
87
|
+
<p>Workspace apps discovered from the local apps directory and governed Data Model configuration.</p>
|
|
86
88
|
</div>
|
|
87
89
|
</div>
|
|
88
90
|
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
<SettingsAccordionGroup defaultOpenId="workspace-apps">
|
|
92
|
+
<SettingsAccordionSection
|
|
93
|
+
id="workspace-apps"
|
|
94
|
+
title="Workspace Apps"
|
|
95
|
+
summary={`${apps.length} app${apps.length === 1 ? "" : "s"} discovered from directory and config.`}
|
|
96
|
+
className="workspace-apps-list-section"
|
|
97
|
+
>
|
|
98
|
+
<AppsList apps={apps} />
|
|
99
|
+
</SettingsAccordionSection>
|
|
100
|
+
|
|
101
|
+
<SettingsAccordionSection
|
|
102
|
+
id="workspace-linkage"
|
|
103
|
+
title="Workspace Linkage"
|
|
104
|
+
summary="Fork, kit, and bridge identity for this workspace."
|
|
105
|
+
className="workspace-apps-linkage-section"
|
|
106
|
+
>
|
|
107
|
+
<div className="workspace-settings-kv">
|
|
108
|
+
<span>Workspace</span><code>{workspaceConfig.id || "workspace-builder-default"}</code>
|
|
109
|
+
<span>Fork</span><code>{fork?.forkId || "local fork metadata unavailable"}</code>
|
|
110
|
+
<span>Kit</span><code>{fork?.kitId || workspaceConfig.provenance?.mirrors || "growthub-custom-workspace-starter-v1"}</code>
|
|
111
|
+
<span>Bridge</span><code>{bridge?.status || bridge?.id || "not connected"}</code>
|
|
112
|
+
</div>
|
|
113
|
+
</SettingsAccordionSection>
|
|
98
114
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
<CodexSitesDataModelCard
|
|
116
|
+
apps={apps}
|
|
117
|
+
dataModel={workspaceConfig.dataModel || {}}
|
|
118
|
+
/>
|
|
119
|
+
</SettingsAccordionGroup>
|
|
103
120
|
</section>
|
|
104
121
|
</SettingsShell>;
|
|
105
122
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState } from "react";
|
|
4
|
+
import { ChevronDown } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
const SettingsAccordionContext = createContext(null);
|
|
7
|
+
|
|
8
|
+
function SettingsAccordionGroup({ defaultOpenId, children }) {
|
|
9
|
+
const [openId, setOpenId] = useState(defaultOpenId || null);
|
|
10
|
+
return <SettingsAccordionContext.Provider value={{ openId, setOpenId }}>
|
|
11
|
+
{children}
|
|
12
|
+
</SettingsAccordionContext.Provider>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SettingsAccordionSection({ id, title, summary, className = "", defaultOpen = false, children }) {
|
|
16
|
+
const group = useContext(SettingsAccordionContext);
|
|
17
|
+
const [localOpen, setLocalOpen] = useState(defaultOpen);
|
|
18
|
+
const open = group ? group.openId === id : localOpen;
|
|
19
|
+
const sectionClass = [
|
|
20
|
+
"workspace-settings-section",
|
|
21
|
+
"workspace-settings-accordion",
|
|
22
|
+
open ? "is-open" : "is-collapsed",
|
|
23
|
+
className
|
|
24
|
+
].filter(Boolean).join(" ");
|
|
25
|
+
const toggle = () => {
|
|
26
|
+
if (group) {
|
|
27
|
+
group.setOpenId(open ? null : id);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
setLocalOpen((value) => !value);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return <section className={sectionClass}>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
className="workspace-settings-accordion-trigger"
|
|
37
|
+
aria-expanded={open}
|
|
38
|
+
onClick={toggle}
|
|
39
|
+
>
|
|
40
|
+
<span>
|
|
41
|
+
<h3>{title}</h3>
|
|
42
|
+
{summary ? <em>{summary}</em> : null}
|
|
43
|
+
</span>
|
|
44
|
+
<ChevronDown size={16} aria-hidden="true" />
|
|
45
|
+
</button>
|
|
46
|
+
{open ? <div className="workspace-settings-accordion-body">{children}</div> : null}
|
|
47
|
+
</section>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { SettingsAccordionGroup, SettingsAccordionSection };
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
Plus,
|
|
46
46
|
Quote,
|
|
47
47
|
RefreshCw,
|
|
48
|
+
Rocket,
|
|
48
49
|
Rows3,
|
|
49
50
|
Save,
|
|
50
51
|
Search,
|
|
@@ -59,6 +60,8 @@ import {
|
|
|
59
60
|
Trash2,
|
|
60
61
|
Type,
|
|
61
62
|
Users,
|
|
63
|
+
Eye,
|
|
64
|
+
Wrench,
|
|
62
65
|
X,
|
|
63
66
|
Zap,
|
|
64
67
|
} from "lucide-react";
|
|
@@ -87,6 +90,11 @@ import {
|
|
|
87
90
|
} from "@/lib/workspace-chart-values";
|
|
88
91
|
import { selectObjectFilterableFields, selectObjectSortableFields } from "@/lib/workspace-metadata-selectors";
|
|
89
92
|
import { deriveWorkspaceActivationState } from "@/lib/workspace-activation";
|
|
93
|
+
import {
|
|
94
|
+
CODEX_SITES_OBJECT_ID,
|
|
95
|
+
ensureCodexSitesDataModel,
|
|
96
|
+
isCodexSiteUrl
|
|
97
|
+
} from "@/lib/codex-sites-workspace-adapter";
|
|
90
98
|
import { HelperSidecar } from "./data-model/components/HelperSidecar.jsx";
|
|
91
99
|
import { WorkspaceRail } from "./workspace-rail.jsx";
|
|
92
100
|
import { WorkspaceActivationPanel } from "./components/WorkspaceActivationPanel.jsx";
|
|
@@ -501,6 +509,34 @@ function listBuilderWorkflowItems(config) {
|
|
|
501
509
|
});
|
|
502
510
|
}
|
|
503
511
|
|
|
512
|
+
function listBuilderSiteItems(config) {
|
|
513
|
+
const object = getDataModelObject(config, CODEX_SITES_OBJECT_ID);
|
|
514
|
+
const rows = Array.isArray(object?.rows) ? object.rows : [];
|
|
515
|
+
return rows.flatMap((row, index) => {
|
|
516
|
+
const url = String(row?.url || "").trim();
|
|
517
|
+
if (!isCodexSiteUrl(url)) return [];
|
|
518
|
+
const title = String(row?.Name || row?.name || `Codex Site ${index + 1}`).trim();
|
|
519
|
+
const rawStatus = String(row?.status || "").trim().toLowerCase();
|
|
520
|
+
const status = rawStatus === "active" ? "live" : rawStatus || "draft";
|
|
521
|
+
return [{
|
|
522
|
+
type: "site",
|
|
523
|
+
id: String(row?.id || row?.Name || `codex-site-${index + 1}`),
|
|
524
|
+
title,
|
|
525
|
+
itemKind: "Site",
|
|
526
|
+
updatedAt: formatBuilderTimestamp(row?.lastRecordedAt || ""),
|
|
527
|
+
status,
|
|
528
|
+
site: {
|
|
529
|
+
row,
|
|
530
|
+
rowIndex: index,
|
|
531
|
+
title,
|
|
532
|
+
url,
|
|
533
|
+
app: String(row?.app || "apps/workspace").trim(),
|
|
534
|
+
client: String(row?.client || "Workspace").trim()
|
|
535
|
+
}
|
|
536
|
+
}];
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
504
540
|
function formatBuilderTimestamp(value) {
|
|
505
541
|
const raw = String(value || "").trim();
|
|
506
542
|
if (!raw || raw === "new") return raw;
|
|
@@ -4244,6 +4280,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4244
4280
|
const canvas = config.canvas;
|
|
4245
4281
|
const dashboards = config.dashboards || [];
|
|
4246
4282
|
const workflows = useMemo(() => listBuilderWorkflowItems(config), [config]);
|
|
4283
|
+
const sites = useMemo(() => listBuilderSiteItems(config), [config]);
|
|
4247
4284
|
const builderItems = useMemo(() => {
|
|
4248
4285
|
const dashboardItems = dashboards.map((dashboard, index) => ({
|
|
4249
4286
|
type: "dashboard",
|
|
@@ -4264,13 +4301,17 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4264
4301
|
status: workflow.lifecycleStatus || "draft",
|
|
4265
4302
|
workflow
|
|
4266
4303
|
}));
|
|
4304
|
+
const siteItems = sites.map((site) => ({
|
|
4305
|
+
...site,
|
|
4306
|
+
updatedAt: site.updatedAt || "new"
|
|
4307
|
+
}));
|
|
4267
4308
|
const q = builderListFilter.query.trim().toLowerCase();
|
|
4268
|
-
return [...dashboardItems, ...workflowItems].filter((item) => {
|
|
4309
|
+
return [...dashboardItems, ...siteItems, ...workflowItems].filter((item) => {
|
|
4269
4310
|
if (builderListFilter.type !== "all" && item.type !== builderListFilter.type) return false;
|
|
4270
4311
|
if (!q) return true;
|
|
4271
|
-
return [item.title, item.itemKind, item.status, item.type].some((part) => String(part || "").toLowerCase().includes(q));
|
|
4312
|
+
return [item.title, item.itemKind, item.status, item.type, item.site?.client, item.site?.app, item.site?.url].some((part) => String(part || "").toLowerCase().includes(q));
|
|
4272
4313
|
});
|
|
4273
|
-
}, [builderListFilter, dashboards, workflows]);
|
|
4314
|
+
}, [builderListFilter, dashboards, sites, workflows]);
|
|
4274
4315
|
const resolvedActiveDashboardId = getActiveDashboardId(dashboards, activeDashboardId);
|
|
4275
4316
|
const resolvedActiveDashboardIndex = activeDashboardIndex(dashboards, resolvedActiveDashboardId);
|
|
4276
4317
|
const widgetTypes = config.widgetTypes;
|
|
@@ -4316,6 +4357,18 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4316
4357
|
workspaceConfig: config,
|
|
4317
4358
|
workspaceSourceRecords,
|
|
4318
4359
|
}), [config, workspaceSourceRecords]);
|
|
4360
|
+
// Safe runtime descriptor for the secondary readiness lenses — assembled from
|
|
4361
|
+
// the persistence/adapter props the builder already receives (no fetch, no
|
|
4362
|
+
// secrets; booleans only).
|
|
4363
|
+
const lensMetadataGraph = useMemo(() => ({
|
|
4364
|
+
runtime: {
|
|
4365
|
+
persistenceMode: persistence?.mode || "",
|
|
4366
|
+
persistenceAdapter: persistence?.mode === "database" ? (adapterConfig?.dataAdapter || null) : null,
|
|
4367
|
+
allowFsWrite: persistence?.mode === "filesystem" && persistence?.canSave === true,
|
|
4368
|
+
nangoConfigured: Boolean(adapterConfig?.nango?.hasSecretKey),
|
|
4369
|
+
deploy: { target: adapterConfig?.deployTarget || "" },
|
|
4370
|
+
},
|
|
4371
|
+
}), [persistence, adapterConfig]);
|
|
4319
4372
|
const activationStarted = activationState.completedCount > 0;
|
|
4320
4373
|
const activationComplete = Boolean(activationState.complete);
|
|
4321
4374
|
const activationUiCache = useMemo(() => getWorkspaceUiCache(config), [config]);
|
|
@@ -4695,6 +4748,50 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4695
4748
|
}
|
|
4696
4749
|
}, [config, saving]);
|
|
4697
4750
|
|
|
4751
|
+
const createCodexSite = useCallback(async () => {
|
|
4752
|
+
if (saving) return;
|
|
4753
|
+
const objects = Array.isArray(config.dataModel?.objects) ? config.dataModel.objects : [];
|
|
4754
|
+
const hasCodexSitesObject = objects.some((object) => object?.id === CODEX_SITES_OBJECT_ID);
|
|
4755
|
+
if (hasCodexSitesObject) {
|
|
4756
|
+
window.open(`/data-model?object=${encodeURIComponent(CODEX_SITES_OBJECT_ID)}`, "_self");
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
const nextDataModel = ensureCodexSitesDataModel(config.dataModel, []);
|
|
4760
|
+
setSaving(true);
|
|
4761
|
+
try {
|
|
4762
|
+
const response = await fetch("/api/workspace", {
|
|
4763
|
+
method: "PATCH",
|
|
4764
|
+
headers: { "content-type": "application/json" },
|
|
4765
|
+
body: JSON.stringify({ dataModel: nextDataModel })
|
|
4766
|
+
});
|
|
4767
|
+
const payload = await response.json();
|
|
4768
|
+
if (!response.ok || !payload.workspaceConfig) {
|
|
4769
|
+
throw new Error(payload.error || "Failed to create Codex Sites object");
|
|
4770
|
+
}
|
|
4771
|
+
setConfig((prev) => ({ ...prev, dataModel: payload.workspaceConfig.dataModel }));
|
|
4772
|
+
setBuilderListFilter({ type: "site", query: "" });
|
|
4773
|
+
setConfigMessage("Created Codex Sites object");
|
|
4774
|
+
window.open(`/data-model?object=${encodeURIComponent(CODEX_SITES_OBJECT_ID)}`, "_self");
|
|
4775
|
+
} catch (error) {
|
|
4776
|
+
setConfigMessage(error.message || "Failed to create Codex Sites object");
|
|
4777
|
+
} finally {
|
|
4778
|
+
setSaving(false);
|
|
4779
|
+
}
|
|
4780
|
+
}, [config, saving]);
|
|
4781
|
+
|
|
4782
|
+
const manageSite = useCallback((site) => {
|
|
4783
|
+
const rowParam = site?.rowIndex !== undefined ? `&row=${encodeURIComponent(String(site.rowIndex))}` : "";
|
|
4784
|
+
window.open(`/data-model?object=${encodeURIComponent(CODEX_SITES_OBJECT_ID)}${rowParam}`, "_self");
|
|
4785
|
+
}, []);
|
|
4786
|
+
|
|
4787
|
+
const openSite = useCallback((site) => {
|
|
4788
|
+
if (site?.url) {
|
|
4789
|
+
window.open(site.url, "_blank", "noopener,noreferrer");
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4792
|
+
manageSite(site);
|
|
4793
|
+
}, [manageSite]);
|
|
4794
|
+
|
|
4698
4795
|
const selectDashboard = useCallback((index) => {
|
|
4699
4796
|
setConfig((prev) => {
|
|
4700
4797
|
const synced = syncActiveDashboard(prev, activeDashboardId);
|
|
@@ -5220,7 +5317,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5220
5317
|
}
|
|
5221
5318
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
5222
5319
|
const menuWidth = 148;
|
|
5223
|
-
const menuHeight = item?.type === "dashboard" ? 136 : 76;
|
|
5320
|
+
const menuHeight = item?.type === "dashboard" ? 136 : item?.type === "site" ? 108 : 76;
|
|
5224
5321
|
const margin = 8;
|
|
5225
5322
|
const left = Math.min(
|
|
5226
5323
|
Math.max(margin, rect.right - menuWidth),
|
|
@@ -5752,6 +5849,40 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5752
5849
|
id: "workspace.builder", group: "Navigation", icon: Home, label: "Go to Builder",
|
|
5753
5850
|
run: () => showDashboardHome()
|
|
5754
5851
|
});
|
|
5852
|
+
list.push({
|
|
5853
|
+
id: "nav.data-model", group: "Navigation", icon: Database, label: "Go to Management (Data Model)",
|
|
5854
|
+
run: () => { window.location.href = "/data-model"; }
|
|
5855
|
+
});
|
|
5856
|
+
|
|
5857
|
+
// Workspace Lens — fast navigation into the post-activation operating
|
|
5858
|
+
// surface and its filtered views. Unlocks once activation completes.
|
|
5859
|
+
const lensReady = Boolean(activationState?.complete);
|
|
5860
|
+
list.push({
|
|
5861
|
+
id: "lens.open", group: "Workspace Lens", icon: Eye,
|
|
5862
|
+
label: lensReady ? "Open Workspace Lens" : "Workspace Lens (finish setup to unlock)",
|
|
5863
|
+
disabled: !lensReady,
|
|
5864
|
+
run: () => { window.location.href = "/workspace-lens"; }
|
|
5865
|
+
});
|
|
5866
|
+
list.push({
|
|
5867
|
+
id: "lens.blocked", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Blocked",
|
|
5868
|
+
disabled: !lensReady,
|
|
5869
|
+
run: () => { window.location.href = "/workspace-lens?filter=blocked"; }
|
|
5870
|
+
});
|
|
5871
|
+
list.push({
|
|
5872
|
+
id: "lens.ready", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Ready",
|
|
5873
|
+
disabled: !lensReady,
|
|
5874
|
+
run: () => { window.location.href = "/workspace-lens?filter=ready"; }
|
|
5875
|
+
});
|
|
5876
|
+
list.push({
|
|
5877
|
+
id: "lens.assignable", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Agent-assignable",
|
|
5878
|
+
disabled: !lensReady,
|
|
5879
|
+
run: () => { window.location.href = "/workspace-lens?filter=assignable"; }
|
|
5880
|
+
});
|
|
5881
|
+
list.push({
|
|
5882
|
+
id: "lens.runs", group: "Workspace Lens", icon: Eye, label: "Workspace Lens — Runs",
|
|
5883
|
+
disabled: !lensReady,
|
|
5884
|
+
run: () => { window.location.href = "/workspace-lens?filter=runs"; }
|
|
5885
|
+
});
|
|
5755
5886
|
|
|
5756
5887
|
return list;
|
|
5757
5888
|
}, [
|
|
@@ -5770,7 +5901,8 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5770
5901
|
saving,
|
|
5771
5902
|
selectedWidget,
|
|
5772
5903
|
showDashboardHome,
|
|
5773
|
-
workspaceView
|
|
5904
|
+
workspaceView,
|
|
5905
|
+
activationState
|
|
5774
5906
|
]);
|
|
5775
5907
|
|
|
5776
5908
|
return <main className="workspace-builder" onPointerDownCapture={resetWidgetSelectionOnOutsidePointer} style={builderStyle}>
|
|
@@ -5798,10 +5930,12 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5798
5930
|
dashboardsSlot={(
|
|
5799
5931
|
<button
|
|
5800
5932
|
type="button"
|
|
5933
|
+
title="Builder"
|
|
5801
5934
|
className={workspaceView === "dashboards" ? "active workspace-nav-button" : "workspace-nav-button"}
|
|
5802
5935
|
onClick={showDashboardHome}
|
|
5803
5936
|
>
|
|
5804
|
-
|
|
5937
|
+
<Wrench size={15} aria-hidden="true" />
|
|
5938
|
+
<span className="workspace-nav-label">Builder</span>
|
|
5805
5939
|
</button>
|
|
5806
5940
|
)}
|
|
5807
5941
|
managementSlot={(
|
|
@@ -5865,6 +5999,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5865
5999
|
</span>
|
|
5866
6000
|
) : null}
|
|
5867
6001
|
<button type="button" onClick={addDashboard}><Plus size={15} />New Dashboard</button>
|
|
6002
|
+
<button type="button" onClick={createCodexSite} disabled={saving}><Rocket size={15} />New Codex Site</button>
|
|
5868
6003
|
<button type="button" onClick={createWorkflow} disabled={saving}><GitBranch size={15} />New Workflow</button>
|
|
5869
6004
|
<button type="button" onClick={() => importInputRef.current?.click()}><Import size={15} />Import</button>
|
|
5870
6005
|
</div>}
|
|
@@ -5881,6 +6016,8 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5881
6016
|
{showActivationPanel ? <WorkspaceActivationPanel
|
|
5882
6017
|
workspaceConfig={config}
|
|
5883
6018
|
workspaceSourceRecords={workspaceSourceRecords}
|
|
6019
|
+
metadataGraph={lensMetadataGraph}
|
|
6020
|
+
showLenses={true}
|
|
5884
6021
|
onStepAction={(step) => {
|
|
5885
6022
|
if (step?.id === "add-widget") return openAddWidgetBuilder();
|
|
5886
6023
|
if (step?.id === "create-workflow") {
|
|
@@ -5899,13 +6036,14 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5899
6036
|
<section className="workspace-table" id="dashboards" aria-label="Builder">
|
|
5900
6037
|
<div className="workspace-table-heading">
|
|
5901
6038
|
<strong>Builder</strong>
|
|
5902
|
-
<span>{dashboards.length} dashboard{dashboards.length === 1 ? "" : "s"} · {workflows.length} workflow{workflows.length === 1 ? "" : "s"}</span>
|
|
6039
|
+
<span>{dashboards.length} dashboard{dashboards.length === 1 ? "" : "s"} · {sites.length} site{sites.length === 1 ? "" : "s"} · {workflows.length} workflow{workflows.length === 1 ? "" : "s"}</span>
|
|
5903
6040
|
</div>
|
|
5904
6041
|
<div className="workspace-builder-filterbar">
|
|
5905
6042
|
<div className="workspace-builder-filterbar__segments" role="group" aria-label="Builder item type">
|
|
5906
6043
|
{[
|
|
5907
6044
|
["all", "All"],
|
|
5908
6045
|
["dashboard", "Dashboards"],
|
|
6046
|
+
["site", "Sites"],
|
|
5909
6047
|
["workflow", "Workflows"]
|
|
5910
6048
|
].map(([type, label]) => (
|
|
5911
6049
|
<button
|
|
@@ -5999,6 +6137,53 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5999
6137
|
</span>
|
|
6000
6138
|
)}
|
|
6001
6139
|
</span>
|
|
6140
|
+
</div> : item.type === "site" ? <div className="workspace-table-row" key={item.id}>
|
|
6141
|
+
<span className="workspace-dashboard-title">
|
|
6142
|
+
{item.site.url ? (
|
|
6143
|
+
<a href={item.site.url} target="_blank" rel="noreferrer">{item.title}</a>
|
|
6144
|
+
) : (
|
|
6145
|
+
<button
|
|
6146
|
+
className="active"
|
|
6147
|
+
onClick={() => manageSite(item.site)}
|
|
6148
|
+
type="button"
|
|
6149
|
+
>{item.title}</button>
|
|
6150
|
+
)}
|
|
6151
|
+
</span>
|
|
6152
|
+
<span>{item.itemKind}</span>
|
|
6153
|
+
<span>{item.updatedAt}</span>
|
|
6154
|
+
<span>
|
|
6155
|
+
<select
|
|
6156
|
+
aria-label={`Status for ${item.title}`}
|
|
6157
|
+
value={item.status || "draft"}
|
|
6158
|
+
disabled
|
|
6159
|
+
>
|
|
6160
|
+
<option value="draft">draft</option>
|
|
6161
|
+
<option value="review">review</option>
|
|
6162
|
+
<option value="live">live</option>
|
|
6163
|
+
<option value="paused">paused</option>
|
|
6164
|
+
</select>
|
|
6165
|
+
</span>
|
|
6166
|
+
<span className="workspace-dashboard-actions">
|
|
6167
|
+
<button
|
|
6168
|
+
type="button"
|
|
6169
|
+
className="workspace-row-action-trigger"
|
|
6170
|
+
aria-label={`Actions for ${item.title}`}
|
|
6171
|
+
onClick={(event) => openBuilderActionMenu(item, event)}
|
|
6172
|
+
>
|
|
6173
|
+
<MoreVertical size={16} aria-hidden="true" />
|
|
6174
|
+
</button>
|
|
6175
|
+
{builderActionMenuId === item.id && (
|
|
6176
|
+
<span className="workspace-row-action-menu" style={builderActionMenuPlacement || undefined}>
|
|
6177
|
+
{item.site.url ? (
|
|
6178
|
+
<a href={item.site.url} target="_blank" rel="noreferrer" onClick={closeBuilderActionMenu}>Open URL</a>
|
|
6179
|
+
) : (
|
|
6180
|
+
<button type="button" disabled>Open URL</button>
|
|
6181
|
+
)}
|
|
6182
|
+
<button type="button" onClick={() => { closeBuilderActionMenu(); manageSite(item.site); }}>Manage</button>
|
|
6183
|
+
<button type="button" onClick={() => { closeBuilderActionMenu(); window.open("/settings/apps", "_self"); }}>Apps</button>
|
|
6184
|
+
</span>
|
|
6185
|
+
)}
|
|
6186
|
+
</span>
|
|
6002
6187
|
</div> : <div className="workspace-table-row" key={item.id}>
|
|
6003
6188
|
<span className="workspace-dashboard-title">
|
|
6004
6189
|
{editingWorkflowId === item.workflow.id ? <span className="workspace-dashboard-title-editor">
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Suspense } from "react";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { readAdapterConfig } from "@/lib/adapters/env";
|
|
4
|
+
import { describePersistenceMode, readWorkspaceConfig, readWorkspaceSourceRecords } from "@/lib/workspace-config";
|
|
5
|
+
import { deriveWorkspaceActivationState } from "@/lib/workspace-activation";
|
|
6
|
+
import { WorkspaceRail } from "../workspace-rail.jsx";
|
|
7
|
+
import { WorkspaceLensPanel } from "../components/WorkspaceLensPanel.jsx";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* /workspace-lens — the dedicated Workspace Lens surface.
|
|
11
|
+
*
|
|
12
|
+
* Server-rendered and force-dynamic so it always reflects the LIVE workspace
|
|
13
|
+
* artifact (a live operating surface must not be statically baked). Reads the
|
|
14
|
+
* same governed helpers the home page uses, assembles a safe runtime
|
|
15
|
+
* descriptor (no secrets), and gates behind activation completeness:
|
|
16
|
+
* onboarding first, operating surface second.
|
|
17
|
+
*/
|
|
18
|
+
export const dynamic = "force-dynamic";
|
|
19
|
+
|
|
20
|
+
async function WorkspaceLens() {
|
|
21
|
+
const adapter = readAdapterConfig();
|
|
22
|
+
const persistence = describePersistenceMode();
|
|
23
|
+
const workspaceConfig = await readWorkspaceConfig();
|
|
24
|
+
let workspaceSourceRecords = {};
|
|
25
|
+
try {
|
|
26
|
+
workspaceSourceRecords = (await readWorkspaceSourceRecords()) || {};
|
|
27
|
+
} catch {
|
|
28
|
+
workspaceSourceRecords = {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const metadataGraph = {
|
|
32
|
+
runtime: {
|
|
33
|
+
persistenceMode: persistence?.mode || "",
|
|
34
|
+
persistenceAdapter: persistence?.mode === "database" ? (adapter?.dataAdapter || null) : null,
|
|
35
|
+
allowFsWrite: persistence?.mode === "filesystem" && persistence?.canSave === true,
|
|
36
|
+
nangoConfigured: Boolean(adapter?.nango?.hasSecretKey),
|
|
37
|
+
deploy: { target: adapter?.deployTarget || "" },
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const activationComplete = deriveWorkspaceActivationState({ workspaceConfig, workspaceSourceRecords }).complete;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<main className="workspace-builder workspace-lens-page">
|
|
45
|
+
<WorkspaceRail workspaceConfig={workspaceConfig || {}} />
|
|
46
|
+
<section className="workspace-surface workspace-lens-surface">
|
|
47
|
+
<div className="workspace-lens-shell">
|
|
48
|
+
{activationComplete ? (
|
|
49
|
+
<WorkspaceLensPanel
|
|
50
|
+
workspaceConfig={workspaceConfig}
|
|
51
|
+
workspaceSourceRecords={workspaceSourceRecords}
|
|
52
|
+
metadataGraph={metadataGraph}
|
|
53
|
+
/>
|
|
54
|
+
) : (
|
|
55
|
+
<div className="workspace-lens-locked">
|
|
56
|
+
<h1 className="workspace-lens-title">Workspace Lens is locked</h1>
|
|
57
|
+
<p className="workspace-lens-subtitle">
|
|
58
|
+
Finish workspace setup to unlock the live operating surface — state, blocked conditions,
|
|
59
|
+
next actions, and agent-assignable work.
|
|
60
|
+
</p>
|
|
61
|
+
<Link href="/" className="workspace-lens-next-link">Finish setup in the Builder</Link>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
66
|
+
</main>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default function WorkspaceLensPage() {
|
|
71
|
+
return (
|
|
72
|
+
<Suspense fallback={null}>
|
|
73
|
+
<WorkspaceLens />
|
|
74
|
+
</Suspense>
|
|
75
|
+
);
|
|
76
|
+
}
|