Files
resolutionflow/docs/archive/WORKSPACE-REMOVAL-PLAN.md
chihlasm a955888acf chore: clean up root directory — archive completed docs, add marketing assets
Move 9 completed/historical docs from root to docs/archive/:
- ARCHITECTURE.md, BACKLOG.md, CLAUDE-SETUP.md, MICHAEL-NOTES.md
- IMPLEMENTATION-SUMMARY-ISSUE-34.md, PHASE-2.5-PERSONAL-BRANCHING.md
- REBRAND-IMPLEMENTATION-GUIDE.md, TS-EXAMPLES.md, WORKSPACE-REMOVAL-PLAN.md

Move QUICK-START.md to docs/

Add previously untracked files:
- DEV-ENV.md (devserver01 setup guide)
- docs/marketing/ (one-pager HTML + PDF)
- docs/ResolutionFlow_Pivot_Architecture.docx

Update CLAUDE.md rebrand guide reference path.

Deleted temp files: .temp_fixed.py, .temp_fixed2.py, ai_provider_*.py,
ai_provider.patch, test_write.txt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:12:07 +00:00

23 KiB

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 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

cd backend
alembic revision --autogenerate -m "add_user_pinned_trees"
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:

// 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:

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.

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

The migration must:

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:

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):

# 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:

// 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:

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

// 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:

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

// 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

// 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 | contentsidebar | content

6b. Verification

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

# 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