diff --git a/docs/plans/ResolutionFlow_UX_Deep_Dive_Final_Plan.md b/docs/plans/ResolutionFlow_UX_Deep_Dive_Final_Plan.md new file mode 100644 index 00000000..b20701b8 --- /dev/null +++ b/docs/plans/ResolutionFlow_UX_Deep_Dive_Final_Plan.md @@ -0,0 +1,441 @@ +# ResolutionFlow UX Deep Dive — Final Merged Implementation Plan + +**Date:** 2026-02-19 +**Author:** Michael Chihlas +**Purpose:** Comprehensive frontend UX sweep merging the original 35-issue audit with Codex-revised plan. Ordered by user impact, covering broken functionality, navigation, shared components, visual consistency, and backend alignment. + +--- + +## Locked Decisions + +These decisions are finalized and should not be revisited: + +1. **Canonical post-editor/post-session destination:** `/trees` (the full flow library). `/my-trees` remains available for creator-specific management but is not a navigation target from editors or sessions. +2. **Step Library handling:** Add a placeholder route now. Not hidden, not fully built. +3. **Cleanup strategy:** Immediate removal of dead code and unused types. No staged deprecation. One final `grep` sweep before each deletion to confirm zero references. + +--- + +## Phase 0 — Tree Editor Authoring Blockers (Immediate) + +**Goal:** Remove "can't reach bottom / too busy form" friction before the broader UX sweep. This phase was absent from the original audit and added by the Codex review. + +### 0.1 Fix node editor scroll trap and bottom clipping + +**File:** `NodeEditorPanel.tsx` + +- Replace fixed viewport math (`h-[calc(100vh-105px)]`) with `h-full min-h-0` +- Keep body as the only scroll container (`min-h-0 flex-1 overflow-y-auto`) +- Make footer sticky (`sticky bottom-0`) so Save/Cancel are always reachable +- Add `scroll-pb-24` on form body to prevent bottom fields hiding behind footer + +### 0.2 Reduce instruction density in decision/action/resolution forms + +**Files:** `NodeFormDecision.tsx`, `NodeFormAction.tsx`, `NodeFormResolution.tsx` + +- Convert long instructional copy to compact labels + InfoTip tooltips +- Keep one short contextual hint per form section +- Remove large always-visible prose blocks + +### 0.3 Keep answer-first branching flow explicit + +**File:** `NodeEditorPanel.tsx` + +- Preserve existing behavior: saving decision options without `next_node_id` auto-creates answer stubs +- Add UI hint in decision form: "Options become answer placeholders you type later." + +### Phase 0 Verification + +- Open a long decision form → verify full vertical scroll to footer fields/buttons +- Focus bottom fields → confirm they are visible (not clipped behind footer) +- Decision instructions are compact with info tooltips (no walls of text) +- Save a decision with two new options → two answer placeholders are auto-created + +--- + +## Phase 1 — Broken Functionality (Critical) + +**Goal:** Fix features that are actively broken or silently wrong right now. + +### 1.1 Register /step-library route + +Sidebar links to `/step-library` but no route exists — users hit a blank page. + +**Changes:** + +- `router.tsx` — add route with lazy-loaded `StepLibraryPage.tsx` placeholder shell +- `StepLibraryPage.tsx` — create placeholder page (e.g., "Step Library — Coming Soon" with consistent layout) +- `Sidebar.tsx` — remove `badge="dot"` while placeholder is live (no false "new feature" signal) + +### 1.2 Preserve backend auth error detail in login/register flows + +Login failures show "Request failed with status code 401" instead of "Invalid credentials." + +**File:** `authStore.ts` (lines ~50-54, 63-67) + +- Extract `error.response?.data?.detail` before falling back to generic message +- Apply to both login and register error paths + +### 1.3 Fix inverted 4xx toast logic and add 429 handling + +**File:** `client.ts` (lines ~36-43) + +4xx errors with a `detail` message currently suppress the toast entirely (inverted condition). No 429 handler exists. + +**Behavior after fix:** + +| Status | Action | +|--------|--------| +| 401 | Suppressed (handled by token refresh flow) | +| 429 | Always toast: backend detail or "Rate limit exceeded, please retry shortly." | +| Other 4xx | Toast `detail` if present, else "Invalid request." | +| 5xx | Existing generic server error toast (unchanged) | + +### 1.4 Fix role update payload contract mismatch + +**File:** `accounts.ts` (line ~28) + +Sends `{ role }` but backend schema expects `{ account_role }`. Role changes silently fail with 422. + +- Change to `{ account_role: role }` + +### 1.5 Fix "Repeat Last Session" broken for non-troubleshooting flows + +**File:** `TreeLibraryPage.tsx` (line ~453) + +Currently hardcodes `/trees/:id/navigate` — loses prefill state via safety redirect for procedural/maintenance flows. + +- Use `getSessionResumePath()` instead of hardcoded path + +### Phase 1 Verification + +- Step Library nav opens placeholder page from Sidebar, AppLayout mobile nav, and Quick Launch +- Login with bad credentials shows backend detail string (e.g., "Invalid credentials"), not axios error +- Force a 429 response and verify toast appears +- Change a team member's role in Account Settings → confirm it actually saves (requires `team_admin` user) +- Repeat Last Session works correctly for troubleshooting, procedural, and maintenance flow types + +--- + +## Phase 2 — Navigation Correctness (Medium Scope) + +**Goal:** Fix cases where users end up at unexpected pages or lack navigation affordances. + +### 2.1 Standardize editor/session back and exit targets to /trees + +**Files:** + +- `ProceduralEditorPage.tsx` (lines ~86, 92, 157) — change `/my-trees` → `/trees` +- `ProceduralNavigationPage.tsx` (lines ~120, 180, 304, 330) — error, cancel, completion, and intake cancel paths all → `/trees` + +### 2.2 Add explicit exit affordance during procedural session execution + +Currently there is no way to leave a procedural session mid-execution except the browser back button. + +**File:** `ProceduralNavigationPage.tsx` (top bar, ~line 345) + +- Add an Exit button to the top bar +- If session has progress, exit opens a `ConfirmDialog` before navigating to `/trees` +- If no progress, navigate directly + +### 2.3 Remove duplicate account links in global nav + +"Team" and "Settings" both point to `/account`. Same duplication in TopBar dropdown. + +**Files:** + +- `Sidebar.tsx` (lines ~206-207) — consolidate footer to single "Account" item +- `TopBar.tsx` — consolidate dropdown to single "Account" entry + +### 2.4 Improve analytics routing for non-owners + +Non-owners click Analytics, hit an access-denied wall, then must click through to personal stats. + +**File:** `TeamAnalyticsPage.tsx` (lines ~48-65) + +- Auto-redirect non-owner/non-admin users to `/analytics/me` +- Show an informational toast explaining the redirect (e.g., "Viewing your personal analytics") + +### 2.5 Add feedback before permission redirects in tree editor + +`TreeEditorPage` silently redirects when users lack permission — no feedback shown. + +**File:** `TreeEditorPage.tsx` (lines ~143-146, 157-159) + +- Add `toast.error("You don't have permission to edit this flow")` before each `navigate()` call + +### 2.6 Delete orphaned AdminCategoriesPage + +Not connected to any route, superseded by `admin/GlobalCategoriesPage.tsx`. + +- Delete `AdminCategoriesPage.tsx` +- Remove its export from `index.ts` if present + +### Phase 2 Verification + +- Procedural editor Back button → `/trees` (not `/my-trees`) +- Procedural session cancel, exit, error, and completion → all route to `/trees` +- Exit button is visible during procedural execution and prompts confirmation if session has progress +- Sidebar footer shows one "Account" item (not "Team" + "Settings") +- Non-owner clicks Analytics → auto-redirects to `/analytics/me` with toast +- Unauthorized tree edit attempt → toast shown, then redirect + +--- + +## Phase 3 — Shared Components & Quick Consistency Wins (Medium Scope) + +**Goal:** Build reusable infrastructure and fix small but important consistency issues. + +### 3.1 Create shared Spinner component + +Currently 4 different spinner implementations across 20+ files. + +**Create:** `Spinner.tsx` with `sm | md | lg` sizes, default `border-t-primary` + +**Migrate page-level loading states in:** + +- `ProceduralNavigationPage.tsx` +- `ProceduralEditorPage.tsx` +- `TreeEditorPage.tsx` +- `TreeNavigationPage.tsx` +- `SessionHistoryPage.tsx` +- `SessionDetailPage.tsx` +- `MySharesPage.tsx` +- `MyTreesPage.tsx` +- `AccountSettingsPage.tsx` +- `SharedSessionPage.tsx` +- `PageLoader.tsx` + +**Deferred:** Leave tiny inline button spinners for later to avoid churn. + +### 3.2 Promote EmptyState to shared component + +Admin has a well-designed EmptyState; main app uses 3+ ad hoc patterns. + +- Move/create `common/EmptyState.tsx` +- Re-export from admin's `EmptyState.tsx` for backward compatibility +- Adopt in: `MySharesPage.tsx`, `SessionHistoryPage.tsx`, `TreeLibraryPage.tsx` + +### 3.3 Replace native window.confirm() with design-system ConfirmDialog + +3 places use native browser dialogs that break the design system. + +**Files:** + +- `MySharesPage.tsx` (line ~81) +- `NodeEditorPanel.tsx` (line ~87) +- `FolderSidebar.tsx` (line ~286) + +### 3.4 Fix sidebar optimistic unpin bug + +State is removed immediately even if API call fails — the flow disappears permanently until page refresh. + +**File:** `Sidebar.tsx` (lines ~105-113) + +- Move `setPinnedFlows` update to after successful `await` (not before) + +### 3.5 Fix PinnedFlow.tree_type missing 'maintenance' + +Maintenance flows can be pinned but navigation will use the wrong path. + +**Files:** + +- `pinnedFlows.ts` (line ~7) — add `'maintenance'` to the `PinnedFlow.tree_type` union type +- `PinnedFlowsSection.tsx` — validate icon/path logic via `getTreeNavigatePath` handles maintenance correctly + +### Phase 3 Verification + +- All page-level loading states use the shared `Spinner` component (visual consistency) +- Empty states in MyShares, SessionHistory, TreeLibrary use the shared `EmptyState` component +- Deleting a share, removing a node, removing a folder → all show styled dialog (not browser native) +- Unpin a flow while network is down → flow should NOT disappear (reverts on failure) +- Pin a maintenance flow → clicking it navigates to the correct path + +--- + +## Phase 4 — Visual Consistency Sweep (Large Scope — Many Small Changes) + +**Goal:** Design system compliance fixes. Each change is small but there are many files. + +### 4.1 Fix Sonner toast styling + +The `richColors` prop overrides custom CSS with garish built-in green/red backgrounds. Existing custom CSS in `index.css` (lines ~201-251) already defines themed toasts (`bg-card`, `border-border`, colored border accents) but `richColors` overrides them. + +**File:** `main.tsx` + +- Remove `richColors` prop +- Add `visibleToasts={3}` and `gap={8}` +- Keep existing custom toast CSS in `index.css`; patch only if needed to ensure themed styles fully apply + +### 4.2 Typography: Add font-heading to all page H1s + +Missing from approximately half the pages. + +**Files to update:** + +- `MyTreesPage.tsx` +- `TeamAnalyticsPage.tsx` +- `MyAnalyticsPage.tsx` +- `FeedbackPage.tsx` +- `AccountSettingsPage.tsx` +- `TeamCategoriesPage.tsx` +- `admin/PageHeader.tsx` + +### 4.3 Typography: Add font-label to TagBadges component + +Design system requires Outfit font for tags/badges. + +**File:** `TagBadges.tsx` + +### 4.4 Fix hardcoded light-mode button in TeamAnalyticsPage + +Only hardcoded `bg-white text-black` button in the app — breaks in dark mode. + +**File:** `TeamAnalyticsPage.tsx` (line ~59) + +- Replace with `bg-gradient-brand text-white` + +### 4.5 Fix non-standard focus ring tokens + +Analytics selects use `focus:ring-ring` instead of the standard token. + +**Files:** `TeamAnalyticsPage.tsx`, `MyAnalyticsPage.tsx` + +- Change `focus:ring-ring` → `focus:ring-primary/20` + +### 4.6 Replace deprecated glass-stat style + +**File:** `AccountSettingsPage.tsx` (line ~588) + +- Replace `glass-stat` with `bg-card border border-border` + +### 4.7 Standardize container/padding on analytics pages + +Missing responsive padding. + +**Files:** `TeamAnalyticsPage.tsx`, `MyAnalyticsPage.tsx` + +- Add `container mx-auto px-4 py-6 sm:px-6 sm:py-8` + +### Phase 4 Verification + +- Toast colors/borders follow custom theme (not Sonner rich presets) — check success, error, and info toasts +- All page H1s use `font-heading` (Plus Jakarta Sans) +- Tag badges use `font-label` (Outfit) +- TeamAnalytics CTA button renders correctly in both light and dark mode +- Focus rings on analytics selects use subtle `primary/20` glow +- No `glass-stat` class remains in the codebase +- Analytics pages have consistent container spacing matching rest of app + +--- + +## Phase 5 — Backend Alignment & Cleanup (Immediate Removals) + +**Goal:** API contract fixes and dead code removal. All removals are immediate (per locked decision #3), with a final `grep` sweep before each deletion. + +### 5.1 Remove non-functional drafts toggle from library UI + +Backend has no `include_drafts` parameter — the toggle does nothing. + +**Files:** + +- `TreeLibraryPage.tsx` — remove `showDrafts` state and drafts filter UI +- `tree.ts` (`TreeFilters` type) — remove `include_drafts` field + +### 5.2 Align invite types with backend schema + +`invited_by_id` and `accepted_by_id` not in backend response — always `undefined`. + +**File:** `account.ts` — remove `invited_by_id` and `accepted_by_id` from `AccountInvite` type + +### 5.3 Remove dead/unused client code + +Before deleting each item, run a global `grep` to confirm zero consumers: + +| Item | File | What to Remove | +|------|------|----------------| +| `pinnedFlowsApi.pin()` | `pinnedFlows.ts` | Dead method (never called) | +| `pinnedFlowsApi.reorder()` | `pinnedFlows.ts` | Dead method (never called) | +| `treesApi.getSharedTree()` | `trees.ts` | Dead method (no route/consumer) | +| `SessionListResponse` | `sessions.ts` | Unused type | +| `RatingCreate.is_verified_use` | `step.ts` | Field ignored by backend | +| `AdminCategoriesPage.tsx` | — | Orphaned file + export (if not already deleted in Phase 2) | + +### 5.4 Add session list truncation indicator + +Session history silently truncates at the backend limit with no indication to the user. + +**File:** `SessionHistoryPage.tsx` + +- Request `size=51` from backend +- If result length is 51: show "Showing first 50 sessions" indicator, render only first 50 +- If result length ≤ 50: show "Showing X sessions" +- No backend API contract changes required (frontend lookahead strategy) + +### Phase 5 Verification + +- No drafts toggle visible in TreeLibrary UI +- `grep -r "include_drafts" frontend/src/` returns zero results +- `grep -r "invited_by_id\|accepted_by_id" frontend/src/` returns zero results for `AccountInvite` usage +- `grep` for each removed method/type confirms zero references +- Session history shows truncation indicator at 50+ results +- Session history shows "Showing X sessions" at fewer than 50 results +- `cd frontend && npm run build` passes with zero errors + +--- + +## Items Explicitly Deferred + +| Item | Reason | +|------|--------| +| Migrate 14+ custom modals to shared Modal component | Very high effort, low breakage risk. Migrate incrementally on new work. | +| Standardize input border radius app-wide | Cosmetic, low user impact | +| Standardize icon sizing method (`className` vs `size` prop) | No visual difference | +| Session pagination (full load-more) | Larger feature, beyond UX sweep scope | +| Inline button spinners | Leave for later to avoid churn (Phase 3 note) | +| Full Step Library feature build | Intentionally placeholder-only this cycle | + +--- + +## Public APIs / Interfaces / Types Changed + +**Frontend routing:** + +- Add new route `/step-library` in `router.tsx` + +**Type/interface updates:** + +- `TreeFilters` in `tree.ts`: remove `include_drafts` +- `AccountInvite` in `account.ts`: remove `invited_by_id`, `accepted_by_id` +- `PinnedFlow.tree_type` in `pinnedFlows.ts`: add `'maintenance'` +- `RatingCreate` in `step.ts`: remove `is_verified_use` +- Remove unused `SessionListResponse` in `sessions.ts` + +**Shared component surface:** + +- Add `Spinner` component in `Spinner.tsx` +- Add common `EmptyState` component in `EmptyState.tsx` + +--- + +## Cross-Phase Verification Checklist + +Run after each phase is complete: + +1. **Build:** `cd frontend && npm run build` — must pass with zero errors +2. **Navigation:** Test all sidebar items, back buttons, editor → library flow, procedural navigation exit +3. **Auth:** Intentional wrong password → shows backend error detail +4. **Role change:** Test in Account Settings (requires `team_admin` user) +5. **Visual spot-check:** H1 fonts, spinner consistency, empty states, toast styling, dark mode +6. **Grep sweep:** Before merging each phase, confirm no dead references remain for removed items + +--- + +## Assumptions + +- `/trees` is the canonical destination for broad flow browsing and all post-task returns +- `/my-trees` remains available but is not a default navigation target +- Step Library is placeholder-only this cycle — no full feature work +- No backend API contract changes are required for any item in this plan +- Session truncation uses the frontend lookahead strategy (`size=51`) +- All cleanup is immediate with pre-deletion reference verification diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 394d5e58..51490e8d 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { useNavigate, useLocation } from 'react-router-dom' -import { LayoutGrid, Box, PenLine, Clock, FileText, Bookmark, BarChart3, Users, Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText } from 'lucide-react' +import { LayoutGrid, Box, PenLine, Clock, FileText, Bookmark, BarChart3, Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText } from 'lucide-react' import { cn } from '@/lib/utils' import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { CategoryList } from '@/components/sidebar/CategoryList' @@ -203,8 +203,7 @@ export function Sidebar() { {!sidebarCollapsed && ( <> - - + )} - - - {/* Filter Toggle */} -
- -
- - {/* Categories List */} - {categories.length === 0 ? ( -
-

- No categories found. Create your first category to get started. -

-
- ) : ( - - c.id)} - strategy={verticalListSortingStrategy} - > -
- {categories.map(category => ( - - ))} -
-
-
- )} - - {/* Create Modal */} - setShowCreateModal(false)} - onSubmit={handleCreate} - isSaving={isSaving} - /> - - {/* Edit Modal */} - { - setShowEditModal(false) - setEditingCategory(null) - }} - onSubmit={handleEdit} - category={editingCategory} - isSaving={isSaving} - /> - - ) -} - -export default AdminCategoriesPage diff --git a/frontend/src/pages/ProceduralEditorPage.tsx b/frontend/src/pages/ProceduralEditorPage.tsx index 2bf6c9d2..67fbbec7 100644 --- a/frontend/src/pages/ProceduralEditorPage.tsx +++ b/frontend/src/pages/ProceduralEditorPage.tsx @@ -83,13 +83,13 @@ export function ProceduralEditorPage() { const tree = await treesApi.get(treeId) if (tree.tree_type !== 'procedural' && tree.tree_type !== 'maintenance') { toast.error('This flow is not a procedural or maintenance flow') - navigate('/my-trees') + navigate('/trees') return } loadTree(tree) } catch { toast.error('Failed to load flow') - navigate('/my-trees') + navigate('/trees') } } @@ -154,7 +154,7 @@ export function ProceduralEditorPage() {
) @@ -354,6 +356,19 @@ export function ProceduralNavigationPage() {

{tree.name}

+
)} + setShowExitConfirm(false)} + onConfirm={() => navigate('/trees')} + title="Exit Session" + message="You have progress in this session. Are you sure you want to exit? Your progress will not be saved." + confirmLabel="Exit" + /> + {/* Parameters popover */} {paramsOpen && (
diff --git a/frontend/src/pages/TeamAnalyticsPage.tsx b/frontend/src/pages/TeamAnalyticsPage.tsx index 3df0ff73..2df599d5 100644 --- a/frontend/src/pages/TeamAnalyticsPage.tsx +++ b/frontend/src/pages/TeamAnalyticsPage.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' -import { Link } from 'react-router-dom' -import { BarChart3, Loader2, Users, Target, Clock, TrendingUp, ShieldX } from 'lucide-react' +import { Link, Navigate } from 'react-router-dom' +import { BarChart3, Loader2, Users, Target, Clock, TrendingUp } from 'lucide-react' import { AreaChart, Area, @@ -44,25 +44,8 @@ export default function TeamAnalyticsPage() { .finally(() => setLoading(false)) }, [period, isAccountOwner, isSuperAdmin]) - // Permission guard if (!isAccountOwner && !isSuperAdmin) { - return ( -
- -

Access Denied

-

- Team Analytics is only available to account owners and administrators. - You can view your personal stats instead. -

- - - View My Stats - -
- ) + return } if (loading) { diff --git a/frontend/src/pages/TreeEditorPage.tsx b/frontend/src/pages/TreeEditorPage.tsx index b0af4285..165b4306 100644 --- a/frontend/src/pages/TreeEditorPage.tsx +++ b/frontend/src/pages/TreeEditorPage.tsx @@ -141,6 +141,7 @@ export function TreeEditorPage() { // Permission guard: redirect viewers away from editor useEffect(() => { if (!canCreateTrees) { + toast.error("You don't have permission to edit flows") navigate('/trees') } }, [canCreateTrees, navigate]) @@ -155,6 +156,7 @@ export function TreeEditorPage() { try { const tree = await treesApi.get(id) if (!canEditTree({ author_id: tree.author_id, account_id: tree.account_id })) { + toast.error("You don't have permission to edit this flow") navigate('/trees') return } @@ -162,6 +164,7 @@ export function TreeEditorPage() { setTreeStatus(tree.status) // Load status from existing tree } catch (err) { console.error('Failed to load tree:', err) + toast.error('Failed to load flow') navigate('/trees') } } else { diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts index 59ac587a..fa4be554 100644 --- a/frontend/src/pages/index.ts +++ b/frontend/src/pages/index.ts @@ -7,4 +7,3 @@ export { default as TreeEditorPage } from './TreeEditorPage' export { default as SessionHistoryPage } from './SessionHistoryPage' export { default as SessionDetailPage } from './SessionDetailPage' export { default as AccountSettingsPage } from './AccountSettingsPage' -export { default as AdminCategoriesPage } from './AdminCategoriesPage'