* docs: add 5 sidebar icon color concepts for UX review Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): add semantic icon colors and updated icons to sidebar nav Swap generic icons for more descriptive alternatives (Network, Wrench, FileOutput, Library, Code2, Lightbulb) and assign each nav item a unique semantic color for instant visual landmarks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): default Sessions page to Active tab, reorder tabs Active sessions are what engineers care about most. Tab order is now Active, Prepared, Completed, All. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add sidebar grouping and AI naming concept mockups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add sidebar redesign context and decision summary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add sidebar redesign spec and implementation plan Design spec covers: activity zone with daily stats + session feed, nav grouping (Resolve/Build/Insights), AI split (FlowPilot + Flow Assist), pinned flows removal. Implementation plan has 5 chunks, 12 tasks, 39 steps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add sidebar stats Pydantic schemas Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add failing tests for sidebar stats endpoint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add sidebar stats endpoint with daily stats and activity feed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add sidebar API client, stats bar, activity feed components New components: SidebarStatsBar, SidebarActivityFeed, ActivityItem. New API client for sidebar stats endpoint. Pulse-dot CSS animation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: restructure sidebar with stats bar, activity feed, and grouped nav Dashboard-first layout with Resolve/Build/Insights groups. AI split: FlowPilot (Resolve) + Flow Assist (Build). Stats bar: Resolved/Active/In Session daily counters. Activity feed: active sessions with CW ticket #, recent completions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove pinned flows frontend (PinnedFlowsSection, store, API, pin buttons) Removed: PinnedFlowsSection component, pinnedFlowsStore, pinnedFlows API client. Cleaned: pin buttons from TreeGridView, TreeListView, TreeTableView. Cleaned: favorites section from QuickStartPage, pin props from TreeLibraryPage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add FlowAssistPage placeholder and /flow-assist route Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: real-time sidebar stats via session-changed events Sidebar now refreshes stats when sessions are created or completed, not just on page navigation. Uses window event bus pattern (same as folder-changed events in codebase). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: live-ticking In Session timer using active session start times SidebarStatsBar now computes active session elapsed time client-side from started_at timestamps, ticking every 60s. Backend only returns completed session minutes to avoid double-counting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sidebar In Session timer ticks every second and shows seconds Timer now uses 1s interval (not 60s) and displays seconds when under a minute so it matches the session timer in the flow UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: trigger PR environment redeploy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * debug: add console.log to SidebarStatsBar for timer investigation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: parse sidebar timestamps as UTC (append Z suffix) Backend returns naive UTC timestamps without timezone indicator. JS Date() treats bare ISO strings as local time, causing the timer to compute negative elapsed time (future timestamps). Appending 'Z' forces UTC parsing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: rename 'In Session' to 'Total Time' for clarity Makes it clear the timer is an aggregate of all sessions today, not just the current one. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
455 lines
14 KiB
CSS
455 lines
14 KiB
CSS
@import 'tailwindcss';
|
|
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
|
|
/* React Flow — imported here (not in component JS) so @tailwindcss/vite
|
|
doesn't wrap it in a cascade layer, which would lower its specificity
|
|
below Tailwind's own styles and hide nodes/edges. */
|
|
@import '@xyflow/react/dist/style.css';
|
|
|
|
@theme {
|
|
/* ── Brand tokens ─────────────────────────────────── */
|
|
--color-brand-gradient-from: oklch(0.65 0.13 195); /* #06b6d4 cyan-500 */
|
|
--color-brand-gradient-to: oklch(0.74 0.12 195); /* #22d3ee cyan-400 */
|
|
--color-brand-dark: oklch(0.145 0.013 264); /* #101114 */
|
|
--color-brand-dark-card: oklch(0.17 0.01 264); /* #14161a */
|
|
--color-brand-dark-surface: oklch(0.17 0.01 264); /* #14161a */
|
|
--color-brand-text-primary: oklch(0.98 0.005 264); /* #f8fafc */
|
|
--color-brand-text-secondary: oklch(0.63 0.02 260); /* #8891a0 */
|
|
--color-brand-text-muted: oklch(0.45 0.015 260); /* #5a6170 */
|
|
--color-brand-border: oklch(0.35 0 0 / 0.06);
|
|
|
|
/* ── Semantic color tokens (OKLCH, direct values) ── */
|
|
--color-background: oklch(0.145 0.013 264); /* dark charcoal #101114 */
|
|
--color-foreground: oklch(0.98 0.005 264); /* near-white #f8fafc */
|
|
|
|
--color-card: oklch(0.178 0.008 264); /* #17191d */
|
|
--color-card-foreground: oklch(0.98 0.005 264);
|
|
|
|
--color-popover: oklch(0.178 0.008 264);
|
|
--color-popover-foreground: oklch(0.98 0.005 264);
|
|
|
|
--color-primary: oklch(0.65 0.13 195); /* cyan #1ea8c4 */
|
|
--color-primary-foreground: oklch(0.145 0.013 264); /* dark bg for contrast */
|
|
|
|
--color-secondary: oklch(0.22 0.008 264); /* #212329 */
|
|
--color-secondary-foreground: oklch(0.98 0.005 264);
|
|
|
|
--color-muted: oklch(0.22 0.008 264);
|
|
--color-muted-foreground: oklch(0.63 0.02 260); /* #8891a0 */
|
|
|
|
--color-accent: oklch(0.22 0.008 264);
|
|
--color-accent-foreground: oklch(0.98 0.005 264);
|
|
|
|
--color-destructive: oklch(0.55 0.22 15); /* rose #e63359 */
|
|
--color-destructive-foreground: oklch(0.98 0.005 264);
|
|
|
|
--color-border: oklch(0.22 0.008 264);
|
|
--color-input: oklch(0.22 0.008 264);
|
|
--color-ring: oklch(0.65 0.13 195); /* cyan, matches primary */
|
|
|
|
/* ── Radii ───────────────────────────────────────── */
|
|
--radius-sm: 0.5rem;
|
|
--radius-md: 0.625rem;
|
|
--radius-lg: 0.75rem;
|
|
--radius-xl: 1rem;
|
|
|
|
/* ── Fonts ───────────────────────────────────────── */
|
|
--font-sans:
|
|
'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
Roboto, sans-serif;
|
|
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
|
|
--font-label: 'JetBrains Mono', monospace;
|
|
|
|
/* ── Gradients ───────────────────────────────────── */
|
|
--background-image-gradient-brand: linear-gradient(
|
|
135deg,
|
|
oklch(0.65 0.13 195) 0%,
|
|
oklch(0.74 0.12 195) 100%
|
|
);
|
|
--background-image-gradient-brand-hover: linear-gradient(
|
|
135deg,
|
|
oklch(0.58 0.12 195) 0%,
|
|
oklch(0.65 0.13 195) 100%
|
|
);
|
|
|
|
/* ── Animations (keyframes inside @theme) ────────── */
|
|
--animate-fade-in: fade-in 200ms ease-out;
|
|
--animate-fade-in-up: fade-in-up 200ms ease-out;
|
|
--animate-slide-in-left: slide-in-from-left 200ms ease-out;
|
|
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out;
|
|
--animate-scale-in: scale-in 150ms ease-out;
|
|
--animate-slide-down: slideDown 400ms ease-out;
|
|
--animate-slide-in-right: slideInRight 300ms ease-out;
|
|
--animate-fade-in-right: fadeInRight 400ms ease-out;
|
|
--animate-breathe-glow: breatheGlow 3s ease-in-out infinite alternate;
|
|
--animate-bell-wobble: bellWobble 500ms ease-in-out;
|
|
--animate-fade: fadeIn 300ms ease forwards;
|
|
|
|
@keyframes fade-in {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
@keyframes fade-in-up {
|
|
from { opacity: 0; transform: translateY(4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes slide-in-from-left {
|
|
from { transform: translateX(-100%); }
|
|
to { transform: translateX(0); }
|
|
}
|
|
|
|
@keyframes slide-in-from-bottom {
|
|
from { opacity: 0; transform: translateY(16px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes scale-in {
|
|
from { opacity: 0; transform: scale(0.95); }
|
|
to { opacity: 1; transform: scale(1); }
|
|
}
|
|
|
|
@keyframes slideDown {
|
|
from { transform: translateY(-100%); opacity: 0; }
|
|
to { transform: translateY(0); opacity: 1; }
|
|
}
|
|
|
|
@keyframes slideInRight {
|
|
from { transform: translateX(100%); }
|
|
to { transform: translateX(0); }
|
|
}
|
|
|
|
@keyframes fadeInRight {
|
|
from { transform: translateX(30px); opacity: 0; }
|
|
to { transform: translateX(0); opacity: 1; }
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(6px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes breatheGlow {
|
|
from {
|
|
box-shadow:
|
|
0 8px 32px rgba(0, 0, 0, 0.3),
|
|
0 0 20px oklch(0.65 0.13 195 / 0.04);
|
|
}
|
|
to {
|
|
box-shadow:
|
|
0 8px 32px rgba(0, 0, 0, 0.3),
|
|
0 0 30px oklch(0.65 0.13 195 / 0.12);
|
|
}
|
|
}
|
|
|
|
@keyframes bellWobble {
|
|
0% { transform: rotate(0deg); }
|
|
20% { transform: rotate(8deg); }
|
|
40% { transform: rotate(-6deg); }
|
|
60% { transform: rotate(4deg); }
|
|
80% { transform: rotate(-2deg); }
|
|
100% { transform: rotate(0deg); }
|
|
}
|
|
|
|
@keyframes pulse-dot {
|
|
0%, 100% { box-shadow: 0 0 4px rgba(52,211,153,0.4); }
|
|
50% { box-shadow: 0 0 8px rgba(52,211,153,0.7); }
|
|
}
|
|
|
|
@keyframes stagger-fade-in {
|
|
from { opacity: 0; transform: translateY(8px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
}
|
|
|
|
/* ── Root CSS variables (non-theme: glass, shadows, layout) ── */
|
|
:root {
|
|
/* App Shell tokens */
|
|
--sidebar-w: 260px;
|
|
--sidebar-bg: oklch(0.13 0.013 264);
|
|
--sidebar-hover: var(--color-secondary);
|
|
--sidebar-active: oklch(0.65 0.13 195 / 0.10);
|
|
--text-dimmed: oklch(0.42 0.015 260);
|
|
|
|
/* Glass system */
|
|
--glass-bg: oklch(0.16 0.01 264 / 0.55);
|
|
--glass-bg-hover: oklch(0.16 0.01 264 / 0.7);
|
|
--glass-border: oklch(1 0 0 / 0.06);
|
|
--glass-border-hover: oklch(1 0 0 / 0.12);
|
|
--glass-blur: blur(16px);
|
|
--glass-blur-strong: blur(20px);
|
|
--glass-blur-light: blur(12px);
|
|
|
|
/* Shadow system */
|
|
--shadow-float: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
--shadow-float-hover: 0 12px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px oklch(1 0 0 / 0.08);
|
|
--shadow-cyan-glow: 0 8px 32px oklch(0.65 0.13 195 / 0.08);
|
|
|
|
/* Easing */
|
|
--ease-out-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
/* ── Base styles ─────────────────────────────────────── */
|
|
@layer base {
|
|
* {
|
|
@apply border-border;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--color-border) transparent;
|
|
}
|
|
*::-webkit-scrollbar {
|
|
width: 6px;
|
|
height: 6px;
|
|
}
|
|
*::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
*::-webkit-scrollbar-thumb {
|
|
background-color: var(--color-border);
|
|
border-radius: 9999px;
|
|
}
|
|
*::-webkit-scrollbar-thumb:hover {
|
|
background-color: var(--color-muted-foreground);
|
|
}
|
|
|
|
body {
|
|
@apply bg-background text-foreground font-sans;
|
|
}
|
|
|
|
h1, h2, h3, h4, h5, h6 {
|
|
font-family: var(--font-heading);
|
|
font-weight: 700;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
}
|
|
|
|
/* ── Custom utilities ────────────────────────────────── */
|
|
@utility btn-press {
|
|
@apply active:scale-[0.98] transition-transform;
|
|
}
|
|
|
|
@utility text-gradient-brand {
|
|
@apply bg-gradient-brand bg-clip-text text-transparent;
|
|
}
|
|
|
|
@utility glass-card {
|
|
background: var(--glass-bg);
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
border: 1px solid var(--glass-border);
|
|
border-radius: 16px;
|
|
box-shadow: var(--shadow-float);
|
|
transition:
|
|
transform 200ms var(--ease-out-smooth),
|
|
border-color 200ms var(--ease-out-smooth),
|
|
box-shadow 200ms var(--ease-out-smooth);
|
|
&:hover {
|
|
transform: scale(1.02);
|
|
border-color: var(--glass-border-hover);
|
|
box-shadow: var(--shadow-float-hover);
|
|
}
|
|
}
|
|
|
|
@utility glass-card-static {
|
|
background: var(--glass-bg);
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
border: 1px solid var(--glass-border);
|
|
border-radius: 16px;
|
|
box-shadow: var(--shadow-float);
|
|
}
|
|
|
|
@utility active-glow {
|
|
animation: breatheGlow 3s ease-in-out infinite alternate;
|
|
}
|
|
|
|
@utility stagger-item {
|
|
opacity: 0;
|
|
animation: stagger-fade-in 350ms var(--ease-out-smooth) forwards;
|
|
animation-delay: calc(var(--stagger-index, 0) * 50ms);
|
|
}
|
|
|
|
@utility rdp-custom {
|
|
@apply text-foreground;
|
|
& .rdp-month { @apply w-full; }
|
|
& .rdp-caption { @apply flex justify-center items-center mb-4; }
|
|
& .rdp-caption_label { @apply text-sm font-medium; }
|
|
& .rdp-nav { @apply flex gap-1; }
|
|
& .rdp-nav_button { @apply h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100; }
|
|
& .rdp-table { @apply w-full border-collapse; }
|
|
& .rdp-head_cell { @apply text-muted-foreground font-normal text-xs; }
|
|
& .rdp-cell { @apply text-center text-sm p-0; }
|
|
& .rdp-day { @apply h-9 w-9 p-0 font-normal hover:bg-accent rounded-md transition-colors; }
|
|
& .rdp-day_selected { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
|
|
& .rdp-day_today { @apply bg-accent text-accent-foreground; }
|
|
& .rdp-day_outside { @apply text-muted-foreground opacity-50; }
|
|
& .rdp-day_disabled { @apply text-muted-foreground opacity-50; }
|
|
& .rdp-day_range_middle { @apply bg-accent text-accent-foreground; }
|
|
& .rdp-day_hidden { @apply invisible; }
|
|
}
|
|
|
|
/* ── Layout utilities ────────────────────────────────── */
|
|
@layer utilities {
|
|
.app-shell {
|
|
display: grid;
|
|
grid-template-columns: var(--sidebar-w) 1fr;
|
|
grid-template-rows: 56px 1fr;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
transition: grid-template-columns 200ms ease;
|
|
}
|
|
|
|
.app-shell--collapsed {
|
|
grid-template-columns: 56px 1fr;
|
|
}
|
|
|
|
.topbar {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.sidebar {
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.main-content {
|
|
min-height: 0;
|
|
min-width: 0;
|
|
/* Each page handles its own scrolling — full-height pages use
|
|
overflow-hidden, scrollable pages use overflow-y-auto */
|
|
}
|
|
|
|
@media (max-width: 767px) {
|
|
.app-shell {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* Staggered fade-in helper */
|
|
.fade-in {
|
|
animation: fadeIn 0.3s ease forwards;
|
|
}
|
|
}
|
|
|
|
/* ── Sonner toast overrides ──────────────────────────── */
|
|
[data-sonner-toast] {
|
|
background-color: var(--color-card) !important;
|
|
color: var(--color-card-foreground) !important;
|
|
border: 1px solid var(--color-border) !important;
|
|
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3) !important;
|
|
border-radius: 0.75rem;
|
|
font-family: var(--font-sans);
|
|
}
|
|
|
|
[data-sonner-toast] [data-title] {
|
|
font-family: var(--font-sans);
|
|
font-weight: 600;
|
|
}
|
|
|
|
[data-sonner-toast][data-type='success'] {
|
|
border-color: oklch(0.76 0.15 163 / 0.3) !important;
|
|
}
|
|
[data-sonner-toast][data-type='success'] [data-icon] {
|
|
color: oklch(0.76 0.15 163);
|
|
}
|
|
|
|
[data-sonner-toast][data-type='error'] {
|
|
border-color: oklch(0.7 0.16 22 / 0.3) !important;
|
|
}
|
|
[data-sonner-toast][data-type='error'] [data-icon] {
|
|
color: oklch(0.7 0.16 22);
|
|
}
|
|
|
|
[data-sonner-toast][data-type='info'] {
|
|
border-color: var(--color-border) !important;
|
|
}
|
|
[data-sonner-toast][data-type='info'] [data-icon] {
|
|
color: var(--color-muted-foreground);
|
|
}
|
|
|
|
[data-sonner-toast][data-type='warning'] {
|
|
border-color: oklch(0.82 0.16 85 / 0.3) !important;
|
|
}
|
|
[data-sonner-toast][data-type='warning'] [data-icon] {
|
|
color: oklch(0.82 0.16 85);
|
|
}
|
|
|
|
[data-sonner-toast] [data-close-button] {
|
|
color: var(--color-muted-foreground);
|
|
border-radius: 0.375rem;
|
|
transition: color 150ms, background-color 150ms;
|
|
}
|
|
[data-sonner-toast] [data-close-button]:hover {
|
|
background-color: var(--color-accent);
|
|
color: var(--color-accent-foreground);
|
|
}
|
|
|
|
/* ── React Flow dark theme overrides ─────────────────── */
|
|
/* React Flow v12 uses --xy-* CSS custom properties for theming.
|
|
Override the defaults to match our Slate & Ice design system. */
|
|
.react-flow.dark {
|
|
--xy-background-color-default: transparent;
|
|
--xy-edge-stroke-default: var(--color-border);
|
|
--xy-edge-stroke-selected-default: var(--color-primary);
|
|
--xy-edge-label-color-default: var(--color-muted-foreground);
|
|
--xy-edge-label-background-color-default: var(--color-card);
|
|
--xy-node-background-color-default: var(--color-card);
|
|
--xy-node-color-default: var(--color-foreground);
|
|
--xy-node-border-default: 1px solid var(--color-border);
|
|
--xy-handle-background-color-default: var(--color-border);
|
|
--xy-handle-border-color-default: var(--color-card);
|
|
--xy-minimap-background-color-default: var(--color-card);
|
|
--xy-minimap-mask-background-color-default: oklch(0.22 0.008 264 / 0.6);
|
|
--xy-controls-button-background-color-default: var(--color-card);
|
|
--xy-controls-button-background-color-hover-default: var(--color-accent);
|
|
--xy-controls-button-color-default: var(--color-muted-foreground);
|
|
--xy-controls-button-color-hover-default: var(--color-foreground);
|
|
--xy-controls-button-border-color-default: var(--color-border);
|
|
}
|
|
|
|
.react-flow__controls {
|
|
border-radius: 0.75rem !important;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3) !important;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.react-flow__minimap {
|
|
border-radius: 0.75rem !important;
|
|
}
|
|
|
|
.react-flow__attribution {
|
|
display: none;
|
|
}
|
|
|
|
/* ── Glow edge animations ────────────────────────────── */
|
|
@keyframes glow-flow-downstream {
|
|
from { stroke-dashoffset: 40; }
|
|
to { stroke-dashoffset: 0; }
|
|
}
|
|
|
|
@keyframes glow-flow-upstream {
|
|
from { stroke-dashoffset: 0; }
|
|
to { stroke-dashoffset: 40; }
|
|
}
|
|
|
|
.glow-edge-flow-downstream {
|
|
animation: glow-flow-downstream 1s linear infinite;
|
|
}
|
|
|
|
.glow-edge-flow-upstream {
|
|
animation: glow-flow-upstream 1s linear infinite;
|
|
}
|
|
|
|
/* ── Accessibility: Reduce motion ────────────────────── */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|