# Workspace Removal & Navigation Refactor — Implementation Plan

> **Purpose:** Combined implementation plan for removing the workspace system, renaming UI labels (Trees→Flows, Procedures→Projects), adding pinned flows, and restructuring sidebar navigation.
> **Source of Truth:** [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md) v2
> **Date:** February 15, 2026
> **Tailwind Version:** v3 only — do not use v4 syntax or patterns (see `package.json` line 56)

---

## Why This Change

Workspaces added unnecessary cognitive overhead for the target MSP audience. UX research (Hick's Law, context-switching studies) shows that at the current product scale (10-15 beta testers, <50 flows per account), a workspace switcher creates friction without organizational benefit. The replacement is a flat navigation model with type sub-items and pinned favorites.

---

## Phase 1 — Backend: Add Pinned Flows (ship BEFORE removing workspaces)

> **Important sequencing:** Add the pinned flows feature first and verify it works. Then remove workspaces in Phase 2. This ensures the replacement feature is stable before tearing out the old one. If both are done in the same session, at minimum run the pinned flows tests before starting workspace removal.

### 1a. Add Pinned Flows Table

```bash
cd backend
alembic revision --autogenerate -m "add_user_pinned_trees"
```

```sql
CREATE TABLE user_pinned_trees (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    tree_id UUID NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
    display_order INTEGER NOT NULL DEFAULT 0,
    pinned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_user_pinned_tree UNIQUE (user_id, tree_id)
);

CREATE INDEX idx_user_pinned_trees_user ON user_pinned_trees(user_id);
CREATE INDEX idx_user_pinned_trees_tree ON user_pinned_trees(tree_id);
```

### 1b. Pinned Flows API Contract

**Endpoints:**

| Method | Path | Description | Auth |
|--------|------|-------------|------|
| `GET` | `/api/v1/trees/pinned` | List user's pinned flows (ordered by display_order) | Required |
| `POST` | `/api/v1/trees/{id}/pin` | Pin a flow to sidebar | Required |
| `DELETE` | `/api/v1/trees/{id}/pin` | Unpin a flow from sidebar | Required |
| `PATCH` | `/api/v1/trees/pinned/reorder` | Update display_order for all pinned flows | Required |

**Constraints & Limits:**
- Max 15 pinned flows per user (return 409 if exceeded)
- `UNIQUE(user_id, tree_id)` — pinning an already-pinned flow returns 200 (idempotent), not an error
- Unpinning an already-unpinned flow returns 200 (idempotent)
- When a tree is deleted, cascade removes the pin automatically (FK ON DELETE CASCADE)
- Pins are per-user, not per-account — each user has their own pinned set

**Response Shapes:**

```typescript
// GET /api/v1/trees/pinned
interface PinnedFlowsResponse {
  items: PinnedFlow[];
  count: number;
}

interface PinnedFlow {
  id: string;           // pin record id
  tree_id: string;
  tree_name: string;
  tree_type: 'troubleshooting' | 'procedural';
  category_emoji?: string;
  category_name?: string;
  pinned_at: string;    // ISO datetime
  display_order: number;
}

// POST /api/v1/trees/{id}/pin → returns PinnedFlow
// DELETE /api/v1/trees/{id}/pin → returns { success: true }
// PATCH /api/v1/trees/pinned/reorder
//   body: { order: [{ tree_id: string, display_order: number }] }
//   returns: PinnedFlowsResponse
```

**Error Codes:**
- `404` — tree not found or user lacks access
- `409` — max pins reached (15)
- `200` — idempotent success (pin already exists / already unpinned)

### 1c. Add Pinned Flows Backend Files

```
CREATE: backend/app/models/user_pinned_tree.py
MODIFY: backend/app/models/__init__.py — add UserPinnedTree import
MODIFY: backend/app/api/endpoints/trees.py — add pin/unpin/list-pinned/reorder endpoints
MODIFY: backend/app/schemas/tree.py — add PinnedFlow schema, add is_pinned to TreeListItem
MODIFY: backend/app/api/router.py — register pin routes (nested under trees router)
```

**Verify pinned flows work before proceeding:**

```bash
cd backend
alembic upgrade head
pytest tests/ -k "pin" -v   # Run pin-related tests
# Manual: POST a pin, GET pinned list, verify response shapes
```

---

## Phase 2 — Backend: Remove Workspace System

### 2a. New Forward Migration (DO NOT use downgrade path)

> **⚠️ Critical:** The existing `036_add_workspaces.py` downgrade function drops `tree_categories.color`, which we want to keep. Create a new forward migration instead.

```bash
cd backend
alembic revision --autogenerate -m "remove_workspace_system"
```

The migration must:

```python
def upgrade():
    # 1. Drop workspace_id FK from trees (if column exists)
    op.drop_constraint('trees_workspace_id_fkey', 'trees', type_='foreignkey')
    op.drop_column('trees', 'workspace_id')

    # 2. Drop workspace_id FK from tree_categories (if column exists)
    op.drop_constraint('tree_categories_workspace_id_fkey', 'tree_categories', type_='foreignkey')
    op.drop_column('tree_categories', 'workspace_id')

    # 3. Drop workspaces table
    op.drop_table('workspaces')

    # DO NOT drop tree_categories.color — we still use it

def downgrade():
    # Recreate workspaces table and FKs if needed (reverse of above)
    pass
```

### 2b. Delete Workspace Backend Files

```
DELETE: backend/app/api/endpoints/workspaces.py
DELETE: backend/app/models/workspace.py
DELETE: backend/app/schemas/workspace.py (if exists)
```

### 2c. Clean Up Orphaned Workspace References

> **⚠️ Important:** Workspace references exist beyond the obvious files. Scrub these:

| File | What to Remove |
|------|---------------|
| `backend/app/api/router.py` | Remove workspace route registration |
| `backend/app/models/__init__.py` (line 23, 55) | Remove `Workspace` import and `__all__` entry |
| `backend/app/models/account.py` (line 18, 49) | Remove `workspaces` relationship on Account model |
| `backend/app/models/category.py` (line 40) | Remove `workspace_id` column if present |
| `backend/app/models/tree.py` | Remove `workspace_id` column and relationship if present |

**Verification:**

```bash
cd backend
grep -r "workspace" app/ --include="*.py" -l
# Should only return this plan file and alembic migration history — no active code
pytest --override-ini="addopts="
# All tests must pass
```

**Migration smoke test (run against BOTH clean and existing DB):**

```bash
# Test on existing DB with workspace data:
alembic upgrade head
# Verify no errors, workspace tables gone, tree_categories.color still exists

# Test on clean DB (full migration chain):
dropdb patherly_test && createdb patherly_test
DATABASE_URL=postgresql://...patherly_test alembic upgrade head
# Verify clean run through all migrations
```

---

## Phase 3 — Frontend: Remove Workspace System

### 3a. Move sidebarCollapsed State First

The `sidebarCollapsed` state currently lives in `workspaceStore.ts`. Move it before deleting:

```typescript
// frontend/src/store/userPreferencesStore.ts — ADD these:
sidebarCollapsed: boolean;
toggleSidebar: () => void;

// Implementation:
sidebarCollapsed: localStorage.getItem('sidebar-collapsed') === 'true',
toggleSidebar: () => {
  const next = !get().sidebarCollapsed;
  localStorage.setItem('sidebar-collapsed', String(next));
  set({ sidebarCollapsed: next });
},
```

> Note: `userPreferencesStore` already uses Zustand persist — verify the localStorage key name doesn't conflict.

### 3b. Delete Workspace Frontend Files

```
DELETE: frontend/src/store/workspaceStore.ts
DELETE: frontend/src/components/workspace/WorkspaceSwitcher.tsx
DELETE: frontend/src/components/workspace/WorkspaceCreateModal.tsx
DELETE: frontend/src/constants/workspaceLabels.ts
DELETE: frontend/src/types/workspace.ts (or remove Workspace type if shared file)
DELETE: frontend/src/api/workspaces.ts
DELETE: docs/mockups/resolutionflow-workspaces-mockup.html
```

**Before deleting `workspace/` directory**, move these components:

```
MOVE: frontend/src/components/workspace/CategoryList.tsx → frontend/src/components/sidebar/CategoryList.tsx
MOVE: frontend/src/components/workspace/TagCloud.tsx → frontend/src/components/sidebar/TagCloud.tsx
```

### 3c. Update Shell Files That Import Workspace Store

> **⚠️ Important:** These files are marked "keep" in the design system doc but they currently import workspace code. Each needs internal refactoring:

| File | Lines to Change | Action |
|------|----------------|--------|
| `AppLayout.tsx` (line 6, 17) | `import { useWorkspaceStore }` | Replace with `import { useUserPreferencesStore }` — use `sidebarCollapsed` and `toggleSidebar` from there |
| `TopBar.tsx` (line 6, 20) | `import { useWorkspaceStore }` | Replace with `useUserPreferencesStore` for `sidebarCollapsed`. Remove `getActiveWorkspace()` and `labels` — use static labels or `getFlowLabels()` |
| `Sidebar.tsx` (line 4, 111+) | `WorkspaceSwitcher` import and render | Remove workspace switcher component. Add pinned flows section and nav sub-items (Phase 4) |

### 3d. Clean Up Orphaned Frontend References

> **⚠️ Important:** Additional workspace references exist beyond the obvious files:

| File | What to Change |
|------|---------------|
| `frontend/src/types/index.ts` (line 11, 14) | Remove `Workspace` type export |
| `frontend/src/components/layout/QuickLaunch.tsx` | Replace workspace-dependent labels with static "New Flow" / "New Project" |
| `frontend/src/components/layout/CommandPalette.tsx` | Replace workspace-dependent search placeholder and result labels |

**Verification:**

```bash
cd frontend
grep -r "workspace" src/ --include="*.ts" --include="*.tsx" -l
# Should return nothing except possibly test files or comments
grep -r "workspaceStore\|WorkspaceSwitcher\|workspacesApi\|workspaceLabels" src/ -l
# Must return nothing
npm run build
# Must compile clean with zero errors
```

---

## Phase 4 — Label Renames (Repo-Wide Audit)

### 4a. Create Flow Type Labels

```typescript
// frontend/src/constants/flowLabels.ts (NEW — replaces workspaceLabels.ts)

export interface FlowTypeLabels {
  navLabel: string;
  singular: string;
  plural: string;
  newButton: string;
  searchPlaceholder: string;
  icon: string;
}

export const FLOW_TYPE_LABELS: Record<string, FlowTypeLabels> = {
  all: {
    navLabel: 'All Flows',
    singular: 'Flow',
    plural: 'Flows',
    newButton: '+ Create Flow',
    searchPlaceholder: 'Search flows, sessions, tags…',
    icon: '📦',
  },
  troubleshooting: {
    navLabel: 'Troubleshooting',
    singular: 'Flow',
    plural: 'Flows',
    newButton: '+ New Troubleshooting Flow',
    searchPlaceholder: 'Search troubleshooting flows…',
    icon: '🔧',
  },
  procedural: {
    navLabel: 'Projects',
    singular: 'Project',
    plural: 'Projects',
    newButton: '+ New Project',
    searchPlaceholder: 'Search projects, runbooks…',
    icon: '📋',
  },
};

export function getFlowLabels(typeFilter?: string): FlowTypeLabels {
  if (typeFilter && typeFilter in FLOW_TYPE_LABELS) {
    return FLOW_TYPE_LABELS[typeFilter];
  }
  return FLOW_TYPE_LABELS.all;
}
```

### 4b. Label Audit — Files Requiring Changes

Every instance of old terminology must be found and replaced. This is a **repo-wide pass**, not a targeted find-replace.

**User-facing label changes:**

| Old Label | New Label | Notes |
|-----------|-----------|-------|
| "All Trees" | "All Flows" | Sidebar nav, page titles |
| "Tree Editor" | "Flow Editor" | Sidebar nav |
| "New Tree" | "Create Flow" | Buttons, menus |
| "All Procedures" | "Projects" | Sub-nav item |
| "New Procedure" | "New Project" | Buttons, menus |
| "Procedure" (singular) | "Project" | Throughout UI |

**Known files with old labels (non-exhaustive):**

| File | Old Text | New Text |
|------|----------|----------|
| `Sidebar.tsx` | "All Trees", "Tree Editor" | "All Flows", "Flow Editor" |
| `TreeLibraryPage.tsx` (line 279, 298) | "All Trees", "Tree" references | "All Flows", "Flow" |
| `QuickStartPage.tsx` (line 150) | Workspace/procedure labels | Flow/Project labels |
| `QuickLaunch.tsx` | "New Tree", "New Procedure" | "Create Flow", "New Project" |
| `CommandPalette.tsx` | Search labels | Flow-based labels |
| `TopBar.tsx` | Search placeholder | Use `getFlowLabels()` or static "Search flows, sessions, tags…" |

**Catch-all verification:**

```bash
cd frontend
# Find any remaining user-facing "Tree" or "Procedure" labels (excluding variable names and imports)
grep -rn '".*Tree.*"' src/ --include="*.tsx" --include="*.ts" | grep -v "import\|//\|interface\|type \|treesApi\|tree_type\|treeId\|TreeNav\|TreeGrid\|TreeList\|TreeTable"
grep -rn '".*Procedure.*"' src/ --include="*.tsx" --include="*.ts" | grep -v "import\|//\|type "
```

> **Important:** Only rename **user-facing strings** (UI text, placeholder text, toast messages, page titles). Do NOT rename: variable names (`treesApi`, `TreeListItem`), route paths (`/trees`), database columns (`tree_type`), or API endpoints (`/api/v1/trees`). Internal code can say "tree" — users never see it.

### 4c. Acceptance Criteria for Label Audit

- [ ] No user-visible text says "Tree" (except proper nouns or technical docs)
- [ ] No user-visible text says "Procedure" — all say "Project"
- [ ] Search bar placeholder says "Search flows, sessions, tags…"
- [ ] Page title on library page says "Flow Library"
- [ ] "+ Create Flow" button on library page (or "+ New Project" when filtered to projects)
- [ ] Sidebar nav says "All Flows", "Flow Editor"
- [ ] Empty states use "flow" / "project" language
- [ ] Toast messages use "flow" / "project" language

---

## Phase 5 — Sidebar: Nav Sub-Items & Pinned Flows UI

### 5a. Extend NavItem for Children

```tsx
// frontend/src/components/layout/NavItem.tsx — extend props
interface NavItemProps {
  href: string;
  icon: LucideIcon;
  label: string;
  badge?: number | 'dot';
  isActive?: boolean;
  children?: NavSubItem[];  // NEW
}

interface NavSubItem {
  href: string;
  label: string;
  count?: number;
  isActive?: boolean;
}
```

**Sub-item rendering:**
- Indented `pl-9` (past parent icon)
- No icon, just text + optional count badge
- Font: `text-[0.8125rem] text-muted-foreground`, active: `text-foreground`
- Active: `bg-[var(--sidebar-active)]` but without the left gradient bar (only parent gets that)
- Sub-items always visible (not collapsible) — there are only 2

### 5b. Sidebar Structure

```
── PINNED ─────────────────── (collapsible section)
   📧 Email Delivery Issues    (click → start session or go to flow)
   🔒 AD Account Lockout
   👤 New User Onboarding
───────────────────────────────
📊 Dashboard
📦 All Flows              47
   🔧 Troubleshooting     29
   📋 Projects             18
✏️ Flow Editor
⏱️ Sessions               4
📄 Exports
📚 Step Library            •
───────────────────────────────
CATEGORIES
   ● Networking            12
   ● Active Directory       8
   ● Email                 11
───────────────────────────────
POPULAR TAGS
   [vpn] [dns] [exchange] [onboarding]
═══════════════════════════════
👥 Team
⚙️ Settings
```

**Behavior:**
- Clicking "All Flows" → `/trees` (no type filter)
- Clicking "Troubleshooting" → `/trees?type=troubleshooting`
- Clicking "Projects" → `/trees?type=procedural`
- When a sub-item is active, parent "All Flows" stays highlighted (dimmer state)
- Badge counts update based on actual tree counts by type

### 5c. Pinned Flows Section

```tsx
// frontend/src/components/sidebar/PinnedFlowsSection.tsx (NEW)
interface PinnedFlowsSectionProps {
  flows: PinnedFlow[];
  onFlowClick: (treeId: string) => void;
  onUnpin: (treeId: string) => void;
}
```

**Behavior:**
- Each pinned item: emoji + name (truncated with ellipsis) + hover reveals quick-start button
- Right-click context menu: "Start Session", "Edit Flow", "Unpin from Sidebar"
- Empty state: "⭐ Pin your most-used flows here" in `text-xs text-muted-foreground`
- Section header has collapse chevron
- Max 15 items shown; section scrolls internally if needed
- Drag-to-reorder (calls `PATCH /api/v1/trees/pinned/reorder`)

### 5d. Pinned Flows Frontend Wiring

```
CREATE: frontend/src/api/pinnedFlows.ts — pinTree(id), unpinTree(id), listPinned(), reorderPinned(order)
CREATE: frontend/src/components/sidebar/PinnedFlowsSection.tsx
MODIFY: frontend/src/types/tree.ts (or index.ts) — add is_pinned?: boolean to TreeListItem, add PinnedFlow type
MODIFY: frontend/src/api/trees.ts — add is_pinned to list response handling
MODIFY: frontend/src/components/layout/Sidebar.tsx — import and render PinnedFlowsSection above nav
MODIFY: frontend/src/pages/TreeLibraryPage.tsx — add pin star to flow cards (visible on hover, filled if pinned)
MODIFY: flow card three-dot menu — add "Pin to Sidebar" / "Unpin from Sidebar" action
```

**Pin/unpin interaction:**
- Toast on pin: "📌 Pinned **{name}** to sidebar"
- Toast on unpin: "Unpinned **{name}**"
- Library flow cards show subtle star icon on hover; filled star if pinned
- Pin star click calls API, optimistically updates UI

---

## Phase 6 — Library Page Cleanup

### 6a. Remove Folder Sidebar Panel

> **⚠️ Important:** `TreeLibraryPage.tsx` has deep folder state dependencies (lines 9, 34, 261+). This is not a simple component removal.

**What to remove:**
- `FolderSidebar` component import and rendering (the persistent left panel)
- `FolderEditModal` import and state
- The CSS column that gives FolderSidebar its own grid track
- `mobileFolderOpen` state

**What to KEEP:**
- `selectedFolderId` state — keep for now, wire to a future "Filter by Folder" dropdown
- `folders` state and `foldersApi.list()` call — keep data available
- `FolderSidebar.tsx` and `FolderEditModal.tsx` files — do not delete, just stop rendering them
- All folder-related backend code (models, API, database tables) — untouched

**Replacement UX (deferred but noted):**
- Future: "Filter by Folder" dropdown in the filters bar, or "Move to Folder" in the three-dot menu
- For now: folder filtering is simply not visible in the UI. The data model and API remain intact.

**Layout change:**
- Library page becomes full-width within the main content area (no second sidebar column)
- Grid goes from `sidebar | folders | content` → `sidebar | content`

### 6b. Verification

```bash
cd frontend && npm run build
# Must compile clean
```

Manual checks:
- [ ] Library page is full-width (no left folder panel)
- [ ] No JavaScript errors in browser console related to folder state
- [ ] Category filtering from sidebar clicks still works
- [ ] Tag filtering from sidebar clicks still works

---

## Complete File Manifest

### Delete (11 files)

| File | Reason |
|------|--------|
| `frontend/src/store/workspaceStore.ts` | Replaced by userPreferencesStore (sidebarCollapsed) |
| `frontend/src/components/workspace/WorkspaceSwitcher.tsx` | Feature removed |
| `frontend/src/components/workspace/WorkspaceCreateModal.tsx` | Feature removed |
| `frontend/src/constants/workspaceLabels.ts` | Replaced by flowLabels.ts |
| `frontend/src/types/workspace.ts` | Type no longer needed |
| `frontend/src/api/workspaces.ts` | API removed |
| `backend/app/api/endpoints/workspaces.py` | API removed |
| `backend/app/models/workspace.py` | Model removed |
| `backend/app/schemas/workspace.py` | Schema removed (if exists) |
| `docs/mockups/resolutionflow-workspaces-mockup.html` | Outdated mockup |

### Move (2 files)

| From | To |
|------|----|
| `frontend/src/components/workspace/CategoryList.tsx` | `frontend/src/components/sidebar/CategoryList.tsx` |
| `frontend/src/components/workspace/TagCloud.tsx` | `frontend/src/components/sidebar/TagCloud.tsx` |

Then delete the empty `frontend/src/components/workspace/` directory.

### Create (6+ files)

| File | Purpose |
|------|---------|
| `backend/alembic/versions/0XX_remove_workspace_system.py` | Drop workspace tables/columns |
| `backend/alembic/versions/0XX_add_user_pinned_trees.py` | New pinned flows table |
| `backend/app/models/user_pinned_tree.py` | UserPinnedTree SQLAlchemy model |
| `frontend/src/constants/flowLabels.ts` | Flow type label constants |
| `frontend/src/api/pinnedFlows.ts` | Pin/unpin API client |
| `frontend/src/components/sidebar/PinnedFlowsSection.tsx` | Pinned flows sidebar component |

### Modify (14 files — key changes only)

| File | Changes |
|------|---------|
| `backend/app/api/router.py` | Remove workspace routes, add pin routes |
| `backend/app/models/__init__.py` | Remove Workspace import (line 23, 55), add UserPinnedTree |
| `backend/app/models/account.py` | Remove `workspaces` relationship (line 18, 49) |
| `backend/app/models/category.py` | Remove `workspace_id` column (line 40) if present |
| `backend/app/models/tree.py` | Remove `workspace_id` column/relationship if present |
| `backend/app/api/endpoints/trees.py` | Add pin/unpin/list-pinned/reorder endpoints |
| `backend/app/schemas/tree.py` | Add PinnedFlow schema, add `is_pinned` to tree list |
| `frontend/src/store/userPreferencesStore.ts` | Absorb `sidebarCollapsed` + `toggleSidebar` |
| `frontend/src/components/layout/AppLayout.tsx` | Replace workspaceStore with userPreferencesStore |
| `frontend/src/components/layout/TopBar.tsx` | Replace workspaceStore, static search labels |
| `frontend/src/components/layout/Sidebar.tsx` | Remove workspace switcher, add pinned section + nav sub-items |
| `frontend/src/components/layout/NavItem.tsx` | Add `children` sub-item support |
| `frontend/src/pages/TreeLibraryPage.tsx` | Remove FolderSidebar, update labels, add pin star |
| `frontend/src/components/layout/QuickLaunch.tsx` | Update action labels |
| `frontend/src/components/layout/CommandPalette.tsx` | Update search labels |
| `frontend/src/pages/QuickStartPage.tsx` | Update workspace/procedure labels (line 150) |
| `frontend/src/types/index.ts` | Remove Workspace export (line 11, 14), add PinnedFlow |

---

## Verification Checklist

### Automated

```bash
# Backend
cd backend
alembic upgrade head                         # Migration applies clean
pytest --override-ini="addopts="             # All tests pass
grep -r "workspace" app/ --include="*.py" -l # No active workspace refs

# Frontend
cd frontend
npm run build                                # Compiles clean, zero errors
grep -r "workspaceStore\|WorkspaceSwitcher\|workspacesApi\|workspaceLabels" src/ -l
                                             # Returns nothing
```

### Manual UI Checks

- [ ] Sidebar shows "All Flows" with "Troubleshooting" and "Projects" sub-items — no workspace switcher
- [ ] Clicking "Troubleshooting" filters library to `?type=troubleshooting`
- [ ] Clicking "Projects" filters library to `?type=procedural`
- [ ] Clicking "All Flows" removes type filter
- [ ] Sub-item counts reflect actual flow counts per type
- [ ] Pin a flow from the library card three-dot menu → appears in sidebar "PINNED" section
- [ ] Unpin a flow → disappears from sidebar
- [ ] Pinned flow click navigates to that flow
- [ ] Library page is full-width (no folder sidebar panel)
- [ ] Search bar is centered in top bar
- [ ] Keyboard shortcut shows "Ctrl+K" on Windows, "⌘K" on Mac
- [ ] All user-visible labels say "Flow" / "Project", never "Tree" / "Procedure"
- [ ] Empty states use correct terminology
- [ ] Toast messages use correct terminology
- [ ] Command palette search works with new labels
- [ ] Quick Launch actions show correct labels
