@growthub/cli 0.13.6 → 0.13.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/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/metadata-graph/route.js +1 -0
- 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 +189 -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/HelperSidecar.jsx +37 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +37 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +437 -26
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +44 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +592 -41
- 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 +148 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +1559 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +3 -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/apps/workspace/lib/workspace-metadata-store.js +82 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +4 -4
- package/dist/index.js +5224 -5225
- package/package.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -36,14 +36,18 @@ import Link from "next/link";
|
|
|
36
36
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
37
37
|
import {
|
|
38
38
|
Archive,
|
|
39
|
+
BarChart3,
|
|
39
40
|
ChevronDown,
|
|
40
41
|
ChevronRight,
|
|
42
|
+
Database,
|
|
43
|
+
Eye,
|
|
41
44
|
Folder,
|
|
42
45
|
FolderPlus,
|
|
43
46
|
GitBranch,
|
|
44
47
|
Home,
|
|
45
48
|
LayoutDashboard,
|
|
46
49
|
MessageCircle,
|
|
50
|
+
Wrench,
|
|
47
51
|
MessageCirclePlus,
|
|
48
52
|
MoreHorizontal,
|
|
49
53
|
MoreVertical,
|
|
@@ -66,6 +70,34 @@ import {
|
|
|
66
70
|
nextNavItemId,
|
|
67
71
|
} from "@/lib/workspace-helper-apply";
|
|
68
72
|
import { listAvailableWorkflows } from "@/lib/nav-workflows";
|
|
73
|
+
import { deriveWorkspaceActivationState, deriveLensWalkthroughState, LENS_WALKTHROUGH_DISMISS_FLAG } from "@/lib/workspace-activation";
|
|
74
|
+
import { WorkspaceLensWalkthrough } from "./components/WorkspaceLensWalkthrough.jsx";
|
|
75
|
+
import { isHelperConfigured, WorkspaceHelperSetupModal } from "./components/WorkspaceHelperSetupModal.jsx";
|
|
76
|
+
|
|
77
|
+
// Set a flag on the governed workspace-ui-cache "activation" row (pure
|
|
78
|
+
// transform) — the same row the onboarding dismiss persists to. Returned
|
|
79
|
+
// config is PATCHed via the existing /api/workspace boundary.
|
|
80
|
+
function withUiCacheFlag(config, key, value) {
|
|
81
|
+
const dataModel = config?.dataModel && typeof config.dataModel === "object" ? config.dataModel : {};
|
|
82
|
+
const objects = Array.isArray(dataModel.objects) ? dataModel.objects : [];
|
|
83
|
+
const existing = objects.find((o) => o?.id === "workspace-ui-cache");
|
|
84
|
+
const cacheObject = existing || {
|
|
85
|
+
id: "workspace-ui-cache", label: "Workspace UI Cache", source: "Workspace UI Cache",
|
|
86
|
+
objectType: "custom", icon: "Settings", columns: ["id", key], rows: [],
|
|
87
|
+
binding: { mode: "manual", source: "Workspace UI Cache" },
|
|
88
|
+
};
|
|
89
|
+
const columns = Array.from(new Set([...(Array.isArray(cacheObject.columns) ? cacheObject.columns : ["id"]), key]));
|
|
90
|
+
const rows = Array.isArray(cacheObject.rows) ? cacheObject.rows : [];
|
|
91
|
+
const hasRow = rows.some((r) => r?.id === "activation");
|
|
92
|
+
const nextRows = hasRow
|
|
93
|
+
? rows.map((r) => (r?.id === "activation" ? { ...r, [key]: value } : r))
|
|
94
|
+
: [...rows, { id: "activation", [key]: value }];
|
|
95
|
+
const nextCache = { ...cacheObject, columns, rows: nextRows };
|
|
96
|
+
const nextObjects = existing
|
|
97
|
+
? objects.map((o) => (o?.id === "workspace-ui-cache" ? nextCache : o))
|
|
98
|
+
: [...objects, nextCache];
|
|
99
|
+
return { ...config, dataModel: { ...dataModel, objects: nextObjects } };
|
|
100
|
+
}
|
|
69
101
|
import { ICON_PICKER_SET, LucideIcon } from "./data-model/components/dm-shared.jsx";
|
|
70
102
|
|
|
71
103
|
function textColorForAccent(accent) {
|
|
@@ -1494,11 +1526,50 @@ export function WorkspaceRail({
|
|
|
1494
1526
|
// moved to the Workspace Settings → Ownership tab.
|
|
1495
1527
|
managementSlot: _managementSlotDeprecated,
|
|
1496
1528
|
settingsSlot,
|
|
1529
|
+
// Customer Activation Layer V1 — optional rail-friendly slot rendered
|
|
1530
|
+
// above the Home tab nav items. The parent page owns the content
|
|
1531
|
+
// (typically a compact WorkspaceActivationPanel) so the rail stays
|
|
1532
|
+
// surface-agnostic.
|
|
1533
|
+
activationSlot,
|
|
1497
1534
|
}) {
|
|
1498
1535
|
const branding = workspaceConfig?.branding || {};
|
|
1499
1536
|
const workspaceName = branding.name || workspaceConfig?.name || "Growthub Workspace";
|
|
1500
1537
|
const pathname = usePathname() || "/";
|
|
1501
1538
|
const router = useRouter();
|
|
1539
|
+
// Workspace Lens unlocks only after the primary activation loop completes —
|
|
1540
|
+
// onboarding first, operating surface second. Derived from the same config
|
|
1541
|
+
// the rail already holds, so the gate is consistent across every page.
|
|
1542
|
+
const lensUnlocked = useMemo(
|
|
1543
|
+
() => Boolean(deriveWorkspaceActivationState({ workspaceConfig: workspaceConfig || {} }).complete),
|
|
1544
|
+
[workspaceConfig],
|
|
1545
|
+
);
|
|
1546
|
+
// One-time Workspace Lens reveal: shown anchored to the (newly visible) lens
|
|
1547
|
+
// nav item only in the in-between state, and never on the lens page itself.
|
|
1548
|
+
const lensWalkthrough = useMemo(
|
|
1549
|
+
() => deriveLensWalkthroughState({ workspaceConfig: workspaceConfig || {} }),
|
|
1550
|
+
[workspaceConfig],
|
|
1551
|
+
);
|
|
1552
|
+
const showLensReveal = lensWalkthrough.show && pathname === "/";
|
|
1553
|
+
const lensNavRef = useRef(null);
|
|
1554
|
+
const [lensRevealStyle, setLensRevealStyle] = useState(null);
|
|
1555
|
+
const dismissLensWalkthrough = useCallback(async () => {
|
|
1556
|
+
const next = withUiCacheFlag(workspaceConfig || {}, LENS_WALKTHROUGH_DISMISS_FLAG, true);
|
|
1557
|
+
if (onConfigChange) onConfigChange(next);
|
|
1558
|
+
try {
|
|
1559
|
+
const res = await fetch("/api/workspace", {
|
|
1560
|
+
method: "PATCH",
|
|
1561
|
+
headers: { "content-type": "application/json" },
|
|
1562
|
+
body: JSON.stringify({ dataModel: next.dataModel }),
|
|
1563
|
+
});
|
|
1564
|
+
const body = await res.json();
|
|
1565
|
+
if (body?.workspaceConfig && onConfigChange) onConfigChange(body.workspaceConfig);
|
|
1566
|
+
} catch {
|
|
1567
|
+
/* optimistic update already applied; cache write is best-effort */
|
|
1568
|
+
}
|
|
1569
|
+
}, [workspaceConfig, onConfigChange]);
|
|
1570
|
+
const enterLensWalkthrough = useCallback(() => {
|
|
1571
|
+
router.push("/workspace-lens?walkthrough=2");
|
|
1572
|
+
}, [router]);
|
|
1502
1573
|
|
|
1503
1574
|
const [activeTab, setActiveTab] = useState("home");
|
|
1504
1575
|
const [railCollapsed, setRailCollapsed] = useState(Boolean(defaultCollapsed));
|
|
@@ -1507,6 +1578,7 @@ export function WorkspaceRail({
|
|
|
1507
1578
|
const [renameDraft, setRenameDraft] = useState("");
|
|
1508
1579
|
const [chatSearch, setChatSearch] = useState("");
|
|
1509
1580
|
const [chatExpanded, setChatExpanded] = useState(false);
|
|
1581
|
+
const [helperSetupOpen, setHelperSetupOpen] = useState(false);
|
|
1510
1582
|
const menuWrapRef = useRef(null);
|
|
1511
1583
|
const CHAT_PREVIEW_COUNT = 10;
|
|
1512
1584
|
|
|
@@ -1520,6 +1592,31 @@ export function WorkspaceRail({
|
|
|
1520
1592
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
1521
1593
|
}, [openMenuId]);
|
|
1522
1594
|
|
|
1595
|
+
useEffect(() => {
|
|
1596
|
+
if (!showLensReveal) {
|
|
1597
|
+
setLensRevealStyle(null);
|
|
1598
|
+
return undefined;
|
|
1599
|
+
}
|
|
1600
|
+
const updatePosition = () => {
|
|
1601
|
+
const rect = lensNavRef.current?.getBoundingClientRect();
|
|
1602
|
+
if (!rect) return;
|
|
1603
|
+
const width = 264;
|
|
1604
|
+
const gap = 12;
|
|
1605
|
+
const minLeft = 16;
|
|
1606
|
+
const maxLeft = Math.max(minLeft, window.innerWidth - width - 16);
|
|
1607
|
+
const left = Math.min(Math.max(rect.right + gap, minLeft), maxLeft);
|
|
1608
|
+
const top = Math.max(16, Math.round(rect.top - 4));
|
|
1609
|
+
setLensRevealStyle({ left, top });
|
|
1610
|
+
};
|
|
1611
|
+
updatePosition();
|
|
1612
|
+
window.addEventListener("resize", updatePosition);
|
|
1613
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
1614
|
+
return () => {
|
|
1615
|
+
window.removeEventListener("resize", updatePosition);
|
|
1616
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
1617
|
+
};
|
|
1618
|
+
}, [showLensReveal, railCollapsed]);
|
|
1619
|
+
|
|
1523
1620
|
const threads = useMemo(() => getHelperThreadRows(workspaceConfig), [workspaceConfig]);
|
|
1524
1621
|
|
|
1525
1622
|
useEffect(() => {
|
|
@@ -1533,6 +1630,10 @@ export function WorkspaceRail({
|
|
|
1533
1630
|
}, [railCollapsed]);
|
|
1534
1631
|
|
|
1535
1632
|
const handleAskHelperClick = () => {
|
|
1633
|
+
if (!isHelperConfigured(workspaceConfig)) {
|
|
1634
|
+
setHelperSetupOpen(true);
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1536
1637
|
if (onOpenHelper) {
|
|
1537
1638
|
onOpenHelper();
|
|
1538
1639
|
return;
|
|
@@ -1740,25 +1841,45 @@ export function WorkspaceRail({
|
|
|
1740
1841
|
model surface IS the user-facing object/list management. */}
|
|
1741
1842
|
{activeTab === "home" ? (
|
|
1742
1843
|
<nav className="workspace-nav" aria-label="Workspace pages">
|
|
1844
|
+
{activationSlot ? (
|
|
1845
|
+
<div className="workspace-rail-activation-slot">{activationSlot}</div>
|
|
1846
|
+
) : null}
|
|
1743
1847
|
{dashboardsSlot ?? (
|
|
1744
|
-
<Link href="/" className={pathname === "/" ? "active" : undefined}>
|
|
1745
|
-
|
|
1848
|
+
<Link href="/" title="Builder" className={pathname === "/" ? "active" : undefined}>
|
|
1849
|
+
<Wrench size={15} aria-hidden="true" />
|
|
1850
|
+
<span className="workspace-nav-label">Builder</span>
|
|
1746
1851
|
</Link>
|
|
1747
1852
|
)}
|
|
1853
|
+
{lensUnlocked ? (
|
|
1854
|
+
<div className="workspace-rail-lens-nav" ref={lensNavRef}>
|
|
1855
|
+
<Link
|
|
1856
|
+
href="/workspace-lens"
|
|
1857
|
+
title="Workspace Lens"
|
|
1858
|
+
className={pathname.startsWith("/workspace-lens") ? "active" : undefined}
|
|
1859
|
+
>
|
|
1860
|
+
<Eye size={15} aria-hidden="true" />
|
|
1861
|
+
<span className="workspace-nav-label">Workspace Lens</span>
|
|
1862
|
+
</Link>
|
|
1863
|
+
</div>
|
|
1864
|
+
) : null}
|
|
1748
1865
|
{dataModelSlot ?? (
|
|
1749
1866
|
<Link
|
|
1750
1867
|
href="/data-model"
|
|
1868
|
+
title="Management"
|
|
1751
1869
|
className={pathname.startsWith("/data-model") ? "active" : undefined}
|
|
1752
1870
|
>
|
|
1753
|
-
|
|
1871
|
+
<Database size={15} aria-hidden="true" />
|
|
1872
|
+
<span className="workspace-nav-label">Management</span>
|
|
1754
1873
|
</Link>
|
|
1755
1874
|
)}
|
|
1756
1875
|
{settingsSlot ?? (
|
|
1757
1876
|
<Link
|
|
1758
1877
|
href="/settings/general"
|
|
1878
|
+
title="Workspace Settings"
|
|
1759
1879
|
className={"workspace-nav-bottom" + (pathname.startsWith("/settings") ? " active" : "")}
|
|
1760
1880
|
>
|
|
1761
|
-
|
|
1881
|
+
<Settings size={15} aria-hidden="true" />
|
|
1882
|
+
<span className="workspace-nav-label">Workspace Settings</span>
|
|
1762
1883
|
</Link>
|
|
1763
1884
|
)}
|
|
1764
1885
|
</nav>
|
|
@@ -1926,6 +2047,29 @@ export function WorkspaceRail({
|
|
|
1926
2047
|
<span className="status-dot" />
|
|
1927
2048
|
{authority || "local-catalog"}
|
|
1928
2049
|
</div>
|
|
2050
|
+
{showLensReveal && lensRevealStyle && typeof document !== "undefined"
|
|
2051
|
+
? createPortal(
|
|
2052
|
+
<WorkspaceLensWalkthrough
|
|
2053
|
+
step={1}
|
|
2054
|
+
className="is-rail-reveal"
|
|
2055
|
+
style={lensRevealStyle}
|
|
2056
|
+
onPrimary={enterLensWalkthrough}
|
|
2057
|
+
onDismiss={dismissLensWalkthrough}
|
|
2058
|
+
/>,
|
|
2059
|
+
document.body,
|
|
2060
|
+
)
|
|
2061
|
+
: null}
|
|
2062
|
+
<WorkspaceHelperSetupModal
|
|
2063
|
+
workspaceConfig={workspaceConfig}
|
|
2064
|
+
open={helperSetupOpen}
|
|
2065
|
+
onClose={() => setHelperSetupOpen(false)}
|
|
2066
|
+
onSaved={(nextConfig) => {
|
|
2067
|
+
setHelperSetupOpen(false);
|
|
2068
|
+
if (onConfigChange) onConfigChange(nextConfig);
|
|
2069
|
+
if (onOpenHelper) onOpenHelper();
|
|
2070
|
+
else router.push("/data-model?helper=open");
|
|
2071
|
+
}}
|
|
2072
|
+
/>
|
|
1929
2073
|
</aside>
|
|
1930
2074
|
);
|
|
1931
2075
|
}
|