feat: bold dashboard redesign with inline stats, section labels, and chip icons

Restructure QuickStartPage for a more professional, informative layout:
- Left-aligned hero greeting (text-4xl) with date context and inline stat strip
- GreetingStatStrip shows resolved/active/MTTR at a glance
- Remove collapsible toggle — dashboard stats always visible
- Section labels with trailing border lines for visual hierarchy
- Suggestion chips with category icons, card-style hover, press feedback
- Fix cyan focus ring and icon color to ember orange design system
- Session cards: line-clamp-2 descriptions, font-medium text, problem_domain metadata
- Widen container max-w-3xl → max-w-4xl for breathing room
- Add .impeccable.md and .github/copilot-instructions.md design context
- CLAUDE.md audit: fix stale references, remove duplication, update counts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-27 05:04:20 +00:00
parent 3c0a29115c
commit dbe66a0568
9 changed files with 284 additions and 144 deletions

32
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,32 @@
# Copilot Instructions — ResolutionFlow
## Design Context
### Users
MSP engineers — IT professionals at Managed Service Providers who troubleshoot infrastructure and support issues for multiple client companies. They work under ticket pressure, need to resolve issues fast, and produce clean documentation automatically.
### Brand Personality
**Three words:** Professional, Modern, SaaS
**Voice:** Direct, competent, no fluff. Built by MSP engineers, for MSP engineers.
**Emotional goals:** Confidence, competence, clarity, focus.
### Aesthetic Direction
- Flat, high-contrast dark theme (Sentry/PostHog-inspired). Premium and clean.
- **References:** Notion (clarity), Stripe (polish), Figma (functional density)
- **Anti-references:** Microsoft Teams (clutter), Kaseya VSA 9 (dated patterns)
- Accent: ember orange (#f97316), max 5% of UI. No glassmorphism, no gradient surfaces, no ambient effects.
- See `DESIGN-SYSTEM.md` for full token and component specs.
### Accessibility
- WCAG 2.2 AA baseline
- Enhanced focus appearance on all interactive elements
- 7:1 contrast ratio for data visualization colors
- `prefers-reduced-motion` fully supported
- Never rely on color alone for status — pair with icons or text
### Design Principles
1. **Clarity over decoration.** Every pixel should communicate. No ornamental effects.
2. **Density without clutter.** Use typography hierarchy and spacing to create structure, not chrome.
3. **Confidence through consistency.** Same patterns, same tokens, same behavior everywhere.
4. **Speed is a feature.** Minimize clicks. Copilot-first — primary interaction is typing.
5. **Accessible by default.** WCAG 2.2, enhanced focus, high-contrast data viz, motion sensitivity.

68
.impeccable.md Normal file
View File

@@ -0,0 +1,68 @@
# Design Context — ResolutionFlow
> Persistent design guidance for all AI sessions. Source of truth for design intent and principles.
> For component specs, tokens, and implementation details, see [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md).
## Users
**MSP engineers** — IT professionals at Managed Service Providers who troubleshoot infrastructure and support issues for multiple client companies. They work under ticket pressure, juggling PSA tools (ConnectWise, Autotask, HaloPSA) and need to resolve issues fast while producing clean documentation.
**Context of use:** Mid-ticket, often stressed, switching between tools. They need the interface to get out of their way and help them think clearly. Documentation is a pain point — it should feel automatic, not like extra work.
**Job to be done:** Describe an issue, get guided through resolution, and walk away with professional ticket notes — without manual writeup.
## Brand Personality
**Three words:** Professional, Modern, SaaS
**Voice:** Direct, competent, no fluff. Built by MSP engineers, for MSP engineers. The product speaks like a senior colleague — helpful without being patronizing, technical without being dense.
**Emotional goals:** Confidence, competence, clarity, focus. The interface should make engineers feel like they have a reliable system backing them up. Every interaction should reinforce trust and reduce cognitive load.
## Aesthetic Direction
**Visual tone:** Flat, high-contrast dark theme. Premium and clean — Sentry/PostHog DNA. Minimal decoration, maximum signal. Information density without clutter.
**References:**
- **Notion** — clarity of layout, whitespace discipline, typography hierarchy
- **Stripe** — polish, professional confidence, attention to micro-detail
- **Figma** — functional density done right, tool-like precision, dark mode execution
**Anti-references:**
- **Microsoft Teams** — cluttered, inconsistent spacing, overwhelming chrome, unclear hierarchy
- **Kaseya VSA 9** — dated UI patterns, poor information density, legacy enterprise feel
**Theme:** Dark mode primary (charcoal palette). Light mode planned but not yet implemented.
**Accent:** Ember orange (#f97316) — conveys urgency fitting a troubleshooting context. Used sparingly (max 5% of UI). Warning uses yellow (#eab308), not amber, to stay distinct.
**Hard rules:** No glassmorphism, no gradient surfaces, no ambient orbs, no backdrop blur, no decorative shadows at rest. Elevation = lighter surface + border, not shadow.
## Accessibility
**Target:** WCAG 2.2 AA as baseline, with two enhanced commitments:
- **Enhanced focus appearance** — all interactive elements must have visible, high-contrast focus indicators (not just inputs). Keyboard navigation must be obvious and consistent.
- **7:1 contrast ratio for data visualization** — chart colors, graph elements, and any data-bearing color must meet AAA contrast against their background. Standard text follows AA (4.5:1 body, 3:1 large).
**Already implemented:**
- `prefers-reduced-motion` fully handled (animations collapse to 0.01ms)
- Mobile responsive (app shell collapses below 768px)
- Bottom-sheet modals on mobile
- Styled scrollbars (6px, subtle)
**Considerations for future work:**
- Color blindness: avoid relying on red/green distinction alone for status — always pair with icons or text labels
- Screen reader: ensure all interactive elements have accessible names
- Keyboard: all flows must be completable without a mouse
## Design Principles
1. **Clarity over decoration.** Every pixel should communicate. If an element doesn't help the user understand or act, remove it. No ornamental gradients, glows, or effects.
2. **Density without clutter.** MSP engineers work with lots of data. Show what matters, hide what doesn't. Use typography hierarchy and spacing — not chrome — to create structure.
3. **Confidence through consistency.** Same patterns, same tokens, same behavior everywhere. Predictability builds trust. Reference [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) for every component decision.
4. **Speed is a feature.** The interface should feel instant. Minimize clicks to action. Auto-generate what can be auto-generated. The copilot-first UX means the primary interaction is typing, not navigating.
5. **Accessible by default.** WCAG 2.2 compliance isn't a checklist item — it's a design constraint. Enhanced focus, high-contrast data viz, and motion sensitivity are built in, not bolted on.

View File

@@ -1,6 +1,6 @@
# CLAUDE.md - Patherly / ResolutionFlow Project Context
> **Last Updated:** March 25, 2026
> **Last Updated:** March 27, 2026
---
@@ -23,25 +23,13 @@
- **Design aesthetic:** Flat, high-contrast dark theme (Sentry/PostHog-inspired). No glass morphism, no gradients on surfaces, no ambient effects. Light mode planned.
- **Accent color:** Ember orange (#f97316 / #ea580c). Used sparingly — ≤5% of the UI. Warning is yellow (#eab308), not amber, to stay distinct from accent.
- **Fonts:** IBM Plex Sans (`font-sans`, body), Bricolage Grotesque (`font-heading`, headings), JetBrains Mono (`font-mono`, code) — loaded via Google Fonts
- **Logo:** 30px gradient square (cyan) + "ResolutionFlow" in Bricolage Grotesque 700
- **Logo:** 30px gradient square (ember orange) + "ResolutionFlow" in Bricolage Grotesque 700
- **Layout:** Icon rail sidebar (72px default) with hover flyout panels. Pinnable to full 260px sidebar. See [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md)
- **Brand assets:** `brand-assets/` (source SVGs), `frontend/src/assets/brand/` (app assets), `frontend/public/icons/` (favicon)
- **Terminology:** User-facing label is "Flows" (not "Trees"). Procedural flows are called "Projects" in the UI. Step Library is called "Solutions Library" in the UI. Maintenance flows are hidden from UI for pilot (backend still supports them). `tree_type` column values unchanged in DB.
- **Reference mockups:** `docs/mockups/` (HTML files, open in browser)
**Component styling rules:**
- Primary buttons: solid `accent` background (#f97316), white text, 5px radius
- Ghost buttons: transparent with 1px `border-default`, hover `bg-elevated`
- Cards: `bg-card` with 1px `border-default`, 8px radius. NO shadows, NO blur, NO gradients.
- Badges: pill-shaped (20px radius), semantic dim background + matching text color
- Active nav: `accent-dim` background + `accent-text` color + 3px left accent bar
- Stat cards: 3px colored left border (accent/success/warning by position)
- Code blocks: `bg-code` with JetBrains Mono, material-inspired syntax highlighting
- Status colors: green/`#34d399` (success), yellow/`#eab308` (warning), red/`#f87171` (danger) — ONLY for semantic meaning
- Section labels: 10px, 600 weight, uppercase, `text-muted`, 1.2px letter-spacing
When adding new pages/components: reference [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md). Use flat dark surfaces, 1px borders, no decorative effects. All colors via CSS variables. Use "Flows" not "Trees" in all user-facing text; use "Projects" not "Procedures" for procedural flows.
**Component styling:** See Design System section below and [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md). All colors via CSS variables. Use "Flows" not "Trees" in user-facing text; use "Projects" not "Procedures" for procedural flows.
## Implementation Principles
@@ -54,9 +42,9 @@ When adding new pages/components: reference [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md)
## Current State
- **Phase:** Go-to-Market Validation (Pre-PMF)
- **Backend:** Complete (35+ API endpoints, 100+ integration tests)
- **Backend:** Complete (55+ API endpoints, 100+ integration tests)
- **Frontend:** Core features complete, Tree Editor functional
- **Database:** PostgreSQL with Docker, 75 migrations
- **Database:** PostgreSQL with Docker, 98 migrations
- **Detailed status:** [CURRENT-STATE.md](CURRENT-STATE.md)
### What's In Progress
@@ -65,20 +53,6 @@ When adding new pages/components: reference [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md)
- Solutions Library spec written (`docs/plans/2026-03-23-solutions-library-design.md`), implementation post-pilot
- Remaining open issues: #66 Templates + Import/Export, #60 Recurring Issue Detection, #58 Step Feedback Flag
### Recently Completed
- Copilot-first dashboard redesign: ChatGPT-style input, suggestion chips, simplified sidebar
- Charcoal color palette: sidebar-darkest approach (`#10121a` sidebar, `#1a1c23` page, `#22252e` cards)
- Unified Command Palette: merged QuickLaunch into omnibar, removed lightning bolt button
- "Solutions Library" rename from "Step Library" site-wide
- Maintenance flows hidden from UI for pilot
- Landing page copy rewrite: copilot-first messaging ("Resolve tickets faster. Notes write themselves.")
- Spring bounce hover animation on dashboard cards
- Amber "New Session" button in sidebar
- Landing page design audit: hamburger menu, Privacy/Terms pages, branding alignment
- Root directory cleanup: archived 9 completed docs, tracked marketing assets
- GitHub issues triage: closed 10 stale issues (6 completed, 4 deferred)
---
## Tech Stack
@@ -95,7 +69,7 @@ When adding new pages/components: reference [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md)
### Frontend
- **Framework:** React 19 + Vite + TypeScript
- **Styling:** Tailwind CSS v4 (`@tailwindcss/vite` plugin, CSS-only config in `index.css`) — flat dark theme with cyan accent (see [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md))
- **Styling:** Tailwind CSS v4 (`@tailwindcss/vite` plugin, CSS-only config in `index.css`) — flat dark theme with ember orange accent (see [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md))
- **State:** Zustand (with immer + zundo for undo/redo)
- **Routing:** React Router v7
- **API Client:** Axios with token refresh interceptor
@@ -110,7 +84,7 @@ patherly/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI entry point
│ │ ├── api/endpoints/ # Route handlers (auth, trees, sessions, admin, steps, survey, copilot, assistant_chat, psa_connections)
│ │ ├── api/endpoints/ # Route handlers (auth, trees, sessions, admin, steps, survey, copilot, assistant_chat, integrations)
│ │ │ ├── flow_proposals.py # Knowledge Flywheel review queue CRUD
│ │ │ └── flowpilot_analytics.py # FlowPilot dashboard metrics
│ │ ├── api/deps.py # Auth dependencies (includes require_team_admin)
@@ -118,7 +92,7 @@ patherly/
│ │ ├── core/ # config, database, permissions, security, audit, rate_limit
│ │ ├── models/ # SQLAlchemy models (includes FlowProposal)
│ │ ├── schemas/ # Pydantic schemas
│ │ ├── services/psa/ # PSA provider abstraction (base, connectwise/, cache, encryption, registry, types)
│ │ ├── services/psa/ # PSA provider abstraction (base, connectwise/, autotask/, halopsa/, cache, encryption, registry, types)
│ │ ├── services/knowledge_flywheel.py # AI session analysis → flow proposals
│ │ ├── services/knowledge_flywheel_scheduler.py # APScheduler job for batch analysis
│ │ └── services/knowledge_gap_service.py # Weak options & escalation signal detection
@@ -131,7 +105,7 @@ patherly/
│ │ ├── components/ # common, layout, dashboard, tree-editor, session, procedural, procedural-editor, library, step-library, ui, flowpilot
│ │ ├── hooks/ # usePermissions, useSessionTimer, useKeyboardShortcuts
│ │ ├── pages/ # All page components
│ │ ├── store/ # Zustand stores (auth, treeEditor, proceduralEditor, userPreferences)
│ │ ├── store/ # Zustand stores (auth, treeEditor, proceduralEditor, userPreferences, scriptGeneratorStore)
│ │ └── types/ # TypeScript interfaces
│ └── (Tailwind v4: CSS-only config in src/index.css)
├── docs/plans/archive/ # Archived design/impl docs (pre-March 2026)
@@ -202,7 +176,7 @@ Official ConnectWise developer guides live in `docs/connectwise/best-practices/`
- Auth: API Key auth (Base64 of `companyId+publicKey:privateKey`) + `clientId` header on every request
- `clientId` is server-side config (`CW_CLIENT_ID` in `config.py`) — identifies the ResolutionFlow app, NOT per-tenant. Per-connection credentials: `company_id`, `public_key`, `private_key`, `server_url`
- All PSA integration code in `services/psa/` — provider pattern with `PSAProvider` abstract base class, `ConnectWiseProvider` implementation, `PsaProviderRegistry` for multi-PSA dispatch
- PSA endpoints in `api/endpoints/psa_connections.py` — connection CRUD, ticket ops, member mapping
- PSA endpoints in `api/endpoints/integrations.py` — connection CRUD, ticket ops, member mapping
- Credentials encrypted at rest via `services/psa/encryption.py` (Fernet)
- Each MSP tenant provides their own CW credentials — ResolutionFlow stores these per-team, never per-user
- Design for the Autotask integration following the same service layer pattern (future PSA)
@@ -366,11 +340,6 @@ gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusi
**88. Charcoal palette — sidebar-darkest approach:** Sidebar `#10121a`, page `#1a1c23`, cards `#22252e`, borders `#2e3240`. This gives more contrast range than true-dark (`#0c0d10`). All colors via CSS variables in `index.css` `@theme` block. Accent is ember orange (#f97316), not cyan.
**89. QuickLaunch merged into CommandPalette:** There is no separate QuickLaunch/lightning bolt. The unified Cmd+K omnibar handles search, navigation, quick actions, and FlowPilot. `QuickLaunch.tsx` was deleted.
**90. Copilot-first UX direction:** The FlowPilot AI chat copilot is the primary experience. Dashboard centers on the chat input. Guided flows (decision trees) are accessible but secondary — in sidebar under "Flows". Maintenance flows are hidden from UI for pilot.
**91. "New Session" button is amber-400:** Sidebar uses `bg-amber-400/15 text-amber-400` for the New Session button, not cyan. This makes it visually distinct from the cyan accent used elsewhere.
**92. `tsc -b` in Dockerfile is stricter than `npx tsc --noEmit`:** The production build (`tsc -b && vite build`) enforces `noUnusedLocals` and `noUnusedParameters` as hard errors. After any refactor that moves logic between components or removes features, trace every import and destructured prop to remove orphans. IDE warnings (yellow squiggles) flag these — check them before pushing.
@@ -390,6 +359,12 @@ gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusi
**100. Hover pop-out card pattern:** For cards that expand on hover "in front of everything": use `pointer-events-none` on the scrim (`fixed inset-0 z-40 bg-black/30`), absolute-position the expanded card at `z-50` with its own `onClick` handler, and dismiss via `onMouseLeave` on the wrapper div. Never put interactive event handlers on the scrim — it blocks clicks on sibling elements.
**101. AI marker format compliance:** The AI assistant uses `[QUESTIONS]`, `[ACTIONS]`, and `[FORK]` markers in responses. Parsed by `unified_chat_service.py` (`_parse_*_marker` functions), returned as structured data in the API response. System prompt in `assistant_chat_service.py` has a final reminder section, and each user message gets an invisible `[SYSTEM: ...]` reminder appended in `_call_anthropic_cached()`. If markers stop appearing: check conversation history stores `display_content` (stripped), verify system prompt final reminder exists, check user message reminder injection is active.
**102. TaskLane activation must happen in ALL chat response paths:** `AssistantChatPage.tsx` has three code paths calling `sendChatMessage`: `handleSend` (regular messages), `sendPrefill` (dashboard handoff), `handleResumeNew` (resume from concluded session). ALL three must check `response.actions`/`response.questions` and call `setShowTaskLane(true)`. Missing this in any path causes TaskLane to not appear on first message.
**103. Docker not available in code-server container:** The dev environment runs code-server inside Docker on the VPS. The `docker` CLI is not available inside the code-server container. To query the database, use the VPS SSH session: `docker exec resolutionflow_postgres psql -U postgres -d resolutionflow -t -c "SQL"`. Python is also not available in the container.
---
## RBAC & Permissions
@@ -517,14 +492,6 @@ When a feature, fix, or significant piece of work is finished and merged/committ
---
## gstack
Use `/browse` from gstack for **all web browsing** — never use `mcp__claude-in-chrome__*` tools.
**Available skills:** `/office-hours`, `/plan-ceo-review`, `/plan-eng-review`, `/plan-design-review`, `/design-consultation`, `/review`, `/ship`, `/browse`, `/qa`, `/qa-only`, `/design-review`, `/setup-browser-cookies`, `/retro`, `/investigate`, `/document-release`, `/codex`, `/careful`, `/freeze`, `/guard`, `/unfreeze`, `/gstack-upgrade`
---
## Future Roadmap
- **Phase 3:** PSA integrations (ConnectWise in progress), file attachments, client context, analytics
@@ -541,6 +508,5 @@ Use `/browse` from gstack for **all web browsing** — never use `mcp__claude-in
| Development Roadmap | [03-DEVELOPMENT-ROADMAP.md](03-DEVELOPMENT-ROADMAP.md) |
| GitHub Issues | `gh issue list --state open` |
| Bugs & Fixes | CLAUDE.md → Critical Lessons Learned section |
| Feature Specs | [04-FEATURE-SPECIFICATIONS.md](04-FEATURE-SPECIFICATIONS.md) |
| Design System | [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) |
| Dev Environment | [DEV-ENV.md](DEV-ENV.md) — 46.202.92.250 setup, Docker, CORS, networking |

View File

@@ -15,7 +15,7 @@ function timeAgo(dateStr: string): string {
return `${Math.floor(hours / 24)}d ago`
}
export function ActiveFlowPilotSessions() {
export function ActiveFlowPilotSessions({ hideHeader = false }: { hideHeader?: boolean }) {
const [sessions, setSessions] = useState<AISessionSummary[]>([])
const [loading, setLoading] = useState(true)
const navigate = useNavigate()
@@ -30,9 +30,11 @@ export function ActiveFlowPilotSessions() {
if (loading) {
return (
<div className="card-flat">
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
</div>
{!hideHeader && (
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-24 rounded-xl bg-card border border-border animate-pulse" />
@@ -44,25 +46,27 @@ export function ActiveFlowPilotSessions() {
return (
<div className="card-flat">
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--color-border-default)' }}
>
<div className="flex items-center gap-2">
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
{sessions.length > 0 && (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-accent-dim px-1.5 text-[0.625rem] font-bold text-primary">
{sessions.length}
</span>
)}
</div>
<Link
to="/sessions?filter=active"
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
{!hideHeader && (
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--color-border-default)' }}
>
View all <ArrowRight size={10} />
</Link>
</div>
<div className="flex items-center gap-2">
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
{sessions.length > 0 && (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-accent-dim px-1.5 text-[0.625rem] font-bold text-primary">
{sessions.length}
</span>
)}
</div>
<Link
to="/sessions?filter=active"
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
>
View all <ArrowRight size={10} />
</Link>
</div>
)}
{sessions.length === 0 ? (
<div className="px-5 py-8 text-center">
@@ -95,7 +99,7 @@ export function ActiveFlowPilotSessions() {
{session.confidence_tier || 'starting'}
</span>
</div>
<p className="text-sm font-medium text-foreground truncate">
<p className="text-sm font-medium text-foreground line-clamp-2">
{session.session_type === 'chat'
? (session.title || session.problem_summary || 'Chat in progress')
: (session.problem_summary || 'Session in progress')}

View File

@@ -0,0 +1,54 @@
import { useState, useEffect } from 'react'
import { CheckCircle, Clock, Zap } from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import { sidebarApi } from '@/api'
interface StatItem {
icon: LucideIcon
value: string | number | null
label: string
color: string
}
export function GreetingStatStrip() {
const [resolved, setResolved] = useState<number | null>(null)
const [active, setActive] = useState<number | null>(null)
const [avgMttr, setAvgMttr] = useState<string | null>(null)
useEffect(() => {
sidebarApi.getStats()
.then((stats) => {
setResolved(stats.resolved_today)
setActive(stats.active_count)
const avg = stats.resolved_today > 0
? Math.round(stats.total_session_minutes_today / stats.resolved_today)
: null
setAvgMttr(avg != null ? `${avg}m` : null)
})
.catch(() => {})
}, [])
const stats: StatItem[] = [
{ icon: CheckCircle, value: resolved, label: 'resolved today', color: '#34d399' },
{ icon: Zap, value: active, label: 'active now', color: '#f97316' },
{ icon: Clock, value: avgMttr, label: 'avg MTTR', color: '#848b9b' },
]
return (
<div className="hidden sm:flex items-center gap-5 pb-1">
{stats.map(({ icon: Icon, value, label, color }) => (
<div key={label} className="flex items-center gap-2">
<Icon size={13} style={{ color }} className="shrink-0" />
<div className="text-right">
<p className="font-heading text-lg font-extrabold leading-none text-[#f0f2f5]">
{value ?? '\u2014'}
</p>
<p className="font-sans text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground mt-0.5">
{label}
</p>
</div>
</div>
))}
</div>
)
}

View File

@@ -52,7 +52,7 @@ export function PerformanceCards() {
label: 'Active Now',
value: active,
icon: TrendingUp,
iconColor: '#38bdf8',
iconColor: '#848b9b',
href: '/sessions?filter=active',
},
{

View File

@@ -22,7 +22,7 @@ const STATUS_CONFIG: Record<string, { icon: typeof CheckCircle; color: string }>
abandoned: { icon: XCircle, color: '#8891a0' },
}
export function RecentFlowPilotSessions() {
export function RecentFlowPilotSessions({ hideHeader = false }: { hideHeader?: boolean }) {
const [sessions, setSessions] = useState<AISessionSummary[]>([])
const navigate = useNavigate()
@@ -42,18 +42,20 @@ export function RecentFlowPilotSessions() {
return (
<div className="card-flat">
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--color-border-default)' }}
>
<h3 className="font-heading text-sm font-bold text-foreground">Recent Sessions</h3>
<Link
to="/sessions"
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
{!hideHeader && (
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--color-border-default)' }}
>
History <ArrowRight size={10} />
</Link>
</div>
<h3 className="font-heading text-sm font-bold text-foreground">Recent Sessions</h3>
<Link
to="/sessions"
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
>
History <ArrowRight size={10} />
</Link>
</div>
)}
<div>
{sessions.map((session, i) => {
const config = STATUS_CONFIG[session.status] || STATUS_CONFIG.abandoned
@@ -73,11 +75,14 @@ export function RecentFlowPilotSessions() {
<StatusIcon size={14} style={{ color: config.color }} className="shrink-0" />
)}
<div className="flex-1 min-w-0">
<p className="text-sm text-foreground truncate">
<p className="text-sm font-medium text-foreground truncate">
{session.session_type === 'chat'
? (session.title || session.problem_summary || 'Chat')
: (session.problem_summary || 'Session')}
</p>
{session.problem_domain && (
<p className="text-[0.625rem] text-muted-foreground mt-0.5 truncate">{session.problem_domain}</p>
)}
</div>
<span className="shrink-0 font-sans text-xs text-muted-foreground">
{timeAgo(session.resolved_at || session.created_at)}

View File

@@ -1,18 +1,19 @@
import { useState, useRef, useEffect, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { Send, Paperclip, Terminal, Loader2, X, RotateCcw, ImagePlus } from 'lucide-react'
import { Send, Paperclip, Terminal, Loader2, X, RotateCcw, ImagePlus, Globe, Mail, Lock, Printer, Shield } from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
import { uploadsApi } from '@/api/uploads'
import { toast } from '@/lib/toast'
import type { PendingUpload } from '@/types/upload'
const SUGGESTIONS = [
'VPN not connecting',
'Outlook not syncing',
'User locked out',
'Slow internet',
'Printer issues',
'MFA problems',
const SUGGESTIONS: { icon: LucideIcon; label: string }[] = [
{ icon: Globe, label: 'VPN not connecting' },
{ icon: Mail, label: 'Outlook not syncing' },
{ icon: Lock, label: 'User locked out' },
{ icon: Globe, label: 'Slow internet' },
{ icon: Printer, label: 'Printer issues' },
{ icon: Shield, label: 'MFA problems' },
]
const ACCEPTED_FILE_TYPES = 'image/png,image/jpeg,image/gif,image/webp,.txt,.log,.csv,.pdf,.docx'
@@ -199,7 +200,7 @@ export function StartSessionInput() {
<div className={cn(
'relative rounded-2xl border bg-card transition-all',
isDragOver ? 'border-primary/50 bg-primary/5' : 'border-border',
'focus-within:border-[rgba(6,182,212,0.3)] focus-within:ring-1 focus-within:ring-primary/20'
'focus-within:border-[rgba(249,115,22,0.25)] focus-within:ring-1 focus-within:ring-[rgba(249,115,22,0.1)]'
)}>
{/* Drag overlay */}
{isDragOver && (
@@ -337,15 +338,16 @@ export function StartSessionInput() {
</div>
{/* Suggestion chips */}
<div className="flex flex-wrap gap-2 mt-3">
{SUGGESTIONS.map((s) => (
<div className="flex flex-wrap gap-2 mt-4">
{SUGGESTIONS.map(({ icon: Icon, label }) => (
<button
key={s}
key={label}
type="button"
onClick={() => handleSuggestionClick(s)}
className="rounded-full border border-border px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:border-primary/30 hover:bg-primary/5 transition-colors"
onClick={() => handleSuggestionClick(label)}
className="group flex items-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground transition-all hover:border-[var(--color-border-hover)] hover:bg-[var(--color-bg-elevated)] hover:text-foreground active:scale-[0.97]"
>
{s}
<Icon size={11} className="text-muted shrink-0 group-hover:text-[#f97316] transition-colors" />
{label}
</button>
))}
</div>

View File

@@ -1,4 +1,3 @@
import { useState } from 'react'
import { PageMeta } from '@/components/common/PageMeta'
import { useAuthStore } from '@/store/authStore'
import { StartSessionInput } from '@/components/dashboard/StartSessionInput'
@@ -8,31 +7,49 @@ import { PerformanceCards } from '@/components/dashboard/PerformanceCards'
import { KnowledgeBaseCards } from '@/components/dashboard/KnowledgeBaseCards'
import { TeamSummary } from '@/components/dashboard/TeamSummary'
import { RecentFlowPilotSessions } from '@/components/dashboard/RecentFlowPilotSessions'
import { ChevronDown } from 'lucide-react'
import { cn } from '@/lib/utils'
import { GreetingStatStrip } from '@/components/dashboard/GreetingStatStrip'
function SectionLabel({ children, action }: { children: React.ReactNode; action?: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="font-sans text-[0.625rem] uppercase tracking-[0.12em] font-semibold text-muted-foreground">
{children}
</span>
<div className="flex-1 h-px bg-border" />
{action && <div className="shrink-0">{action}</div>}
</div>
)
}
export function QuickStartPage() {
const user = useAuthStore((s) => s.user)
const [dashboardExpanded, setDashboardExpanded] = useState(false)
const greeting = new Date().getHours() < 12
const now = new Date()
const greeting = now.getHours() < 12
? 'morning'
: new Date().getHours() < 18
: now.getHours() < 18
? 'afternoon'
: 'evening'
const dayOfWeek = now.toLocaleDateString('en-US', { weekday: 'long' })
const formattedDate = now.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })
const firstName = user?.name?.split(' ')[0] || 'there'
return (
<div className="overflow-y-auto h-full">
<PageMeta title="ResolutionFlow" />
<div className="max-w-3xl mx-auto px-6 pt-12 pb-8">
{/* Hero: Greeting + Input */}
<div className="text-center mb-6">
<h1 className="font-heading text-2xl font-extrabold tracking-tight text-foreground">
Good {greeting}, {user?.name?.split(' ')[0] || 'there'}
</h1>
<p className="mt-1 text-base text-muted-foreground">
What are you troubleshooting?
</p>
<div className="max-w-4xl mx-auto px-6 pt-12 pb-12">
{/* Hero: Greeting + Stat Strip */}
<div className="flex items-end justify-between mb-8 animate-fade-in-up">
<div>
<p className="font-sans text-xs uppercase tracking-[0.12em] text-muted-foreground mb-1">
{dayOfWeek}, {formattedDate}
</p>
<h1 className="font-heading text-3xl sm:text-4xl font-extrabold tracking-tight text-[#f0f2f5] leading-tight">
Good {greeting},<br className="hidden sm:block" />
{firstName}.
</h1>
</div>
<GreetingStatStrip />
</div>
{/* Chat-style input */}
@@ -44,39 +61,31 @@ export function QuickStartPage() {
</div>
{/* Active Sessions */}
<div className="mt-4">
<ActiveFlowPilotSessions />
<div className="mt-8">
<SectionLabel>Active Sessions</SectionLabel>
<div className="mt-3">
<ActiveFlowPilotSessions hideHeader />
</div>
</div>
{/* Recent Sessions */}
<div className="mt-4">
<RecentFlowPilotSessions />
<div className="mt-8">
<SectionLabel>Recent Sessions</SectionLabel>
<div className="mt-3">
<RecentFlowPilotSessions hideHeader />
</div>
</div>
{/* Collapsible Dashboard section */}
<div className="mt-8">
<button
type="button"
onClick={() => setDashboardExpanded(!dashboardExpanded)}
className="flex items-center gap-2 text-xs font-sans uppercase tracking-wide text-muted-foreground hover:text-foreground transition-colors w-full"
>
<div className="flex-1 h-px bg-border" />
<span className="flex items-center gap-1.5 px-3">
Dashboard
<ChevronDown size={12} className={cn('transition-transform', dashboardExpanded && 'rotate-180')} />
</span>
<div className="flex-1 h-px bg-border" />
</button>
{dashboardExpanded && (
<div className="mt-4 space-y-4 animate-fade-in">
<PerformanceCards />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<KnowledgeBaseCards />
<TeamSummary />
</div>
{/* Dashboard — always visible */}
<div className="mt-10">
<SectionLabel>Dashboard</SectionLabel>
<div className="mt-3 space-y-4 animate-fade-in">
<PerformanceCards />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<KnowledgeBaseCards />
<TeamSummary />
</div>
)}
</div>
</div>
</div>
</div>