@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.
Files changed (23) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +98 -34
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +1 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +106 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +189 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceContributionGraph.jsx +119 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +357 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +488 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensWalkthrough.jsx +69 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +37 -2
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +37 -2
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +437 -26
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +44 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +592 -41
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-lens/page.jsx +76 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +148 -4
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +1559 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +3 -3
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +24 -8
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +82 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +8 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +4 -4
  22. package/dist/index.js +5224 -5225
  23. 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
- Builder
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
- Management
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
- Workspace Settings
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
  }