Skip to content

Workspace Open Page – Implementation Spec

Historical design reference: Figma – node 4013-20296

Use this doc and the current implementation as the source of truth. The Figma link above is legacy context only.

Target file: ciq-mvp2-app/src/app/account/workspaces/[workspaceId]/page.tsx


Use the target layout documented here: header row (title + status chips) → tabs → page-owned toolbar in the same bordered top section → group section + KB cards.


  • Left: Workspace avatar (36×36, rounded 6px, bg #f7f7f7, border #ccc) with photo_camera icon if no image, else workspace image. Then workspace name: 22px, semibold, tracking -0.22px, black.
  • Right: Bordered container (border #e5e5e5, rounded 8px) with:
    • Description control (required): A clickable 36×36px area with the Material Symbols notes icon (~20px) in a muted color (e.g. #4d4d4d). This control must appear before the vertical divider and before the app status chip. Clicking it opens the Description popover.
    • Divider (1px vertical #f0f0f0).
    • App status chip (14px):
      • widgets icon + Not activated when no apps are connected.
      • widgets icon + app names separated by vertical dividers when apps are connected.

Breadcrumbs stay as now (Account > Workspaces > [Name]); they may be rendered by the shell, so keep the existing shell behavior if already present.

When the user clicks the Description control, show a popover (anchored to the description button) that:

  • Content: Shows the workspace description text (e.g. workspace.description). If the workspace has no description, show a short placeholder such as “No description” (or leave the body empty and show that message).
  • Placement: Anchored to the trigger (typically below the button, aligned to its edge). It appears as a small panel next to the trigger — not a centered dialog/modal.
  • No overlay: Do not show a full-screen overlay/backdrop.
  • Dismissal: Clicking outside the popover or pressing Escape closes it. An explicit Close control inside the popover is optional (may be kept for accessibility), but outside-click + Escape is sufficient.
  • Styling (use the same surface styling as the previous modal spec, but as a compact popover panel):
    • Surface: white background, border #e5e5e5, rounded corners, shadow.
    • Body: ~16px padding, 14px body text.
    • Footer: optional; if present, may contain a subtle Close control.

  • Container: bg-[#fafafa] (neutral/20), rounded 12px, p-1, flex gap-1.
  • Tabs: “Knowledge Bases” (icon database, show live KB count in label e.g. Knowledge Bases (5)), “Agents” (icon robot, show live total agents count), “Channels” (icon graph_4, show live workspace connections count), “Users” (icon group, show live users count), “Activity log” (icon fingerprint). 14px, medium weight.
  • Active tab: white bg, rounded 8px, shadow 0px 1px 2px 0px rgba(0,0,0,0.25), text #4d4d4d.
  • Inactive: text #808080, hover #333. No background.
  • Use client state for active tab (e.g. knowledge-bases | users | user-activity).

For workspace tabs (Knowledge Bases, Agents, Channels, Users, Activity log), the toolbar must appear as part of the same visual top section as the header and tabs, separated only by spacing, not by another divider. The page should present a single divider below that combined top section.

  • Left: “All groups” control (stacks icon + label + chevron). Use existing GroupFilterCombobox or a simple Select; for MVP, if no groups in scope, show a read-only pill “All groups” or single group name. Then a filter icon button (filter_list), rounded 8px, border #ccc.
  • Right: Optional sort icon (discover_tune), then view switcher (grid_view / dehaze / graph_1) in a pill (bg #f7f7f7, p-0.5, rounded 8px), then primary button “Add knowledge base” (black bg, white text, rounded 8px, 14px medium). View switcher and Add button are required; sort can be placeholder.

  • Group topper: Reusable GroupBadge component (_components/group-badge.tsx): shadcn Badge (variant secondary, solid) with group name; on hover, three-dots menu with Rename, Add group, Delete. On this page only, Add group adds a KB group (workspace-scoped); validation is against KB group names in this workspace only (so a name that exists at the workspace level, e.g. “test”, is allowed). Rename and Delete apply to the workspace group. Same component is used on the Workspaces page for each group header. When the current workspace’s group is not in state, fallback to a plain Badge with group name only (no menu).
  • KB cards grid: Use the shared KnowledgeBaseCard component from @/app/account/knowledge-bases/_components/knowledge-base-card — the same component used on the Knowledge Bases page. Do not define a local card component.
    • Grid class: grid grid-cols-[repeat(auto-fill,minmax(min(100%,360px),360px))] gap-4 (matches Knowledge Bases page).
    • Enrichment: Call enrichKnowledgeBasesWithChannels(kbs, safeConnections) where safeConnections = isConnectionsLoading ? [] : connections (from useChannels()). Pass the enriched array to the cards.
    • Props per card:
      • kb — enriched KB object
      • workspaceNameworkspace.name
      • openHref`/account/knowledge-bases/${kb.id}`
      • onEdit — navigate to openHref
      • onSetAsMainactions.updateWorkspace(workspace.id, { defaultKbId: kb.id })
      • isDefaultKbworkspace.defaultKbId === kb.id
      • onDuplicateactions.addKnowledgeBase({ ...kb, id: \${kb.id}-copy-${Date.now()}`, name: `${kb.name} (Copy)` })`
      • onDeleteactions.deleteKnowledgeBase(kb.id)
      • canDeletekbs.length > 1 (raw unenriched list length)
    • Overflow menu: Full four-item menu — Edit, Set as default (disabled when isDefaultKb), Duplicate, Delete (disabled when !canDelete). Identical to the Knowledge Bases page.
    • Empty state: If kbs.length === 0, show <p className="text-[14px] text-muted-foreground">No knowledge bases in this workspace.</p>.
    • Details content scope: KB card Details render KB-local fields only (Objectives, Documents).
  • Remove the old local KbCard component and DETAIL_LABELS constant entirely from page.tsx.
  • Add third view mode node in the same switcher as card/table.
  • Selecting node opens the graph in a bottom sheet (side bottom, full viewport height) instead of inline content.
  • The sheet header does not include a workspace dropdown.
  • The workspace node header acts as the single workspace dropdown select for the node view, so users switch workspace directly from the node card.
  • Workspace dropdown behavior:
    • searchable input (Search workspace...) for quick lookup
    • selecting another workspace updates the node graph in-place inside the open sheet (no sheet close/reopen animation)
    • after workspace selection, the graph auto-fits to the viewport so the new network is immediately readable
  • Visualize workspace information in node view:
    • Workspace node (root), showing compact workspace-card data groups in this exact order:
      • Knowledge bases: <num>
      • Agents: <num>
      • Users: <num>
      • Channels: <num>
      • Apps: <labels> (comma-separated connected app names, or Not activated)
  • Current implementation renders a horizontal row of node cards:
    • Workspace node
    • Knowledge Bases list node
    • Agents list node
    • Users list node
    • Channels list node
    • one right-most KB detail node (Objectives, Documents)
  • Initial state renders the Workspace, Knowledge Bases, Agents, Users, Channels, and Apps nodes together. All five sections are open on first load. Clicking section rows in the workspace node toggles visibility of related nodes:
    • Knowledge bases -> Knowledge Bases list + KB detail node
    • Agents -> Agents list node
    • Users -> Users list node
    • Channels -> Channels list node
  • Channels list rows use the six standard channel families (Social comments, Review platforms, FB Messenger, Web chat, WhatsApp, SMS), but the node view only renders families whose live workspace-scoped count is greater than zero.
  • On sheet open, auto-fit the graph to the sheet viewport so users land on a readable full-network view by default.
  • Node cards are draggable. Bezier edge connectors are rendered between each workspace section row and its corresponding visible collection node (Knowledge Bases, Agents, Users, Channels). Edges appear only when the target collection node is visible and are removed when the section is toggled off.
  • Group filter respect: When the node sheet is showing the current page workspace, the graph displays only the knowledge bases that match the active KB group filter. When a different workspace is selected in the sheet dropdown, all KBs for that workspace are shown (no group filter applied).
  • Closing the bottom sheet resets the toggle back to card view.
MetricSource
Knowledge basesCount of KBs passed to node view (post group-filter when sheet = page workspace)
AgentsgetAgentsForWorkspace(workspaceId).length from AgentsContextnot KB-level agentsSetCount
Usersworkspace.numberOfUsers (kept in sync with Users tab list length)
ChannelsTotal connections scoped to the node-sheet workspace KBs
AppsDerived via getWorkspaceAppsFromKbsAndChannels + WORKSPACE_APP_LABEL
  • Purpose: operational channels surface for the current workspace only.
  • Reuses global channels feature actions and dialogs:
    • Add channel wizard
    • Connection settings dialog
    • Delete connection confirmation
  • Scope rule: show only connections linked to knowledge bases belonging to the current workspace.
  • Filters:
    • knowledge base multi-select
    • platform multi-select for social/review tabs
  • Tabs follow channel families:
    • social-comments, review-platforms, facebook-messenger, web-chat, whatsapp, sms
    • each tab label shows the current total count for that channel family in scope (e.g. Social comments (3))

The Knowledge base column behaves differently depending on the channel family — see the full ownership model in Account – Channels“Channel ownership model: Agent → KB → Channel”.

Channel familyKB columnEditable?
Social commentsDirect KB on connectionYes — inline selector
Review platformsDirect KB on connectionYes — inline selector
FB MessengerInherited from assigned agentNo — read-only
Web chatInherited from assigned agentNo — read-only
WhatsAppInherited from assigned agentNo — read-only
SMSInherited from assigned agentNo — read-only

For agent-managed families (FB Messenger, Web chat, WhatsApp, SMS), the KB cell displays the name of the KB owned by the connection’s assigned agent, or when no agent is assigned yet. Users must go to the Agents tab to change the KB for these channels.

On mount (and whenever agents or connections change), the Channels tab checks whether any agent-managed connections (facebook-messenger, web-chat, whatsapp, sms) in this workspace have a linked KB with no agent assigned. For each such KB, it automatically calls createAgent to provision a default agent named "{KB name} Agent" with no channel connections.

  • This runs once per workspaceId + kbId pair per session (tracked via attemptedAutoAgentKeysRef).
  • If createAgent fails for a pair, the attempt key is removed so it can be retried.
  • The provisioned agent starts with channelIds: [] and must be configured manually in the Agents tab.
  • This side-effect is silent — no toast or UI feedback is shown to the user.

Full agent lifecycle management surface. See Workspace – Agents Tab for the complete spec.

Summary:

  • Toolbar: status filter (All / Active / Inactive) + “Add agent” primary CTA.
  • Summary badges: Total agents and Active agents (counts from AgentsContext).
  • Agents table: Name, Knowledge base (inline editable select with Sonner confirm toast), Active (toggle switch), Channels (count badge with hover card), Actions (Edit / Delete row dropdown).
  • Edit/Create: AgentModal with a single General section only.
  • Delete: DeleteAgentDialog confirmation. Delete is blocked for active agents — the row shows a disabled “Delete (deactivate first)” menu item; the context also enforces AGENT_ACTIVE_DELETE_BLOCKED.
  • Data source: getAgentsForWorkspace(workspaceId) from AgentsContext (not KB-level agentsSetCount).

KB ownership role of agents: For agent-managed channel families (FB Messenger, Web chat, WhatsApp, SMS), the agent is the owner of the Knowledge base. Changing an agent’s KB here propagates to all connections assigned to that agent — the KB column in those channel rows updates automatically. This is the correct place to change the effective KB for any agent-managed channel. See Account – Channels“Channel ownership model: Agent → KB → Channel” for the full model.


  • Use existing Icon component (Material Symbols) and project Tailwind conventions.
  • Reuse GroupFilterCombobox or Select from workspaces for “All groups” if groups are available; otherwise a static pill.
  • Colors: border #e5e5e5, #ebebeb; bg #f7f7f7, #fafafa; text #333, #666, #808080; red #ef4444; primary black #0d0d0d.
  • Spacing: section padding 24px (p-6); gaps 8px–12px.

Keep existing “Workspace not found” block and “Back to Workspaces” link unchanged.


  • Workspace: from selectors.getWorkspace(workspaceId) (with seed fallback when state.version === 0).
  • Group name: from selectors.getGroupName(workspace.groupId) (with SEED_GROUPS fallback).
  • KB list: selectors.getKnowledgeBasesForWorkspace(workspace.id) — real KB list from context, enriched with channel counts via enrichKnowledgeBasesWithChannels.
  • Connections: useChannels().connections and useChannels().isConnectionsLoading. Use safeConnections = isConnectionsLoading ? [] : connections to avoid showing stale counts.
  • Workspace channels scope: derive from workspace KB ids when rendering Channels tab.
  • Workspace agents stage data (Agents tab): aggregate knowledgeBase.agentsSetCount.
  • Node view agents count: getAgentsForWorkspace(workspaceId).length from AgentsContext — real agent count, not KB-level agentsSetCount.
  • Workspace users count parity: workspace.numberOfUsers is kept in sync with the Users tab list length, so card/table/node metrics use the same users total.

Global-vs-scoped responsibility split:

  • Global hubs:
    • /account/channels: all workspaces
    • /account/agents: all workspaces (currently coming soon)
  • Workspace route:
    • scoped operational context for one workspace (/account/workspaces/[workspaceId])

Coder: Implement the above in src/app/account/workspaces/[workspaceId]/page.tsx. Preserve not-found handling and client-side only. Remove the local KbCard component and DETAIL_LABELS constant; replace with the shared KnowledgeBaseCard component with channel enrichment and full overflow menu as specified in Section 4.


The Users tab shows a table of users belonging to this workspace. For MVP, data is mock-only (local React state seeded from SEED_WORKSPACE_USERS).

WorkspaceUser (in src/types/workspaces.ts):

FieldTypeNotes
idstringUnique user id
namestringDisplay name
emailstringEmail address
avatarUrlstring?Optional avatar URL
workspaceRole"admin" | "member"Role within this workspace
status"active" | "invited" | "inactive"Membership status
lastActivestring?ISO date of last activity; omit if never active
joinedAtstringISO date when user joined or was invited

Above the users table, show a local section header titled Users plus a live table-item count and a view_column control button aligned to the right.

  • The view_column button opens a popover with per-column toggles, matching the table column-visibility pattern used on other account list surfaces.
  • The first column (user) is always visible and its toggle is disabled.
ColumnAlways visibleNotes
userYesAvatar initial (or image) + name + email
workspaceRoleNoBadge: Admin (primary tint) / Member (muted)
statusNoBadge: Active (success), Invited (warning), Inactive (muted)
lastActiveNoRelative date (e.g. “2d ago”) or em-dash if null
actionsYesRight-aligned row dropdown; visible on row hover

Table headers are sortable (click header to toggle asc/desc) for: user, workspaceRole, status, and lastActive.

Deferred from MVP: Groups, Knowledge base access, Joined date.

  • Left: Role single-select filter (All roles default, Admin, Member) that filters the Users table by workspaceRole.
  • Right: Invite user primary button with person_add icon; uses PAGE_LIST_TOOLBAR_CTA_CLS.
  • Change role — toggles the user’s workspaceRole between admin and member.
  • Remove from workspace — opens a confirmation dialog (RemoveUserDialog); disabled (and visually muted) when the row is the last admin in the workspace.
  • InviteUserDialog (_components/invite-user-dialog.tsx): email input with basic validation; on submit, adds a new WorkspaceUser with status: "invited" to local state.
  • RemoveUserDialog (_components/remove-user-dialog.tsx): destructive confirmation; on confirm, removes the user from local state.

When users.length === 0, the table body shows: No users in this workspace.

Uses TablePagination component; default 25 rows per page.

FilePurpose
src/types/workspaces.tsWorkspaceUser type
src/app/account/workspaces/_data/seed.tsSEED_WORKSPACE_USERS mock data (4 users)
src/app/account/workspaces/[workspaceId]/_components/workspace-users-table.tsxTable component
src/app/account/workspaces/[workspaceId]/_components/invite-user-dialog.tsxInvite dialog
src/app/account/workspaces/[workspaceId]/_components/remove-user-dialog.tsxRemove confirmation dialog
src/app/account/workspaces/[workspaceId]/page.tsxState wiring and tab rendering

The Activity log tab is implemented as an MVP insight surface (mock event generation from workspace context).

  • Date range chips: 24h, 7d, 30d (single-select)
  • User filter: single-select (All users + workspace users)
  • Event type filter: single-select (All events + known event types)

Five secondary badges:

  • Total events
  • Unique actors
  • KB changes
  • Channel changes
  • Access changes
  • Two-column grid:
    • Left: Activity feed list (actor, action/entity summary, timestamp, source, status badge)
    • Right: Activity by user ranking bars
  • Empty state text when no events match current filters: No activity for selected filters.
  • Event records are generated client-side from current workspace data (users, KBs, channel connections).
  • This tab is MVP mock data, not a backend audit log endpoint.