diff --git a/apps/app/src/components-v2/layout/WorkspaceSwitcher.vue b/apps/app/src/components-v2/layout/WorkspaceSwitcher.vue
index 35d94891..e371917c 100644
--- a/apps/app/src/components-v2/layout/WorkspaceSwitcher.vue
+++ b/apps/app/src/components-v2/layout/WorkspaceSwitcher.vue
@@ -57,9 +57,24 @@ interface WorkspaceDisplay {
id: string
initials: string
name: string
+ /**
+ * Light-grey second line under the name. Plan 2.5 P6-styling-switcher-sub
+ * re-adds the two-line layout (reverses AD-2.5-W1 option A) after visual
+ * review against the crewli-starter SoT. The value is a neutral
+ * PLACEHOLDER — the real org type + metrics (e.g. "Festival · 12 days ·
+ * 14 stages") require the organisations.type enum + a metrics endpoint,
+ * which stay deferred under WORKSPACE-DROPDOWN-SUB-CONTENT. The org
+ * object exposes no field that reads well as a subtitle today (only
+ * id/name/slug/role; role is an access identifier, not a description),
+ * so a static neutral string is used rather than fabricated metrics.
+ */
+ sub: string
gradient: [string, string]
}
+// TODO: replace with org type + metrics when WORKSPACE-DROPDOWN-SUB-CONTENT backend lands
+const SUB_PLACEHOLDER = 'Organisatie'
+
function buildDisplay(org: Organisation): WorkspaceDisplay {
const words = org.name.trim().split(/\s+/)
@@ -72,6 +87,7 @@ function buildDisplay(org: Organisation): WorkspaceDisplay {
id: org.id,
initials,
name: org.name,
+ sub: SUB_PLACEHOLDER,
gradient: computeOrgGradient(org.id),
}
}
@@ -159,22 +175,28 @@ function inviteUser(): void {
:class="collapsed ? 'h-[56px] flex items-center px-4' : 'px-4 py-2'"
>
+
{{ current.name }}
+
+ {{ current.sub }}
+ {{ ws.initials }}
-
-
{{ ws.name }}
+
+
{{ ws.name }}
+
{{ ws.sub }}
diff --git a/apps/app/src/components-v2/layout/__tests__/WorkspaceSwitcher.spec.ts b/apps/app/src/components-v2/layout/__tests__/WorkspaceSwitcher.spec.ts
index d72695a8..84c7f549 100644
--- a/apps/app/src/components-v2/layout/__tests__/WorkspaceSwitcher.spec.ts
+++ b/apps/app/src/components-v2/layout/__tests__/WorkspaceSwitcher.spec.ts
@@ -2,8 +2,10 @@
* WorkspaceSwitcher.spec.ts
*
* Locks two slices of contract:
- * 1. AD-2.5-W1 + AD-2.5-W1 option A: NO sub line on trigger OR on any
- * dropdown row.
+ * 1. Sub line (P6-styling-switcher-sub reverses AD-2.5-W1 option A):
+ * a placeholder sub line renders under the name in the expanded
+ * trigger AND on each dropdown row; the collapsed trigger stays
+ * bare-avatar-only (no sub).
* 2. Plan 2.5 P5 Fix 5: dropdown panel structure per crewli-starter —
* header (title + manage link), list (one .opt per org, .is-current
* + .check-mark on active), footer (two buttons).
@@ -71,14 +73,36 @@ describe('WorkspaceSwitcher', () => {
})
// -------------------------------------------------------------------------
- // AD-2.5-W1 — no sub on trigger
+ // P6-styling-switcher-sub — placeholder sub line (reverses option A)
// -------------------------------------------------------------------------
- it('does not render a sub line on the trigger (AD-2.5-W1)', () => {
- const wrapper = mountSwitcher({ orgs: [orgA] })
+ it('renders a placeholder sub line under the name in the expanded trigger', () => {
+ const wrapper = mountSwitcher({ orgs: [orgA], collapsed: false })
- expect(wrapper.text()).not.toContain(orgA.role)
- expect(wrapper.html()).not.toMatch(/workspace-sub|ws-sub|meta-sub/)
+ const sub = wrapper.find('.meta .sub')
+
+ expect(sub.exists()).toBe(true)
+ expect(sub.text().length).toBeGreaterThan(0)
+ })
+
+ it('the trigger sub is a neutral placeholder, NOT the org role', () => {
+ const wrapper = mountSwitcher({ orgs: [orgA], collapsed: false })
+
+ // The placeholder must not leak the access-control role string —
+ // that was the original AD-2.5-W1 concern; the reversal restores the
+ // line but with honest placeholder content, not org.role.
+ expect(wrapper.find('.meta .sub').text()).not.toContain(orgA.role)
+ })
+
+ it('does not render a sub line in the collapsed trigger (bare avatar only)', () => {
+ const wrapper = mountSwitcher({ orgs: [orgA], collapsed: true })
+
+ // The trigger sub lives inside `.meta`, which only the expanded
+ // trigger renders. The dropdown's `.opt .sub` rows render inline
+ // (Popover stubbed) regardless of collapse state, so assert against
+ // the trigger-scoped `.meta .sub` rather than a bare `.sub`.
+ expect(wrapper.find('.meta').exists()).toBe(false)
+ expect(wrapper.find('.meta .sub').exists()).toBe(false)
})
it('renders the workspace name on the trigger', () => {
@@ -105,15 +129,17 @@ describe('WorkspaceSwitcher', () => {
expect(wrapper.find('.trigger').classes()).not.toContain('justify-center')
})
- it('collapsed renders a bare avatar button (no .trigger container, just .ws-logo)', () => {
+ it('collapsed renders a bare avatar button (no .trigger container, no .meta)', () => {
const wrapper = mountSwitcher({ collapsed: true })
// The padded trigger container with name/chevron is gone — no .trigger,
- // no .meta. The avatar IS the button (.ws-logo + .ws-logo-square on the
- // bare