fix(flowpilot): fix 4 Phase 2 escalation bugs + update Tailwind version in CLAUDE.md

- Fix escalation status mismatch: hook set 'escalated' but backend returns
  'requesting_escalation'
- Fix list_sessions to include sessions picked up by Engineer B via
  escalation_package->>'picked_up_by' JSONB query
- Fix sidebar escalation icon color: was Tailwind class 'text-amber-400'
  passed to style={{color}}, now hex '#fbbf24'
- Replace window.location.reload() after ticket linking with
  onReloadSession callback to preserve session state
- Update CLAUDE.md: Tailwind CSS v3 → v4 (@tailwindcss/vite, CSS-only config)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 03:10:11 +00:00
parent bbe590bfec
commit ce118b51d8
6 changed files with 21 additions and 10 deletions

View File

@@ -23,7 +23,7 @@
- **Fonts:** Bricolage Grotesque (`font-heading`, headings/titles), IBM Plex Sans (`font-sans`, body text), JetBrains Mono (`font-label`, labels/badges/timestamps) — loaded via Google Fonts - **Fonts:** Bricolage Grotesque (`font-heading`, headings/titles), IBM Plex Sans (`font-sans`, body text), JetBrains Mono (`font-label`, labels/badges/timestamps) — loaded via Google Fonts
- **Logo:** Inline SVG in `BrandLogo.tsx` (decision-tree icon with cyan gradient). Wordmark: "Resolution" in `text-foreground` + "Flow" in `text-gradient-brand` - **Logo:** Inline SVG in `BrandLogo.tsx` (decision-tree icon with cyan gradient). Wordmark: "Resolution" in `text-foreground` + "Flow" in `text-gradient-brand`
- **Brand assets:** `brand-assets/` (source SVGs + brand-guide.html), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon) - **Brand assets:** `brand-assets/` (source SVGs + brand-guide.html), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon)
- **CSS utilities:** `text-gradient-brand`, `bg-gradient-brand`, `bg-gradient-brand-hover` (defined in `tailwind.config.js` and `index.css`). Glass utilities: `.glass-card` (interactive, `scale(1.02)` hover), `.glass-card-static` (no hover transform), `.active-glow` (breathing cyan shadow) - **CSS utilities:** `text-gradient-brand`, `bg-gradient-brand`, `bg-gradient-brand-hover` (defined in `index.css` via `@theme`). Glass utilities: `.glass-card` (interactive, `scale(1.02)` hover), `.glass-card-static` (no hover transform), `.active-glow` (breathing cyan shadow)
- **Layout:** App shell with persistent sidebar + top bar + main content (CSS Grid). Two fixed atmosphere orbs (cyan top-right, purple bottom-left) behind the shell for ambient glow. See [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md) - **Layout:** App shell with persistent sidebar + top bar + main content (CSS Grid). Two fixed atmosphere orbs (cyan top-right, purple bottom-left) behind the shell for ambient glow. See [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md)
- **Navigation:** Sidebar nav with type sub-items (All Flows → Troubleshooting / Projects / Maintenance). Pinned flows section for quick access. NO workspace switcher. See [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md) - **Navigation:** Sidebar nav with type sub-items (All Flows → Troubleshooting / Projects / Maintenance). Pinned flows section for quick access. NO workspace switcher. See [UI-DESIGN-SYSTEM.md](UI-DESIGN-SYSTEM.md)
- **Terminology:** User-facing label is "Flows" (not "Trees"). Procedural flows are called "Projects" in the UI. Maintenance flows are called "Maintenance" in the UI. `tree_type` column values unchanged in DB. - **Terminology:** User-facing label is "Flows" (not "Trees"). Procedural flows are called "Projects" in the UI. Maintenance flows are called "Maintenance" in the UI. `tree_type` column values unchanged in DB.
@@ -91,7 +91,7 @@ When adding new pages/components: use "ResolutionFlow" branding, ice-cyan gradie
### Frontend ### Frontend
- **Framework:** React 19 + Vite + TypeScript - **Framework:** React 19 + Vite + TypeScript
- **Styling:** Tailwind CSS v3 — dark-first with purple gradient accents (see Branding section) - **Styling:** Tailwind CSS v4 (`@tailwindcss/vite` plugin, CSS-only config in `index.css`) — dark-first with ice-cyan gradient accents (see Branding section)
- **State:** Zustand (with immer + zundo for undo/redo) - **State:** Zustand (with immer + zundo for undo/redo)
- **Routing:** React Router v7 - **Routing:** React Router v7
- **API Client:** Axios with token refresh interceptor - **API Client:** Axios with token refresh interceptor
@@ -124,7 +124,7 @@ patherly/
│ │ ├── pages/ # All page components │ │ ├── pages/ # All page components
│ │ ├── store/ # Zustand stores (auth, treeEditor, proceduralEditor, userPreferences) │ │ ├── store/ # Zustand stores (auth, treeEditor, proceduralEditor, userPreferences)
│ │ └── types/ # TypeScript interfaces │ │ └── types/ # TypeScript interfaces
│ └── tailwind.config.js │ └── (Tailwind v4: CSS-only config in src/index.css)
├── docs/plans/archive/ # Archived design/impl docs (pre-March 2026) ├── docs/plans/archive/ # Archived design/impl docs (pre-March 2026)
├── CLAUDE.md # This file ├── CLAUDE.md # This file
├── CURRENT-STATE.md # Detailed feature status ├── CURRENT-STATE.md # Detailed feature status

