From 1cebeb155591353dd949e41c7da2e2ebae5afc4c Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 20 Aug 2026 06:30:04 -0700 Subject: [PATCH 1/5] Add coverage: the team space tenant never appears as a workbench row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /workbenches currently lists the requested tenant's own aggregate row alongside its child workbenches — for the account root that row reads as a workbench named after the account itself. Updates the fixture to expect it excluded (CL-6368). --- packages/insights/test/routes-scope.test.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/insights/test/routes-scope.test.ts b/packages/insights/test/routes-scope.test.ts index 621f9b930..7624fe9a2 100644 --- a/packages/insights/test/routes-scope.test.ts +++ b/packages/insights/test/routes-scope.test.ts @@ -414,17 +414,15 @@ describeIfDb("createInsightsRoutes workspace rollup (deps.db wired)", () => { items: { tenantId: string; name: string; turns: number }[]; }; // Ranked by turns descending — childB has recorded more turns than - // childA across this describe block's fixtures, and the parent - // itself (a rolled-up total, no usage of its own) sorts last. - expect(body.items.map((i) => i.tenantId)).toEqual([ - childBId, - childAId, - parentId, - ]); + // childA across this describe block's fixtures. The parent itself + // is the team space, not a workbench (CL-6368) — it never appears + // as a row here, even though it is the tenant this rollup was + // requested against. + expect(body.items.map((i) => i.tenantId)).toEqual([childBId, childAId]); expect(body.items.find((i) => i.tenantId === childBId)?.name).toBe( "Acme — Sales", ); - expect(body.items.find((i) => i.tenantId === parentId)?.turns).toBe(0); + expect(body.items.some((i) => i.tenantId === parentId)).toBe(false); // The unrelated root tenant never appears in the parent's scope. expect(body.items.some((i) => i.tenantId === unrelatedId)).toBe(false); From 011b3d8372c10eae985028fe3c7ac9028bd719e9 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 20 Aug 2026 06:30:08 -0700 Subject: [PATCH 2/5] Insights: drop the team space tenant from the workbench breakdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The account's root tenant (parentId null) is a container, not a workbench itself — real workbenches are its child tenants (CL-6089). /workbenches was including its own rollup-of-itself row (e.g. "alice's workbench") in the activity-by-workbench chart, alongside the actual children. Exclude it (CL-6368). --- packages/insights/src/routes.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/insights/src/routes.ts b/packages/insights/src/routes.ts index 4434a9f36..1dfd5493f 100644 --- a/packages/insights/src/routes.ts +++ b/packages/insights/src/routes.ts @@ -206,6 +206,13 @@ export function createInsightsRoutes( * link each bar to `/insights/workbench/:tenantId` instead of only * seeing the scope's sum. Calling it for a leaf workbench (no * descendants) returns that one workbench's own row. + * + * A `parentId === null` requested tenant is never itself a workbench — + * it is the account root, the container real workbenches (each its own + * child tenant, CL-6089) live under. Its own row would just be a + * zero-usage rollup-of-itself duplicate of the "All workbenches" + * landing this chart already sits on, so it is dropped rather than + * listed alongside its children (CL-6368). */ app.get( "/workbenches", @@ -219,7 +226,9 @@ export function createInsightsRoutes( summarizeUsageByTenant(deps.store, scope, range), tenantNames(deps.db, tenant.id, tenant.name, scope), ]); + const isTeamSpace = tenant.parentId === null; const items = rows + .filter((row) => !isTeamSpace || row.tenantId !== tenant.id) .map((row) => ({ tenantId: row.tenantId, name: names.get(row.tenantId) ?? row.tenantId, From 880a3907f83938d84605af584b0370908381ec23 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 20 Aug 2026 06:30:12 -0700 Subject: [PATCH 3/5] Onboarding: mint the account root under its own name, not '...'s workbench' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The account's one root tenant is a container real workbenches live under (CL-6089), never a workbench itself. Naming it "…'s workbench" at mint time is what made it show up looking like one everywhere workbenches are listed. Dev-data note: only fresh signups get the corrected name — no rename migration for existing dev tenants (CL-6368). --- apps/web/src/pages/onboarding-page.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/web/src/pages/onboarding-page.tsx b/apps/web/src/pages/onboarding-page.tsx index 56e9b9dca..5f5fdc658 100644 --- a/apps/web/src/pages/onboarding-page.tsx +++ b/apps/web/src/pages/onboarding-page.tsx @@ -55,11 +55,18 @@ import type { SessionUser } from "../session"; * derives one from the account so `/api/onboarding/provision` never gets * called bare. Prefers the account's display name; an account with no * usable name falls back to the email's local part. Editable later from - * Settings, same as any other display name. */ -function defaultWorkbenchName(user: SessionUser): string { + * Settings, same as any other display name. + * + * This names the account's one root tenant — the container real + * workbenches (each its own child tenant, CL-6089) live under, never a + * workbench itself (CL-6368). "…'s workbench" mislabeled it as one; + * every fresh account now mints under its own name instead ("team space" + * / "workspace" stay off the table too — check:ui-vocabulary bans both as + * synonyms the CL-6089 product collapse deliberately retired). */ +function defaultTeamName(user: SessionUser): string { const source = user.name.trim().length > 0 ? user.name.trim() : user.email.split("@")[0]; - return `${source || "Your"}'s workbench`; + return `${source || "Your"}'s team`; } type WizardState = @@ -281,7 +288,7 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) { // or a stale connect error from a duplicate callback this page never // saw resolved — provisions with a default name derived from the // account: there is no naming step to gate this on, so it must always - // send a name (see `defaultWorkbenchName`). A returning member's + // send a name (see `defaultTeamName`). A returning member's // already-provisioned workbench is unaffected — the hub route only // creates one the first time an account has none. useEffect(() => { @@ -306,7 +313,7 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) { }); return; } - runProvisioning(defaultWorkbenchName(user)); + runProvisioning(defaultTeamName(user)); // Mount-only: this reads `state.phase` exactly once, at the value // `initialWizardState` produced, to decide which of the two checks // above applies to this landing. @@ -408,7 +415,7 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) { action={ From 86b34a56511892724c54f4df98118a00148c6e9f Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 20 Aug 2026 06:30:17 -0700 Subject: [PATCH 4/5] Add tests for Skills' table row idiom, and shared chrome across the utility rail pages Row selection is asserted against a , not a @@ -432,31 +427,35 @@ export function SkillsPage({ description={`Nothing matches “${query.trim()}”.`} /> ) : ( -
- {filtered.map((skill) => ( - } - name={ - - {skill.name} - {skill.description} - - } - meta={ - + + + + Name + Description + Access + + + + {filtered.map((skill) => ( + select(skill.name)} > - {skill.scope === "tenant" ? "Shared" : "Private"} - - } - onSelect={() => select(skill.name)} - /> - ))} + {skill.name} + + {skill.description} + + + + {skill.scope === "tenant" ? "Shared" : "Private"} + + + + ))} + +
)} {createDialog}