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>
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.jsonline 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 access409— 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.pydowngrade function dropstree_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:
userPreferencesStorealready 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.tsxhas deep folder state dependencies (lines 9, 34, 261+). This is not a simple component removal.
What to remove:
FolderSidebarcomponent import and rendering (the persistent left panel)FolderEditModalimport and state- The CSS column that gives FolderSidebar its own grid track
mobileFolderOpenstate
What to KEEP:
selectedFolderIdstate — keep for now, wire to a future "Filter by Folder" dropdownfoldersstate andfoldersApi.list()call — keep data availableFolderSidebar.tsxandFolderEditModal.tsxfiles — 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
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