View File

@@ -15,7 +15,7 @@ from typing import Annotated, Optional
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import select, func from sqlalchemy import or_, select, func
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
@@ -465,10 +465,16 @@ async def list_sessions(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100), limit: int = Query(20, ge=1, le=100),
): ):
"""List the current user's AI sessions.""" """List the current user's AI sessions (owned or picked up)."""
user_id_str = str(current_user.id)
query = ( query = (
select(AISession) select(AISession)
.where(AISession.user_id == current_user.id) .where(
or_(
AISession.user_id == current_user.id,
AISession.escalation_package["picked_up_by"].as_string() == user_id_str,
)
)
.order_by(AISession.created_at.desc()) .order_by(AISession.created_at.desc())
.offset(skip) .offset(skip)
.limit(limit) .limit(limit)

View File

@@ -35,6 +35,7 @@ interface FlowPilotSessionProps {
onPause?: () => Promise<void> onPause?: () => Promise<void>
onResume?: () => Promise<void> onResume?: () => Promise<void>
onRate: (rating: number) => void onRate: (rating: number) => void
onReloadSession?: () => Promise<void>
} }
export function FlowPilotSession({ export function FlowPilotSession({
@@ -54,6 +55,7 @@ export function FlowPilotSession({
onPause, onPause,
onResume, onResume,
onRate, onRate,
onReloadSession,
}: FlowPilotSessionProps) { }: FlowPilotSessionProps) {
const scrollRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null)
const [showTicketPicker, setShowTicketPicker] = useState(false) const [showTicketPicker, setShowTicketPicker] = useState(false)
@@ -87,7 +89,9 @@ export function FlowPilotSession({
}) })
toast.success(`Linked to ticket #${ticketId}`) toast.success(`Linked to ticket #${ticketId}`)
// Reload session to get updated ticket_data // Reload session to get updated ticket_data
window.location.reload() if (onReloadSession) {
await onReloadSession()
}
} catch { } catch {
toast.error('Failed to link ticket') toast.error('Failed to link ticket')
} finally { } finally {

View File

@@ -86,7 +86,7 @@ export function Sidebar() {
<NavItem href="/pilot" icon={Sparkles} label="New Session" iconColor={NAV_COLORS.dashboard} collapsed /> <NavItem href="/pilot" icon={Sparkles} label="New Session" iconColor={NAV_COLORS.dashboard} collapsed />
<NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} collapsed /> <NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} collapsed />
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} collapsed /> <NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} collapsed />
<NavItem href="/escalations" icon={AlertTriangle} label="Escalations" iconColor="text-amber-400" collapsed /> <NavItem href="/escalations" icon={AlertTriangle} label="Escalations" iconColor="#fbbf24" collapsed />
<NavItem href="/trees" icon={Network} label="All Flows" matchPaths={['/trees', '/flows']} iconColor={NAV_COLORS.flows} collapsed /> <NavItem href="/trees" icon={Network} label="All Flows" matchPaths={['/trees', '/flows']} iconColor={NAV_COLORS.flows} collapsed />
<NavItem href="/assistant" icon={Brain} label="FlowPilot" iconColor={NAV_COLORS.flowPilot} collapsed /> <NavItem href="/assistant" icon={Brain} label="FlowPilot" iconColor={NAV_COLORS.flowPilot} collapsed />
<NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} collapsed /> <NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} collapsed />
@@ -136,7 +136,7 @@ export function Sidebar() {
Resolve Resolve
</div> </div>
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} /> <NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} />
<NavItem href="/escalations" icon={AlertTriangle} label="Escalations" iconColor="text-amber-400" /> <NavItem href="/escalations" icon={AlertTriangle} label="Escalations" iconColor="#fbbf24" />
<NavItem <NavItem
href="/trees" href="/trees"
icon={Network} icon={Network}

View File

@@ -151,7 +151,7 @@ export function useFlowPilotSession(): UseFlowPilotSession {
setIsProcessing(true) setIsProcessing(true)
try { try {
const result = await aiSessionsApi.escalateSession(session.id, data) const result = await aiSessionsApi.escalateSession(session.id, data)
setSession(prev => prev ? { ...prev, status: 'escalated' } : null) setSession(prev => prev ? { ...prev, status: 'requesting_escalation' } : null)
setDocumentation(result.documentation) setDocumentation(result.documentation)
setPsaPushStatus(result.psa_push_status) setPsaPushStatus(result.psa_push_status)
setPsaPushError(result.psa_push_error) setPsaPushError(result.psa_push_error)

View File

@@ -179,6 +179,7 @@ export default function FlowPilotSessionPage() {
onPause={fp.pauseSession} onPause={fp.pauseSession}
onResume={fp.resumeOwnSession} onResume={fp.resumeOwnSession}
onRate={fp.rateSession} onRate={fp.rateSession}
onReloadSession={() => fp.loadSession(fp.session!.id)}
/> />
</div> </div>
</div> </div>