feat: UI design system - sidebar layout, workspace system, and shell redesign (#77)

* feat: add workspace system and sidebar layout (UI design system Phase A+B)

Backend: Workspace model, migration (036), schemas, CRUD API endpoints.
Adds workspace_id to trees and categories, seeds 4 default workspaces
per account, auto-assigns existing trees by tree_type.

Frontend: Complete AppLayout rewrite from top-nav to CSS Grid shell
with persistent sidebar + topbar. New components: WorkspaceSwitcher,
NavItem, CategoryList, TagCloud, TopBar, Sidebar. Dashboard components:
QuickStats, FiltersBar, SectionGroup, TreeListItem, SessionsPanel.
WorkspaceStore with localStorage persistence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add command palette search, dashboard rewrite, and shell height fixes (Phase C)

- Add ⌘K command palette with debounced search across flows and sessions
- Rewrite QuickStartPage as dashboard with stats, filters, sessions panel
- Fix h-[calc(100vh-4rem)] → h-full across all pages for CSS Grid shell
- Add active session count badge to sidebar Sessions nav item

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add sidebar collapse, category/tag filtering, and workspace CRUD (Phase D)

- Sidebar collapse/expand toggle with icon-only rail mode (persisted)
- Sidebar category/tag clicks navigate to /trees with URL params
- TreeLibraryPage syncs filters from URL search params bidirectionally
- Workspace create modal with icon picker and auto-slug generation
- TopBar logo adapts to collapsed sidebar state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Quick Launch modal with actions and recent flows

- Zap button opens Quick Launch with create/navigate shortcuts
- Shows recent flows for quick session start
- Keyboard navigation support (arrows + enter)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add activity notifications panel with session feed

- Bell icon shows dot indicator for recent activity
- Dropdown panel shows recent sessions with status icons
- Links to session detail and sessions list page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: remove workspace system, add pinned flows and label renames

Replace workspace system with pinned flows API (pin/unpin/list/reorder).
Rename user-facing labels: Tree→Flow, Procedure→Project. Add sidebar
nav sub-items for flow type filtering. Remove 11 workspace files,
add migrations 037-038, clean all workspace references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: collapsed sidebar layout scaling and toggle button size

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate auth pages to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate TreeLibraryPage to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate session pages to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate TreeEditorPage to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate TreeNavigationPage to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate session sharing components to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove workspace dropdown animation (dead code)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate common components to new design system

Migrate 15 components from monochrome glass-card design to purple gradient
accent design system tokens (bg-card, border-border, text-foreground,
bg-gradient-brand, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate procedural and step library components to new design system

Migrate 10 components from monochrome glass-card design to purple gradient
accent design system tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate admin pages and components to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate remaining pages to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate remaining components to new design system

Migrates 38 files: tree-editor forms, session modals, step library,
common components, library views, tree preview, and misc UI to use
design tokens (bg-card, border-border, text-foreground, bg-accent,
bg-gradient-brand) replacing old monochrome patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: keep brand text visible on sidebar collapse, hide sub-items until hover

- TopBar: always show "ResolutionFlow" text regardless of sidebar state
- NavItem: sub-items (Troubleshooting, Projects) hidden by default,
  revealed on hover or when a child route is active

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #77.
This commit is contained in:
chihlasm
2026-02-15 22:45:19 -05:00
committed by GitHub
parent ef829f06a4
commit fa709faa60
138 changed files with 5011 additions and 3796 deletions

View File

@@ -53,8 +53,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
ref={buttonRef}
onClick={() => setOpen(!open)}
className={cn(
'rounded-md p-1.5 text-white/50 transition-colors',
'hover:bg-white/10 hover:text-white'
'rounded-md p-1.5 text-muted-foreground transition-colors',
'hover:bg-accent hover:text-foreground'
)}
>
<MoreHorizontal className="h-4 w-4" />
@@ -63,8 +63,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
<div
ref={menuRef}
className={cn(
'fixed z-50 min-w-[160px] rounded-md border border-white/10',
'bg-black py-1 shadow-lg animate-scale-in'
'fixed z-50 min-w-[160px] rounded-md border border-border',
'bg-card py-1 shadow-lg animate-scale-in'
)}
style={{
top: `${menuPosition.top}px`,
@@ -81,7 +81,7 @@ export function ActionMenu({ items }: ActionMenuProps) {
'disabled:opacity-50 disabled:pointer-events-none',
item.destructive
? 'text-red-400 hover:bg-red-400/10'
: 'text-white/70 hover:bg-white/[0.06]'
: 'text-muted-foreground hover:bg-accent'
)}
>
{item.icon}

View File

@@ -34,9 +34,9 @@ export function AdminLayout() {
}, [mobileOpen, handleKeyDown])
return (
<div className="flex h-[calc(100vh-4rem)]">
<div className="flex h-full">
{/* Desktop sidebar */}
<div className="hidden w-60 flex-shrink-0 border-r border-white/[0.06] bg-black md:block">
<div className="hidden w-60 flex-shrink-0 border-r border-border bg-card md:block">
<AdminSidebar />
</div>
@@ -44,14 +44,14 @@ export function AdminLayout() {
{mobileOpen && (
<div className="fixed inset-0 z-40 md:hidden">
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
className="absolute inset-0 bg-card/80 backdrop-blur-sm"
onClick={() => setMobileOpen(false)}
/>
<div className="absolute inset-y-0 left-0 w-60 border-r border-white/[0.06] bg-black shadow-xl">
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">
<div className="flex h-12 items-center justify-end px-3">
<button
onClick={() => setMobileOpen(false)}
className="rounded-md p-1.5 text-white/50 hover:bg-white/[0.06]"
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent"
>
<X className="h-4 w-4" />
</button>
@@ -67,7 +67,7 @@ export function AdminLayout() {
{/* Mobile menu button */}
<button
onClick={() => setMobileOpen(true)}
className="mb-4 rounded-md p-2 text-white/50 hover:bg-white/[0.06] md:hidden"
className="mb-4 rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden"
>
<Menu className="h-5 w-5" />
</button>

View File

@@ -39,7 +39,7 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
return (
<aside className={cn('flex h-full flex-col', className)}>
<div className="p-4">
<h2 className="text-lg font-bold text-white">Admin Panel</h2>
<h2 className="text-lg font-bold text-foreground">Admin Panel</h2>
</div>
<nav className="flex-1 space-y-1 px-3">
{navItems.map((item) => (
@@ -50,8 +50,8 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
className={cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
isActive(item.path, item.end)
? 'bg-white/10 text-white'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
? 'bg-accent text-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<item.icon className="h-4 w-4" />
@@ -59,13 +59,13 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
</Link>
))}
</nav>
<div className="border-t border-white/[0.06] p-3">
<div className="border-t border-border p-3">
<Link
to="/trees"
onClick={onNavigate}
className={cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium',
'text-white/50 hover:bg-white/[0.06] hover:text-white'
'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<ArrowLeft className="h-4 w-4" />

View File

@@ -38,7 +38,7 @@ export function CategoryRow({
ref={setNodeRef}
style={style}
className={cn(
'flex items-center gap-3 glass-card rounded-2xl p-4',
'flex items-center gap-3 bg-card border border-border rounded-xl p-4',
isDragging && 'opacity-50'
)}
>
@@ -47,7 +47,7 @@ export function CategoryRow({
type="button"
{...attributes}
{...listeners}
className="cursor-grab touch-none text-white/50 hover:text-white active:cursor-grabbing"
className="cursor-grab touch-none text-muted-foreground hover:text-foreground active:cursor-grabbing"
aria-label="Drag to reorder"
>
<GripVertical className="h-5 w-5" />
@@ -56,17 +56,17 @@ export function CategoryRow({
{/* Category Info */}
<div className="flex-1">
<div className="flex items-center gap-2">
<h3 className="font-medium text-white">{category.name}</h3>
<h3 className="font-medium text-foreground">{category.name}</h3>
{!category.is_active && (
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs font-medium text-white/70">
<span className="rounded-full bg-accent px-2 py-0.5 text-xs font-medium text-muted-foreground">
Archived
</span>
)}
</div>
{category.description && (
<p className="mt-1 text-sm text-white/40">{category.description}</p>
<p className="mt-1 text-sm text-muted-foreground">{category.description}</p>
)}
<p className="mt-1 text-xs text-white/40">
<p className="mt-1 text-xs text-muted-foreground">
{stepCount} step{stepCount !== 1 ? 's' : ''}
</p>
</div>
@@ -77,8 +77,8 @@ export function CategoryRow({
type="button"
onClick={() => onEdit(category)}
className={cn(
'rounded-md border border-white/10 bg-black/50 p-2 text-white/50',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border bg-card p-2 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Edit category"
aria-label="Edit category"

View File

@@ -59,14 +59,14 @@ export function CreateCategoryModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Create Category</h2>
<h2 className="text-lg font-semibold text-foreground">Create Category</h2>
<button
onClick={handleClose}
disabled={isSaving}
className="rounded-full p-1 text-white/50 hover:bg-white/10 hover:text-white disabled:opacity-50"
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
@@ -83,7 +83,7 @@ export function CreateCategoryModal({
{/* Name Field */}
<div>
<label htmlFor="name" className="mb-1 block text-sm font-medium text-white">
<label htmlFor="name" className="mb-1 block text-sm font-medium text-foreground">
Category Name <span className="text-red-400">*</span>
</label>
<input
@@ -96,21 +96,21 @@ export function CreateCategoryModal({
placeholder="e.g., Network Troubleshooting"
required
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
<p className="mt-1 text-xs text-white/40">
<p className="mt-1 text-xs text-muted-foreground">
{name.length}/100 characters
</p>
</div>
{/* Description Field */}
<div>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
id="description"
@@ -120,9 +120,9 @@ export function CreateCategoryModal({
rows={3}
placeholder="Brief description of this category..."
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
@@ -135,8 +135,8 @@ export function CreateCategoryModal({
onClick={handleClose}
disabled={isSaving}
className={cn(
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground disabled:opacity-50'
)}
>
Cancel
@@ -145,8 +145,8 @@ export function CreateCategoryModal({
type="submit"
disabled={isSaving || !name.trim()}
className={cn(
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
'rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium',
'hover:opacity-90 disabled:opacity-50'
)}
>
{isSaving ? 'Creating...' : 'Create Category'}

View File

@@ -50,16 +50,16 @@ export function DataTable<T>({
}
return (
<div className="overflow-x-auto rounded-lg border border-white/[0.06]">
<div className="overflow-x-auto rounded-lg border border-border">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/[0.06] bg-white/[0.02]">
<tr className="border-b border-border bg-accent">
{columns.map((col) => (
<th
key={col.key}
className={cn(
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-white/50',
col.sortable && 'cursor-pointer select-none hover:text-white',
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-muted-foreground',
col.sortable && 'cursor-pointer select-none hover:text-foreground',
col.className
)}
onClick={col.sortable ? () => handleSort(col.key) : undefined}
@@ -87,10 +87,10 @@ export function DataTable<T>({
<tbody>
{isLoading ? (
Array.from({ length: skeletonRows }).map((_, i) => (
<tr key={i} className="border-b border-white/[0.06] last:border-0">
<tr key={i} className="border-b border-border last:border-0">
{columns.map((col) => (
<td key={col.key} className="px-4 py-3">
<div className="h-4 w-3/4 animate-pulse rounded bg-white/10" />
<div className="h-4 w-3/4 animate-pulse rounded bg-accent" />
</td>
))}
</tr>
@@ -99,7 +99,7 @@ export function DataTable<T>({
<tr>
<td colSpan={columns.length} className="px-4 py-12 text-center">
{emptyState || (
<span className="text-white/40">No data found</span>
<span className="text-muted-foreground">No data found</span>
)}
</td>
</tr>
@@ -107,7 +107,7 @@ export function DataTable<T>({
data.map((item) => (
<tr
key={keyExtractor(item)}
className="border-b border-white/[0.06] last:border-0 hover:bg-white/[0.04] transition-colors"
className="border-b border-border last:border-0 hover:bg-accent transition-colors"
>
{columns.map((col) => (
<td key={col.key} className={cn('px-4 py-3', col.className)}>

View File

@@ -68,14 +68,14 @@ export function EditCategoryModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Edit Category</h2>
<h2 className="text-lg font-semibold text-foreground">Edit Category</h2>
<button
onClick={handleClose}
disabled={isSaving}
className="rounded-full p-1 text-white/50 hover:bg-white/10 hover:text-white disabled:opacity-50"
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
@@ -92,7 +92,7 @@ export function EditCategoryModal({
{/* Name Field */}
<div>
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-white">
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-foreground">
Category Name <span className="text-red-400">*</span>
</label>
<input
@@ -105,21 +105,21 @@ export function EditCategoryModal({
placeholder="e.g., Network Troubleshooting"
required
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
<p className="mt-1 text-xs text-white/40">
<p className="mt-1 text-xs text-muted-foreground">
{name.length}/100 characters
</p>
</div>
{/* Description Field */}
<div>
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
id="edit-description"
@@ -129,9 +129,9 @@ export function EditCategoryModal({
rows={3}
placeholder="Brief description of this category..."
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
@@ -144,8 +144,8 @@ export function EditCategoryModal({
onClick={handleClose}
disabled={isSaving}
className={cn(
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground disabled:opacity-50'
)}
>
Cancel
@@ -154,8 +154,8 @@ export function EditCategoryModal({
type="submit"
disabled={isSaving || !name.trim()}
className={cn(
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
'rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium',
'hover:opacity-90 disabled:opacity-50'
)}
>
{isSaving ? 'Saving...' : 'Save Changes'}

View File

@@ -12,10 +12,10 @@ interface EmptyStateProps {
export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
return (
<div className={cn('flex flex-col items-center justify-center py-12 text-center', className)}>
{icon && <div className="mb-4 text-white/50">{icon}</div>}
<h3 className="text-lg font-semibold text-white">{title}</h3>
{icon && <div className="mb-4 text-muted-foreground">{icon}</div>}
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
{description && (
<p className="mt-1 max-w-sm text-sm text-white/40">{description}</p>
<p className="mt-1 max-w-sm text-sm text-muted-foreground">{description}</p>
)}
{action && <div className="mt-4">{action}</div>}
</div>

View File

@@ -36,20 +36,20 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
return (
<div className="flex items-center justify-between gap-4 pt-4">
<span className="text-sm text-white/40">
<span className="text-sm text-muted-foreground">
Showing {start}-{end} of {total}
</span>
<div className="flex items-center gap-1">
<button
onClick={() => onPageChange(page - 1)}
disabled={page <= 1}
className={cn(btnBase, 'px-2 text-white/50 hover:bg-white/[0.06] hover:text-white')}
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
>
<ChevronLeft className="h-4 w-4" />
</button>
{getPageNumbers().map((p, i) =>
p === 'ellipsis' ? (
<span key={`e${i}`} className="px-1 text-white/40">...</span>
<span key={`e${i}`} className="px-1 text-muted-foreground">...</span>
) : (
<button
key={p}
@@ -58,8 +58,8 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
btnBase,
'px-2',
p === page
? 'bg-white text-black'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
{p}
@@ -69,7 +69,7 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
<button
onClick={() => onPageChange(page + 1)}
disabled={page >= totalPages}
className={cn(btnBase, 'px-2 text-white/50 hover:bg-white/[0.06] hover:text-white')}
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
>
<ChevronRight className="h-4 w-4" />
</button>

View File

@@ -40,21 +40,21 @@ export function SearchInput({ value = '', onSearch, placeholder = 'Search...', c
return (
<div className={cn('relative', className)}>
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/50" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
type="text"
value={localValue}
onChange={handleChange}
placeholder={placeholder}
className={cn(
'h-9 w-full rounded-md border border-white/10 bg-black/50 pl-9 pr-8 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
'h-9 w-full rounded-md border border-border bg-card pl-9 pr-8 text-sm text-foreground',
'placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20'
)}
/>
{localValue && (
<button
onClick={handleClear}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-white/50 hover:text-white"
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground"
>
<X className="h-3.5 w-3.5" />
</button>

View File

@@ -12,7 +12,7 @@ const variantClasses: Record<BadgeVariant, string> = {
success: 'bg-emerald-400/10 text-emerald-400',
destructive: 'bg-red-400/10 text-red-400',
warning: 'bg-yellow-400/10 text-yellow-400',
default: 'bg-white/10 text-white/70',
default: 'bg-accent text-muted-foreground',
}
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) {

View File

@@ -53,8 +53,8 @@ export function ActionMenu({ actions, align = 'right' }: ActionMenuProps) {
<button
onClick={() => setIsOpen(!isOpen)}
className={cn(
'rounded-md border border-white/10 p-2 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-2 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
aria-label="Actions"
>
@@ -64,7 +64,7 @@ export function ActionMenu({ actions, align = 'right' }: ActionMenuProps) {
{isOpen && (
<div
className={cn(
'absolute z-50 mt-1 min-w-[180px] glass-card rounded-lg p-1',
'absolute z-50 mt-1 min-w-[180px] bg-card border border-border rounded-lg p-1',
align === 'right' ? 'right-0' : 'left-0'
)}
>
@@ -80,8 +80,8 @@ export function ActionMenu({ actions, align = 'right' }: ActionMenuProps) {
action.disabled
? 'cursor-not-allowed opacity-40'
: action.variant === 'destructive'
? 'text-red-400 hover:bg-white/10 hover:text-red-300'
: 'text-white/70 hover:bg-white/10 hover:text-white'
? 'text-red-400 hover:bg-accent hover:text-red-300'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
{Icon && <Icon className="h-4 w-4" />}

View File

@@ -34,8 +34,8 @@ export function ConfirmDialog({
onClick={onClose}
disabled={isLoading}
className={cn(
'rounded-xl border border-white/10 px-4 py-2 text-sm font-medium',
'text-white/60 hover:bg-white/10 hover:text-white',
'rounded-xl border border-border px-4 py-2 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-foreground',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
@@ -49,7 +49,7 @@ export function ConfirmDialog({
'disabled:opacity-50 disabled:cursor-not-allowed',
confirmVariant === 'destructive'
? 'bg-red-400/10 text-red-400 hover:bg-red-400/20 border border-red-400/20'
: 'bg-white text-black hover:bg-white/90'
: 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90'
)}
>
{isLoading ? 'Processing...' : confirmLabel}
@@ -57,7 +57,7 @@ export function ConfirmDialog({
</div>
}
>
<p className="text-sm text-white/70">{message}</p>
<p className="text-sm text-muted-foreground">{message}</p>
</Modal>
)
}

View File

@@ -37,19 +37,19 @@ export class ErrorBoundary extends Component<Props, State> {
<h2 className="mb-2 text-xl font-semibold text-red-400">
Something went wrong
</h2>
<p className="mb-4 text-white/70">
<p className="mb-4 text-muted-foreground">
An unexpected error occurred. Please try refreshing the page.
</p>
{this.state.error && (
<pre className="mb-4 overflow-auto rounded-xl bg-white/5 border border-white/[0.06] p-3 text-left text-xs text-red-400">
<pre className="mb-4 overflow-auto rounded-xl bg-white/5 border border-border p-3 text-left text-xs text-red-400">
{this.state.error.message}
</pre>
)}
<button
onClick={() => window.location.reload()}
className={cn(
'rounded-xl bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
'rounded-xl bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90'
)}
>
Refresh Page

View File

@@ -60,23 +60,23 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }:
{/* Modal Content */}
<div
className={cn(
'relative flex w-full flex-col border border-white/[0.06] bg-[#0a0a0a] shadow-lg',
'relative flex w-full flex-col border border-border bg-card shadow-lg',
'max-h-[100vh] rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
'animate-scale-in',
sizeClasses[size]
)}
>
{/* Header - Fixed at top */}
<div className="flex flex-shrink-0 items-center justify-between border-b border-white/[0.06] px-4 py-3 sm:px-6 sm:py-4">
<h2 id="modal-title" className="text-lg font-semibold text-white">
<div className="flex flex-shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
{title}
</h2>
<button
onClick={onClose}
className={cn(
'rounded-md p-1.5 text-white/40 transition-colors sm:p-1',
'hover:bg-white/10 hover:text-white',
'focus:outline-none focus:ring-2 focus:ring-white/20'
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
'hover:bg-accent hover:text-foreground',
'focus:outline-none focus:ring-2 focus:ring-primary/20'
)}
aria-label="Close modal"
>
@@ -91,7 +91,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }:
{/* Footer - Fixed at bottom */}
{footer && (
<div className="flex-shrink-0 border-t border-white/[0.06] px-4 py-3 sm:px-6 sm:py-4">
<div className="flex-shrink-0 border-t border-border px-4 py-3 sm:px-6 sm:py-4">
{footer}
</div>
)}

View File

@@ -2,8 +2,8 @@ export function PageLoader() {
return (
<div className="flex h-screen items-center justify-center bg-black">
<div className="flex flex-col items-center gap-4">
<div className="h-12 w-12 animate-spin rounded-full border-4 border-white/20 border-t-white" />
<p className="text-sm text-white/40">Loading...</p>
<div className="h-12 w-12 animate-spin rounded-full border-4 border-border border-t-foreground" />
<p className="text-sm text-muted-foreground">Loading...</p>
</div>
</div>
)

View File

@@ -19,7 +19,7 @@ export function PasswordInput({ className, ...props }: PasswordInputProps) {
<button
type="button"
onClick={() => setVisible((v) => !v)}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
tabIndex={-1}
title={visible ? 'Hide password' : 'Show password'}
>

View File

@@ -19,17 +19,17 @@ export function RouteError() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-black p-8">
<div className="max-w-md text-center">
<h1 className="mb-2 text-4xl font-bold text-white">Oops!</h1>
<h1 className="mb-2 text-4xl font-bold text-foreground">Oops!</h1>
<h2 className="mb-2 text-xl font-semibold text-red-400">{errorMessage}</h2>
{errorDetails && (
<p className="mb-4 text-white/70">{errorDetails}</p>
<p className="mb-4 text-muted-foreground">{errorDetails}</p>
)}
<div className="flex justify-center gap-4">
<button
onClick={() => navigate(-1)}
className={cn(
'rounded-xl border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-xl border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
>
Go Back
@@ -37,8 +37,8 @@ export function RouteError() {
<button
onClick={() => navigate('/trees')}
className={cn(
'rounded-xl bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
'rounded-xl bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90'
)}
>
Go Home

View File

@@ -48,14 +48,14 @@ export function StarRating({
sizeClasses[size],
star <= value
? 'fill-yellow-400 text-yellow-400'
: 'fill-none text-white/30',
: 'fill-none text-muted-foreground',
!readonly && 'hover:text-yellow-300'
)}
/>
</button>
))}
{showCount && (
<span className="ml-1 text-sm text-white/40">
<span className="ml-1 text-sm text-muted-foreground">
({value}/5)
</span>
)}

View File

@@ -37,8 +37,8 @@ export function TagBadges({
'rounded-full transition-colors',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
variant === 'default'
? 'bg-white/10 text-white/70 hover:bg-white/15'
: 'bg-white/5 text-white/40 hover:bg-white/10',
? 'bg-accent text-muted-foreground hover:bg-accent'
: 'bg-accent/50 text-muted-foreground hover:bg-accent',
!onTagClick && 'cursor-default'
)}
>
@@ -50,7 +50,7 @@ export function TagBadges({
className={cn(
'rounded-full',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
'bg-white/5 text-white/40'
'bg-accent/50 text-muted-foreground'
)}
title={tags.slice(maxVisible).join(', ')}
>

View File

@@ -123,10 +123,10 @@ export function TagInput({
<div
className={cn(
'flex flex-wrap gap-1.5 rounded-xl border px-2 py-1.5',
'bg-black/50 text-white',
'focus-within:border-white/30 focus-within:ring-1 focus-within:ring-white/20',
'bg-card text-foreground',
'focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/20',
disabled ? 'cursor-not-allowed opacity-50' : '',
'border-white/10'
'border-border'
)}
onClick={() => inputRef.current?.focus()}
>
@@ -136,7 +136,7 @@ export function TagInput({
key={tag}
className={cn(
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs',
'bg-white/10 text-white/70'
'bg-accent text-muted-foreground'
)}
>
{tag}
@@ -147,7 +147,7 @@ export function TagInput({
e.stopPropagation()
removeTag(tag)
}}
className="rounded-full p-0.5 hover:bg-white/20"
className="rounded-full p-0.5 hover:bg-accent"
>
<X className="h-3 w-3" />
</button>
@@ -184,8 +184,8 @@ export function TagInput({
placeholder={tags.length === 0 ? placeholder : ''}
disabled={disabled}
className={cn(
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-white',
'placeholder:text-white/40',
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:outline-none focus:ring-0'
)}
/>
@@ -196,8 +196,8 @@ export function TagInput({
{showSuggestions && suggestions.length > 0 && (
<div
className={cn(
'absolute z-10 mt-1 w-full rounded-xl border border-white/[0.06]',
'bg-[#0a0a0a] shadow-lg'
'absolute z-10 mt-1 w-full rounded-xl border border-border',
'bg-card shadow-lg'
)}
>
{suggestions.map((suggestion, index) => (
@@ -206,13 +206,13 @@ export function TagInput({
type="button"
onClick={() => addTag(suggestion.name)}
className={cn(
'flex w-full items-center justify-between px-3 py-2 text-sm text-white/70',
'hover:bg-white/10',
index === selectedIndex && 'bg-white/10'
'flex w-full items-center justify-between px-3 py-2 text-sm text-muted-foreground',
'hover:bg-accent',
index === selectedIndex && 'bg-accent'
)}
>
<span>{suggestion.name}</span>
<span className="text-xs text-white/40">
<span className="text-xs text-muted-foreground">
{suggestion.usage_count} trees
</span>
</button>
@@ -225,8 +225,8 @@ export function TagInput({
type="button"
onClick={() => addTag(inputValue)}
className={cn(
'flex w-full items-center gap-2 border-t border-white/[0.06] px-3 py-2 text-sm',
'hover:bg-white/10 text-white'
'flex w-full items-center gap-2 border-t border-border px-3 py-2 text-sm',
'hover:bg-accent text-foreground'
)}
>
<Plus className="h-4 w-4" />
@@ -237,7 +237,7 @@ export function TagInput({
)}
{/* Helper text */}
<p className="mt-1 text-xs text-white/40">
<p className="mt-1 text-xs text-muted-foreground">
{tags.length}/{maxTags} tags. Press Enter, Tab, comma, or semicolon to add.
</p>
</div>

View File

@@ -0,0 +1,39 @@
import { Filter } from 'lucide-react'
import { cn } from '@/lib/utils'
interface FilterChip {
id: string
label: string
}
interface FiltersBarProps {
filters: FilterChip[]
activeFilter: string
onFilterChange: (id: string) => void
}
export function FiltersBar({ filters, activeFilter, onFilterChange }: FiltersBarProps) {
return (
<div className="fade-in flex items-center gap-1.5 overflow-x-auto py-1" style={{ animationDelay: '100ms' }}>
{filters.map(f => (
<button
key={f.id}
onClick={() => onFilterChange(f.id)}
className={cn(
'shrink-0 rounded-lg border px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
activeFilter === f.id
? 'border-primary/30 bg-primary/10 text-primary'
: 'border-border bg-card text-muted-foreground hover:border-border/80 hover:text-foreground'
)}
>
{f.label}
</button>
))}
<div className="mx-1.5 h-5 w-px shrink-0 bg-border" />
<button className="flex shrink-0 items-center gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-[0.8125rem] text-muted-foreground hover:text-foreground transition-colors">
<Filter size={14} />
More Filters
</button>
</div>
)
}

View File

@@ -0,0 +1,44 @@
import { cn } from '@/lib/utils'
interface StatCard {
label: string
value: string | number
meta?: string
gradient?: boolean
color?: string
}
interface QuickStatsProps {
stats: StatCard[]
}
export function QuickStats({ stats }: QuickStatsProps) {
return (
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
{stats.map((stat, i) => (
<div
key={stat.label}
className="fade-in rounded-xl border border-border bg-card p-4 transition-colors hover:border-border/80"
style={{ animationDelay: `${50 + i * 30}ms` }}
>
<p className="font-label text-[0.6875rem] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
{stat.label}
</p>
<p
className={cn(
'mt-1 font-heading text-2xl font-bold tracking-tight',
stat.gradient && 'text-gradient-brand',
stat.color
)}
style={stat.color && !stat.color.startsWith('text-') ? { color: stat.color } : undefined}
>
{stat.value}
</p>
{stat.meta && (
<p className="mt-0.5 text-[0.6875rem] text-[hsl(var(--text-dimmed))]">{stat.meta}</p>
)}
</div>
))}
</div>
)
}

View File

@@ -0,0 +1,40 @@
import { useState } from 'react'
import { ChevronDown } from 'lucide-react'
import { cn } from '@/lib/utils'
interface SectionGroupProps {
title: string
count?: number
defaultOpen?: boolean
delay?: number
children: React.ReactNode
}
export function SectionGroup({ title, count, defaultOpen = true, delay = 150, children }: SectionGroupProps) {
const [open, setOpen] = useState(defaultOpen)
return (
<div className="fade-in" style={{ animationDelay: `${delay}ms` }}>
<button
onClick={() => setOpen(!open)}
className="flex w-full items-center gap-2 py-2"
>
<span className="h-2 w-2 shrink-0 rounded-full bg-gradient-brand" />
<span className="font-heading text-[0.8125rem] font-bold uppercase tracking-[0.04em] text-foreground">
{title}
</span>
{count !== undefined && (
<span className="rounded-full bg-secondary px-2 py-0.5 font-label text-[0.6875rem] text-muted-foreground">
{count}
</span>
)}
<div className="flex-1" />
<ChevronDown
size={14}
className={cn('text-muted-foreground transition-transform', !open && '-rotate-90')}
/>
</button>
{open && <div className="mt-1 space-y-1">{children}</div>}
</div>
)
}

View File

@@ -0,0 +1,75 @@
import { Link } from 'react-router-dom'
import { cn } from '@/lib/utils'
interface SessionItem {
id: string
treeName: string
status: 'in_progress' | 'completed' | 'abandoned'
currentStep?: string
totalSteps?: number
stepNumber?: number
ticketNumber?: string
timeAgo: string
}
interface SessionsPanelProps {
sessions: SessionItem[]
delay?: number
}
export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
if (sessions.length === 0) return null
return (
<div className="fade-in rounded-xl border border-border bg-card" style={{ animationDelay: `${delay}ms` }}>
<div className="flex items-center justify-between border-b border-border px-4 py-3">
<h3 className="font-heading text-sm font-semibold text-foreground">Recent Sessions</h3>
<Link to="/sessions" className="text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors">
View All
</Link>
</div>
<div className="divide-y divide-border">
{sessions.map(session => (
<Link
key={session.id}
to={`/sessions/${session.id}`}
className="grid items-center gap-3 px-4 py-2.5 transition-colors hover:bg-accent/50"
style={{ gridTemplateColumns: '8px 1fr 140px 80px 100px' }}
>
{/* Status dot */}
<span
className={cn(
'h-2 w-2 rounded-full',
session.status === 'completed' ? 'bg-green-500' :
session.status === 'in_progress' ? 'bg-amber-500' :
'bg-muted-foreground'
)}
/>
{/* Name */}
<span className="text-sm text-foreground truncate">{session.treeName}</span>
{/* Progress */}
<span className="text-[0.6875rem] text-muted-foreground truncate">
{session.status === 'completed'
? '✓ Resolved'
: session.stepNumber && session.totalSteps
? `→ step ${session.stepNumber}/${session.totalSteps}`
: '→ In progress'}
</span>
{/* Ticket */}
<span className="font-label text-[0.6875rem] text-muted-foreground truncate">
{session.ticketNumber || '—'}
</span>
{/* Time */}
<span className="text-right text-[0.6875rem] text-[hsl(var(--text-dimmed))]">
{session.timeAgo}
</span>
</Link>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,109 @@
import { useNavigate } from 'react-router-dom'
import { MoreHorizontal } from 'lucide-react'
import { getTreeNavigatePath, getTreeEditorPath } from '@/lib/routing'
interface TreeListItemProps {
id: string
name: string
description?: string | null
treeType: string
category?: { name: string; color?: string } | null
tags?: string[]
usageCount?: number
updatedAt: string
icon?: string
}
export function TreeListItem({
id,
name,
description,
treeType,
category,
tags = [],
usageCount = 0,
updatedAt,
icon,
}: TreeListItemProps) {
const navigate = useNavigate()
const categoryColor = category?.color || '#3b82f6'
const timeAgo = getTimeAgo(updatedAt)
return (
<div
onClick={() => navigate(getTreeNavigatePath(id, treeType))}
className="group grid cursor-pointer items-center gap-3 rounded-lg border border-transparent bg-card px-4 py-3 transition-colors hover:border-border hover:bg-[hsl(var(--sidebar-hover))]"
style={{ gridTemplateColumns: '40px 1fr 130px 80px 100px 40px' }}
>
{/* Icon box */}
<div
className="flex h-9 w-9 items-center justify-center rounded-lg text-base"
style={{ backgroundColor: `${categoryColor}15` }}
>
{icon || (treeType === 'procedural' ? '📋' : '🔧')}
</div>
{/* Info */}
<div className="min-w-0">
<p className="font-heading text-sm font-semibold text-foreground truncate">{name}</p>
<div className="mt-0.5 flex items-center gap-2">
{tags.slice(0, 3).map(tag => (
<span key={tag} className="rounded border border-border bg-secondary px-1.5 py-px font-label text-[0.625rem] text-muted-foreground">
{tag}
</span>
))}
{description && tags.length === 0 && (
<span className="text-[0.6875rem] text-muted-foreground truncate">{description}</span>
)}
</div>
</div>
{/* Category */}
<div className="flex items-center gap-1.5">
{category && (
<>
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: categoryColor }} />
<span className="font-label text-xs text-muted-foreground truncate">{category.name}</span>
</>
)}
</div>
{/* Usage count */}
<div className="text-right font-label text-xs text-muted-foreground">
{usageCount} uses
</div>
{/* Updated */}
<div className="text-right text-[0.6875rem] text-[hsl(var(--text-dimmed))]">
{timeAgo}
</div>
{/* Actions */}
<button
onClick={(e) => {
e.stopPropagation()
navigate(getTreeEditorPath(id, treeType))
}}
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100"
>
<MoreHorizontal size={16} />
</button>
</div>
)
}
function getTimeAgo(dateStr: string): string {
const now = Date.now()
const date = new Date(dateStr).getTime()
const diff = now - date
const minutes = Math.floor(diff / 60000)
if (minutes < 1) return 'Just now'
if (minutes < 60) return `${minutes} min ago`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}h ago`
const days = Math.floor(hours / 24)
if (days === 1) return 'Yesterday'
if (days < 7) return `${days}d ago`
return new Date(dateStr).toLocaleDateString()
}

View File

@@ -1,61 +1,34 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom'
import { useEffect, useState, useCallback } from 'react'
import { Outlet, useLocation, useNavigate, Link } from 'react-router-dom'
import { Menu, X, LayoutGrid, Box, PenLine, Clock, FileText, Bookmark, Users, Settings, LogOut, Shield } from 'lucide-react'
import { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { BrandLogo } from '@/components/common/BrandLogo'
import { Menu, X, LogOut, User, Shield, ChevronDown, FolderTree, ListOrdered, Layers } from 'lucide-react'
import { TopBar } from './TopBar'
import { Sidebar } from './Sidebar'
import { cn } from '@/lib/utils'
interface NavItem {
path: string
label: string
children?: { path: string; label: string; icon: React.ReactNode }[]
}
export function AppLayout() {
const location = useLocation()
const navigate = useNavigate()
const { user, logout } = useAuthStore()
const { effectiveRole, isSuperAdmin } = usePermissions()
const { effectiveRole } = usePermissions()
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [flowsDropdownOpen, setFlowsDropdownOpen] = useState(false)
const flowsDropdownRef = useRef<HTMLDivElement>(null)
const handleLogout = async () => {
setMobileMenuOpen(false)
await logout()
navigate('/login')
}
// Close mobile menu on route change
const [prevPath, setPrevPath] = useState(location.pathname)
if (prevPath !== location.pathname) {
setPrevPath(location.pathname)
if (mobileMenuOpen) setMobileMenuOpen(false)
setFlowsDropdownOpen(false)
}
// Close on Escape
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
setMobileMenuOpen(false)
setFlowsDropdownOpen(false)
}
if (e.key === 'Escape') setMobileMenuOpen(false)
}, [])
// Close dropdown on outside click
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (flowsDropdownRef.current && !flowsDropdownRef.current.contains(e.target as Node)) {
setFlowsDropdownOpen(false)
}
}
if (flowsDropdownOpen) {
document.addEventListener('mousedown', handleClickOutside)
}
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [flowsDropdownOpen])
useEffect(() => {
if (mobileMenuOpen) {
document.addEventListener('keydown', handleKeyDown)
@@ -69,250 +42,98 @@ export function AppLayout() {
}
}, [mobileMenuOpen, handleKeyDown])
const isFlowsActive = location.pathname.startsWith('/trees') || location.pathname.startsWith('/flows')
const handleLogout = async () => {
setMobileMenuOpen(false)
await logout()
navigate('/login')
}
const navItems: NavItem[] = [
{ path: '/', label: 'Home' },
{
path: '/trees',
label: 'Flows',
children: [
{ path: '/trees', label: 'All Flows', icon: <Layers className="h-4 w-4 text-white/50" /> },
{ path: '/trees?type=troubleshooting', label: 'Troubleshooting', icon: <FolderTree className="h-4 w-4 text-white/50" /> },
{ path: '/trees?type=procedural', label: 'Procedures', icon: <ListOrdered className="h-4 w-4 text-white/50" /> },
],
},
{ path: '/my-trees', label: 'My Flows' },
{ path: '/sessions', label: 'Sessions' },
{ path: '/shares', label: 'My Shares' },
{ path: '/account', label: 'Account' },
...(isSuperAdmin ? [{ path: '/admin', label: 'Admin Panel' }] : []),
const mobileNavItems = [
{ path: '/', label: 'Dashboard', icon: LayoutGrid },
{ path: '/trees', label: 'All Flows', icon: Box },
{ path: '/my-trees', label: 'My Flows', icon: PenLine },
{ path: '/sessions', label: 'Sessions', icon: Clock },
{ path: '/shares', label: 'Exports', icon: FileText },
{ path: '/step-library', label: 'Step Library', icon: Bookmark },
{ path: '/account', label: 'Team', icon: Users },
{ path: '/account', label: 'Settings', icon: Settings },
]
return (
<div className="min-h-screen bg-black">
{/* Subtle radial overlay for depth */}
<div className="pointer-events-none fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(100,100,120,0.03),transparent_50%),radial-gradient(circle_at_80%_80%,rgba(80,80,100,0.02),transparent_50%)]" />
<div className={cn('app-shell', sidebarCollapsed && 'app-shell--collapsed')}>
{/* Top Bar - spans full width */}
<TopBar />
{/* Header */}
<header className="sticky top-0 z-50 border-b border-white/[0.06] bg-black/80 backdrop-blur-xl">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-8">
{/* Mobile hamburger */}
<button
onClick={() => setMobileMenuOpen(true)}
className="rounded-xl p-2 text-white/50 hover:bg-white/10 hover:text-white transition-all sm:hidden"
aria-label="Open menu"
>
<Menu className="h-5 w-5" />
</button>
{/* Sidebar - desktop only */}
<div className="hidden md:block">
<Sidebar />
</div>
{/* Logo */}
<Link to="/" className="flex items-center gap-3 group">
<div className="w-9 h-9 rounded-xl bg-white flex items-center justify-center transition-transform group-hover:scale-105">
<BrandLogo size="sm" className="h-5 w-5 invert" />
</div>
<span className="text-xl font-semibold text-white tracking-tight">
ResolutionFlow
</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden items-center gap-1 sm:flex">
{navItems.map((item) => {
if (item.children) {
return (
<div key={item.path} className="relative" ref={flowsDropdownRef}>
<button
onClick={() => setFlowsDropdownOpen(!flowsDropdownOpen)}
className={cn(
'flex items-center gap-1 rounded-xl px-4 py-2 text-sm font-medium transition-all',
isFlowsActive
? 'bg-white/10 text-white border border-white/20'
: 'text-white/50 hover:text-white hover:bg-white/[0.06]'
)}
>
{item.label}
<ChevronDown className={cn('h-3.5 w-3.5 transition-transform', flowsDropdownOpen && 'rotate-180')} />
</button>
{flowsDropdownOpen && (
<div className="absolute left-0 z-50 mt-1 w-52 rounded-lg border border-white/10 bg-black/95 p-1 shadow-xl backdrop-blur-sm">
{item.children.map((child) => (
<Link
key={child.path}
to={child.path}
onClick={() => setFlowsDropdownOpen(false)}
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-white/70 hover:bg-white/10 hover:text-white"
>
{child.icon}
{child.label}
</Link>
))}
</div>
)}
</div>
)
}
const isActive = item.path === '/'
? location.pathname === '/'
: location.pathname.startsWith(item.path)
return (
<Link
key={item.path}
to={item.path}
className={cn(
'rounded-xl px-4 py-2 text-sm font-medium transition-all',
isActive
? 'bg-white/10 text-white border border-white/20'
: 'text-white/50 hover:text-white hover:bg-white/[0.06]'
)}
>
{item.label}
</Link>
)
})}
</nav>
</div>
{/* Right side controls */}
<div className="flex items-center gap-3">
{/* User info */}
<div className="hidden items-center gap-3 sm:flex">
<div className="flex items-center gap-2 rounded-xl bg-white/[0.06] px-3 py-1.5 border border-white/10">
<User className="h-4 w-4 text-white/40" />
<span className="text-sm text-white/70">
{user?.name || user?.email}
</span>
</div>
{/* Role badge */}
{effectiveRole && effectiveRole !== 'engineer' && (
<div className="px-3 py-1.5 rounded-xl bg-white/10 border border-white/20">
<span className="flex items-center gap-1.5 text-xs text-white font-semibold">
<Shield className="h-3 w-3" />
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
</div>
)}
</div>
{/* Logout button */}
<button
onClick={handleLogout}
className={cn(
'hidden items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium sm:flex',
'text-white/50 hover:text-white hover:bg-white/10 transition-all',
'border border-white/10 hover:border-white/20'
)}
>
<LogOut className="h-4 w-4" />
Logout
</button>
</div>
</div>
</header>
{/* Mobile hamburger - overlaid on topbar */}
<button
onClick={() => setMobileMenuOpen(true)}
className="fixed left-4 top-3.5 z-50 rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors md:hidden"
aria-label="Open menu"
>
<Menu size={20} />
</button>
{/* Mobile Nav Drawer */}
{mobileMenuOpen && (
<div className="fixed inset-0 z-50 sm:hidden">
{/* Backdrop */}
<div className="fixed inset-0 z-50 md:hidden">
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm animate-in fade-in duration-200"
className="absolute inset-0 bg-black/80 backdrop-blur-sm animate-fade-in"
onClick={() => setMobileMenuOpen(false)}
aria-hidden="true"
/>
{/* Drawer */}
<nav className="absolute inset-y-0 left-0 w-72 border-r border-white/[0.06] bg-black shadow-2xl animate-in slide-in-from-left duration-300">
<div className="flex h-16 items-center justify-between border-b border-white/[0.06] px-4">
<Link to="/" className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-white flex items-center justify-center">
<BrandLogo size="sm" className="h-5 w-5 invert" />
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-[hsl(var(--sidebar-bg))] shadow-2xl animate-slide-in-left">
<div className="flex h-14 items-center justify-between border-b border-border px-4">
<Link to="/" className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-brand">
<BrandLogo size="sm" className="h-4 w-4" />
</div>
<span className="text-xl font-semibold text-white tracking-tight">
ResolutionFlow
</span>
<span className="text-sm font-heading font-bold">ResolutionFlow</span>
</Link>
<button
onClick={() => setMobileMenuOpen(false)}
className="rounded-xl p-2 text-white/50 hover:bg-white/10 hover:text-white transition-all"
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground"
aria-label="Close menu"
>
<X className="h-5 w-5" />
<X size={18} />
</button>
</div>
<div className="flex flex-col p-4">
<div className="flex flex-col p-3">
{/* User info */}
<div className="mb-4 border-b border-white/[0.06] pb-4">
<div className="flex items-center gap-2 mb-2">
<User className="h-4 w-4 text-white/40" />
<p className="text-sm font-medium text-white">
{user?.name || user?.email}
</p>
</div>
<div className="mb-3 border-b border-border pb-3 px-3">
<p className="text-sm font-medium text-foreground">{user?.name || user?.email}</p>
{effectiveRole && effectiveRole !== 'engineer' && (
<div className="inline-flex px-3 py-1.5 rounded-xl bg-white/10 border border-white/20">
<span className="flex items-center gap-1.5 text-xs text-white font-semibold">
<Shield className="h-3 w-3" />
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
</div>
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
<Shield size={10} />
{effectiveRole === 'super_admin' ? 'Super Admin' : effectiveRole === 'owner' ? 'Owner' : 'Viewer'}
</span>
)}
</div>
{/* Nav items */}
<div className="space-y-1">
{navItems.map((item) => {
if (item.children) {
return (
<div key={item.path}>
<div className={cn(
'px-4 py-2 text-xs font-semibold uppercase tracking-wider',
isFlowsActive ? 'text-white/60' : 'text-white/30'
)}>
{item.label}
</div>
<div className="space-y-0.5">
{item.children.map((child) => (
<Link
key={child.path}
to={child.path}
className={cn(
'flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition-all ml-1',
'text-white/50 hover:text-white hover:bg-white/[0.06]'
)}
>
{child.icon}
{child.label}
</Link>
))}
</div>
</div>
)
}
<div className="space-y-0.5">
{mobileNavItems.map((item) => {
const Icon = item.icon
const isActive = item.path === '/'
? location.pathname === '/'
: location.pathname.startsWith(item.path)
return (
<Link
key={item.path}
key={item.path + item.label}
to={item.path}
className={cn(
'block rounded-xl px-4 py-3 text-sm font-medium transition-all',
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
isActive
? 'bg-white/10 text-white border border-white/20'
: 'text-white/50 hover:text-white hover:bg-white/[0.06]'
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
)}
>
<Icon size={18} />
{item.label}
</Link>
)
@@ -320,16 +141,12 @@ export function AppLayout() {
</div>
{/* Logout */}
<div className="mt-4 border-t border-white/[0.06] pt-4">
<div className="mt-3 border-t border-border pt-3">
<button
onClick={handleLogout}
className={cn(
'w-full flex items-center gap-2 rounded-xl px-4 py-3 text-sm font-medium',
'text-white/50 hover:text-white hover:bg-white/10 transition-all',
'border border-white/10 hover:border-white/20'
)}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground transition-colors"
>
<LogOut className="h-4 w-4" />
<LogOut size={18} />
Logout
</button>
</div>
@@ -339,7 +156,7 @@ export function AppLayout() {
)}
{/* Main Content */}
<main className="relative animate-in fade-in duration-500">
<main className="main-content overflow-y-auto">
<Outlet />
</main>
</div>

View File

@@ -0,0 +1,217 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { Search, Loader2, ArrowRight, FileText, Clock } from 'lucide-react'
import { treesApi } from '@/api/trees'
import { sessionsApi } from '@/api/sessions'
import type { TreeListItem } from '@/types'
import type { Session } from '@/types/session'
import { getTreeNavigatePath } from '@/lib/routing'
import { cn } from '@/lib/utils'
interface CommandPaletteProps {
open: boolean
onClose: () => void
}
interface ResultItem {
id: string
type: 'tree' | 'session'
title: string
subtitle?: string
icon: 'tree' | 'session'
path: string
}
export function CommandPalette({ open, onClose }: CommandPaletteProps) {
const navigate = useNavigate()
const inputRef = useRef<HTMLInputElement>(null)
const [query, setQuery] = useState('')
const [results, setResults] = useState<ResultItem[]>([])
const [isSearching, setIsSearching] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(0)
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
// Focus input when opened
useEffect(() => {
if (open) {
setQuery('')
setResults([])
setSelectedIndex(0)
// Slight delay to ensure modal is rendered
setTimeout(() => inputRef.current?.focus(), 50)
}
}, [open])
// Close on Escape
useEffect(() => {
if (!open) return
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', handler)
return () => document.removeEventListener('keydown', handler)
}, [open, onClose])
// Debounced search
useEffect(() => {
if (debounceRef.current) clearTimeout(debounceRef.current)
if (query.length < 2) {
setResults([])
setIsSearching(false)
return
}
setIsSearching(true)
debounceRef.current = setTimeout(async () => {
try {
const [trees, sessions] = await Promise.all([
treesApi.search(query, 6),
sessionsApi.list({ size: 5 }).catch(() => [] as Session[]),
])
const treeResults: ResultItem[] = trees.map((t: TreeListItem) => ({
id: t.id,
type: 'tree' as const,
title: t.name,
subtitle: t.description || undefined,
icon: 'tree' as const,
path: getTreeNavigatePath(t.id, t.tree_type),
}))
// Filter sessions by tree name matching query
const sessionResults: ResultItem[] = sessions
.filter((s: Session) =>
s.tree_snapshot?.name?.toLowerCase().includes(query.toLowerCase())
)
.slice(0, 3)
.map((s: Session) => ({
id: s.id,
type: 'session' as const,
title: s.tree_snapshot?.name || 'Session',
subtitle: s.completed_at ? 'Completed' : 'In progress',
icon: 'session' as const,
path: `/sessions/${s.id}`,
}))
setResults([...treeResults, ...sessionResults])
} catch {
setResults([])
} finally {
setIsSearching(false)
}
}, 250)
return () => { if (debounceRef.current) clearTimeout(debounceRef.current) }
}, [query])
const handleSelect = useCallback((item: ResultItem) => {
onClose()
navigate(item.path)
}, [navigate, onClose])
// Keyboard navigation
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
e.preventDefault()
setSelectedIndex(i => Math.min(i + 1, results.length - 1))
} else if (e.key === 'ArrowUp') {
e.preventDefault()
setSelectedIndex(i => Math.max(i - 1, 0))
} else if (e.key === 'Enter' && results[selectedIndex]) {
e.preventDefault()
handleSelect(results[selectedIndex])
}
}
if (!open) return null
return (
<div className="fixed inset-0 z-[100] flex items-start justify-center pt-[20vh]">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-fade-in"
onClick={onClose}
/>
{/* Palette */}
<div className="relative w-full max-w-lg rounded-xl border border-border bg-card shadow-2xl animate-scale-in">
{/* Search input */}
<div className="flex items-center gap-3 border-b border-border px-4 py-3">
<Search size={18} className="shrink-0 text-muted-foreground" />
<input
ref={inputRef}
type="text"
value={query}
onChange={e => { setQuery(e.target.value); setSelectedIndex(0) }}
onKeyDown={handleKeyDown}
placeholder="Search flows, sessions…"
className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-none"
/>
<kbd className="rounded border border-border bg-background px-1.5 py-0.5 font-label text-[0.625rem] text-muted-foreground">
ESC
</kbd>
</div>
{/* Results */}
<div className="max-h-72 overflow-y-auto">
{isSearching ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
</div>
) : query.length >= 2 && results.length === 0 ? (
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
No results for &ldquo;{query}&rdquo;
</div>
) : results.length > 0 ? (
<div className="p-1">
{results.map((item, i) => (
<button
key={item.id}
onClick={() => handleSelect(item)}
onMouseEnter={() => setSelectedIndex(i)}
className={cn(
'flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors',
i === selectedIndex
? 'bg-accent text-foreground'
: 'text-muted-foreground hover:bg-accent/50'
)}
>
{item.type === 'tree' ? (
<FileText size={16} className="shrink-0 opacity-60" />
) : (
<Clock size={16} className="shrink-0 opacity-60" />
)}
<div className="min-w-0 flex-1">
<p className="text-sm font-medium truncate">{item.title}</p>
{item.subtitle && (
<p className="text-[0.6875rem] text-muted-foreground truncate">{item.subtitle}</p>
)}
</div>
{i === selectedIndex && (
<ArrowRight size={14} className="shrink-0 opacity-40" />
)}
</button>
))}
</div>
) : (
<div className="px-4 py-6 text-center text-sm text-muted-foreground">
Type to search flows and sessions
</div>
)}
</div>
{/* Footer hints */}
{results.length > 0 && (
<div className="flex items-center gap-4 border-t border-border px-4 py-2">
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
Navigate
</span>
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
Open
</span>
</div>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,127 @@
import { Link, useLocation } from 'react-router-dom'
import type { LucideIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
interface NavSubItem {
href: string
label: string
count?: number
isActive?: boolean
}
interface NavItemProps {
href: string
icon: LucideIcon
label: string
badge?: number | 'dot'
matchPaths?: string[]
collapsed?: boolean
children?: NavSubItem[]
}
export function NavItem({ href, icon: Icon, label, badge, matchPaths, collapsed, children }: NavItemProps) {
const location = useLocation()
const fullPath = location.pathname + location.search
const isActive = matchPaths
? matchPaths.some(p => location.pathname.startsWith(p))
: href === '/'
? location.pathname === '/'
: location.pathname.startsWith(href)
// Check if any child is specifically active
const activeChild = children?.find(c => fullPath === c.href || fullPath.startsWith(c.href + '&'))
const isParentDimmed = !!activeChild && isActive
if (collapsed) {
return (
<Link
to={href}
className={cn(
'group relative flex items-center justify-center rounded-lg p-2 transition-all duration-120',
isActive
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
)}
title={label}
>
{isActive && (
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-gradient-brand" />
)}
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} />
{badge !== undefined && badge !== 0 && badge !== 'dot' && (
<span className="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[0.5rem] font-bold text-primary-foreground">
{badge}
</span>
)}
</Link>
)
}
return (
<div className="group/nav">
<Link
to={href}
className={cn(
'group relative flex items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium transition-all duration-120',
isActive
? isParentDimmed
? 'bg-[hsl(var(--sidebar-active))]/50 text-foreground/70'
: 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
)}
>
{/* Active indicator bar */}
{isActive && !isParentDimmed && (
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-gradient-brand" />
)}
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} />
<span className="truncate">{label}</span>
{/* Badge */}
{badge !== undefined && badge !== 0 && (
badge === 'dot' ? (
<span className="ml-auto h-1.5 w-1.5 shrink-0 rounded-full bg-brand-gradient-from" />
) : (
<span className="ml-auto shrink-0 rounded-full bg-card border border-border px-2 text-[0.6875rem] font-label text-muted-foreground">
{badge}
</span>
)
)}
</Link>
{/* Sub-items — visible on hover or when a child is active */}
{children && children.length > 0 && (
<div className={cn(
'mt-0.5 space-y-0.5 overflow-hidden transition-all duration-200',
isActive || activeChild
? 'max-h-40 opacity-100'
: 'max-h-0 opacity-0 group-hover/nav:max-h-40 group-hover/nav:opacity-100'
)}>
{children.map(child => {
const childActive = fullPath === child.href || fullPath.startsWith(child.href + '&')
return (
<Link
key={child.href}
to={child.href}
className={cn(
'flex items-center gap-2 rounded-lg pl-9 pr-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
childActive
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
)}
>
<span className="truncate">{child.label}</span>
{child.count !== undefined && (
<span className="ml-auto shrink-0 rounded-full bg-card border border-border px-2 text-[0.6875rem] font-label text-muted-foreground">
{child.count}
</span>
)}
</Link>
)
})}
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,105 @@
import { useState, useEffect, useRef } from 'react'
import { Link } from 'react-router-dom'
import { Bell, CheckCircle, Clock } from 'lucide-react'
import { sessionsApi } from '@/api/sessions'
import type { Session } from '@/types/session'
function timeAgo(dateStr: string): string {
const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000)
if (diff < 60) return 'just now'
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`
return `${Math.floor(diff / 86400)}d ago`
}
export function NotificationsPanel() {
const [open, setOpen] = useState(false)
const [sessions, setSessions] = useState<Session[]>([])
const [hasNew, setHasNew] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
sessionsApi.list({ size: 8 })
.then(data => {
setSessions(data)
// Mark as "new" if any session was updated in the last hour
const oneHourAgo = Date.now() - 3600000
setHasNew(data.some(s => new Date(s.started_at).getTime() > oneHourAgo))
})
.catch(() => {})
}, [])
useEffect(() => {
const handler = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
}
if (open) document.addEventListener('mousedown', handler)
return () => document.removeEventListener('mousedown', handler)
}, [open])
return (
<div className="relative" ref={ref}>
<button
onClick={() => { setOpen(!open); setHasNew(false) }}
className="relative rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors"
title="Notifications"
>
<Bell size={18} />
{hasNew && (
<span className="absolute right-1.5 top-1.5 h-2 w-2 rounded-full bg-primary" />
)}
</button>
{open && (
<div className="absolute right-0 z-50 mt-2 w-80 rounded-xl border border-border bg-card shadow-xl animate-scale-in">
<div className="flex items-center justify-between border-b border-border px-4 py-3">
<h3 className="text-sm font-heading font-semibold text-foreground">Activity</h3>
<Link
to="/sessions"
onClick={() => setOpen(false)}
className="text-[0.6875rem] text-muted-foreground hover:text-foreground"
>
View All
</Link>
</div>
{sessions.length === 0 ? (
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
No recent activity
</div>
) : (
<div className="max-h-72 overflow-y-auto divide-y divide-border">
{sessions.map(session => (
<Link
key={session.id}
to={`/sessions/${session.id}`}
onClick={() => setOpen(false)}
className="flex items-start gap-3 px-4 py-3 hover:bg-accent/50 transition-colors"
>
<div className="mt-0.5">
{session.completed_at ? (
<CheckCircle size={16} className="text-emerald-400" />
) : (
<Clock size={16} className="text-amber-400" />
)}
</div>
<div className="min-w-0 flex-1">
<p className="text-sm text-foreground truncate">
{session.tree_snapshot?.name || 'Session'}
</p>
<p className="text-[0.6875rem] text-muted-foreground">
{session.completed_at
? `Completed ${timeAgo(session.completed_at)}`
: `Started ${timeAgo(session.started_at)}`}
{session.client_name && ` · ${session.client_name}`}
</p>
</div>
</Link>
))}
</div>
)}
</div>
)}
</div>
)
}

View File

@@ -15,7 +15,7 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps)
if (isLoading) {
return (
<div className="flex h-screen items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-border border-t-foreground" />
</div>
)
}

View File

@@ -0,0 +1,157 @@
import { useState, useEffect, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { Plus, Play, FileText, Bookmark, Users, X } from 'lucide-react'
import { treesApi } from '@/api/trees'
import type { TreeListItem } from '@/types'
import { getTreeNavigatePath } from '@/lib/routing'
import { cn } from '@/lib/utils'
interface QuickLaunchProps {
open: boolean
onClose: () => void
}
interface QuickAction {
id: string
icon: typeof Plus
label: string
description: string
path: string
color: string
}
const ACTIONS: QuickAction[] = [
{ id: 'new-tree', icon: Plus, label: 'New Troubleshooting Flow', description: 'Create a branching decision tree', path: '/trees/new', color: '#3b82f6' },
{ id: 'new-project', icon: Plus, label: 'New Project', description: 'Create a step-by-step project', path: '/flows/new', color: '#8b5cf6' },
{ id: 'sessions', icon: Play, label: 'View Sessions', description: 'See active and recent sessions', path: '/sessions', color: '#f59e0b' },
{ id: 'step-library', icon: Bookmark, label: 'Step Library', description: 'Browse reusable steps', path: '/step-library', color: '#10b981' },
{ id: 'exports', icon: FileText, label: 'Exports & Shares', description: 'View shared session exports', path: '/shares', color: '#6366f1' },
{ id: 'team', icon: Users, label: 'Team Settings', description: 'Manage team members and roles', path: '/account', color: '#ec4899' },
]
export function QuickLaunch({ open, onClose }: QuickLaunchProps) {
const navigate = useNavigate()
const [recentTrees, setRecentTrees] = useState<TreeListItem[]>([])
const [selectedIndex, setSelectedIndex] = useState(0)
const containerRef = useRef<HTMLDivElement>(null)
const allItems = [...ACTIONS.map(a => ({ type: 'action' as const, ...a })), ...recentTrees.slice(0, 4).map(t => ({ type: 'tree' as const, ...t }))]
const totalItems = allItems.length
useEffect(() => {
if (open) {
setSelectedIndex(0)
treesApi.list({ sort_by: 'updated_at' })
.then(trees => setRecentTrees(trees.slice(0, 4)))
.catch(() => {})
}
}, [open])
useEffect(() => {
if (!open) return
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex(i => Math.min(i + 1, totalItems - 1)) }
if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex(i => Math.max(i - 1, 0)) }
if (e.key === 'Enter') {
e.preventDefault()
const item = allItems[selectedIndex]
if (!item) return
onClose()
if (item.type === 'action') navigate(item.path)
else navigate(getTreeNavigatePath(item.id, item.tree_type))
}
}
document.addEventListener('keydown', handler)
return () => document.removeEventListener('keydown', handler)
}, [open, selectedIndex, totalItems, allItems, navigate, onClose])
if (!open) return null
return (
<div className="fixed inset-0 z-[100] flex items-start justify-center pt-[15vh]">
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-fade-in" onClick={onClose} />
<div ref={containerRef} className="relative w-full max-w-md rounded-xl border border-border bg-card shadow-2xl animate-scale-in">
<div className="flex items-center justify-between border-b border-border px-4 py-3">
<h3 className="text-sm font-heading font-semibold text-foreground">Quick Launch</h3>
<button onClick={onClose} className="rounded-lg p-1 text-muted-foreground hover:text-foreground">
<X size={16} />
</button>
</div>
<div className="max-h-80 overflow-y-auto p-1">
{/* Actions */}
<p className="px-3 py-1.5 text-[0.625rem] font-bold uppercase tracking-wider text-muted-foreground">Actions</p>
{ACTIONS.map((action, i) => {
const Icon = action.icon
return (
<button
key={action.id}
onClick={() => { onClose(); navigate(action.path) }}
onMouseEnter={() => setSelectedIndex(i)}
className={cn(
'flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors',
i === selectedIndex ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent/50'
)}
>
<div
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg"
style={{ backgroundColor: `${action.color}15` }}
>
<Icon size={16} style={{ color: action.color }} />
</div>
<div className="min-w-0">
<p className="text-sm font-medium">{action.label}</p>
<p className="text-[0.6875rem] text-muted-foreground">{action.description}</p>
</div>
</button>
)
})}
{/* Recent flows */}
{recentTrees.length > 0 && (
<>
<p className="mt-2 px-3 py-1.5 text-[0.625rem] font-bold uppercase tracking-wider text-muted-foreground">Recent Flows</p>
{recentTrees.slice(0, 4).map((tree, ti) => {
const idx = ACTIONS.length + ti
return (
<button
key={tree.id}
onClick={() => { onClose(); navigate(getTreeNavigatePath(tree.id, tree.tree_type)) }}
onMouseEnter={() => setSelectedIndex(idx)}
className={cn(
'flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors',
idx === selectedIndex ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent/50'
)}
>
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-card border border-border text-base">
{tree.tree_type === 'procedural' ? '📋' : '🔧'}
</div>
<div className="min-w-0">
<p className="text-sm font-medium truncate">{tree.name}</p>
<p className="text-[0.6875rem] text-muted-foreground">
{tree.tree_type === 'procedural' ? 'Project' : 'Troubleshooting'} · {tree.usage_count} uses
</p>
</div>
<Play size={14} className="ml-auto shrink-0 opacity-40" />
</button>
)
})}
</>
)}
</div>
<div className="flex items-center gap-4 border-t border-border px-4 py-2">
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
Navigate
</span>
<span className="flex items-center gap-1 text-[0.625rem] text-muted-foreground">
<kbd className="rounded border border-border bg-background px-1 py-px font-label"></kbd>
Open
</span>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,199 @@
import { useEffect, useState } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { LayoutGrid, Box, PenLine, Clock, FileText, Bookmark, Users, Settings, PanelLeftClose, PanelLeftOpen } from 'lucide-react'
import { cn } from '@/lib/utils'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { CategoryList } from '@/components/sidebar/CategoryList'
import { TagCloud } from '@/components/sidebar/TagCloud'
import { PinnedFlowsSection } from '@/components/sidebar/PinnedFlowsSection'
import { NavItem } from './NavItem'
import { categoriesApi, tagsApi, sessionsApi, treesApi } from '@/api'
import { pinnedFlowsApi } from '@/api/pinnedFlows'
import type { PinnedFlow } from '@/api/pinnedFlows'
import { toast } from '@/lib/toast'
interface CategoryItem {
id: string
name: string
color: string
count: number
}
export function Sidebar() {
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar)
const [categories, setCategories] = useState<CategoryItem[]>([])
const [tags, setTags] = useState<string[]>([])
const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null)
const [activeTags, setActiveTags] = useState<string[]>([])
const [activeSessionCount, setActiveSessionCount] = useState(0)
const [pinnedFlows, setPinnedFlows] = useState<PinnedFlow[]>([])
const [treeCounts, setTreeCounts] = useState({ total: 0, troubleshooting: 0, procedural: 0 })
// Fetch sidebar data on mount
useEffect(() => {
const fetchData = async () => {
try {
const [cats, tagList, activeSessions, allTrees, pinnedData] = await Promise.all([
categoriesApi.list(),
tagsApi.list().catch(() => []),
sessionsApi.list({ completed: false, size: 50 }).catch(() => []),
treesApi.list({ sort_by: 'name' }).catch(() => []),
pinnedFlowsApi.list().catch(() => ({ items: [], count: 0 })),
])
setCategories(cats.map(c => ({
id: c.id,
name: c.name,
color: c.color || '#3b82f6',
count: c.tree_count || 0,
})))
setTags(tagList.map((t: { name: string }) => t.name).slice(0, 15))
setActiveSessionCount(activeSessions.length)
setPinnedFlows(pinnedData.items)
const total = allTrees.length
const troubleshooting = allTrees.filter(t => t.tree_type === 'troubleshooting').length
const procedural = allTrees.filter(t => t.tree_type === 'procedural').length
setTreeCounts({ total, troubleshooting, procedural })
} catch {
// Silently handle errors
}
}
fetchData()
}, [])
const navigate = useNavigate()
const location = useLocation()
// Sync active filters from URL when on /trees page
useEffect(() => {
if (location.pathname === '/trees') {
const params = new URLSearchParams(location.search)
setActiveCategoryId(params.get('category') || null)
const tagsParam = params.get('tags')
setActiveTags(tagsParam ? tagsParam.split(',') : [])
}
}, [location.pathname, location.search])
const handleCategorySelect = (id: string | null) => {
setActiveCategoryId(id)
const params = new URLSearchParams(location.search)
if (id) {
params.set('category', id)
} else {
params.delete('category')
}
navigate(`/trees?${params.toString()}`)
}
const handleTagClick = (tag: string) => {
const next = activeTags.includes(tag)
? activeTags.filter(t => t !== tag)
: [...activeTags, tag]
setActiveTags(next)
const params = new URLSearchParams(location.search)
if (next.length > 0) {
params.set('tags', next.join(','))
} else {
params.delete('tags')
}
navigate(`/trees?${params.toString()}`)
}
const handleUnpin = async (treeId: string) => {
try {
await pinnedFlowsApi.unpin(treeId)
setPinnedFlows(prev => prev.filter(f => f.tree_id !== treeId))
toast.success('Unpinned from sidebar')
} catch {
toast.error('Failed to unpin flow')
}
}
return (
<nav className="sidebar flex flex-col border-r border-border bg-[hsl(var(--sidebar-bg))]">
{sidebarCollapsed ? (
<>
{/* Collapsed: icon-only nav */}
<div className="flex flex-col items-center px-1.5 py-3 space-y-1">
<NavItem href="/" icon={LayoutGrid} label="Dashboard" collapsed />
<NavItem href="/trees" icon={Box} label="All Flows" matchPaths={['/trees', '/flows']} collapsed />
<NavItem href="/my-trees" icon={PenLine} label="Flow Editor" collapsed />
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={activeSessionCount || undefined} collapsed />
<NavItem href="/shares" icon={FileText} label="Exports" collapsed />
<NavItem href="/step-library" icon={Bookmark} label="Step Library" collapsed />
</div>
</>
) : (
<>
{/* Pinned Flows */}
<PinnedFlowsSection flows={pinnedFlows} onUnpin={handleUnpin} />
<div className="border-b border-[hsl(var(--border-subtle))]" />
{/* Primary Navigation */}
<div className="px-3 py-2 space-y-0.5">
<NavItem href="/" icon={LayoutGrid} label="Dashboard" />
<NavItem
href="/trees"
icon={Box}
label="All Flows"
badge={treeCounts.total || undefined}
matchPaths={['/trees', '/flows']}
children={[
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: treeCounts.troubleshooting || undefined },
{ href: '/trees?type=procedural', label: 'Projects', count: treeCounts.procedural || undefined },
]}
/>
<NavItem href="/my-trees" icon={PenLine} label="Flow Editor" />
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={activeSessionCount || undefined} />
<NavItem href="/shares" icon={FileText} label="Exports" />
<NavItem href="/step-library" icon={Bookmark} label="Step Library" badge="dot" />
</div>
<div className="border-b border-[hsl(var(--border-subtle))]" />
{/* Categories */}
<CategoryList
categories={categories}
activeId={activeCategoryId}
onSelect={handleCategorySelect}
/>
<div className="border-b border-[hsl(var(--border-subtle))]" />
{/* Tags */}
<TagCloud tags={tags} activeTags={activeTags} onTagClick={handleTagClick} />
</>
)}
{/* Spacer */}
<div className="flex-1" />
{/* Footer */}
<div className={cn(
"border-t border-[hsl(var(--border-subtle))]",
sidebarCollapsed ? "px-1.5 py-2 flex flex-col items-center" : "px-3 py-2 space-y-0.5"
)}>
{!sidebarCollapsed && (
<>
<NavItem href="/account" icon={Users} label="Team" />
<NavItem href="/account" icon={Settings} label="Settings" />
</>
)}
<button
onClick={toggleSidebar}
className={cn(
"flex w-full items-center rounded-lg text-[0.8125rem] font-medium text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground transition-colors",
sidebarCollapsed ? "justify-center p-2.5" : "gap-3 px-3 py-2"
)}
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{sidebarCollapsed ? <PanelLeftOpen size={20} /> : <PanelLeftClose size={18} />}
{!sidebarCollapsed && <span>Collapse</span>}
</button>
</div>
</nav>
)
}

View File

@@ -0,0 +1,174 @@
import { useState, useRef, useEffect, useCallback } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Search, Zap, LogOut, User, Shield, Settings } from 'lucide-react'
import { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions'
import { BrandLogo } from '@/components/common/BrandLogo'
import { CommandPalette } from './CommandPalette'
import { QuickLaunch } from './QuickLaunch'
import { NotificationsPanel } from './NotificationsPanel'
import { cn } from '@/lib/utils'
export function TopBar() {
const navigate = useNavigate()
const { user, logout } = useAuthStore()
const { effectiveRole, isSuperAdmin } = usePermissions()
const [userMenuOpen, setUserMenuOpen] = useState(false)
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false)
const [quickLaunchOpen, setQuickLaunchOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
const handleLogout = async () => {
setUserMenuOpen(false)
await logout()
navigate('/login')
}
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setUserMenuOpen(false)
}
}
if (userMenuOpen) document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [userMenuOpen])
// ⌘K / Ctrl+K global shortcut
const handleGlobalKeyDown = useCallback((e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault()
setCommandPaletteOpen(prev => !prev)
}
}, [])
useEffect(() => {
document.addEventListener('keydown', handleGlobalKeyDown)
return () => document.removeEventListener('keydown', handleGlobalKeyDown)
}, [handleGlobalKeyDown])
const initials = user?.name
? user.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
: user?.email?.[0]?.toUpperCase() || '?'
return (
<>
<header className="topbar flex items-center gap-4 border-b border-border bg-background px-4">
{/* Logo area */}
<Link
to="/"
className="flex items-center gap-2.5 pr-4 transition-all duration-200"
>
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gradient-brand">
<BrandLogo size="sm" className="h-4 w-4" />
</div>
<span className="text-sm font-heading font-bold tracking-tight whitespace-nowrap">
<span className="text-foreground">Resolution</span>
<span className="text-gradient-brand">Flow</span>
</span>
</Link>
{/* Spacer - push search to center */}
<div className="flex-1" />
{/* Search trigger */}
<button
onClick={() => setCommandPaletteOpen(true)}
className="relative w-full text-left"
style={{ maxWidth: '480px' }}
>
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
<div className="w-full rounded-lg border border-border bg-card py-2 pl-9 pr-14 text-[0.8125rem] text-muted-foreground cursor-pointer hover:border-primary/30 transition-colors">
Search flows, sessions, tags
</div>
<span className="absolute right-3 top-1/2 -translate-y-1/2 rounded border border-border bg-background px-1.5 py-0.5 font-label text-[0.625rem] text-muted-foreground">
{navigator.platform?.toLowerCase().includes('mac') ? '⌘K' : 'Ctrl+K'}
</span>
</button>
{/* Spacer - push actions to right */}
<div className="flex-1" />
{/* Action buttons */}
<div className="flex items-center gap-1">
<button
onClick={() => setQuickLaunchOpen(true)}
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors"
title="Quick Launch"
>
<Zap size={18} />
</button>
<NotificationsPanel />
{/* User avatar & menu */}
<div className="relative ml-2" ref={menuRef}>
<button
onClick={() => setUserMenuOpen(!userMenuOpen)}
className="flex h-8 w-8 items-center justify-center rounded-full bg-gradient-brand text-xs font-heading font-bold text-white hover:opacity-90 transition-opacity"
title={user?.name || user?.email || 'User'}
>
{initials}
</button>
{userMenuOpen && (
<div className="absolute right-0 z-50 mt-2 w-56 rounded-lg border border-border bg-card p-1 shadow-xl animate-scale-in">
<div className="border-b border-border px-3 py-2.5 mb-1">
<p className="text-sm font-medium text-foreground truncate">{user?.name || user?.email}</p>
{effectiveRole && effectiveRole !== 'engineer' && (
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
<Shield size={10} />
{effectiveRole === 'super_admin' ? 'Super Admin' : effectiveRole === 'owner' ? 'Owner' : 'Viewer'}
</span>
)}
</div>
<Link
to="/account"
onClick={() => setUserMenuOpen(false)}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<User size={14} />
Account
</Link>
<Link
to="/account"
onClick={() => setUserMenuOpen(false)}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Settings size={14} />
Settings
</Link>
{isSuperAdmin && (
<Link
to="/admin"
onClick={() => setUserMenuOpen(false)}
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Shield size={14} />
Admin Panel
</Link>
)}
<div className="border-t border-border mt-1 pt-1">
<button
onClick={handleLogout}
className={cn(
'flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm',
'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<LogOut size={14} />
Logout
</button>
</div>
</div>
)}
</div>
</div>
</header>
{/* Command Palette */}
<CommandPalette open={commandPaletteOpen} onClose={() => setCommandPaletteOpen(false)} />
<QuickLaunch open={quickLaunchOpen} onClose={() => setQuickLaunchOpen(false)} />
</>
)
}

View File

@@ -89,8 +89,8 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
setIsOpen(!isOpen)
}}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Add to folder"
aria-label="Add to folder"
@@ -101,14 +101,14 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
{isOpen && (
<div
className={cn(
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-white/10',
'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border',
'bg-card backdrop-blur-sm py-1 shadow-lg'
)}
>
{isLoading ? (
<div className="px-3 py-2 text-sm text-white/40">Loading...</div>
<div className="px-3 py-2 text-sm text-muted-foreground">Loading...</div>
) : folders.length === 0 ? (
<div className="px-3 py-2 text-sm text-white/40">No folders yet</div>
<div className="px-3 py-2 text-sm text-muted-foreground">No folders yet</div>
) : (
folders.map((folder) => (
<button
@@ -117,7 +117,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
e.stopPropagation()
toggleFolder(folder.id)
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<div
className="h-3 w-3 rounded-sm"
@@ -125,13 +125,13 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
/>
<span className="flex-1 truncate text-left">{folder.name}</span>
{treeFolderIds.has(folder.id) && (
<Check className="h-4 w-4 text-white" />
<Check className="h-4 w-4 text-foreground" />
)}
</button>
))
)}
<div className="border-t border-white/10 my-1" />
<div className="border-t border-border my-1" />
<button
onClick={(e) => {
@@ -139,7 +139,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
setIsOpen(false)
onFolderCreated?.()
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Plus className="h-4 w-4" />
Create new folder

View File

@@ -177,12 +177,12 @@ export function FolderEditModal({
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
{/* Modal */}
<div className="relative z-10 w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
<div className="relative z-10 w-full max-w-md bg-card border border-border rounded-2xl p-6 shadow-lg">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">
<h2 className="text-lg font-semibold text-foreground">
{isEditMode ? 'Edit Folder' : initialParentId ? 'Create Subfolder' : 'Create Folder'}
</h2>
<button onClick={onClose} className="rounded-md p-1 text-white/40 hover:bg-white/[0.06] hover:text-white">
<button onClick={onClose} className="rounded-md p-1 text-muted-foreground hover:bg-accent/50 hover:text-foreground">
<X className="h-5 w-5" />
</button>
</div>
@@ -190,7 +190,7 @@ export function FolderEditModal({
<form onSubmit={handleSubmit}>
{/* Name input */}
<div className="mb-4">
<label htmlFor="folder-name" className="block text-sm font-medium text-white">
<label htmlFor="folder-name" className="block text-sm font-medium text-foreground">
Name
</label>
<input
@@ -201,9 +201,9 @@ export function FolderEditModal({
placeholder="e.g., Citrix Issues"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'border-white/10'
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'border-border'
)}
autoFocus
/>
@@ -211,7 +211,7 @@ export function FolderEditModal({
{/* Parent folder dropdown */}
<div className="mb-4">
<label htmlFor="folder-parent" className="block text-sm font-medium text-white">
<label htmlFor="folder-parent" className="block text-sm font-medium text-foreground">
Parent Folder
</label>
<select
@@ -220,9 +220,9 @@ export function FolderEditModal({
onChange={(e) => setParentId(e.target.value || null)}
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-black/50 text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'border-white/10'
'bg-card text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'border-border'
)}
>
<option value="">None (root level)</option>
@@ -232,14 +232,14 @@ export function FolderEditModal({
</option>
))}
</select>
<p className="mt-1 text-xs text-white/40">
<p className="mt-1 text-xs text-muted-foreground">
Folders can be nested up to 3 levels deep.
</p>
</div>
{/* Color picker */}
<div className="mb-6">
<label className="block text-sm font-medium text-white">Color</label>
<label className="block text-sm font-medium text-foreground">Color</label>
<div className="mt-2 flex flex-wrap gap-2">
{FOLDER_COLORS.map((c) => (
<button
@@ -262,7 +262,7 @@ export function FolderEditModal({
<button
type="button"
onClick={onClose}
className={cn('rounded-md border border-white/10 px-4 py-2 text-sm text-white/60', 'hover:bg-white/10 hover:text-white')}
className={cn('rounded-md border border-border px-4 py-2 text-sm text-muted-foreground', 'hover:bg-accent hover:text-foreground')}
>
Cancel
</button>
@@ -270,8 +270,8 @@ export function FolderEditModal({
type="submit"
disabled={isSubmitting}
className={cn(
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90',
'rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90',
'disabled:opacity-50'
)}
>

View File

@@ -113,8 +113,8 @@ function FolderItem({
onClick={() => onFolderSelect(folder.id)}
className={cn(
'flex w-full items-center gap-1 rounded-md py-1.5 text-sm',
'transition-colors hover:bg-white/[0.06]',
selectedFolderId === folder.id && 'bg-white/10 text-white font-medium'
'transition-colors hover:bg-accent',
selectedFolderId === folder.id && 'bg-accent text-foreground font-medium'
)}
style={{ paddingLeft: `${8 + depth * 16}px`, paddingRight: '8px' }}
>
@@ -125,7 +125,7 @@ function FolderItem({
e.stopPropagation()
onToggleExpand(folder.id)
}}
className="shrink-0 p-0.5 hover:bg-white/[0.06] rounded"
className="shrink-0 p-0.5 hover:bg-accent rounded"
>
{isExpanded ? (
<ChevronDown className="h-3 w-3" />
@@ -138,7 +138,7 @@ function FolderItem({
)}
<Folder className="h-4 w-4 shrink-0" style={{ color: folder.color }} />
<span className="flex-1 truncate text-left">{folder.name}</span>
<span className="text-xs text-white/40 group-hover:hidden">{folder.tree_count}</span>
<span className="text-xs text-muted-foreground group-hover:hidden">{folder.tree_count}</span>
</button>
{/* Folder menu button - replaces tree count on hover */}
@@ -150,7 +150,7 @@ function FolderItem({
className={cn(
'absolute right-1 top-1/2 -translate-y-1/2 rounded p-1',
'hidden group-hover:block',
'hover:bg-white/[0.06]'
'hover:bg-accent'
)}
>
<MoreVertical className="h-3 w-3" />
@@ -160,8 +160,8 @@ function FolderItem({
{menuOpenId === folder.id && (
<div
className={cn(
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-white/10',
'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border',
'bg-card backdrop-blur-sm py-1 shadow-lg'
)}
>
<button
@@ -170,7 +170,7 @@ function FolderItem({
onEditFolder(folder)
onMenuToggle(null)
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Pencil className="h-3 w-3" />
Edit
@@ -182,7 +182,7 @@ function FolderItem({
onAddSubfolder(folder.id)
onMenuToggle(null)
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<FolderPlus className="h-3 w-3" />
Add Subfolder
@@ -362,7 +362,7 @@ export function FolderSidebar({
/>
)}
<div className={cn(
'w-56 shrink-0 border-r border-white/[0.06] bg-transparent',
'w-56 shrink-0 border-r border-border bg-transparent',
'hidden md:block',
mobileOpen && 'fixed inset-y-0 left-0 z-50 block animate-slide-in-left md:relative md:animate-none'
)}>
@@ -370,10 +370,10 @@ export function FolderSidebar({
{/* Mobile close button */}
{mobileOpen && (
<div className="mb-3 flex items-center justify-between md:hidden">
<span className="text-sm font-medium text-white">Folders</span>
<span className="text-sm font-medium text-foreground">Folders</span>
<button
onClick={onMobileClose}
className="rounded-md p-1.5 text-white/40 hover:bg-white/[0.06]"
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent"
aria-label="Close folders"
>
<X className="h-4 w-4" />
@@ -382,7 +382,7 @@ export function FolderSidebar({
)}
<button
onClick={() => setIsExpanded(!isExpanded)}
className="flex w-full items-center gap-2 text-sm font-medium text-white"
className="flex w-full items-center gap-2 text-sm font-medium text-foreground"
>
{isExpanded ? (
<ChevronDown className="h-4 w-4" />
@@ -399,8 +399,8 @@ export function FolderSidebar({
onClick={() => onFolderSelect(null)}
className={cn(
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
'transition-colors hover:bg-white/[0.06]',
selectedFolderId === null && 'bg-white/10 text-white font-medium'
'transition-colors hover:bg-accent',
selectedFolderId === null && 'bg-accent text-foreground font-medium'
)}
>
<Folder className="h-4 w-4" />
@@ -409,7 +409,7 @@ export function FolderSidebar({
{/* Loading state */}
{isLoading ? (
<div className="px-2 py-1.5 text-sm text-white/40">Loading...</div>
<div className="px-2 py-1.5 text-sm text-muted-foreground">Loading...</div>
) : (
<>
{/* User folders (hierarchical) */}
@@ -439,7 +439,7 @@ export function FolderSidebar({
onClick={() => onCreateFolder(null)}
className={cn(
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
'text-white/50 transition-colors hover:bg-white/[0.06] hover:text-white'
'text-muted-foreground transition-colors hover:bg-accent hover:text-foreground'
)}
>
<Plus className="h-4 w-4" />
@@ -454,8 +454,8 @@ export function FolderSidebar({
{contextMenu && (
<div
className={cn(
'fixed z-50 w-44 rounded-md border border-white/10',
'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
'fixed z-50 w-44 rounded-md border border-border',
'bg-card backdrop-blur-sm py-1 shadow-lg'
)}
style={{ left: contextMenu.x, top: contextMenu.y }}
onClick={(e) => e.stopPropagation()}
@@ -465,7 +465,7 @@ export function FolderSidebar({
onEditFolder(contextMenu.folder)
closeContextMenu()
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Pencil className="h-3 w-3" />
Edit
@@ -476,7 +476,7 @@ export function FolderSidebar({
handleAddSubfolder(contextMenu.folder.id)
closeContextMenu()
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<FolderPlus className="h-3 w-3" />
Add Subfolder

View File

@@ -119,13 +119,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
/>
{/* Modal */}
<div className="relative w-full max-w-lg glass-card rounded-2xl shadow-lg">
<div className="relative w-full max-w-lg bg-card border border-border rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-white/[0.06] px-6 py-4">
<h2 className="text-lg font-semibold text-white">Share Tree</h2>
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<h2 className="text-lg font-semibold text-foreground">Share Tree</h2>
<button
onClick={onClose}
className="rounded-md p-1 text-white/40 hover:bg-white/[0.06] hover:text-white"
className="rounded-md p-1 text-muted-foreground hover:bg-accent/50 hover:text-foreground"
>
<X className="h-5 w-5" />
</button>
@@ -135,9 +135,9 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
<div className="px-6 py-4 space-y-6">
{/* Tree Info */}
<div>
<h3 className="font-medium text-white">{tree.name}</h3>
<h3 className="font-medium text-foreground">{tree.name}</h3>
{tree.description && (
<p className="mt-1 text-sm text-white/70 line-clamp-2">
<p className="mt-1 text-sm text-muted-foreground line-clamp-2">
{tree.description}
</p>
)}
@@ -145,7 +145,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Visibility Settings */}
<div>
<label className="mb-2 block text-sm font-medium text-white">
<label className="mb-2 block text-sm font-medium text-foreground">
Visibility
</label>
<div className="space-y-2">
@@ -156,19 +156,19 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
className={cn(
'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
visibility === level
? 'border-white/20 bg-white/10 text-white'
: 'border-white/[0.06] bg-transparent text-white/50 hover:border-white/20 hover:bg-white/[0.06]'
? 'border-border bg-accent text-foreground'
: 'border-border bg-transparent text-muted-foreground hover:border-primary/30 hover:bg-accent/50'
)}
>
{getVisibilityIcon(level)}
<div className="flex-1">
<div className="text-sm font-medium capitalize">{level}</div>
<div className="text-xs text-white/40">
<div className="text-xs text-muted-foreground">
{getVisibilityDescription(level)}
</div>
</div>
{visibility === level && (
<div className="h-2 w-2 rounded-full bg-white" />
<div className="h-2 w-2 rounded-full bg-foreground" />
)}
</button>
))}
@@ -178,7 +178,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Share Link Generation */}
{visibility !== 'private' && (
<div>
<label className="mb-2 block text-sm font-medium text-white">
<label className="mb-2 block text-sm font-medium text-foreground">
Share Link
</label>
@@ -189,11 +189,11 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
id="allow-forking"
checked={allowForking}
onChange={(e) => setAllowForking(e.target.checked)}
className="h-4 w-4 rounded border-white/10 bg-black/50 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-2 focus:ring-offset-black"
className="h-4 w-4 rounded border-border bg-card text-foreground focus:ring-2 focus:ring-primary/20 focus:ring-offset-2 focus:ring-offset-black"
/>
<label
htmlFor="allow-forking"
className="text-sm text-white/70 cursor-pointer"
className="text-sm text-muted-foreground cursor-pointer"
>
Allow recipients to fork this tree
</label>
@@ -205,8 +205,8 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
onClick={handleGenerateLink}
disabled={isGenerating}
className={cn(
'w-full rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
'w-full rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
{isGenerating ? 'Generating...' : 'Generate Share Link'}
@@ -216,20 +216,20 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Active Share Link */}
{activeShare && (
<div className="space-y-2">
<div className="flex items-center gap-2 rounded-md border border-white/10 bg-black/50 p-3">
<div className="flex items-center gap-2 rounded-md border border-border bg-card p-3">
<input
type="text"
value={activeShare.share_url}
readOnly
className="flex-1 bg-transparent text-sm text-white outline-none"
className="flex-1 bg-transparent text-sm text-foreground outline-none"
/>
<button
onClick={handleCopyLink}
className={cn(
'flex items-center gap-2 rounded-md border border-white/10 px-3 py-1.5 text-sm font-medium transition-colors',
'flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-sm font-medium transition-colors',
copied
? 'border-green-500 bg-green-500/10 text-green-400'
: 'text-white/60 hover:bg-white/10 hover:text-white'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
{copied ? (
@@ -245,13 +245,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
)}
</button>
</div>
<p className="text-xs text-white/40">
<p className="text-xs text-muted-foreground">
{activeShare.allow_forking
? 'Recipients can fork this tree'
: 'Forking disabled for this share'}
</p>
{shares.length > 1 && (
<p className="text-xs text-white/40">
<p className="text-xs text-muted-foreground">
{shares.length} active share links
</p>
)}
@@ -262,12 +262,12 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
</div>
{/* Footer */}
<div className="flex justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
<div className="flex justify-end gap-3 border-t border-border px-6 py-4">
<button
onClick={onClose}
className={cn(
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
>
Close

View File

@@ -21,7 +21,7 @@ const sortOptions: { value: SortBy; label: string }[] = [
export function SortDropdown({ value, onChange, className }: SortDropdownProps) {
return (
<div className={cn('relative inline-flex items-center', className)}>
<span className="mr-2 flex items-center gap-1.5 text-sm text-white/40">
<span className="mr-2 flex items-center gap-1.5 text-sm text-muted-foreground">
<ArrowUpDown className="h-4 w-4" />
<span className="hidden sm:inline">Sort:</span>
</span>
@@ -29,8 +29,8 @@ export function SortDropdown({ value, onChange, className }: SortDropdownProps)
value={value}
onChange={(e) => onChange(e.target.value as SortBy)}
className={cn(
'rounded-md border border-white/10 bg-black/50 px-3 py-1.5 text-sm',
'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'rounded-md border border-border bg-card px-3 py-1.5 text-sm',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
>
{sortOptions.map((option) => (

View File

@@ -30,11 +30,11 @@ export function TreeGridView({
{trees.map((tree) => (
<div
key={tree.id}
className="glass-card rounded-2xl p-4 transition-all hover:-translate-y-0.5 hover:border-white/20 hover:shadow-md sm:p-6"
className="bg-card border border-border rounded-2xl p-4 transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md sm:p-6"
>
<div className="mb-2 flex items-start justify-between gap-2">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-white">{tree.name}</h3>
<h3 className="font-semibold text-foreground">{tree.name}</h3>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
<FileText className="h-3 w-3" />
@@ -45,21 +45,21 @@ export function TreeGridView({
<div className="flex items-center gap-2">
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-4 w-4 text-white/40" />
<Globe className="h-4 w-4 text-muted-foreground" />
</span>
) : (
<span title="Private tree">
<Lock className="h-4 w-4 text-white/40" />
<Lock className="h-4 w-4 text-muted-foreground" />
</span>
)}
{tree.category_info && (
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70">
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
{tree.category_info.name}
</span>
)}
</div>
</div>
<p className="mb-3 text-sm text-white/70 line-clamp-2">
<p className="mb-3 text-sm text-muted-foreground line-clamp-2">
{tree.description || 'No description available'}
</p>
@@ -71,7 +71,7 @@ export function TreeGridView({
)}
<div className="flex items-center justify-between">
<span className="text-xs text-white/40">
<span className="text-xs text-muted-foreground">
v{tree.version} · {tree.usage_count} uses
</span>
<div className="flex items-center gap-2">
@@ -81,8 +81,8 @@ export function TreeGridView({
type="button"
onClick={() => onForkTree(tree.id)}
className={cn(
'rounded-md border border-white/10 p-2 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-2 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Fork tree"
aria-label="Fork tree"
@@ -94,8 +94,8 @@ export function TreeGridView({
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-white/10 p-2 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-2 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Edit tree"
aria-label="Edit tree"
@@ -108,7 +108,7 @@ export function TreeGridView({
type="button"
onClick={() => onDeleteTree(tree)}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-red-400/10 hover:text-red-400'
)}
title="Delete tree"
@@ -121,8 +121,8 @@ export function TreeGridView({
type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn(
'rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
'rounded-md bg-gradient-brand px-3 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90'
)}
>
Start Session

View File

@@ -30,12 +30,12 @@ export function TreeListView({
{trees.map((tree) => (
<div
key={tree.id}
className="flex items-center gap-4 glass-card rounded-2xl p-4 transition-all hover:border-white/20 hover:shadow-sm"
className="flex items-center gap-4 bg-card border border-border rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-sm"
>
{/* Left: Name and Description */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-white truncate">{tree.name}</h3>
<h3 className="font-semibold text-foreground truncate">{tree.name}</h3>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 flex-shrink-0">
<FileText className="h-3 w-3" />
@@ -44,15 +44,15 @@ export function TreeListView({
)}
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
</span>
) : (
<span title="Private tree">
<Lock className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
</span>
)}
</div>
<p className="text-sm text-white/70 truncate">
<p className="text-sm text-muted-foreground truncate">
{tree.description || 'No description available'}
</p>
</div>
@@ -60,7 +60,7 @@ export function TreeListView({
{/* Center: Category and Tags */}
<div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}>
{tree.category_info && (
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70 whitespace-nowrap">
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground whitespace-nowrap">
{tree.category_info.name}
</span>
)}
@@ -73,7 +73,7 @@ export function TreeListView({
{/* Right: Metadata and Actions */}
<div className="flex items-center gap-3 flex-shrink-0">
<div className="hidden sm:flex flex-col items-end text-xs text-white/40">
<div className="hidden sm:flex flex-col items-end text-xs text-muted-foreground">
<span>v{tree.version}</span>
<span>{tree.usage_count} uses</span>
</div>
@@ -85,8 +85,8 @@ export function TreeListView({
type="button"
onClick={() => onForkTree(tree.id)}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Fork tree"
aria-label="Fork tree"
@@ -99,8 +99,8 @@ export function TreeListView({
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Edit tree"
aria-label="Edit tree"
@@ -111,7 +111,7 @@ export function TreeListView({
type="button"
onClick={() => onDeleteTree(tree)}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-red-500/20 hover:text-red-400'
)}
title="Delete tree"
@@ -125,8 +125,8 @@ export function TreeListView({
type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn(
'rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black',
'hover:bg-white/90 whitespace-nowrap'
'rounded-md bg-gradient-brand px-3 py-1.5 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 whitespace-nowrap'
)}
>
Start

View File

@@ -70,12 +70,12 @@ export function TreeTableView({
}
return (
<div className="overflow-x-auto rounded-2xl border border-white/[0.06]">
<div className="overflow-x-auto rounded-2xl border border-border">
<table className="w-full">
<thead className="bg-white/[0.02] sticky top-0 z-10">
<tr className="border-b border-white/[0.06]">
<thead className="bg-accent/50 sticky top-0 z-10">
<tr className="border-b border-border">
<th
className="px-4 py-3 text-left text-sm font-medium text-white/50 cursor-pointer hover:text-white"
className="px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
onClick={() => handleSort('name')}
>
<div className="flex items-center gap-1">
@@ -83,11 +83,11 @@ export function TreeTableView({
{getSortIcon('name')}
</div>
</th>
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-white/50">
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
Description
</th>
<th
className="hidden lg:table-cell px-4 py-3 text-left text-sm font-medium text-white/50 cursor-pointer hover:text-white"
className="hidden lg:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
onClick={() => handleSort('category')}
>
<div className="flex items-center gap-1">
@@ -95,11 +95,11 @@ export function TreeTableView({
{getSortIcon('category')}
</div>
</th>
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-white/50">
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
Tags
</th>
<th
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-white/50 cursor-pointer hover:text-white"
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
onClick={() => handleSort('version')}
>
<div className="flex items-center justify-center gap-1">
@@ -108,7 +108,7 @@ export function TreeTableView({
</div>
</th>
<th
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-white/50 cursor-pointer hover:text-white"
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
onClick={() => handleSort('usage')}
>
<div className="flex items-center justify-center gap-1">
@@ -117,7 +117,7 @@ export function TreeTableView({
</div>
</th>
<th
className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-white/50 cursor-pointer hover:text-white"
className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
onClick={() => handleSort('updated')}
>
<div className="flex items-center gap-1">
@@ -125,17 +125,17 @@ export function TreeTableView({
{getSortIcon('updated')}
</div>
</th>
<th className="px-4 py-3 text-right text-sm font-medium text-white/50">
<th className="px-4 py-3 text-right text-sm font-medium text-muted-foreground">
Actions
</th>
</tr>
</thead>
<tbody className="bg-transparent">
{trees.map((tree) => (
<tr key={tree.id} className="border-b border-white/[0.06] last:border-0 hover:bg-white/[0.04]">
<tr key={tree.id} className="border-b border-border last:border-0 hover:bg-accent/50">
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<span className="font-medium text-white truncate max-w-[200px]">
<span className="font-medium text-foreground truncate max-w-[200px]">
{tree.name}
</span>
{tree.status === 'draft' && (
@@ -146,23 +146,23 @@ export function TreeTableView({
)}
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
</span>
) : (
<span title="Private tree">
<Lock className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
</span>
)}
</div>
</td>
<td className="hidden md:table-cell px-4 py-3 text-sm text-white/70">
<td className="hidden md:table-cell px-4 py-3 text-sm text-muted-foreground">
<span className="truncate block max-w-[250px]">
{tree.description || 'No description'}
</span>
</td>
<td className="hidden lg:table-cell px-4 py-3">
{tree.category_info && (
<span className="inline-block rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70">
<span className="inline-block rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
{tree.category_info.name}
</span>
)}
@@ -172,13 +172,13 @@ export function TreeTableView({
<TagBadges tags={tree.tags} maxVisible={2} onTagClick={onTagClick} />
)}
</td>
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-white/70">
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-muted-foreground">
v{tree.version}
</td>
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-white/70">
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-muted-foreground">
{tree.usage_count}
</td>
<td className="hidden md:table-cell px-4 py-3 text-sm text-white/70">
<td className="hidden md:table-cell px-4 py-3 text-sm text-muted-foreground">
{formatDate(tree.updated_at)}
</td>
<td className="px-4 py-3">
@@ -189,8 +189,8 @@ export function TreeTableView({
type="button"
onClick={() => onForkTree(tree.id)}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Fork tree"
aria-label="Fork tree"
@@ -203,8 +203,8 @@ export function TreeTableView({
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Edit tree"
aria-label="Edit tree"
@@ -215,7 +215,7 @@ export function TreeTableView({
type="button"
onClick={() => onDeleteTree(tree)}
className={cn(
'rounded-md border border-white/10 p-1.5 text-white/60',
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-red-500/20 hover:text-red-400'
)}
title="Delete tree"
@@ -229,8 +229,8 @@ export function TreeTableView({
type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn(
'rounded-md bg-white px-3 py-1.5 text-xs font-medium text-black',
'hover:bg-white/90 whitespace-nowrap'
'rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 whitespace-nowrap'
)}
>
Start

View File

@@ -11,15 +11,15 @@ interface ViewToggleProps {
export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
return (
<div className={cn('flex items-center gap-1 rounded-md border border-white/10 p-1', className)}>
<div className={cn('flex items-center gap-1 rounded-md border border-border p-1', className)}>
<button
type="button"
onClick={() => onChange('grid')}
className={cn(
'rounded p-1.5 transition-colors',
view === 'grid'
? 'bg-white/10 text-white border-white/20'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
? 'bg-accent text-foreground border-border'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
title="Grid view"
aria-label="Grid view"
@@ -32,8 +32,8 @@ export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
className={cn(
'rounded p-1.5 transition-colors',
view === 'list'
? 'bg-white/10 text-white border-white/20'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
? 'bg-accent text-foreground border-border'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
title="List view"
aria-label="List view"
@@ -46,8 +46,8 @@ export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
className={cn(
'rounded p-1.5 transition-colors',
view === 'table'
? 'bg-white/10 text-white border-white/20'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
? 'bg-accent text-foreground border-border'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
title="Table view"
aria-label="Table view"

View File

@@ -26,49 +26,49 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
const needsOptions = field.field_type === 'select' || field.field_type === 'multi_select'
return (
<div className="glass-card rounded-xl p-3">
<div className="bg-card border border-border rounded-xl p-3">
{/* Header row */}
<div className="flex items-center gap-2">
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-white/30" />
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-muted-foreground" />
<input
type="text"
value={field.label}
onChange={(e) => onUpdate({ label: e.target.value })}
placeholder="Field label"
className="min-w-0 flex-1 rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="min-w-0 flex-1 rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
<select
value={field.field_type}
onChange={(e) => onUpdate({ field_type: e.target.value as IntakeFieldType })}
className="rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
>
{FIELD_TYPE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<label className="flex items-center gap-1 text-xs text-white/50">
<label className="flex items-center gap-1 text-xs text-muted-foreground">
<input
type="checkbox"
checked={field.required}
onChange={(e) => onUpdate({ required: e.target.checked })}
className="rounded border-white/20"
className="rounded border-border"
/>
Req
</label>
<button
onClick={() => setExpanded(!expanded)}
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
{expanded ? <ChevronUp className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
</button>
<button
onClick={onRemove}
className="rounded p-1 text-white/40 hover:bg-red-500/20 hover:text-red-400"
className="rounded p-1 text-muted-foreground hover:bg-red-500/20 hover:text-red-400"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
@@ -76,66 +76,66 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
{/* Expanded details */}
{expanded && (
<div className="mt-3 grid grid-cols-2 gap-3 border-t border-white/[0.06] pt-3">
<div className="mt-3 grid grid-cols-2 gap-3 border-t border-border pt-3">
<div>
<label className="mb-1 block text-xs text-white/50">Variable Name</label>
<label className="mb-1 block text-xs text-muted-foreground">Variable Name</label>
<input
type="text"
value={field.variable_name}
onChange={(e) => onUpdate({ variable_name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
placeholder="e.g. server_name"
className="w-full rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm font-mono text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
<p className="mt-0.5 text-[10px] text-white/30">Used as [VAR:{field.variable_name}]</p>
<p className="mt-0.5 text-[10px] text-muted-foreground">Used as [VAR:{field.variable_name}]</p>
</div>
<div>
<label className="mb-1 block text-xs text-white/50">Placeholder</label>
<label className="mb-1 block text-xs text-muted-foreground">Placeholder</label>
<input
type="text"
value={field.placeholder || ''}
onChange={(e) => onUpdate({ placeholder: e.target.value || undefined })}
placeholder="Hint text"
className="w-full rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
<div className="col-span-2">
<label className="mb-1 block text-xs text-white/50">Help Text</label>
<label className="mb-1 block text-xs text-muted-foreground">Help Text</label>
<input
type="text"
value={field.help_text || ''}
onChange={(e) => onUpdate({ help_text: e.target.value || undefined })}
placeholder="Description or instructions"
className="w-full rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
<label className="mb-1 block text-xs text-white/50">Default Value</label>
<label className="mb-1 block text-xs text-muted-foreground">Default Value</label>
<input
type="text"
value={field.default_value || ''}
onChange={(e) => onUpdate({ default_value: e.target.value || undefined })}
placeholder="Pre-filled value"
className="w-full rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
<label className="mb-1 block text-xs text-white/50">Group Name</label>
<label className="mb-1 block text-xs text-muted-foreground">Group Name</label>
<input
type="text"
value={field.group_name || ''}
onChange={(e) => onUpdate({ group_name: e.target.value || undefined })}
placeholder="e.g. Network Settings"
className="w-full rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
{needsOptions && (
<div className="col-span-2">
<label className="mb-1 block text-xs text-white/50">Options (one per line)</label>
<label className="mb-1 block text-xs text-muted-foreground">Options (one per line)</label>
<textarea
value={(field.options || []).join('\n')}
onChange={(e) => {
@@ -144,7 +144,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
}}
placeholder="Option 1&#10;Option 2&#10;Option 3"
rows={3}
className="w-full rounded border border-white/10 bg-black/50 px-2 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
)}

View File

@@ -6,18 +6,18 @@ export function IntakeFormBuilder() {
const { intakeForm, addField, removeField, updateField } = useProceduralEditorStore()
return (
<div className="glass-card rounded-2xl p-4 sm:p-6">
<div className="bg-card border border-border rounded-2xl p-4 sm:p-6">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<FileText className="h-5 w-5 text-white/50" />
<h2 className="text-lg font-semibold text-white">Intake Form</h2>
<span className="text-sm text-white/40">
<FileText className="h-5 w-5 text-muted-foreground" />
<h2 className="text-lg font-semibold text-foreground">Intake Form</h2>
<span className="text-sm text-muted-foreground">
({intakeForm.length} field{intakeForm.length !== 1 ? 's' : ''})
</span>
</div>
<button
onClick={addField}
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Plus className="h-3.5 w-3.5" />
Add Field
@@ -25,10 +25,10 @@ export function IntakeFormBuilder() {
</div>
{intakeForm.length === 0 ? (
<div className="rounded-lg border border-dashed border-white/10 bg-white/[0.02] py-8 text-center">
<FileText className="mx-auto mb-2 h-8 w-8 text-white/20" />
<p className="text-sm text-white/40">No intake form fields yet</p>
<p className="mt-1 text-xs text-white/30">
<div className="rounded-lg border border-dashed border-border bg-white/[0.02] py-8 text-center">
<FileText className="mx-auto mb-2 h-8 w-8 text-muted-foreground" />
<p className="text-sm text-muted-foreground">No intake form fields yet</p>
<p className="mt-1 text-xs text-muted-foreground">
Add fields to collect project data before the procedure starts
</p>
</div>

View File

@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
const CONTENT_TYPE_OPTIONS: { value: StepContentType; label: string; color: string }[] = [
{ value: 'action', label: 'Action', color: 'text-blue-400' },
{ value: 'informational', label: 'Info', color: 'text-white/60' },
{ value: 'informational', label: 'Info', color: 'text-muted-foreground' },
{ value: 'verification', label: 'Verify', color: 'text-emerald-400' },
{ value: 'warning', label: 'Warning', color: 'text-yellow-400' },
]
@@ -24,24 +24,24 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
// Section header steps get a minimal editor
if (step.type === 'section_header') {
return (
<div className="glass-card rounded-xl border border-white/10 p-4">
<div className="bg-card border border-border rounded-xl p-4">
<div className="mb-4 flex items-center justify-between">
<span className="text-sm font-medium text-white/50">Edit Section Header</span>
<span className="text-sm font-medium text-muted-foreground">Edit Section Header</span>
<button
onClick={onCollapse}
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ChevronUp className="h-4 w-4" />
</button>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-white/50">Title</label>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Title</label>
<input
type="text"
value={step.title}
onChange={(e) => onUpdate({ title: e.target.value })}
placeholder="Section title"
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
</div>
@@ -49,18 +49,18 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
}
return (
<div className="glass-card rounded-xl border border-white/10 p-4">
<div className="bg-card border border-border rounded-xl p-4">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-white/10 text-xs font-medium text-white">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-accent text-xs font-medium text-foreground">
{stepNumber}
</span>
<span className="text-sm font-medium text-white">Edit Step</span>
<span className="text-sm font-medium text-foreground">Edit Step</span>
</div>
<button
onClick={onCollapse}
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ChevronUp className="h-4 w-4" />
</button>
@@ -69,18 +69,18 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
<div className="space-y-4">
{/* Title */}
<div>
<label className="mb-1 block text-xs font-medium text-white/50">Title</label>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Title</label>
<input
type="text"
value={step.title}
onChange={(e) => onUpdate({ title: e.target.value })}
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
{/* Est. Minutes */}
<div className="w-40">
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-white/50">
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
<Clock className="h-3 w-3" />
Est. Minutes
</label>
@@ -90,28 +90,28 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ estimated_minutes: e.target.value ? parseInt(e.target.value) : undefined })}
placeholder="—"
min={1}
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
{/* Description */}
<div>
<label className="mb-1 block text-xs font-medium text-white/50">Description / Instructions</label>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Description / Instructions</label>
<textarea
value={step.description || ''}
onChange={(e) => onUpdate({ description: e.target.value })}
placeholder="Step instructions. Use [VAR:name] for variables."
rows={4}
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
{availableVariables.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
<span className="text-[10px] text-white/30">Variables:</span>
<span className="text-[10px] text-muted-foreground">Variables:</span>
{availableVariables.map((v) => (
<button
key={v.variable_name}
onClick={() => onUpdate({ description: (step.description || '') + `[VAR:${v.variable_name}]` })}
className="rounded bg-white/5 px-1.5 py-0.5 font-mono text-[10px] text-white/50 hover:bg-white/10 hover:text-white/70"
className="rounded bg-accent/50 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground hover:bg-accent hover:text-muted-foreground"
>
{v.variable_name}
</button>
@@ -122,7 +122,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
{/* Commands */}
<div>
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-white/50">
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
<Terminal className="h-3 w-3" />
Commands (optional)
</label>
@@ -131,7 +131,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ commands: e.target.value || undefined })}
placeholder="Install-WindowsFeature AD-Domain-Services -IncludeManagementTools"
rows={3}
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 font-mono text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -139,7 +139,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
<button
type="button"
onClick={() => setShowMore(!showMore)}
className="flex items-center gap-1.5 text-xs text-white/40 hover:text-white/60"
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-muted-foreground"
>
<Settings2 className="h-3 w-3" />
More Options
@@ -147,10 +147,10 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
</button>
{showMore && (
<div className="space-y-4 border-t border-white/[0.06] pt-4">
<div className="space-y-4 border-t border-border pt-4">
{/* Content Type */}
<div>
<label className="mb-1 block text-xs font-medium text-white/50">Content Type</label>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Content Type</label>
<div className="flex gap-1">
{CONTENT_TYPE_OPTIONS.map((opt) => (
<button
@@ -160,7 +160,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
'rounded px-2 py-1 text-xs font-medium transition-colors',
step.content_type === opt.value
? 'bg-white/15 ' + opt.color
: 'text-white/40 hover:bg-white/10 hover:text-white/60'
: 'text-muted-foreground hover:bg-accent hover:text-muted-foreground'
)}
>
{opt.label}
@@ -181,27 +181,27 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ warning_text: e.target.value || undefined })}
placeholder="Caution: This will restart the service..."
rows={2}
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-yellow-400/30 focus:outline-none focus:ring-1 focus:ring-yellow-400/20"
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-yellow-400/30 focus:outline-none focus:ring-1 focus:ring-yellow-400/20"
/>
</div>
)}
{/* Expected Outcome */}
<div>
<label className="mb-1 block text-xs font-medium text-white/50">Expected Outcome (optional)</label>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Expected Outcome (optional)</label>
<input
type="text"
value={step.expected_outcome || ''}
onChange={(e) => onUpdate({ expected_outcome: e.target.value || undefined })}
placeholder="Server should respond with..."
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
{/* Verification */}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-white/50">
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
<CheckSquare className="h-3 w-3" />
Verification Prompt (optional)
</label>
@@ -210,15 +210,15 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.verification_prompt || ''}
onChange={(e) => onUpdate({ verification_prompt: e.target.value || undefined })}
placeholder="Confirm the role was installed"
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-white/50">Verification Type</label>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Verification Type</label>
<select
value={step.verification_type || ''}
onChange={(e) => onUpdate({ verification_type: e.target.value as 'checkbox' | 'text_input' || undefined })}
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
>
<option value="">None</option>
<option value="checkbox">Checkbox (confirm done)</option>
@@ -230,7 +230,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
{/* Reference URL + Notes toggle */}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-white/50">
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
<ExternalLink className="h-3 w-3" />
Reference URL (optional)
</label>
@@ -239,16 +239,16 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.reference_url || ''}
onChange={(e) => onUpdate({ reference_url: e.target.value || undefined })}
placeholder="https://learn.microsoft.com/..."
className="w-full rounded border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
<div className="flex items-end pb-1">
<label className="flex items-center gap-2 text-sm text-white/60">
<label className="flex items-center gap-2 text-sm text-muted-foreground">
<input
type="checkbox"
checked={step.notes_enabled !== false}
onChange={(e) => onUpdate({ notes_enabled: e.target.checked })}
className="rounded border-white/20"
className="rounded border-border"
/>
Allow tech notes
</label>

View File

@@ -6,7 +6,7 @@ import { cn } from '@/lib/utils'
const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: string; label: string }> = {
action: { icon: Zap, color: 'text-blue-400', label: 'Action' },
informational: { icon: Info, color: 'text-white/50', label: 'Info' },
informational: { icon: Info, color: 'text-muted-foreground', label: 'Info' },
verification: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Verify' },
warning: { icon: AlertTriangle, color: 'text-yellow-400', label: 'Warning' },
}
@@ -27,26 +27,26 @@ export function StepList() {
let stepCounter = 0
return (
<div className="glass-card rounded-2xl p-4 sm:p-6">
<div className="bg-card border border-border rounded-2xl p-4 sm:p-6">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Shield className="h-5 w-5 text-white/50" />
<h2 className="text-lg font-semibold text-white">Steps</h2>
<span className="text-sm text-white/40">
<Shield className="h-5 w-5 text-muted-foreground" />
<h2 className="text-lg font-semibold text-foreground">Steps</h2>
<span className="text-sm text-muted-foreground">
({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''})
</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => addSectionHeader()}
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<SeparatorHorizontal className="h-3.5 w-3.5" />
Add Section
</button>
<button
onClick={() => addStep()}
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Plus className="h-3.5 w-3.5" />
Add Step
@@ -60,17 +60,17 @@ export function StepList() {
return (
<div
key={step.id}
className="flex items-center gap-2 rounded-lg border border-dashed border-white/10 bg-white/[0.02] px-3 py-2"
className="flex items-center gap-2 rounded-lg border border-dashed border-border bg-accent/50 px-3 py-2"
>
<CheckCircle2 className="h-4 w-4 text-emerald-400/50" />
<input
type="text"
value={step.title}
onChange={(e) => updateStep(step.id, { title: e.target.value })}
className="flex-1 bg-transparent text-sm text-white/50 focus:outline-none"
className="flex-1 bg-transparent text-sm text-muted-foreground focus:outline-none"
placeholder="Procedure Complete"
/>
<span className="text-[10px] text-white/30">END</span>
<span className="text-[10px] text-muted-foreground">END</span>
</div>
)
}
@@ -96,18 +96,18 @@ export function StepList() {
return (
<div
key={step.id}
className="group flex items-center gap-2 border-b border-white/[0.06] pb-1 pt-3"
className="group flex items-center gap-2 border-b border-border pb-1 pt-3"
>
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-white/20 group-hover:text-white/40" />
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-muted-foreground group-hover:text-muted-foreground" />
<span
className="min-w-0 flex-1 cursor-pointer text-xs font-semibold uppercase tracking-wider text-white/40 hover:text-white/60"
className="min-w-0 flex-1 cursor-pointer text-xs font-semibold uppercase tracking-wider text-muted-foreground hover:text-muted-foreground"
onClick={() => setExpandedStepId(step.id)}
>
{step.title || 'Untitled Section'}
</span>
<button
onClick={() => removeStep(step.id)}
className="shrink-0 rounded p-1 text-white/30 opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
className="shrink-0 rounded p-1 text-muted-foreground opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
@@ -141,13 +141,13 @@ export function StepList() {
<div key={step.id}>
<div
className={cn(
'group flex items-center gap-2 rounded-xl border border-white/[0.06] px-3 py-2.5 transition-colors',
'hover:border-white/10 hover:bg-white/[0.03]'
'group flex items-center gap-2 rounded-xl border border-border px-3 py-2.5 transition-colors',
'hover:border-primary/30 hover:bg-accent/50'
)}
>
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-white/20 group-hover:text-white/40" />
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-muted-foreground group-hover:text-muted-foreground" />
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/10 text-xs font-medium text-white/70">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-accent text-xs font-medium text-muted-foreground">
{stepNumber}
</span>
@@ -156,28 +156,28 @@ export function StepList() {
</span>
<span
className="min-w-0 flex-1 cursor-pointer truncate text-sm text-white"
className="min-w-0 flex-1 cursor-pointer truncate text-sm text-foreground"
onClick={() => setExpandedStepId(step.id)}
>
{step.title || 'Untitled step'}
</span>
{step.estimated_minutes && (
<span className="shrink-0 text-[10px] text-white/30">
<span className="shrink-0 text-[10px] text-muted-foreground">
~{step.estimated_minutes}m
</span>
)}
<button
onClick={() => setExpandedStepId(step.id)}
className="shrink-0 rounded p-1 text-white/30 hover:bg-white/10 hover:text-white"
className="shrink-0 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ChevronDown className="h-3.5 w-3.5" />
</button>
<button
onClick={() => removeStep(step.id)}
className="shrink-0 rounded p-1 text-white/30 opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
className="shrink-0 rounded p-1 text-muted-foreground opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
@@ -190,7 +190,7 @@ export function StepList() {
{/* Add step button at bottom */}
<button
onClick={() => addStep()}
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-white/10 py-2 text-sm text-white/40 transition-colors hover:border-white/20 hover:text-white/60"
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-border py-2 text-sm text-muted-foreground transition-colors hover:border-primary/30 hover:text-muted-foreground"
>
<Plus className="h-3.5 w-3.5" />
Add Step

View File

@@ -58,38 +58,38 @@ export function CompletionSummary({
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-emerald-400/10">
<CheckCircle2 className="h-8 w-8 text-emerald-400" />
</div>
<h1 className="text-2xl font-bold text-white">Procedure Complete</h1>
<p className="mt-1 text-white/40">{treeName}</p>
<h1 className="text-2xl font-bold text-foreground">Procedure Complete</h1>
<p className="mt-1 text-muted-foreground">{treeName}</p>
</div>
{/* Summary stats */}
<div className="grid grid-cols-3 gap-3">
<div className="glass-card rounded-xl p-3 text-center">
<div className="bg-card border border-border rounded-xl p-3 text-center">
<CheckCircle2 className="mx-auto mb-1 h-5 w-5 text-emerald-400" />
<div className="text-lg font-semibold text-white">{procedureSteps.length}</div>
<div className="text-xs text-white/40">Steps Completed</div>
<div className="text-lg font-semibold text-foreground">{procedureSteps.length}</div>
<div className="text-xs text-muted-foreground">Steps Completed</div>
</div>
<div className="glass-card rounded-xl p-3 text-center">
<Clock className="mx-auto mb-1 h-5 w-5 text-white/50" />
<div className="text-lg font-semibold text-white">{formatTime(totalMinutes)}</div>
<div className="text-xs text-white/40">Total Time</div>
<div className="bg-card border border-border rounded-xl p-3 text-center">
<Clock className="mx-auto mb-1 h-5 w-5 text-muted-foreground" />
<div className="text-lg font-semibold text-foreground">{formatTime(totalMinutes)}</div>
<div className="text-xs text-muted-foreground">Total Time</div>
</div>
<div className="glass-card rounded-xl p-3 text-center">
<FileText className="mx-auto mb-1 h-5 w-5 text-white/50" />
<div className="text-lg font-semibold text-white">{Object.keys(variables).length}</div>
<div className="text-xs text-white/40">Parameters</div>
<div className="bg-card border border-border rounded-xl p-3 text-center">
<FileText className="mx-auto mb-1 h-5 w-5 text-muted-foreground" />
<div className="text-lg font-semibold text-foreground">{Object.keys(variables).length}</div>
<div className="text-xs text-muted-foreground">Parameters</div>
</div>
</div>
{/* Project parameters */}
{Object.keys(variables).length > 0 && (
<div className="glass-card rounded-xl p-4">
<h3 className="mb-3 text-sm font-semibold text-white/60">Project Parameters</h3>
<div className="bg-card border border-border rounded-xl p-4">
<h3 className="mb-3 text-sm font-semibold text-muted-foreground">Project Parameters</h3>
<div className="space-y-1.5">
{Object.entries(variables).map(([key, value]) => (
<div key={key} className="flex items-baseline justify-between gap-4 text-sm">
<span className="font-mono text-white/40">{key}</span>
<span className="text-right text-white/70">{value}</span>
<span className="font-mono text-muted-foreground">{key}</span>
<span className="text-right text-muted-foreground">{value}</span>
</div>
))}
</div>
@@ -97,8 +97,8 @@ export function CompletionSummary({
)}
{/* Step details */}
<div className="glass-card rounded-xl p-4">
<h3 className="mb-3 text-sm font-semibold text-white/60">Step Summary</h3>
<div className="bg-card border border-border rounded-xl p-4">
<h3 className="mb-3 text-sm font-semibold text-muted-foreground">Step Summary</h3>
<div className="space-y-2">
{procedureSteps.map((step, index) => {
const completion = completions.get(step.id)
@@ -107,15 +107,15 @@ export function CompletionSummary({
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-emerald-400" />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="text-white/70">
<span className="text-muted-foreground">
{index + 1}. {step.title}
</span>
</div>
{completion?.notes && (
<p className="mt-0.5 text-xs text-white/30">Note: {completion.notes}</p>
<p className="mt-0.5 text-xs text-muted-foreground">Note: {completion.notes}</p>
)}
{completion?.verificationValue && (
<p className="mt-0.5 text-xs text-white/30">
<p className="mt-0.5 text-xs text-muted-foreground">
Verified: {completion.verificationValue}
</p>
)}
@@ -130,14 +130,14 @@ export function CompletionSummary({
<div className="flex items-center gap-3">
<button
onClick={onExport}
className="flex flex-1 items-center justify-center gap-2 rounded-lg border border-white/10 px-4 py-2.5 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
className="flex flex-1 items-center justify-center gap-2 rounded-lg border border-border px-4 py-2.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Download className="h-4 w-4" />
Export Report
</button>
<button
onClick={onClose}
className="flex flex-1 items-center justify-center gap-2 rounded-lg bg-white px-4 py-2.5 text-sm font-medium text-black hover:bg-white/90"
className="flex flex-1 items-center justify-center gap-2 rounded-lg bg-gradient-brand px-4 py-2.5 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
>
Done
</button>

View File

@@ -71,10 +71,10 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
const error = errors[field.variable_name]
const baseInputClass = cn(
'w-full rounded-lg border bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:outline-none focus:ring-1',
'w-full rounded-lg border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1',
error
? 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20'
: 'border-white/10 focus:border-white/30 focus:ring-white/20'
: 'border-border focus:border-primary focus:ring-primary/20'
)
let input: React.ReactNode
@@ -111,9 +111,9 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
type="checkbox"
checked={value === 'true'}
onChange={(e) => setValue(field.variable_name, e.target.checked ? 'true' : 'false')}
className="rounded border-white/20"
className="rounded border-border"
/>
<span className="text-sm text-white/70">{field.placeholder || field.label}</span>
<span className="text-sm text-muted-foreground">{field.placeholder || field.label}</span>
</label>
)
break
@@ -161,9 +161,9 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
: [...selected, opt]
setValue(field.variable_name, next.join(','))
}}
className="rounded border-white/20"
className="rounded border-border"
/>
<span className="text-sm text-white/70">{opt}</span>
<span className="text-sm text-muted-foreground">{opt}</span>
</label>
)
})}
@@ -197,12 +197,12 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
return (
<div key={field.variable_name}>
<label className="mb-1 flex items-center gap-1 text-sm font-medium text-white/60">
<label className="mb-1 flex items-center gap-1 text-sm font-medium text-muted-foreground">
{field.label}
{field.required && <span className="text-red-400">*</span>}
</label>
{field.help_text && (
<p className="mb-1.5 text-xs text-white/30">{field.help_text}</p>
<p className="mb-1.5 text-xs text-muted-foreground">{field.help_text}</p>
)}
{input}
{error && <p className="mt-1 text-xs text-red-400">{error}</p>}
@@ -212,12 +212,12 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="mx-4 w-full max-w-lg rounded-2xl border border-white/10 bg-[#0a0a0a] shadow-xl">
<div className="mx-4 w-full max-w-lg rounded-2xl border border-border bg-[#0a0a0a] shadow-xl">
{/* Header */}
<div className="border-b border-white/[0.06] px-6 py-4">
<h2 className="text-lg font-semibold text-white">Project Information</h2>
<p className="mt-0.5 text-sm text-white/40">
Fill in the details for <span className="text-white/60">{treeName}</span>
<div className="border-b border-border px-6 py-4">
<h2 className="text-lg font-semibold text-foreground">Project Information</h2>
<p className="mt-0.5 text-sm text-muted-foreground">
Fill in the details for <span className="text-muted-foreground">{treeName}</span>
</p>
</div>
@@ -227,7 +227,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
{Array.from(groups.entries()).map(([groupName, groupFields]) => (
<div key={groupName}>
{groupName && (
<h3 className="mb-3 border-b border-white/[0.06] pb-1 text-xs font-semibold uppercase tracking-wider text-white/40">
<h3 className="mb-3 border-b border-border pb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{groupName}
</h3>
)}
@@ -239,17 +239,17 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
</div>
{/* Footer */}
<div className="flex items-center justify-end gap-2 border-t border-white/[0.06] px-6 py-4">
<div className="flex items-center justify-end gap-2 border-t border-border px-6 py-4">
<button
type="button"
onClick={onCancel}
className="rounded-md border border-white/10 px-4 py-2 text-sm text-white/60 hover:bg-white/10 hover:text-white"
className="rounded-md border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
Cancel
</button>
<button
type="submit"
className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
className="rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
>
Start Procedure
</button>

View File

@@ -20,24 +20,24 @@ export function ProgressBar({ currentStep, totalSteps, elapsedMinutes, estimated
return (
<div className="space-y-1.5">
<div className="flex items-center justify-between text-xs">
<span className="text-white/60">
<span className="text-muted-foreground">
Step {currentStep} of {totalSteps}
</span>
<div className="flex items-center gap-3">
{elapsedMinutes !== undefined && (
<span className="text-white/50">
<span className="text-muted-foreground">
{formatTime(elapsed)}
{estimatedTotalMinutes ? (
<span className="text-white/25"> / est. {formatTime(estimatedTotalMinutes)}</span>
<span className="text-muted-foreground"> / est. {formatTime(estimatedTotalMinutes)}</span>
) : null}
</span>
)}
<span className="font-medium text-white/70">{percentage}%</span>
<span className="font-medium text-muted-foreground">{percentage}%</span>
</div>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-white/10">
<div className="h-1.5 overflow-hidden rounded-full bg-accent">
<div
className="h-full rounded-full bg-white transition-all duration-300"
className="h-full rounded-full bg-gradient-brand transition-all duration-300"
style={{ width: `${percentage}%` }}
/>
</div>

View File

@@ -31,7 +31,7 @@ export function StepChecklist({ steps, currentStepIndex, completedStepIds, onSte
return (
<div key={step.id}>
{showSection && (
<div className="mb-1 mt-3 border-b border-white/[0.06] pb-1 text-[10px] font-semibold uppercase tracking-wider text-white/40 first:mt-0">
<div className="mb-1 mt-3 border-b border-border pb-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground first:mt-0">
{step.section_header}
</div>
)}
@@ -39,24 +39,24 @@ export function StepChecklist({ steps, currentStepIndex, completedStepIds, onSte
onClick={() => onStepClick(index)}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors',
isCurrent && 'bg-white/10 text-white',
!isCurrent && isCompleted && 'text-white/40',
!isCurrent && !isCompleted && 'text-white/50 hover:bg-white/[0.04]'
isCurrent && 'bg-accent text-foreground',
!isCurrent && isCompleted && 'text-muted-foreground',
!isCurrent && !isCompleted && 'text-muted-foreground hover:bg-accent/50'
)}
>
{isCompleted ? (
<CheckCircle2 className="h-4 w-4 shrink-0 text-emerald-400" />
) : isCurrent ? (
<ArrowRight className="h-4 w-4 shrink-0 text-white" />
<ArrowRight className="h-4 w-4 shrink-0 text-foreground" />
) : (
<Circle className="h-4 w-4 shrink-0 text-white/20" />
<Circle className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-white/10 text-[10px] font-medium">
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-accent text-[10px] font-medium">
{index + 1}
</span>
<span className="min-w-0 flex-1 truncate">{step.title || 'Untitled step'}</span>
{step.estimated_minutes && (
<span className="shrink-0 text-[10px] text-white/30">~{step.estimated_minutes}m</span>
<span className="shrink-0 text-[10px] text-muted-foreground">~{step.estimated_minutes}m</span>
)}
</button>
</div>

View File

@@ -6,7 +6,7 @@ import { cn } from '@/lib/utils'
const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: string; bg: string; label: string }> = {
action: { icon: Zap, color: 'text-blue-400', bg: 'bg-blue-400/10', label: 'Action' },
informational: { icon: Info, color: 'text-white/50', bg: 'bg-white/10', label: 'Info' },
informational: { icon: Info, color: 'text-muted-foreground', bg: 'bg-accent', label: 'Info' },
verification: { icon: CheckCircle2, color: 'text-emerald-400', bg: 'bg-emerald-400/10', label: 'Verification' },
warning: { icon: AlertTriangle, color: 'text-yellow-400', bg: 'bg-yellow-400/10', label: 'Warning' },
}
@@ -81,21 +81,21 @@ export function StepDetail({
<div className="space-y-4">
{/* Step header */}
<div className="flex items-start gap-3">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-white/10 text-sm font-semibold text-white">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent text-sm font-semibold text-foreground">
{stepNumber}
</span>
<div className="min-w-0 flex-1">
<h2 className="text-lg font-semibold text-white">{step.title}</h2>
<h2 className="text-lg font-semibold text-foreground">{step.title}</h2>
<div className="mt-1 flex items-center gap-2">
<span className={cn('inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs', config.bg, config.color)}>
<Icon className="h-3 w-3" />
{config.label}
</span>
<span className="text-xs text-white/30">
<span className="text-xs text-muted-foreground">
Step {stepNumber} of {totalSteps}
</span>
{step.estimated_minutes && (
<span className="text-xs text-white/30">~{step.estimated_minutes} min</span>
<span className="text-xs text-muted-foreground">~{step.estimated_minutes} min</span>
)}
</div>
</div>
@@ -111,7 +111,7 @@ export function StepDetail({
{/* Description */}
{step.description && (
<div className="prose prose-invert prose-sm max-w-none text-white/70">
<div className="prose prose-invert prose-sm max-w-none text-muted-foreground">
<p className="whitespace-pre-wrap">{resolve(step.description)}</p>
</div>
)}
@@ -120,14 +120,14 @@ export function StepDetail({
{commandBlocks.length > 0 && (
<div className="space-y-3">
{commandBlocks.map((cmd, i) => (
<div key={i} className="rounded-lg border border-white/[0.06] bg-black/50">
<div className="flex items-center justify-between border-b border-white/[0.06] px-3 py-1.5">
<span className="text-xs font-medium text-white/40">
<div key={i} className="rounded-lg border border-border bg-card">
<div className="flex items-center justify-between border-b border-border px-3 py-1.5">
<span className="text-xs font-medium text-muted-foreground">
{cmd.label || (cmd.language ? cmd.language : 'Command')}
</span>
<button
onClick={() => handleCopyCommand(cmd.code, i)}
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-white/40 hover:bg-white/10 hover:text-white"
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
{copiedIndex === i ? <Check className="h-3 w-3 text-emerald-400" /> : <Copy className="h-3 w-3" />}
{copiedIndex === i ? 'Copied' : 'Copy'}
@@ -143,37 +143,37 @@ export function StepDetail({
{/* Expected outcome */}
{step.expected_outcome && (
<div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3">
<h4 className="mb-1 text-xs font-medium text-white/50">Expected Outcome</h4>
<p className="text-sm text-white/70">{resolve(step.expected_outcome)}</p>
<div className="rounded-lg border border-border bg-white/[0.02] p-3">
<h4 className="mb-1 text-xs font-medium text-muted-foreground">Expected Outcome</h4>
<p className="text-sm text-muted-foreground">{resolve(step.expected_outcome)}</p>
</div>
)}
{/* Verification */}
{verificationPrompt && (
<div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3">
<h4 className="mb-2 text-xs font-medium text-white/50">Verification</h4>
<div className="rounded-lg border border-border bg-white/[0.02] p-3">
<h4 className="mb-2 text-xs font-medium text-muted-foreground">Verification</h4>
{verificationType === 'checkbox' ? (
<label className="flex items-center gap-2 text-sm text-white/70">
<label className="flex items-center gap-2 text-sm text-muted-foreground">
<input
type="checkbox"
checked={!!verificationValue}
onChange={(e) => onVerificationChange(e.target.checked ? 'confirmed' : '')}
disabled={isCompleted}
className="rounded border-white/20"
className="rounded border-border"
/>
{resolve(verificationPrompt)}
</label>
) : (
<div>
<p className="mb-2 text-sm text-white/70">{resolve(verificationPrompt)}</p>
<p className="mb-2 text-sm text-muted-foreground">{resolve(verificationPrompt)}</p>
<input
type="text"
value={verificationValue}
onChange={(e) => onVerificationChange(e.target.value)}
disabled={isCompleted}
placeholder="Enter observed value..."
className="w-full rounded border border-white/10 bg-black/50 px-3 py-1.5 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20 disabled:opacity-50"
className="w-full rounded border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20 disabled:opacity-50"
/>
</div>
)}
@@ -183,13 +183,13 @@ export function StepDetail({
{/* Notes */}
{step.notes_enabled !== false && (
<div>
<label className="mb-1 block text-xs font-medium text-white/50">Notes</label>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Notes</label>
<textarea
value={notes}
onChange={(e) => onNotesChange(e.target.value)}
placeholder="Add notes for this step..."
rows={2}
className="w-full rounded-lg border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
/>
</div>
)}
@@ -200,7 +200,7 @@ export function StepDetail({
href={resolve(step.reference_url)}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-sm text-white/40 hover:text-white"
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
>
<ExternalLink className="h-3.5 w-3.5" />
Reference Documentation
@@ -216,7 +216,7 @@ export function StepDetail({
'flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
isCompleted
? 'bg-emerald-400/10 text-emerald-400'
: 'bg-white text-black hover:bg-white/90 disabled:opacity-40 disabled:hover:bg-white'
: 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-40 disabled:hover:opacity-100'
)}
>
{isCompleted ? (

View File

@@ -45,7 +45,7 @@ export function ContinuationModal({
{/* Descendant Selection */}
{hasDescendants && (
<div>
<p className="mb-4 text-sm text-white/70">
<p className="mb-4 text-sm text-muted-foreground">
Select the next step in your troubleshooting path:
</p>
@@ -56,20 +56,20 @@ export function ContinuationModal({
onClick={() => onSelectNode(node.id)}
title={`From: ${node.parentOptionLabel}`}
className={cn(
'flex w-full items-center gap-3 rounded-lg border border-white/[0.06] p-3 text-left transition-colors',
'hover:border-white/20 hover:bg-white/10'
'flex w-full items-center gap-3 rounded-lg border border-border p-3 text-left transition-colors',
'hover:border-border hover:bg-accent'
)}
>
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-white/10">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-accent">
{nodeTypeIcons[node.type]}
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-white">{node.label}</p>
<p className="text-xs text-white/40">
<p className="truncate font-medium text-foreground">{node.label}</p>
<p className="text-xs text-muted-foreground">
{nodeTypeLabels[node.type]}
</p>
</div>
<ArrowRight className="h-4 w-4 flex-shrink-0 text-white/40" />
<ArrowRight className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
</button>
))}
</div>
@@ -79,11 +79,11 @@ export function ContinuationModal({
{/* Divider */}
{hasDescendants && (
<div className="flex items-center gap-4">
<div className="h-px flex-1 bg-white/[0.06]" />
<span className="text-xs font-medium uppercase tracking-wide text-white/40">
<div className="h-px flex-1 bg-border" />
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
Or
</span>
<div className="h-px flex-1 bg-white/[0.06]" />
<div className="h-px flex-1 bg-border" />
</div>
)}
@@ -100,8 +100,8 @@ export function ContinuationModal({
<GitBranch className="h-5 w-5 text-amber-500" />
</div>
<div className="flex-1">
<p className="font-medium text-white">Build Custom Branch</p>
<p className="text-sm text-white/70">
<p className="font-medium text-foreground">Build Custom Branch</p>
<p className="text-sm text-muted-foreground">
Create your own troubleshooting path with custom steps
</p>
</div>

View File

@@ -69,9 +69,9 @@ export function ExportPreviewModal({
<Modal isOpen={isOpen} onClose={handleClose} title="Export Preview" size="xl">
{/* Filename, format info, and controls */}
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<p className="text-sm text-white/70">
Filename: <span className="font-mono text-white">{filename}</span>
<span className="ml-3 rounded bg-white/10 px-2 py-0.5 text-xs text-white/70">
<p className="text-sm text-muted-foreground">
Filename: <span className="font-mono text-foreground">{filename}</span>
<span className="ml-3 rounded bg-accent px-2 py-0.5 text-xs text-muted-foreground">
{format === 'markdown' ? 'Markdown' : format === 'html' ? 'HTML' : format === 'psa' ? 'PSA' : 'Plain Text'}
</span>
{isModified && (
@@ -81,23 +81,23 @@ export function ExportPreviewModal({
<div className="flex flex-col items-end gap-1">
<div className="flex items-center gap-3">
{onToggleSummary && (
<label className="flex items-center gap-2 text-sm text-white/60 cursor-pointer">
<label className="flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
<input
type="checkbox"
checked={includeSummary}
onChange={(e) => onToggleSummary(e.target.checked)}
className="h-4 w-4 rounded border-white/20 bg-black/50"
className="h-4 w-4 rounded border-border bg-card"
/>
Include Summary
</label>
)}
{onToggleRedaction && (
<label className="flex items-center gap-2 text-sm text-white/60 cursor-pointer">
<label className="flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
<input
type="checkbox"
checked={redactionEnabled}
onChange={(e) => onToggleRedaction(e.target.checked)}
className="h-4 w-4 rounded border-white/20 bg-black/50"
className="h-4 w-4 rounded border-border bg-card"
/>
Mask Sensitive Data
</label>
@@ -114,13 +114,13 @@ export function ExportPreviewModal({
</p>
)}
{redactionEnabled && redactionSummary && redactionSummary.total === 0 && (
<p className="text-xs text-white/40">No sensitive data detected</p>
<p className="text-xs text-muted-foreground">No sensitive data detected</p>
)}
{isModified && (
<button
type="button"
onClick={handleReset}
className="flex items-center gap-1 text-xs text-white/40 hover:text-white"
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
title="Reset to original"
>
<RotateCcw className="h-3 w-3" />
@@ -139,9 +139,9 @@ export function ExportPreviewModal({
value={editedContent}
onChange={(e) => setEditedContent(e.target.value)}
className={cn(
'h-96 w-full resize-y rounded-md border border-white/10 bg-black/50 p-4',
'font-mono text-sm text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'h-96 w-full resize-y rounded-md border border-border bg-card p-4',
'font-mono text-sm text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
@@ -151,9 +151,9 @@ export function ExportPreviewModal({
type="button"
onClick={handleCopy}
className={cn(
'flex items-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium',
'text-white/60 hover:bg-white/10 hover:text-white',
'focus:outline-none focus:ring-2 focus:ring-white/20'
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-foreground',
'focus:outline-none focus:ring-2 focus:ring-primary/20'
)}
>
{copied ? (
@@ -172,8 +172,8 @@ export function ExportPreviewModal({
type="button"
onClick={handleDownload}
className={cn(
'flex items-center gap-2 rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90 focus:outline-none focus:ring-2 focus:ring-white/20'
'flex items-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium',
'hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-primary/20'
)}
>
<Download className="h-4 w-4" />

View File

@@ -49,7 +49,7 @@ export function ForkTreeModal({
disabled={isSaving}
className={cn(
'rounded-md px-4 py-2 text-sm font-medium transition-colors',
'text-white/60 hover:bg-white/10 hover:text-white',
'text-muted-foreground hover:bg-accent hover:text-foreground',
'disabled:cursor-not-allowed disabled:opacity-50'
)}
>
@@ -59,8 +59,8 @@ export function ForkTreeModal({
onClick={handleFork}
disabled={isSaving || !name.trim()}
className={cn(
'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black transition-colors',
'hover:bg-white/90',
'flex items-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium transition-colors',
'hover:opacity-90',
'disabled:cursor-not-allowed disabled:opacity-50'
)}
>
@@ -82,13 +82,13 @@ export function ForkTreeModal({
return (
<Modal isOpen={isOpen} onClose={onClose} title="Save Custom Tree?" footer={footer}>
<div className="space-y-4">
<div className="flex items-start gap-3 rounded-lg bg-white/5 p-4">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-white/10">
<GitFork className="h-5 w-5 text-white" />
<div className="flex items-start gap-3 rounded-lg bg-accent/50 p-4">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-accent">
<GitFork className="h-5 w-5 text-foreground" />
</div>
<div>
<p className="font-medium text-white">You've created a custom troubleshooting path!</p>
<p className="mt-1 text-sm text-white/70">
<p className="font-medium text-foreground">You've created a custom troubleshooting path!</p>
<p className="mt-1 text-sm text-muted-foreground">
Save it as your own personal tree to reuse this troubleshooting flow in the future.
</p>
</div>
@@ -96,7 +96,7 @@ export function ForkTreeModal({
<div className="space-y-4">
<div>
<label htmlFor="tree-name" className="mb-1.5 block text-sm font-medium text-white">
<label htmlFor="tree-name" className="mb-1.5 block text-sm font-medium text-foreground">
Tree Name <span className="text-red-400">*</span>
</label>
<input
@@ -106,15 +106,15 @@ export function ForkTreeModal({
onChange={(e) => setName(e.target.value)}
placeholder="My Custom Tree"
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20'
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
<div>
<label htmlFor="tree-description" className="mb-1.5 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
<label htmlFor="tree-description" className="mb-1.5 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
id="tree-description"
@@ -123,8 +123,8 @@ export function ForkTreeModal({
placeholder="Describe what this tree helps troubleshoot..."
rows={3}
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
'resize-none'
)}
/>
@@ -135,7 +135,7 @@ export function ForkTreeModal({
<p className="text-sm text-red-400">{error}</p>
)}
<p className="text-xs text-white/40">
<p className="text-xs text-muted-foreground">
The new tree will include your custom steps and will be saved to your personal tree library.
</p>
</div>

View File

@@ -28,8 +28,8 @@ export function PostStepActionModal({
return (
<Modal isOpen={isOpen} onClose={onClose} title="What would you like to do?">
<div className="space-y-3">
<p className="mb-4 text-sm text-white/70">
You've created: <strong className="text-white">{step.title}</strong>
<p className="mb-4 text-sm text-muted-foreground">
You've created: <strong className="text-foreground">{step.title}</strong>
</p>
{/* Save for Later - Only show if not already from library */}
@@ -48,8 +48,8 @@ export function PostStepActionModal({
<Bookmark className="h-5 w-5 text-blue-500" />
</div>
<div>
<p className="font-medium text-white">Save for Later</p>
<p className="text-sm text-white/70">
<p className="font-medium text-foreground">Save for Later</p>
<p className="text-sm text-muted-foreground">
Add to your step library for future use
</p>
</div>
@@ -62,8 +62,8 @@ export function PostStepActionModal({
onClick={onUseNow}
disabled={isSaving}
className={cn(
'w-full rounded-lg border border-white/[0.06] p-4 text-left transition-colors',
'hover:border-white/20 hover:bg-white/10',
'w-full rounded-lg border border-border p-4 text-left transition-colors',
'hover:border-border hover:bg-accent',
'disabled:cursor-not-allowed disabled:opacity-50'
)}
>
@@ -96,8 +96,8 @@ export function PostStepActionModal({
<BookmarkPlus className="h-5 w-5 text-purple-500" />
</div>
<div>
<p className="font-medium text-white">Do Both</p>
<p className="text-sm text-white/70">
<p className="font-medium text-foreground">Do Both</p>
<p className="text-sm text-muted-foreground">
Save to library AND use in this session
</p>
</div>
@@ -106,7 +106,7 @@ export function PostStepActionModal({
)}
{isSaving && (
<p className="text-center text-sm text-white/40">Saving...</p>
<p className="text-center text-sm text-muted-foreground">Saving...</p>
)}
</div>
</Modal>

View File

@@ -34,21 +34,21 @@ export function SaveSessionAsTreeModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="glass-card w-full max-w-lg rounded-2xl p-6 shadow-lg">
<div className="bg-card border border-border w-full max-w-lg rounded-2xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Save Session as Tree</h2>
<h2 className="text-lg font-semibold text-foreground">Save Session as Tree</h2>
<button
onClick={onClose}
disabled={isSaving}
className="rounded-full p-1 text-white/40 hover:bg-white/10 hover:text-white disabled:opacity-50"
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Info */}
<p className="mb-4 text-sm text-white/70">
<p className="mb-4 text-sm text-muted-foreground">
Create a new tree from this session's path. The tree will be linked to the original tree as a fork.
</p>
@@ -56,8 +56,8 @@ export function SaveSessionAsTreeModal({
<form onSubmit={handleSubmit} className="space-y-4">
{/* Tree Name */}
<div>
<label htmlFor="treeName" className="mb-1 block text-sm font-medium text-white">
Tree Name <span className="text-white/40">(optional)</span>
<label htmlFor="treeName" className="mb-1 block text-sm font-medium text-foreground">
Tree Name <span className="text-muted-foreground">(optional)</span>
</label>
<input
id="treeName"
@@ -68,9 +68,9 @@ export function SaveSessionAsTreeModal({
disabled={isSaving}
maxLength={255}
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
@@ -78,8 +78,8 @@ export function SaveSessionAsTreeModal({
{/* Description */}
<div>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
id="description"
@@ -89,9 +89,9 @@ export function SaveSessionAsTreeModal({
disabled={isSaving}
rows={3}
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
@@ -99,7 +99,7 @@ export function SaveSessionAsTreeModal({
{/* Status */}
<div>
<label className="mb-2 block text-sm font-medium text-white">Status</label>
<label className="mb-2 block text-sm font-medium text-foreground">Status</label>
<div className="flex gap-4">
<label className="flex cursor-pointer items-center gap-2">
<input
@@ -109,9 +109,9 @@ export function SaveSessionAsTreeModal({
checked={status === 'draft'}
onChange={() => setStatus('draft')}
disabled={isSaving}
className="h-4 w-4 border-white/10 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-0"
className="h-4 w-4 border-border text-foreground focus:ring-2 focus:ring-primary/20 focus:ring-offset-0"
/>
<span className="text-sm text-white">Draft</span>
<span className="text-sm text-foreground">Draft</span>
</label>
<label className="flex cursor-pointer items-center gap-2">
<input
@@ -121,9 +121,9 @@ export function SaveSessionAsTreeModal({
checked={status === 'published'}
onChange={() => setStatus('published')}
disabled={isSaving}
className="h-4 w-4 border-white/10 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-0"
className="h-4 w-4 border-border text-foreground focus:ring-2 focus:ring-primary/20 focus:ring-offset-0"
/>
<span className="text-sm text-white">Published</span>
<span className="text-sm text-foreground">Published</span>
</label>
</div>
</div>
@@ -135,8 +135,8 @@ export function SaveSessionAsTreeModal({
onClick={onClose}
disabled={isSaving}
className={cn(
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground disabled:opacity-50'
)}
>
Cancel
@@ -145,8 +145,8 @@ export function SaveSessionAsTreeModal({
type="submit"
disabled={isSaving}
className={cn(
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
'rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 disabled:opacity-50'
)}
>
{isSaving ? 'Saving...' : 'Save as Tree'}

View File

@@ -124,8 +124,8 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
onClick={() => setIsCollapsed(false)}
className={cn(
'fixed right-2 top-1/2 z-40 -translate-y-1/2 rounded-md p-2.5',
'bg-[#0a0a0a] border border-white/[0.06] shadow-md',
'text-white/40 hover:bg-white/10 hover:text-white',
'bg-card border border-border shadow-md',
'text-muted-foreground hover:bg-accent hover:text-foreground',
'transition-opacity duration-200',
isCollapsed ? 'opacity-100' : 'pointer-events-none opacity-0'
)}
@@ -153,29 +153,29 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
'fixed z-40',
'inset-0 sm:inset-auto sm:right-2 sm:top-1/2 sm:-translate-y-1/2',
'flex w-full flex-col sm:h-[55vh] sm:w-[420px]',
'border-white/[0.06] bg-[#0a0a0a]/95 backdrop-blur-md shadow-xl sm:rounded-lg sm:border',
'border-border bg-card/95 backdrop-blur-md shadow-xl sm:rounded-lg sm:border',
'transition-transform duration-200 ease-out',
isCollapsed ? 'translate-x-full' : 'translate-x-0'
)}
>
{/* Header */}
<div className="flex items-center justify-between border-b border-white/[0.06] px-3 py-2">
<div className="flex items-center justify-between border-b border-border px-3 py-2">
<div className="flex items-center gap-2">
<StickyNote className="h-4 w-4 text-white/40" />
<span className="text-sm font-medium text-white">Scratchpad</span>
<span className="text-xs text-white/30">Ctrl+/</span>
<StickyNote className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium text-foreground">Scratchpad</span>
<span className="text-xs text-muted-foreground">Ctrl+/</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => setShowPreview(!showPreview)}
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
title={showPreview ? 'Edit' : 'Preview'}
>
{showPreview ? <Pencil className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
</button>
<button
onClick={() => setIsCollapsed(true)}
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
title="Close scratchpad"
aria-label="Close scratchpad"
>
@@ -191,7 +191,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
{content.trim() ? (
<MarkdownContent content={content} className="text-sm" />
) : (
<p className="text-sm italic text-white/40">Nothing to preview</p>
<p className="text-sm italic text-muted-foreground">Nothing to preview</p>
)}
</div>
) : (
@@ -202,7 +202,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
placeholder={"Capture IPs, error codes, server names, user info...\n\nSupports markdown formatting."}
className={cn(
'h-full min-h-[200px] w-full resize-none rounded-md border-0 bg-transparent p-0 text-sm',
'text-white placeholder:text-white/40',
'text-foreground placeholder:text-muted-foreground',
'focus:outline-none focus:ring-0'
)}
/>
@@ -210,15 +210,15 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
</div>
{/* Save Indicator */}
<div className="border-t border-white/[0.06] px-3 py-1.5">
<div className="border-t border-border px-3 py-1.5">
<div className="flex items-center gap-1.5 text-xs">
{saveStatus === 'unsaved' && (
<span className="text-white/40">Unsaved changes</span>
<span className="text-muted-foreground">Unsaved changes</span>
)}
{saveStatus === 'saving' && (
<>
<Loader2 className="h-3 w-3 animate-spin text-white/40" />
<span className="text-white/40">Saving...</span>
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
<span className="text-muted-foreground">Saving...</span>
</>
)}
{saveStatus === 'saved' && (
@@ -228,7 +228,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
<span className="text-red-400">Save failed</span>
)}
{saveStatus === 'idle' && (
<span className="text-white/30">Markdown supported</span>
<span className="text-muted-foreground">Markdown supported</span>
)}
</div>
</div>

View File

@@ -93,32 +93,32 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<div className="flex flex-col gap-3 sm:flex-row">
{/* Ticket Number Search */}
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/40" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
type="text"
placeholder="Search by ticket number..."
value={filters.ticketNumber}
onChange={(e) => handleFilterChange('ticketNumber', e.target.value)}
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 py-2 pl-9 pr-3',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
{/* Client Name Search */}
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/40" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
type="text"
placeholder="Search by client name..."
value={filters.clientName}
onChange={(e) => handleFilterChange('clientName', e.target.value)}
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 py-2 pl-9 pr-3',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -128,8 +128,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
value={filters.treeName}
onChange={(e) => handleFilterChange('treeName', e.target.value)}
className={cn(
'rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'rounded-md border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'sm:min-w-[200px]'
)}
>
@@ -148,19 +148,19 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button
onClick={() => setShowDatePicker(!showDatePicker)}
className={cn(
'flex w-full items-center gap-2 rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm',
'text-white hover:bg-white/10',
filters.dateRange?.from && 'border-white/30'
'flex w-full items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm',
'text-foreground hover:bg-accent',
filters.dateRange?.from && 'border-primary/30'
)}
>
<Calendar className="h-4 w-4 text-white/40" />
<span className={cn(!filters.dateRange?.from && 'text-white/40')}>
<Calendar className="h-4 w-4 text-muted-foreground" />
<span className={cn(!filters.dateRange?.from && 'text-muted-foreground')}>
{formatDateRange(filters.dateRange)}
</span>
</button>
{showDatePicker && (
<div className="absolute left-0 top-full z-50 mt-2 rounded-lg border border-white/[0.06] bg-[#0a0a0a] p-4 shadow-lg">
<div className="absolute left-0 top-full z-50 mt-2 rounded-lg border border-border bg-[#0a0a0a] p-4 shadow-lg">
{/* Date Type Toggle */}
<div className="mb-3 flex gap-2">
<button
@@ -168,8 +168,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn(
'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
filters.dateType === 'started'
? 'bg-white text-black'
: 'border border-white/10 text-white/60 hover:bg-white/10 hover:text-white'
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
: 'border border-border text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
Started
@@ -179,8 +179,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn(
'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
filters.dateType === 'completed'
? 'bg-white text-black'
: 'border border-white/10 text-white/60 hover:bg-white/10 hover:text-white'
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
: 'border border-border text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
Completed
@@ -194,8 +194,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
key={preset.value}
onClick={() => applyDatePreset(preset.value)}
className={cn(
'rounded-md bg-white/10 px-3 py-1.5 text-sm font-medium text-white/70',
'hover:bg-white/20 hover:text-white'
'rounded-md bg-accent px-3 py-1.5 text-sm font-medium text-muted-foreground',
'hover:bg-accent/80 hover:text-foreground'
)}
>
{preset.label}
@@ -227,8 +227,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
setShowDatePicker(false)
}}
className={cn(
'flex-1 rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black',
'hover:bg-white/90'
'flex-1 rounded-md bg-gradient-brand px-3 py-1.5 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90'
)}
>
Apply
@@ -236,8 +236,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button
onClick={() => setShowDatePicker(false)}
className={cn(
'rounded-md border border-white/10 px-3 py-1.5 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border px-3 py-1.5 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
>
Cancel
@@ -252,8 +252,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button
onClick={onClear}
className={cn(
'flex items-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium',
'text-white/60 hover:bg-white/10 hover:text-white'
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<Filter className="h-4 w-4" />
@@ -265,46 +265,46 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
{/* Active Filter Chips */}
{hasActiveFilters && (
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-white/40">Active filters:</span>
<span className="text-sm text-muted-foreground">Active filters:</span>
{filters.ticketNumber && (
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-muted-foreground">
Ticket: {filters.ticketNumber}
<button
onClick={() => handleFilterChange('ticketNumber', '')}
className="rounded-full p-0.5 hover:bg-white/20"
className="rounded-full p-0.5 hover:bg-accent/80"
>
<X className="h-3 w-3" />
</button>
</span>
)}
{filters.clientName && (
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-muted-foreground">
Client: {filters.clientName}
<button
onClick={() => handleFilterChange('clientName', '')}
className="rounded-full p-0.5 hover:bg-white/20"
className="rounded-full p-0.5 hover:bg-accent/80"
>
<X className="h-3 w-3" />
</button>
</span>
)}
{filters.treeName && (
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-muted-foreground">
Tree: {filters.treeName}
<button
onClick={() => handleFilterChange('treeName', '')}
className="rounded-full p-0.5 hover:bg-white/20"
className="rounded-full p-0.5 hover:bg-accent/80"
>
<X className="h-3 w-3" />
</button>
</span>
)}
{filters.dateRange?.from && (
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-muted-foreground">
{formatDateRange(filters.dateRange)} ({filters.dateType})
<button
onClick={clearDateRange}
className="rounded-full p-0.5 hover:bg-white/20"
className="rounded-full p-0.5 hover:bg-accent/80"
>
<X className="h-3 w-3" />
</button>

View File

@@ -51,8 +51,8 @@ export function SessionOutcomeModal({
onClick={onClose}
disabled={isSubmitting}
className={cn(
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground disabled:opacity-50'
)}
>
Cancel
@@ -62,8 +62,8 @@ export function SessionOutcomeModal({
onClick={handleSubmit}
disabled={isSubmitting}
className={cn(
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
'rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium',
'hover:opacity-90 disabled:opacity-50'
)}
>
{isSubmitting ? 'Completing...' : 'Complete Session'}
@@ -72,7 +72,7 @@ export function SessionOutcomeModal({
)}
>
<form key={String(isOpen)} ref={formRef} className="space-y-4">
<p className="text-sm text-white/70">
<p className="text-sm text-muted-foreground">
Select the session outcome before completion.
</p>
<div className="space-y-2">
@@ -80,8 +80,8 @@ export function SessionOutcomeModal({
<label
key={option.value}
className={cn(
'block cursor-pointer rounded-lg border border-white/10 p-3 transition-colors',
'hover:bg-white/[0.04]'
'block cursor-pointer rounded-lg border border-border p-3 transition-colors',
'hover:bg-accent/50'
)}
>
<div className="flex items-start gap-3">
@@ -93,8 +93,8 @@ export function SessionOutcomeModal({
className="mt-1 h-4 w-4"
/>
<div>
<p className="text-sm font-medium text-white">{option.label}</p>
<p className="text-xs text-white/50">{option.description}</p>
<p className="text-sm font-medium text-foreground">{option.label}</p>
<p className="text-xs text-muted-foreground">{option.description}</p>
</div>
</div>
</label>
@@ -102,31 +102,31 @@ export function SessionOutcomeModal({
</div>
<div>
<label className="block text-sm font-medium text-white">Outcome Notes (optional)</label>
<label className="block text-sm font-medium text-foreground">Outcome Notes (optional)</label>
<textarea
name="outcome-notes"
defaultValue=""
rows={3}
placeholder="Add context for this outcome..."
className={cn(
'mt-1 block w-full rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-sm text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
'text-sm text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
<div>
<label className="block text-sm font-medium text-white">Next Steps / Follow-Up (optional)</label>
<label className="block text-sm font-medium text-foreground">Next Steps / Follow-Up (optional)</label>
<textarea
name="next-steps"
defaultValue=""
rows={3}
placeholder="Actions to take after this session..."
className={cn(
'mt-1 block w-full rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-sm text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
'text-sm text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>

View File

@@ -53,7 +53,7 @@ export function SessionTimeline({
if (treeType === 'procedural') {
return (
<div className="mb-8">
<h2 className="mb-4 text-lg font-semibold text-white">Procedure Steps</h2>
<h2 className="mb-4 text-lg font-semibold text-foreground">Procedure Steps</h2>
<div className="space-y-2">
{decisions.map((decision, index) => {
const isCompleted = decision.answer === 'completed'
@@ -61,31 +61,31 @@ export function SessionTimeline({
<div
key={index}
className={cn(
'glass-card rounded-xl p-4',
'bg-card border border-border rounded-xl p-4',
isCompleted && 'border-l-2 border-emerald-400/50'
)}
>
<div className="flex items-start gap-3">
<span className={cn(
'mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-medium',
isCompleted ? 'bg-emerald-400/10 text-emerald-400' : 'bg-white/10 text-white/50'
isCompleted ? 'bg-emerald-400/10 text-emerald-400' : 'bg-accent text-muted-foreground'
)}>
{isCompleted ? '\u2713' : index + 1}
</span>
<div className="min-w-0 flex-1">
<p className="font-medium text-white">{decision.question || 'Step'}</p>
<p className="font-medium text-foreground">{decision.question || 'Step'}</p>
{decision.notes && (
<p className="mt-1.5 rounded bg-white/5 p-2 text-sm text-white/40">
<p className="mt-1.5 rounded bg-white/5 p-2 text-sm text-muted-foreground">
Notes: {decision.notes}
</p>
)}
{decision.command_output && (
<p className="mt-1 text-sm text-white/40">
<p className="mt-1 text-sm text-muted-foreground">
Verification: {decision.command_output}
</p>
)}
{decision.duration_seconds != null && (
<p className="mt-1 text-xs text-white/30">
<p className="mt-1 text-xs text-muted-foreground">
Duration: {formatDuration(decision.duration_seconds)}
</p>
)}
@@ -94,7 +94,7 @@ export function SessionTimeline({
<button
onClick={() => handleCopyStep(decision, index)}
title="Copy step to clipboard"
className="rounded p-1 text-white/30 hover:bg-white/10 hover:text-white"
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
{copiedStepIndex === index ? (
<Check className="h-4 w-4 text-emerald-400" />
@@ -123,52 +123,52 @@ export function SessionTimeline({
// Default: troubleshooting decision timeline
return (
<div className="mb-8">
<h2 className="mb-4 text-lg font-semibold text-white">Decision Timeline</h2>
<h2 className="mb-4 text-lg font-semibold text-foreground">Decision Timeline</h2>
<div className="space-y-4">
<div className="flex items-center gap-3 text-sm">
<span className="h-3 w-3 rounded-full bg-white" />
<span className="text-white/40">
<span className="h-3 w-3 rounded-full bg-foreground" />
<span className="text-muted-foreground">
Session started: {formatDate(startedAt)}
</span>
</div>
{decisions.map((decision, index) => (
<div key={index} className="ml-1 border-l-2 border-white/[0.06] pl-6">
<div key={index} className="ml-1 border-l-2 border-border pl-6">
<div className="relative">
<span className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full bg-white/20" />
<div className="glass-card rounded-xl p-4">
<span className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full bg-muted-foreground" />
<div className="bg-card border border-border rounded-xl p-4">
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
{decision.question && (
<p className="font-medium text-white">{decision.question}</p>
<p className="font-medium text-foreground">{decision.question}</p>
)}
{decision.answer && (
<p className="mt-1 text-sm text-white">Answer: {decision.answer}</p>
<p className="mt-1 text-sm text-foreground">Answer: {decision.answer}</p>
)}
{decision.action_performed && (
<p className="mt-1 text-sm text-white/40">
<p className="mt-1 text-sm text-muted-foreground">
Action: {decision.action_performed}
</p>
)}
{decision.notes && (
<p className="mt-2 rounded bg-white/5 p-2 text-sm text-white/40">
<p className="mt-2 rounded bg-white/5 p-2 text-sm text-muted-foreground">
Notes: {decision.notes}
</p>
)}
{decision.command_output && (
<div className="mt-2">
<p className="mb-1 text-xs font-medium text-white/50">Command Output</p>
<pre className="overflow-x-auto rounded bg-white/5 p-2 text-xs font-mono text-white/60 whitespace-pre-wrap">
<p className="mb-1 text-xs font-medium text-muted-foreground">Command Output</p>
<pre className="overflow-x-auto rounded bg-white/5 p-2 text-xs font-mono text-muted-foreground whitespace-pre-wrap">
{decision.command_output}
</pre>
</div>
)}
{decision.duration_seconds != null && (
<p className="mt-2 text-xs text-white/50">
<p className="mt-2 text-xs text-muted-foreground">
Duration: {formatDuration(decision.duration_seconds)}
</p>
)}
<p className="mt-2 text-xs text-white/40">
<p className="mt-2 text-xs text-muted-foreground">
{formatDate(decision.timestamp)}
</p>
</div>
@@ -176,7 +176,7 @@ export function SessionTimeline({
<button
onClick={() => handleCopyStep(decision, index)}
title="Copy step to clipboard"
className="rounded p-1 text-white/30 hover:bg-white/10 hover:text-white"
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
{copiedStepIndex === index ? (
<Check className="h-4 w-4 text-emerald-400" />

View File

@@ -185,16 +185,16 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
/>
{/* Modal */}
<div className="relative w-full max-w-lg glass-card rounded-2xl shadow-lg">
<div className="relative w-full max-w-lg bg-card border border-border rounded-xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-white/[0.06] px-6 py-4">
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<div>
<h2 className="text-lg font-semibold text-white">Share Session</h2>
<p className="text-sm text-white/40">{sessionLabel}</p>
<h2 className="text-lg font-heading font-semibold text-foreground">Share Session</h2>
<p className="text-sm text-muted-foreground">{sessionLabel}</p>
</div>
<button
onClick={onClose}
className="rounded-md p-1 text-white/40 hover:bg-white/[0.06] hover:text-white"
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<X className="h-5 w-5" />
</button>
@@ -206,7 +206,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
<div className="space-y-4">
{/* Visibility */}
<div>
<label className="mb-2 block text-sm font-medium text-white">
<label className="mb-2 block text-sm font-medium text-foreground">
Visibility
</label>
<div className="space-y-2">
@@ -215,17 +215,17 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
className={cn(
'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
visibility === 'account'
? 'border-white/20 bg-white/10 text-white'
: 'border-white/[0.06] bg-transparent text-white/50 hover:border-white/20 hover:bg-white/[0.06]'
? 'border-primary/30 bg-primary/10 text-foreground'
: 'border-border bg-transparent text-muted-foreground hover:border-border hover:bg-accent'
)}
>
<Users className="h-4 w-4" />
<div className="flex-1">
<div className="text-sm font-medium">Account Only</div>
<div className="text-xs text-white/40">Visible to your team</div>
<div className="text-xs text-muted-foreground">Visible to your team</div>
</div>
{visibility === 'account' && (
<div className="h-2 w-2 rounded-full bg-white" />
<div className="h-2 w-2 rounded-full bg-primary" />
)}
</button>
<button
@@ -233,17 +233,17 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
className={cn(
'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
visibility === 'public'
? 'border-white/20 bg-white/10 text-white'
: 'border-white/[0.06] bg-transparent text-white/50 hover:border-white/20 hover:bg-white/[0.06]'
? 'border-primary/30 bg-primary/10 text-foreground'
: 'border-border bg-transparent text-muted-foreground hover:border-border hover:bg-accent'
)}
>
<Globe className="h-4 w-4" />
<div className="flex-1">
<div className="text-sm font-medium">Public</div>
<div className="text-xs text-white/40">Anyone with the link</div>
<div className="text-xs text-muted-foreground">Anyone with the link</div>
</div>
{visibility === 'public' && (
<div className="h-2 w-2 rounded-full bg-white" />
<div className="h-2 w-2 rounded-full bg-primary" />
)}
</button>
</div>
@@ -254,8 +254,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
{/* Share Name */}
<div>
<label className="mb-2 block text-sm font-medium text-white">
Share Name <span className="text-white/40">(optional)</span>
<label className="mb-2 block text-sm font-medium text-foreground">
Share Name <span className="text-muted-foreground">(optional)</span>
</label>
<input
type="text"
@@ -263,8 +263,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
onChange={(e) => setShareName(e.target.value.slice(0, 100))}
placeholder="e.g. Training link, Customer escalation"
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white placeholder-white/30',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
maxLength={100}
/>
@@ -272,7 +272,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
{/* Expiration */}
<div>
<label className="mb-2 block text-sm font-medium text-white">
<label className="mb-2 block text-sm font-medium text-foreground">
Expiration
</label>
<div className="flex flex-wrap gap-2">
@@ -283,8 +283,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
className={cn(
'rounded-md border px-3 py-1.5 text-sm transition-colors',
expirationPreset === preset.value
? 'border-white/20 bg-white/10 text-white'
: 'border-white/10 text-white/50 hover:border-white/20 hover:bg-white/[0.06]'
? 'border-primary/30 bg-primary/10 text-foreground'
: 'border-border text-muted-foreground hover:border-border hover:bg-accent'
)}
>
{preset.label}
@@ -297,8 +297,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
value={customDatetime}
onChange={(e) => setCustomDatetime(e.target.value)}
className={cn(
'mt-2 w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'mt-2 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'[color-scheme:dark]'
)}
/>
@@ -310,8 +310,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
onClick={handleGenerateLink}
disabled={isGenerating || (expirationPreset === 'custom' && !customDatetime)}
className={cn(
'flex w-full items-center justify-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
'flex w-full items-center justify-center gap-2 rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
<Link2 className="h-4 w-4" />
@@ -322,7 +322,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
{/* Existing Shares */}
{shares.length > 0 && (
<div>
<h3 className="mb-3 text-sm font-medium text-white">
<h3 className="mb-3 text-sm font-medium text-foreground">
Active Shares ({shares.length})
</h3>
<div className="space-y-3">
@@ -332,7 +332,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
return (
<div
key={share.id}
className="glass-card rounded-xl p-4 space-y-2"
className="bg-card border border-border rounded-xl p-4 space-y-2"
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
@@ -340,8 +340,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
<span className={cn(
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs',
share.visibility === 'public'
? 'bg-white/10 text-white/70'
: 'bg-white/10 text-white/70'
? 'bg-accent text-muted-foreground'
: 'bg-accent text-muted-foreground'
)}>
{share.visibility === 'public' ? (
<Globe className="h-3 w-3" />
@@ -350,11 +350,11 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
)}
{share.visibility === 'public' ? 'Public' : 'Account'}
</span>
<span className="truncate text-sm font-medium text-white">
<span className="truncate text-sm font-medium text-foreground">
{share.share_name || 'Untitled share'}
</span>
</div>
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-white/40">
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
<span>{getRelativeTime(share.created_at)}</span>
<span>
{share.view_count > 0
@@ -375,10 +375,10 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
onClick={() => handleCopyUrl(share)}
title="Copy share URL"
className={cn(
'rounded-md border border-white/10 p-1.5 text-sm transition-colors',
'rounded-md border border-border p-1.5 text-sm transition-colors',
isCopied
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400'
: 'text-white/50 hover:bg-white/10 hover:text-white'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
{isCopied ? (
@@ -390,7 +390,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
<button
onClick={() => handleRevoke(share.id)}
title="Revoke share"
className="rounded-md border border-white/10 p-1.5 text-white/50 hover:bg-red-500/10 hover:border-red-500/30 hover:text-red-400 transition-colors"
className="rounded-md border border-border p-1.5 text-muted-foreground hover:bg-red-500/10 hover:border-red-500/30 hover:text-red-400 transition-colors"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
@@ -406,18 +406,18 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
{/* Loading state */}
{isLoadingShares && shares.length === 0 && (
<div className="flex items-center justify-center py-4">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-white/20 border-t-white" />
<div className="h-5 w-5 animate-spin rounded-full border-2 border-border border-t-foreground" />
</div>
)}
</div>
{/* Footer */}
<div className="flex justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
<div className="flex justify-end gap-3 border-t border-border px-6 py-4">
<button
onClick={onClose}
className={cn(
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
>
Close

View File

@@ -7,11 +7,11 @@ interface SharedSessionTreePreviewProps {
}
const nodeTypeColors: Record<string, string> = {
root: 'bg-white',
root: 'bg-foreground',
decision: 'bg-blue-400',
action: 'bg-yellow-400',
solution: 'bg-emerald-400',
information: 'bg-white/50',
information: 'bg-muted-foreground',
}
function getNodeTitle(node: Record<string, unknown>): string {
@@ -36,7 +36,7 @@ function TreeNode({
const nodeType = (node.node_type as string) || 'decision'
const isInPath = pathTaken.includes(nodeId)
const children = (node.children as Record<string, unknown>[]) || []
const colorClass = nodeTypeColors[nodeType] || 'bg-white/50'
const colorClass = nodeTypeColors[nodeType] || 'bg-muted-foreground'
return (
<>
@@ -44,8 +44,8 @@ function TreeNode({
className={cn(
'flex items-center gap-2 px-3 py-1.5 text-sm',
isInPath
? 'rounded-md border-l-2 border-white/40 bg-white/10 font-medium text-white'
: 'text-white/30'
? 'rounded-md border-l-2 border-muted-foreground bg-accent font-medium text-foreground'
: 'text-muted-foreground'
)}
style={{ paddingLeft: `${depth * 16 + 12}px` }}
>
@@ -75,9 +75,9 @@ export function SharedSessionTreePreview({
}
return (
<div className="glass-card rounded-2xl">
<div className="sticky top-0 z-10 rounded-t-2xl border-b border-white/[0.06] bg-black/80 px-6 py-4 backdrop-blur">
<h3 className="text-sm font-semibold text-white">Tree Structure</h3>
<div className="bg-card border border-border rounded-2xl">
<div className="sticky top-0 z-10 rounded-t-2xl border-b border-border bg-black/80 px-6 py-4 backdrop-blur">
<h3 className="text-sm font-semibold text-foreground">Tree Structure</h3>
</div>
<div className="max-h-[600px] overflow-y-auto py-2">
<TreeNode node={treeStructure as unknown as Record<string, unknown>} depth={0} pathTaken={pathTaken} />

View File

@@ -78,19 +78,19 @@ export function StepRatingModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
<div className="glass-card w-full max-w-2xl max-h-[90vh] flex flex-col rounded-2xl shadow-lg">
<div className="bg-card border border-border w-full max-w-2xl max-h-[90vh] flex flex-col rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-white/[0.06] px-6 py-4">
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<div>
<h2 className="text-lg font-semibold text-white">Rate Your Experience</h2>
<p className="mt-1 text-sm text-white/70">
<h2 className="text-lg font-semibold text-foreground">Rate Your Experience</h2>
<p className="mt-1 text-sm text-muted-foreground">
Help others by rating the steps you used ({librarySteps.length} step{librarySteps.length !== 1 ? 's' : ''})
</p>
</div>
<button
onClick={onClose}
disabled={isSaving}
className="rounded-full p-1 text-white/40 hover:bg-white/10 hover:text-white disabled:opacity-50"
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
@@ -102,14 +102,14 @@ export function StepRatingModal({
{librarySteps.map((step) => {
const rating = getRating(step.id)
return (
<div key={step.id} className="rounded-lg border border-white/[0.06] bg-[#0a0a0a] p-4">
<div key={step.id} className="rounded-lg border border-border bg-[#0a0a0a] p-4">
{/* Step Title */}
<h3 className="font-medium text-white">{step.title}</h3>
<p className="mt-1 text-sm text-white/40 capitalize">{step.step_type}</p>
<h3 className="font-medium text-foreground">{step.title}</h3>
<p className="mt-1 text-sm text-muted-foreground capitalize">{step.step_type}</p>
{/* Star Rating */}
<div className="mt-3">
<label className="mb-1 block text-sm font-medium text-white">
<label className="mb-1 block text-sm font-medium text-foreground">
Rating
</label>
<StarRating
@@ -121,7 +121,7 @@ export function StepRatingModal({
{/* Was this helpful? */}
<div className="mt-3">
<label className="mb-2 block text-sm font-medium text-white">
<label className="mb-2 block text-sm font-medium text-foreground">
Was this helpful?
</label>
<div className="flex gap-2">
@@ -133,7 +133,7 @@ export function StepRatingModal({
'flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors',
rating?.helpful === true
? 'border-emerald-400/20 bg-emerald-400/10 text-emerald-400'
: 'border-white/10 text-white/60 hover:bg-white/10 hover:text-white',
: 'border-border text-muted-foreground hover:bg-accent hover:text-foreground',
'disabled:opacity-50'
)}
>
@@ -148,7 +148,7 @@ export function StepRatingModal({
'flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors',
rating?.helpful === false
? 'border-red-400/20 bg-red-400/10 text-red-400'
: 'border-white/10 text-white/60 hover:bg-white/10 hover:text-white',
: 'border-border text-muted-foreground hover:bg-accent hover:text-foreground',
'disabled:opacity-50'
)}
>
@@ -160,8 +160,8 @@ export function StepRatingModal({
{/* Optional Review */}
<div className="mt-3">
<label htmlFor={`review-${step.id}`} className="mb-1 block text-sm font-medium text-white">
Review <span className="text-white/40">(optional)</span>
<label htmlFor={`review-${step.id}`} className="mb-1 block text-sm font-medium text-foreground">
Review <span className="text-muted-foreground">(optional)</span>
</label>
<textarea
id={`review-${step.id}`}
@@ -172,13 +172,13 @@ export function StepRatingModal({
rows={2}
placeholder="Share your experience with this step..."
className={cn(
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
<p className="mt-1 text-xs text-white/40 text-right">
<p className="mt-1 text-xs text-muted-foreground text-right">
{rating?.review?.length || 0}/500
</p>
</div>
@@ -189,14 +189,14 @@ export function StepRatingModal({
</div>
{/* Footer */}
<div className="flex justify-end gap-2 border-t border-white/[0.06] px-6 py-4">
<div className="flex justify-end gap-2 border-t border-border px-6 py-4">
<button
type="button"
onClick={onClose}
disabled={isSaving}
className={cn(
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground disabled:opacity-50'
)}
>
Skip
@@ -206,8 +206,8 @@ export function StepRatingModal({
onClick={handleSubmit}
disabled={isSaving}
className={cn(
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
'rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 disabled:opacity-50'
)}
>
{isSaving ? 'Submitting...' : 'Submit Ratings'}

View File

@@ -22,14 +22,14 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="glass-card w-full max-w-md rounded-2xl p-6 shadow-lg">
<h2 className="mb-1 text-lg font-semibold text-white">Input Required</h2>
<p className="mb-4 text-sm text-white/40">
<div className="bg-card border border-border w-full max-w-md rounded-2xl p-6 shadow-lg">
<h2 className="mb-1 text-lg font-semibold text-foreground">Input Required</h2>
<p className="mb-4 text-sm text-muted-foreground">
This step requires you to provide a value.
</p>
<form onSubmit={handleSubmit}>
<label className="mb-2 block text-sm font-medium text-white/70">
<label className="mb-2 block text-sm font-medium text-muted-foreground">
{prompt}
</label>
<input
@@ -39,8 +39,8 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
placeholder="Enter value..."
autoFocus
className={cn(
'w-full rounded-lg border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/30 focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
@@ -49,8 +49,8 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
type="submit"
disabled={!value.trim()}
className={cn(
'flex-1 rounded-lg bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
'flex-1 rounded-lg bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
Continue
@@ -59,8 +59,8 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
type="button"
onClick={onCancel}
className={cn(
'rounded-lg border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
'rounded-lg border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
>
Skip

View File

@@ -0,0 +1,47 @@
import { cn } from '@/lib/utils'
interface CategoryItem {
id: string
name: string
color: string
count: number
}
interface CategoryListProps {
categories: CategoryItem[]
activeId?: string | null
onSelect: (id: string | null) => void
}
export function CategoryList({ categories, activeId, onSelect }: CategoryListProps) {
if (categories.length === 0) return null
return (
<div className="px-3 py-2">
<p className="mb-2 px-3 font-heading text-[0.6875rem] font-bold uppercase tracking-[0.04em] text-muted-foreground">
Categories
</p>
<div className="space-y-0.5">
{categories.map(cat => (
<button
key={cat.id}
onClick={() => onSelect(activeId === cat.id ? null : cat.id)}
className={cn(
'flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-sm transition-colors',
activeId === cat.id
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
)}
>
<span
className="h-2 w-2 shrink-0 rounded-full"
style={{ backgroundColor: cat.color }}
/>
<span className="flex-1 truncate text-left">{cat.name}</span>
<span className="font-label text-[0.6875rem] text-[hsl(var(--text-dimmed))]">{cat.count}</span>
</button>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,60 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { ChevronDown, ChevronRight, Pin } from 'lucide-react'
import { getTreeNavigatePath } from '@/lib/routing'
import { cn } from '@/lib/utils'
import type { PinnedFlow } from '@/api/pinnedFlows'
interface PinnedFlowsSectionProps {
flows: PinnedFlow[]
onUnpin: (treeId: string) => void
}
export function PinnedFlowsSection({ flows, onUnpin }: PinnedFlowsSectionProps) {
const navigate = useNavigate()
const [collapsed, setCollapsed] = useState(false)
return (
<div className="px-3 py-2">
<button
onClick={() => setCollapsed(!collapsed)}
className="flex w-full items-center gap-1 px-3 mb-1 font-heading text-[0.6875rem] font-bold uppercase tracking-[0.04em] text-muted-foreground hover:text-foreground transition-colors"
>
{collapsed ? <ChevronRight size={12} /> : <ChevronDown size={12} />}
Pinned
</button>
{!collapsed && (
<div className="space-y-0.5 max-h-[280px] overflow-y-auto">
{flows.length === 0 ? (
<p className="px-3 py-2 text-xs text-muted-foreground">
Pin your most-used flows here
</p>
) : (
flows.map(flow => (
<button
key={flow.tree_id}
onClick={() => navigate(getTreeNavigatePath(flow.tree_id, flow.tree_type))}
onContextMenu={(e) => {
e.preventDefault()
onUnpin(flow.tree_id)
}}
className={cn(
'group flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
)}
title={`${flow.tree_name} (right-click to unpin)`}
>
<span className="text-sm shrink-0">
{flow.tree_type === 'procedural' ? '📋' : '🔧'}
</span>
<span className="truncate flex-1 text-left">{flow.tree_name}</span>
<Pin size={12} className="shrink-0 opacity-0 group-hover:opacity-40 transition-opacity" />
</button>
))
)}
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,38 @@
import { cn } from '@/lib/utils'
interface TagCloudProps {
tags: string[]
activeTags?: string[]
onTagClick: (tag: string) => void
}
export function TagCloud({ tags, activeTags = [], onTagClick }: TagCloudProps) {
if (tags.length === 0) return null
return (
<div className="px-3 py-2">
<p className="mb-2 px-3 font-heading text-[0.6875rem] font-bold uppercase tracking-[0.04em] text-muted-foreground">
Popular Tags
</p>
<div className="flex flex-wrap gap-1 px-3">
{tags.map(tag => {
const isActive = activeTags.includes(tag)
return (
<button
key={tag}
onClick={() => onTagClick(tag)}
className={cn(
'rounded-md border px-2 py-0.5 font-label text-[0.625rem] transition-colors',
isActive
? 'border-primary/30 bg-primary/10 text-primary'
: 'border-border bg-card text-muted-foreground hover:border-primary/20 hover:text-foreground'
)}
>
{tag}
</button>
)
})}
</div>
</div>
)
}

View File

@@ -65,13 +65,13 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
return (
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-sm sm:items-center sm:p-4">
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-white/[0.06] bg-[#0a0a0a] shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-border bg-card shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-white/[0.06] p-4">
<h2 className="text-lg font-semibold text-white">Add Custom Step</h2>
<div className="flex items-center justify-between border-b border-border p-4">
<h2 className="text-lg font-semibold text-foreground">Add Custom Step</h2>
<button
onClick={onClose}
className="rounded-md p-1.5 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
aria-label="Close"
>
<X className="h-5 w-5" />
@@ -79,15 +79,15 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
</div>
{/* Tabs */}
<div className="flex border-b border-white/[0.06]">
<div className="flex border-b border-border">
{canCreateSteps && (
<button
onClick={() => setActiveTab('create')}
className={cn(
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
activeTab === 'create'
? 'border-b-2 border-white bg-white/5 text-white'
: 'text-white/40 hover:bg-white/10 hover:text-white'
? 'border-b-2 border-primary bg-primary/5 text-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
Type My Own
@@ -134,8 +134,8 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
{isSubmitting && (
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="flex flex-col items-center gap-3">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
<p className="text-sm text-white/40">Creating step...</p>
<div className="h-8 w-8 animate-spin rounded-full border-4 border-border border-t-foreground" />
<p className="text-sm text-muted-foreground">Creating step...</p>
</div>
</div>
)}

View File

@@ -27,7 +27,7 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
const remainingTags = step.tags.length - 3
return (
<div className="group rounded-lg border border-white/[0.06] bg-[#0a0a0a] p-4 transition-shadow hover:shadow-md">
<div className="group rounded-lg border border-border bg-card p-4 transition-shadow hover:shadow-md">
{/* Header */}
<div className="mb-3 flex items-start justify-between gap-2">
<div className="flex-1">
@@ -52,12 +52,12 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
</div>
{/* Title */}
<h3 className="font-semibold text-white line-clamp-2">{step.title}</h3>
<h3 className="font-semibold text-foreground line-clamp-2">{step.title}</h3>
</div>
</div>
{/* Metadata */}
<div className="mb-3 space-y-1.5 text-sm text-white/40">
<div className="mb-3 space-y-1.5 text-sm text-muted-foreground">
{/* Category */}
{step.category_name && (
<div className="flex items-center gap-1.5">
@@ -103,7 +103,7 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
{visibleTags.map(tag => (
<span
key={tag}
className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70"
className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground"
>
{tag}
</span>
@@ -121,8 +121,8 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
<button
onClick={() => onPreview(step)}
className={cn(
'flex flex-1 items-center justify-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white transition-colors'
'flex flex-1 items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground transition-colors'
)}
>
<Eye className="h-4 w-4" />
@@ -131,8 +131,8 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
<button
onClick={() => onInsert(step)}
className={cn(
'flex flex-1 items-center justify-center gap-2 rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90 transition-colors'
'flex flex-1 items-center justify-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium',
'hover:opacity-90 transition-colors'
)}
>
<Plus className="h-4 w-4" />

View File

@@ -70,11 +70,11 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col glass-card rounded-2xl shadow-lg">
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col bg-card border border-border rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-start justify-between border-b border-white/[0.06] p-6 pb-4">
<div className="flex items-start justify-between border-b border-border p-6 pb-4">
{isLoading ? (
<div className="h-6 w-48 animate-pulse rounded bg-white/10" />
<div className="h-6 w-48 animate-pulse rounded bg-accent" />
) : error ? (
<h2 className="text-lg font-semibold text-red-400">{error}</h2>
) : step ? (
@@ -90,7 +90,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{step.step_type}
</span>
{step.category_name && (
<span className="text-xs text-white/40">📁 {step.category_name}</span>
<span className="text-xs text-muted-foreground">{step.category_name}</span>
)}
{step.is_featured && (
<span className="rounded bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
@@ -99,16 +99,16 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
)}
{step.is_verified && (
<span className="rounded bg-emerald-400/10 px-2 py-0.5 text-xs font-medium text-emerald-400">
Verified
Verified
</span>
)}
</div>
<h2 className="text-xl font-semibold text-white">{step.title}</h2>
<h2 className="text-xl font-semibold text-foreground">{step.title}</h2>
</div>
) : null}
<button
onClick={onClose}
className="rounded-md p-1 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
aria-label="Close"
>
<X className="h-5 w-5" />
@@ -119,18 +119,18 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
<div className="flex-1 overflow-y-auto p-6">
{isLoading ? (
<div className="space-y-4">
<div className="h-4 w-full animate-pulse rounded bg-white/10" />
<div className="h-4 w-3/4 animate-pulse rounded bg-white/10" />
<div className="h-4 w-5/6 animate-pulse rounded bg-white/10" />
<div className="h-4 w-full animate-pulse rounded bg-accent" />
<div className="h-4 w-3/4 animate-pulse rounded bg-accent" />
<div className="h-4 w-5/6 animate-pulse rounded bg-accent" />
</div>
) : error ? (
<p className="text-sm text-white/40">{error}</p>
<p className="text-sm text-muted-foreground">{error}</p>
) : step ? (
<div className="space-y-6">
{/* Rating */}
{hasRating && (
<div>
<h3 className="mb-2 text-sm font-semibold text-white">Rating</h3>
<h3 className="mb-2 text-sm font-semibold text-foreground">Rating</h3>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
{[1, 2, 3, 4, 5].map(i => (
@@ -140,12 +140,12 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
'h-4 w-4',
i <= Math.round(step.rating_average)
? 'fill-yellow-400 text-yellow-400'
: 'text-white/20'
: 'text-muted-foreground'
)}
/>
))}
</div>
<span className="text-sm text-white/70">
<span className="text-sm text-muted-foreground">
{step.rating_average.toFixed(1)} ({step.rating_count} {step.rating_count === 1 ? 'rating' : 'ratings'})
</span>
</div>
@@ -155,12 +155,12 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Tags */}
{step.tags.length > 0 && (
<div>
<h3 className="mb-2 text-sm font-semibold text-white">Tags</h3>
<h3 className="mb-2 text-sm font-semibold text-foreground">Tags</h3>
<div className="flex flex-wrap gap-1.5">
{step.tags.map(tag => (
<span
key={tag}
className="rounded-full bg-white/10 px-2 py-1 text-xs text-white/70"
className="rounded-full bg-accent px-2 py-1 text-xs text-muted-foreground"
>
{tag}
</span>
@@ -172,7 +172,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Instructions */}
<div>
<h3 className="mb-2 text-sm font-semibold">Instructions</h3>
<div className="rounded-lg border border-white/[0.06] bg-white/5 p-4">
<div className="rounded-lg border border-border bg-accent/50 p-4">
<MarkdownContent content={step.content.instructions} />
</div>
</div>
@@ -180,8 +180,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Help Text */}
{step.content.help_text && (
<div>
<h3 className="mb-2 text-sm font-semibold text-white">Help Text</h3>
<div className="rounded-lg border border-white/[0.06] bg-blue-400/5 p-4 text-sm">
<h3 className="mb-2 text-sm font-semibold text-foreground">Help Text</h3>
<div className="rounded-lg border border-border bg-blue-400/5 p-4 text-sm">
<MarkdownContent content={step.content.help_text} />
</div>
</div>
@@ -190,19 +190,19 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Commands */}
{step.content.commands && step.content.commands.length > 0 && (
<div>
<h3 className="mb-2 text-sm font-semibold text-white">Commands</h3>
<h3 className="mb-2 text-sm font-semibold text-foreground">Commands</h3>
<div className="space-y-2">
{step.content.commands.map((cmd, index) => (
<div key={index} className="group relative">
<div className="mb-1 flex items-center justify-between">
<span className="text-xs font-medium text-white/40">{cmd.label}</span>
<span className="text-xs font-medium text-muted-foreground">{cmd.label}</span>
<button
onClick={() => handleCopyCommand(cmd.command, index)}
className={cn(
'flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors',
copiedCommandIndex === index
? 'bg-emerald-400/10 text-emerald-400'
: 'bg-white/10 text-white/40 hover:bg-white/20 hover:text-white'
: 'bg-accent text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
{copiedCommandIndex === index ? (
@@ -218,7 +218,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
)}
</button>
</div>
<pre className="overflow-x-auto rounded bg-black/50 p-3 text-xs text-white">
<pre className="overflow-x-auto rounded bg-card p-3 text-xs text-foreground">
<code>{cmd.command}</code>
</pre>
</div>
@@ -231,23 +231,23 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{topReviews.length > 0 && (
<div>
<div className="mb-2 flex items-center justify-between">
<h3 className="text-sm font-semibold text-white">Reviews</h3>
<h3 className="text-sm font-semibold text-foreground">Reviews</h3>
{reviews.length > 3 && (
<button className="text-xs text-white/70 hover:text-white hover:underline">
<button className="text-xs text-muted-foreground hover:text-foreground hover:underline">
See all {reviews.length} reviews
</button>
)}
</div>
<div className="space-y-3">
{topReviews.map(review => (
<div key={review.id} className="rounded-lg border border-white/[0.06] bg-white/5 p-3">
<div key={review.id} className="rounded-lg border border-border bg-accent/50 p-3">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2 text-sm">
<User className="h-3.5 w-3.5" />
<span className="font-medium text-white">{review.user_name || 'Anonymous'}</span>
<span className="font-medium text-foreground">{review.user_name || 'Anonymous'}</span>
{review.verified_use && (
<span className="rounded bg-emerald-400/10 px-1.5 py-0.5 text-xs text-emerald-400">
Verified Use
Verified Use
</span>
)}
</div>
@@ -259,14 +259,14 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
'h-3 w-3',
i <= review.rating
? 'fill-yellow-400 text-yellow-400'
: 'text-white/20'
: 'text-muted-foreground'
)}
/>
))}
</div>
</div>
<p className="text-sm text-white/70">{review.review_text}</p>
<div className="mt-2 flex items-center gap-2 text-xs text-white/40">
<p className="text-sm text-muted-foreground">{review.review_text}</p>
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
<Calendar className="h-3 w-3" />
{new Date(review.created_at).toLocaleDateString()}
</div>
@@ -277,22 +277,22 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
)}
{/* Metadata */}
<div className="rounded-lg border border-white/[0.06] bg-white/5 p-4">
<div className="rounded-lg border border-border bg-accent/50 p-4">
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<span className="text-white/40">Author:</span>
<span className="ml-2 font-medium text-white">{step.author_name || 'Unknown'}</span>
<span className="text-muted-foreground">Author:</span>
<span className="ml-2 font-medium text-foreground">{step.author_name || 'Unknown'}</span>
</div>
<div>
<span className="text-white/40">Usage Count:</span>
<span className="ml-2 font-medium text-white">{step.usage_count}</span>
<span className="text-muted-foreground">Usage Count:</span>
<span className="ml-2 font-medium text-foreground">{step.usage_count}</span>
</div>
<div>
<span className="text-white/40">Created:</span>
<span className="ml-2 font-medium text-white">{new Date(step.created_at).toLocaleDateString()}</span>
<span className="text-muted-foreground">Created:</span>
<span className="ml-2 font-medium text-foreground">{new Date(step.created_at).toLocaleDateString()}</span>
</div>
<div>
<span className="text-white/40">Visibility:</span>
<span className="text-muted-foreground">Visibility:</span>
<span className="ml-2 font-medium capitalize">{step.visibility}</span>
</div>
</div>
@@ -302,10 +302,10 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
</div>
{/* Footer - Actions */}
<div className="flex gap-2 border-t border-white/[0.06] p-4">
<div className="flex gap-2 border-t border-border p-4">
<button
onClick={onClose}
className="flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
className="flex-1 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
Cancel
</button>
@@ -313,8 +313,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
onClick={handleInsert}
disabled={!step}
className={cn(
'flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
'flex-1 rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90 disabled:opacity-50'
)}
>
Insert Into Session

View File

@@ -136,7 +136,7 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
<form onSubmit={handleSubmit} className="space-y-6">
{/* Step Type */}
<div>
<label className="mb-2 block text-sm font-medium text-white">
<label className="mb-2 block text-sm font-medium text-foreground">
Step Type <span className="text-red-400">*</span>
</label>
<div className="grid grid-cols-3 gap-2">
@@ -150,15 +150,15 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
className={cn(
'rounded-lg border p-3 text-left transition-colors',
stepType === option.value
? 'border-white/20 bg-white/10 ring-2 ring-white/20'
: 'border-white/[0.06] hover:border-white/20'
? 'border-border bg-accent ring-2 ring-primary/20'
: 'border-border hover:border-border'
)}
>
<div className="mb-1 flex items-center gap-2">
<Icon className="h-4 w-4" />
<span className="font-medium text-sm text-white">{option.label}</span>
<span className="font-medium text-sm text-foreground">{option.label}</span>
</div>
<p className="text-xs text-white/40">{option.description}</p>
<p className="text-xs text-muted-foreground">{option.description}</p>
</button>
)
})}
@@ -167,7 +167,7 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Title */}
<div>
<label htmlFor="title" className="mb-2 block text-sm font-medium text-white">
<label htmlFor="title" className="mb-2 block text-sm font-medium text-foreground">
Title <span className="text-red-400">*</span>
</label>
<input
@@ -177,8 +177,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter step title"
className={cn(
'w-full rounded-md border bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20',
errors.title ? 'border-red-400/50' : 'border-white/10'
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
errors.title ? 'border-red-400/50' : 'border-border'
)}
/>
{errors.title && (
@@ -188,9 +188,9 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Instructions */}
<div>
<label htmlFor="instructions" className="mb-2 block text-sm font-medium text-white">
<label htmlFor="instructions" className="mb-2 block text-sm font-medium text-foreground">
Instructions <span className="text-red-400">*</span>
<span className="ml-2 text-xs font-normal text-white/40">(Markdown supported)</span>
<span className="ml-2 text-xs font-normal text-muted-foreground">(Markdown supported)</span>
</label>
<textarea
id="instructions"
@@ -199,8 +199,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
placeholder="Describe what to do in this step..."
rows={6}
className={cn(
'w-full rounded-md border bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20',
errors.instructions ? 'border-red-400/50' : 'border-white/10'
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
errors.instructions ? 'border-red-400/50' : 'border-border'
)}
/>
{errors.instructions && (
@@ -210,8 +210,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Help Text */}
<div>
<label htmlFor="helpText" className="mb-2 block text-sm font-medium text-white">
Help Text <span className="text-xs font-normal text-white/40">(Optional)</span>
<label htmlFor="helpText" className="mb-2 block text-sm font-medium text-foreground">
Help Text <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
</label>
<textarea
id="helpText"
@@ -219,20 +219,20 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => setHelpText(e.target.value)}
placeholder="Additional context or tips..."
rows={3}
className="w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
/>
</div>
{/* Commands */}
<div>
<div className="mb-2 flex items-center justify-between">
<label className="text-sm font-medium text-white">
Commands <span className="text-xs font-normal text-white/40">(Optional)</span>
<label className="text-sm font-medium text-foreground">
Commands <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
</label>
<button
type="button"
onClick={addCommand}
className="flex items-center gap-1 rounded-md bg-white/10 px-2 py-1 text-xs font-medium text-white/70 hover:bg-white/20 hover:text-white"
className="flex items-center gap-1 rounded-md bg-accent px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Plus className="h-3 w-3" />
Add Command
@@ -241,13 +241,13 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{commands.length > 0 && (
<div className="space-y-3">
{commands.map((cmd, index) => (
<div key={index} className="rounded-lg border border-white/[0.06] bg-white/5 p-3">
<div key={index} className="rounded-lg border border-border bg-accent/50 p-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-xs font-medium text-white/40">Command {index + 1}</span>
<span className="text-xs font-medium text-muted-foreground">Command {index + 1}</span>
<button
type="button"
onClick={() => removeCommand(index)}
className="rounded p-1 text-white/40 hover:bg-red-400/10 hover:text-red-400"
className="rounded p-1 text-muted-foreground hover:bg-red-400/10 hover:text-red-400"
>
<X className="h-3 w-3" />
</button>
@@ -259,8 +259,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => updateCommand(index, 'label', e.target.value)}
placeholder="Command label (e.g., 'Restart service')"
className={cn(
'w-full rounded-md border bg-black/50 px-3 py-1.5 text-sm text-white',
errors[`command_${index}_label`] ? 'border-red-400/50' : 'border-white/10'
'w-full rounded-md border bg-card px-3 py-1.5 text-sm text-foreground',
errors[`command_${index}_label`] ? 'border-red-400/50' : 'border-border'
)}
/>
<input
@@ -269,8 +269,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => updateCommand(index, 'command', e.target.value)}
placeholder="Command (e.g., 'systemctl restart nginx')"
className={cn(
'w-full rounded-md border bg-black/50 px-3 py-1.5 font-mono text-sm text-white',
errors[`command_${index}_command`] ? 'border-red-400/50' : 'border-white/10'
'w-full rounded-md border bg-card px-3 py-1.5 font-mono text-sm text-foreground',
errors[`command_${index}_command`] ? 'border-red-400/50' : 'border-border'
)}
/>
{(errors[`command_${index}_label`] || errors[`command_${index}_command`]) && (
@@ -287,14 +287,14 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Category */}
<div>
<label htmlFor="category" className="mb-2 block text-sm font-medium text-white">
Category <span className="text-xs font-normal text-white/40">(Optional)</span>
<label htmlFor="category" className="mb-2 block text-sm font-medium text-foreground">
Category <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
</label>
<select
id="category"
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
className="w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">None</option>
{categories.map(cat => (
@@ -305,8 +305,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Tags */}
<div>
<label htmlFor="tagInput" className="mb-2 block text-sm font-medium text-white">
Tags <span className="text-xs font-normal text-white/40">(Optional)</span>
<label htmlFor="tagInput" className="mb-2 block text-sm font-medium text-foreground">
Tags <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
</label>
<div className="flex gap-2">
<input
@@ -316,12 +316,12 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleTagInputKeyDown}
placeholder="Type tag and press Enter"
className="flex-1 rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
/>
<button
type="button"
onClick={addTag}
className="rounded-md bg-white/10 px-4 py-2 text-sm font-medium text-white/70 hover:bg-white/20 hover:text-white"
className="rounded-md bg-accent px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
Add
</button>
@@ -331,13 +331,13 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{tags.map(tag => (
<span
key={tag}
className="flex items-center gap-1 rounded-full bg-white/10 px-2.5 py-1 text-xs text-white/70"
className="flex items-center gap-1 rounded-full bg-accent px-2.5 py-1 text-xs text-muted-foreground"
>
{tag}
<button
type="button"
onClick={() => removeTag(tag)}
className="rounded-full hover:bg-white/20"
className="rounded-full hover:bg-accent"
aria-label={`Remove tag ${tag}`}
>
<X className="h-3 w-3" />
@@ -350,14 +350,14 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Visibility */}
<div>
<label htmlFor="visibility" className="mb-2 block text-sm font-medium text-white">
<label htmlFor="visibility" className="mb-2 block text-sm font-medium text-foreground">
Visibility
</label>
<select
id="visibility"
value={visibility}
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
className="w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="private">Private (only me)</option>
<option value="team">Team (my team members)</option>
@@ -370,13 +370,13 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
<button
type="button"
onClick={onCancel}
className="flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
className="flex-1 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
Cancel
</button>
<button
type="submit"
className="flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
className="flex-1 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium hover:opacity-90"
>
Insert Step
</button>

View File

@@ -133,16 +133,16 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
return (
<div className="flex h-full flex-col">
{/* Header - Filters */}
<div className="space-y-4 border-b border-white/[0.06] p-4">
<div className="space-y-4 border-b border-border p-4">
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/40" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
type="text"
placeholder="Search steps..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full rounded-md border border-white/10 bg-black/50 py-2 pl-10 pr-4 text-sm text-white placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="w-full rounded-md border border-border bg-card py-2 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -153,7 +153,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by category"
value={selectedCategoryId || ''}
onChange={(e) => setSelectedCategoryId(e.target.value || undefined)}
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">All Categories</option>
{categories.map(cat => (
@@ -166,7 +166,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by step type"
value={selectedStepType || ''}
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">All Types</option>
<option value="decision">Decision</option>
@@ -179,7 +179,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by minimum rating"
value={minRating?.toString() || ''}
onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : undefined)}
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">Any Rating</option>
<option value="4">4+ Stars</option>
@@ -192,7 +192,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Sort steps by"
value={sortBy}
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="recent">Most Recent</option>
<option value="popular">Most Popular</option>
@@ -204,7 +204,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
{/* Popular Tags */}
{popularTags.length > 0 && (
<div>
<div className="mb-2 text-xs font-medium text-white/40">Popular Tags:</div>
<div className="mb-2 text-xs font-medium text-muted-foreground">Popular Tags:</div>
<div className="flex flex-wrap gap-1.5">
{popularTags.map(tag => (
<button
@@ -213,8 +213,8 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
className={cn(
'rounded-full px-2.5 py-1 text-xs transition-colors',
selectedTag === tag.tag
? 'bg-white text-black'
: 'bg-white/10 text-white/70 hover:bg-white/20'
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
: 'bg-accent text-muted-foreground hover:bg-accent'
)}
>
{tag.tag} ({tag.count})
@@ -228,7 +228,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
{hasActiveFilters && (
<button
onClick={clearFilters}
className="text-sm text-white/70 hover:text-white hover:underline"
className="text-sm text-muted-foreground hover:text-foreground hover:underline"
>
Clear all filters
</button>
@@ -239,16 +239,16 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
<div className="flex-1 overflow-y-auto p-4">
{isLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-white/40" />
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : error ? (
<div className="rounded-lg border border-red-400/20 bg-red-400/10 p-4 text-center text-sm text-red-400">
{error}
</div>
) : steps.length === 0 ? (
<div className="rounded-lg border border-white/[0.06] bg-white/5 p-12 text-center">
<p className="mb-2 text-lg font-medium text-white">No steps found</p>
<p className="text-sm text-white/40">
<div className="rounded-lg border border-border bg-accent/50 p-12 text-center">
<p className="mb-2 text-lg font-medium text-foreground">No steps found</p>
<p className="text-sm text-muted-foreground">
{hasActiveFilters ? 'Try adjusting your filters' : 'Create your first step to get started!'}
</p>
</div>
@@ -261,7 +261,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
onClick={() => toggleSection('private')}
className="mb-3 flex w-full items-center justify-between"
>
<h3 className="text-sm font-semibold text-white">My Steps ({groupedSteps.private.length})</h3>
<h3 className="text-sm font-semibold text-foreground">My Steps ({groupedSteps.private.length})</h3>
{collapsedSections.private ? (
<ChevronDown className="h-4 w-4" />
) : (
@@ -290,7 +290,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
onClick={() => toggleSection('team')}
className="mb-3 flex w-full items-center justify-between"
>
<h3 className="text-sm font-semibold text-white">Team Steps ({groupedSteps.team.length})</h3>
<h3 className="text-sm font-semibold text-foreground">Team Steps ({groupedSteps.team.length})</h3>
{collapsedSections.team ? (
<ChevronDown className="h-4 w-4" />
) : (
@@ -319,7 +319,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
onClick={() => toggleSection('public')}
className="mb-3 flex w-full items-center justify-between"
>
<h3 className="text-sm font-semibold text-white">Community ({groupedSteps.public.length})</h3>
<h3 className="text-sm font-semibold text-foreground">Community ({groupedSteps.public.length})</h3>
{collapsedSections.public ? (
<ChevronDown className="h-4 w-4" />
) : (
@@ -346,10 +346,10 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
{/* Footer - Optional Create Button */}
{showCreateButton && onCreateNew && (
<div className="border-t border-white/[0.06] p-4">
<div className="border-t border-border p-4">
<button
onClick={onCreateNew}
className="w-full rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
className="w-full rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
>
+ Create New Step
</button>

View File

@@ -51,7 +51,7 @@ export function DynamicArrayField<T>({
type="button"
onClick={() => handleMoveUp(index)}
disabled={index === 0}
className="rounded p-0.5 text-white/50 hover:bg-white/[0.06] hover:text-white disabled:opacity-30"
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-30"
title="Move up"
aria-label="Move up"
>
@@ -61,7 +61,7 @@ export function DynamicArrayField<T>({
type="button"
onClick={() => handleMoveDown(index)}
disabled={index === items.length - 1}
className="rounded p-0.5 text-white/50 hover:bg-white/[0.06] hover:text-white disabled:opacity-30"
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-30"
title="Move down"
aria-label="Move down"
>
@@ -78,7 +78,7 @@ export function DynamicArrayField<T>({
<button
type="button"
onClick={() => onRemove(index)}
className="mt-1 rounded p-1 text-white/50 hover:bg-red-400/20 hover:text-red-400"
className="mt-1 rounded p-1 text-muted-foreground hover:bg-red-400/20 hover:text-red-400"
title="Remove"
aria-label="Remove"
>
@@ -94,9 +94,9 @@ export function DynamicArrayField<T>({
type="button"
onClick={onAdd}
className={cn(
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-white/10',
'px-3 py-2 text-sm text-white/50',
'hover:border-white/30 hover:text-white'
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-border',
'px-3 py-2 text-sm text-muted-foreground',
'hover:border-border hover:text-foreground'
)}
>
<Plus className="h-4 w-4" />
@@ -106,7 +106,7 @@ export function DynamicArrayField<T>({
{/* Empty state */}
{items.length === 0 && !canAdd && (
<p className="text-center text-sm text-white/40">No items</p>
<p className="text-center text-sm text-muted-foreground">No items</p>
)}
</div>
)

View File

@@ -68,14 +68,14 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
<button
type="button"
onClick={handleCancel}
className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
Cancel
</button>
<button
type="button"
onClick={handleSave}
className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
className="rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium hover:opacity-90"
>
Done
</button>
@@ -85,8 +85,8 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
return (
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent}>
{/* Node ID display */}
<div className="mb-4 text-xs text-white/40">
Node ID: <code className="rounded bg-white/10 px-1 py-0.5">{node.id}</code>
<div className="mb-4 text-xs text-muted-foreground">
Node ID: <code className="rounded bg-accent px-1 py-0.5">{node.id}</code>
</div>
{/* Validation errors */}

View File

@@ -52,7 +52,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
<div className="space-y-4">
{/* Title */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Title <span className="text-red-400">*</span>
</label>
<input
@@ -62,9 +62,9 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
placeholder="e.g., Restart the Service"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
titleError ? 'border-red-400' : 'border-white/10'
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
titleError ? 'border-red-400' : 'border-border'
)}
/>
{titleError && (
@@ -75,24 +75,24 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
{/* Description */}
<div>
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Description
</label>
{node.description && (
<button
type="button"
onClick={() => setShowPreview(!showPreview)}
className="text-xs text-white/50 hover:text-white hover:underline"
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
>
{showPreview ? 'Edit' : 'Preview'}
</button>
)}
</div>
<p className="mb-1 text-xs text-white/40">
<p className="mb-1 text-xs text-muted-foreground">
Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>
{showPreview && node.description ? (
<div className="mt-1 rounded-md border border-white/10 bg-white/[0.04] p-3 text-sm">
<div className="mt-1 rounded-md border border-border bg-accent/50 p-3 text-sm">
<MarkdownContent content={node.description} />
</div>
) : (
@@ -108,7 +108,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
**Note:** Important information here"
rows={5}
className={cn(
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -118,10 +118,10 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
{/* Commands */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Commands
</label>
<p className="mb-2 text-xs text-white/40">
<p className="mb-2 text-xs text-muted-foreground">
PowerShell or CLI commands to execute
</p>
<DynamicArrayField
@@ -137,7 +137,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
onChange={(e) => handleUpdateCommand(index, e.target.value)}
placeholder="e.g., Get-Service BrokerAgent"
className={cn(
'block w-full rounded-md border border-white/10 px-3 py-2 font-mono text-sm',
'block w-full rounded-md border border-border px-3 py-2 font-mono text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -148,7 +148,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
{/* Expected Outcome */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Expected Outcome
</label>
<input
@@ -157,7 +157,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
onChange={(e) => onUpdate({ expected_outcome: e.target.value })}
placeholder="e.g., Service should show as Running"
className={cn(
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}

View File

@@ -67,7 +67,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
<h3 className="font-semibold text-blue-400">
Starting Question
</h3>
<p className="mt-1 text-sm text-white/40">
<p className="mt-1 text-sm text-muted-foreground">
This is the first question users will see when they start this troubleshooting tree.
Each option below creates a different troubleshooting path.
</p>
@@ -78,11 +78,11 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Question */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
{isRootNode ? 'Starting Question' : 'Question'} <span className="text-red-400">*</span>
</label>
{isRootNode && (
<p className="mt-0.5 text-xs text-white/40">
<p className="mt-0.5 text-xs text-muted-foreground">
What's the main question to diagnose the issue?
</p>
)}
@@ -95,9 +95,9 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
: "e.g., Can you ping the server?"}
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
questionError ? 'border-red-400' : 'border-white/10'
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
questionError ? 'border-red-400' : 'border-border'
)}
/>
{questionError && (
@@ -107,7 +107,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Help Text */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Help Text
</label>
<textarea
@@ -116,7 +116,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
placeholder="Additional context or instructions for this decision..."
rows={2}
className={cn(
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -125,15 +125,15 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Options */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
{isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
</label>
{isRootNode ? (
<p className="mt-0.5 text-xs text-white/40">
<p className="mt-0.5 text-xs text-muted-foreground">
Add as many options as needed (A, B, C, D...). Each option leads to a completely different troubleshooting path.
</p>
) : (
<p className="mt-0.5 text-xs text-white/40">
<p className="mt-0.5 text-xs text-muted-foreground">
Each option can branch to a different next step.
</p>
)}
@@ -158,14 +158,14 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
const letter = indexToLetter(index)
return (
<div className="rounded-md border border-white/10 bg-white/[0.04] p-3">
<div className="rounded-md border border-border bg-accent/50 p-3">
<div className="mb-2 flex items-center gap-2">
{/* Letter badge */}
<span className={cn(
'flex h-6 w-6 items-center justify-center rounded-full text-xs font-bold',
isRootNode
? 'bg-blue-500/20 text-blue-400'
: 'bg-white/10 text-white/50'
: 'bg-accent text-muted-foreground'
)}>
{letter}
</span>
@@ -180,7 +180,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
'block flex-1 rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
optionLabelError ? 'border-red-400' : 'border-white/10'
optionLabelError ? 'border-red-400' : 'border-border'
)}
/>
</div>
@@ -207,7 +207,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Example hint for root node */}
{isRootNode && (node.options?.length || 0) < 2 && (
<div className="mt-3 rounded-md border border-dashed border-white/10 bg-white/[0.02] p-3 text-xs text-white/40">
<div className="mt-3 rounded-md border border-dashed border-border bg-accent/50 p-3 text-xs text-muted-foreground">
<strong>Tip:</strong> Most troubleshooting trees start with 2-5 main branches.
For example: "Connection Issues", "Performance Problems", "Error Messages", "Other".
</div>

View File

@@ -47,7 +47,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
<div className="space-y-4">
{/* Title */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Title <span className="text-red-400">*</span>
</label>
<input
@@ -57,9 +57,9 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
placeholder="e.g., VDA Successfully Registered"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
titleError ? 'border-red-400' : 'border-white/10'
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
titleError ? 'border-red-400' : 'border-border'
)}
/>
{titleError && (
@@ -70,24 +70,24 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
{/* Description */}
<div>
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Description
</label>
{node.description && (
<button
type="button"
onClick={() => setShowPreview(!showPreview)}
className="text-xs text-white/50 hover:text-white hover:underline"
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
>
{showPreview ? 'Edit' : 'Preview'}
</button>
)}
</div>
<p className="mb-1 text-xs text-white/40">
<p className="mb-1 text-xs text-muted-foreground">
Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>
{showPreview && node.description ? (
<div className="mt-1 rounded-md border border-white/10 bg-white/[0.04] p-3 text-sm">
<div className="mt-1 rounded-md border border-border bg-accent/50 p-3 text-sm">
<MarkdownContent content={node.description} />
</div>
) : (
@@ -102,7 +102,7 @@ Document what was done and the outcome.
**Close ticket as:** Resolved"
rows={5}
className={cn(
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -112,10 +112,10 @@ Document what was done and the outcome.
{/* Resolution Steps */}
<div>
<label className="block text-sm font-medium text-white">
<label className="block text-sm font-medium text-foreground">
Resolution Steps
</label>
<p className="mb-2 text-xs text-white/40">
<p className="mb-2 text-xs text-muted-foreground">
Step-by-step instructions for resolving the issue
</p>
<DynamicArrayField
@@ -135,7 +135,7 @@ Document what was done and the outcome.
onChange={(e) => handleUpdateStep(index, e.target.value)}
placeholder={`Step ${index + 1}`}
className={cn(
'block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}

View File

@@ -95,9 +95,9 @@ function NodeListItem({
}
const nodeTypeColors: Record<NodeType, string> = {
decision: 'bg-blue-500/20 text-blue-600 dark:text-blue-400',
action: 'bg-yellow-500/20 text-yellow-600 dark:text-yellow-400',
solution: 'bg-green-500/20 text-green-600 dark:text-green-400'
decision: 'bg-blue-500/20 text-blue-400',
action: 'bg-yellow-500/20 text-yellow-400',
solution: 'bg-green-500/20 text-green-400'
}
const getNodeLabel = () => {
@@ -223,7 +223,7 @@ function NodeListItem({
{/* Node type icon - special treatment for root */}
{isRootNode ? (
<span className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs bg-blue-500/30 text-blue-600 dark:text-blue-400 font-semibold">
<span className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs bg-blue-500/30 text-blue-400 font-semibold">
<Play className="h-4 w-4" />
<span className="hidden sm:inline">START</span>
</span>
@@ -254,7 +254,7 @@ function NodeListItem({
'flex items-center gap-1 rounded px-1.5 py-0.5 text-xs',
hasError
? 'bg-destructive/20 text-destructive'
: 'bg-yellow-500/20 text-yellow-600 dark:text-yellow-500'
: 'bg-yellow-500/20 text-yellow-500'
)}
>
{hasError ? (

View File

@@ -11,9 +11,9 @@ const CREATE_SOLUTION = `${CREATE_PREFIX}solution__`
// Unicode symbols for node types (works in select options)
const NODE_TYPE_SYMBOLS: Record<NodeType, string> = {
decision: '', // Information/question symbol
action: '', // Lightning bolt for action
solution: '' // Checkmark for solution
decision: '\u24D8', // Information/question symbol
action: '\u26A1', // Lightning bolt for action
solution: '\u2713' // Checkmark for solution
}
// Node type labels for UI
@@ -139,7 +139,7 @@ export function NodePicker({
return (
<div className={className}>
{label && (
<label className="mb-1 block text-sm font-medium text-white">
<label className="mb-1 block text-sm font-medium text-foreground">
{label}
</label>
)}
@@ -147,8 +147,8 @@ export function NodePicker({
{/* Inline node creation UI */}
{creatingNodeType ? (
<div className="space-y-2">
<div className="flex items-center gap-2 rounded-md border border-white/20 bg-white/[0.04] p-2">
<span className="text-xs font-medium text-white">
<div className="flex items-center gap-2 rounded-md border border-border bg-accent/50 p-2">
<span className="text-xs font-medium text-foreground">
New {NODE_TYPE_LABELS[creatingNodeType]}:
</span>
<input
@@ -159,9 +159,9 @@ export function NodePicker({
onKeyDown={handleKeyDown}
placeholder={creatingNodeType === 'decision' ? 'Enter question...' : 'Enter title...'}
className={cn(
'flex-1 rounded-md border border-white/10 px-2 py-1 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'flex-1 rounded-md border border-border px-2 py-1 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -169,7 +169,7 @@ export function NodePicker({
<button
type="button"
onClick={handleCancelCreate}
className="flex-1 rounded-md border border-white/10 px-3 py-1.5 text-xs font-medium text-white/60 hover:bg-white/10 hover:text-white"
className="flex-1 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
Cancel
</button>
@@ -179,7 +179,7 @@ export function NodePicker({
disabled={!newNodeTitle.trim()}
className={cn(
'flex-1 rounded-md px-3 py-1.5 text-xs font-medium',
'bg-white text-black hover:bg-white/90',
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
@@ -194,9 +194,9 @@ export function NodePicker({
onChange={(e) => handleChange(e.target.value)}
className={cn(
'block w-full rounded-md border px-3 py-2 text-sm',
'bg-black/50 text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
error ? 'border-red-400' : 'border-white/10'
'bg-card text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
error ? 'border-red-400' : 'border-border'
)}
>
<option value="">{placeholder}</option>
@@ -242,7 +242,7 @@ export function NodePicker({
{/* Show what's selected */}
{value && selectedNode && (
<p className="mt-1 text-xs text-white/40">
<p className="mt-1 text-xs text-muted-foreground">
{selectedNode.label}
</p>
)}

View File

@@ -28,12 +28,12 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
<>
{/* Code Mode: Monaco editor (60%) + Preview (40%) */}
<div className={cn(
'flex flex-col overflow-hidden border-white/[0.06]',
'flex flex-col overflow-hidden border-border',
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
)}>
<Suspense fallback={
<div className="flex h-full items-center justify-center bg-[#0a0a0a]">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-white/20 border-t-white" />
<div className="flex h-full items-center justify-center bg-card">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-border border-t-foreground" />
</div>
}>
<CodeModeEditor />
@@ -42,7 +42,7 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
{/* Right Panel - Preview */}
<div className={cn(
'flex-1 overflow-hidden bg-white/[0.02]',
'flex-1 overflow-hidden bg-accent/50',
isMobile ? 'hidden' : 'block'
)}>
<TreePreviewPanel />
@@ -52,7 +52,7 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
<>
{/* Flow Mode: Form editor (60%) + Preview (40%) */}
<div className={cn(
'flex flex-col overflow-y-auto border-white/[0.06]',
'flex flex-col overflow-y-auto border-border',
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
)}>
<div className="space-y-4 p-4">
@@ -63,7 +63,7 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
{/* Right Panel - Preview */}
<div className={cn(
'flex-1 overflow-hidden bg-white/[0.02]',
'flex-1 overflow-hidden bg-accent/50',
isMobile ? 'hidden' : 'block'
)}>
<TreePreviewPanel />

View File

@@ -56,12 +56,12 @@ export function TreeMetadataForm() {
)
return (
<div className="space-y-4 glass-card rounded-2xl p-4">
<h2 className="text-sm font-semibold text-white">Tree Details</h2>
<div className="space-y-4 bg-card border border-border rounded-2xl p-4">
<h2 className="text-sm font-semibold text-foreground">Tree Details</h2>
{/* Name */}
<div>
<label htmlFor="tree-name" className="block text-sm font-medium text-white">
<label htmlFor="tree-name" className="block text-sm font-medium text-foreground">
Name <span className="text-red-400">*</span>
</label>
<input
@@ -72,9 +72,9 @@ export function TreeMetadataForm() {
placeholder="e.g., VDA Registration Troubleshooting"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
nameError ? 'border-red-400' : 'border-white/10'
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
nameError ? 'border-red-400' : 'border-border'
)}
/>
{nameError && <p className="mt-1 text-xs text-red-400">{nameError.message}</p>}
@@ -82,7 +82,7 @@ export function TreeMetadataForm() {
{/* Description */}
<div>
<label htmlFor="tree-description" className="block text-sm font-medium text-white">
<label htmlFor="tree-description" className="block text-sm font-medium text-foreground">
Description
</label>
<textarea
@@ -92,16 +92,16 @@ export function TreeMetadataForm() {
placeholder="Brief description of what this tree troubleshoots..."
rows={2}
className={cn(
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
{/* Category */}
<div>
<label htmlFor="tree-category" className="block text-sm font-medium text-white">
<label htmlFor="tree-category" className="block text-sm font-medium text-foreground">
Category
</label>
{!customCategory ? (
@@ -110,9 +110,9 @@ export function TreeMetadataForm() {
value={categoryId || ''}
onChange={(e) => handleCategoryChange(e.target.value)}
className={cn(
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-black/50 text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-card text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
>
<option value="">No category</option>
@@ -132,9 +132,9 @@ export function TreeMetadataForm() {
onChange={(e) => setCategory(e.target.value)}
placeholder="Enter new category"
className={cn(
'block flex-1 rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
'block flex-1 rounded-md border border-border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
)}
autoFocus
/>
@@ -144,7 +144,7 @@ export function TreeMetadataForm() {
setCategory('')
setCategoryId(null)
}}
className="rounded-md border border-white/10 px-3 py-2 text-sm text-white/60 hover:bg-white/10 hover:text-white"
className="rounded-md border border-border px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
>
Cancel
</button>
@@ -154,7 +154,7 @@ export function TreeMetadataForm() {
{/* Tags */}
<div>
<label className="block text-sm font-medium text-white">Tags</label>
<label className="block text-sm font-medium text-foreground">Tags</label>
<div className="mt-1">
<TagInput tags={tags} onChange={setTags} maxTags={10} placeholder="Add tags..." />
</div>
@@ -162,13 +162,13 @@ export function TreeMetadataForm() {
{/* Visibility */}
<div>
<label className="block text-sm font-medium text-white">Visibility</label>
<label className="block text-sm font-medium text-foreground">Visibility</label>
<div className="mt-2 flex gap-4">
<label
className={cn(
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
'transition-colors',
!isPublic ? 'border-white/30 bg-white/10 text-white' : 'border-white/10 text-white/60 hover:bg-white/[0.06]'
!isPublic ? 'border-primary/30 bg-accent text-foreground' : 'border-border text-muted-foreground hover:bg-accent/50'
)}
>
<input
@@ -185,7 +185,7 @@ export function TreeMetadataForm() {
className={cn(
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
'transition-colors',
isPublic ? 'border-white/30 bg-white/10 text-white' : 'border-white/10 text-white/60 hover:bg-white/[0.06]'
isPublic ? 'border-primary/30 bg-accent text-foreground' : 'border-border text-muted-foreground hover:bg-accent/50'
)}
>
<input
@@ -199,7 +199,7 @@ export function TreeMetadataForm() {
<span className="text-sm">Public</span>
</label>
</div>
<p className="mt-1 text-xs text-white/40">
<p className="mt-1 text-xs text-muted-foreground">
{isPublic
? 'Anyone can view this tree'
: 'Only you and your team can view this tree'}

View File

@@ -35,7 +35,7 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
<button
onClick={() => setIsExpanded(!isExpanded)}
className={cn(
'flex w-full items-center justify-between p-3 text-left transition-colors hover:bg-white/5',
'flex w-full items-center justify-between p-3 text-left transition-colors hover:bg-accent',
errorItems.length > 0 ? 'text-red-400' : 'text-yellow-400'
)}
>
@@ -81,7 +81,7 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
<div className="flex-1">
<p className="text-red-400">{error.message}</p>
{error.nodeId && (
<p className="mt-0.5 text-xs text-white/40">
<p className="mt-0.5 text-xs text-muted-foreground">
Click to select node: {error.nodeId}
</p>
)}
@@ -105,7 +105,7 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
<div className="flex-1">
<p className="text-yellow-400">{warning.message}</p>
{warning.nodeId && (
<p className="mt-0.5 text-xs text-white/40">
<p className="mt-0.5 text-xs text-muted-foreground">
Click to select node: {warning.nodeId}
</p>
)}

View File

@@ -166,8 +166,8 @@ export function CodeModeEditor() {
beforeMount={handleEditorWillMount}
onMount={handleEditorDidMount}
loading={
<div className="flex h-full items-center justify-center bg-[#0a0a0a]">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-white/20 border-t-white" />
<div className="flex h-full items-center justify-center bg-card">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-border border-t-foreground" />
</div>
}
options={{

View File

@@ -91,15 +91,15 @@ export function CodeModeToolbar({
}, [])
return (
<div className="flex items-center justify-between border-b border-white/[0.06] bg-black/50 px-3 py-1.5">
<div className="flex items-center justify-between border-b border-border bg-card px-3 py-1.5">
<div className="flex items-center gap-2">
{/* Insert Node dropdown */}
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setInsertOpen(!insertOpen)}
className={cn(
'flex items-center gap-1 rounded-md border border-white/10 px-2.5 py-1 text-xs font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
'flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
>
<Plus className="h-3 w-3" />
@@ -107,24 +107,24 @@ export function CodeModeToolbar({
<ChevronDown className="h-3 w-3" />
</button>
{insertOpen && (
<div className="absolute left-0 top-full z-50 mt-1 w-44 rounded-lg border border-white/10 bg-[#111] py-1 shadow-xl">
<div className="absolute left-0 top-full z-50 mt-1 w-44 rounded-lg border border-border bg-card py-1 shadow-xl">
<button
onClick={() => { onInsertTemplate(NODE_TEMPLATES.decision); setInsertOpen(false) }}
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-white/70 hover:bg-white/10"
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent"
>
<span className="h-2 w-2 rounded-full bg-blue-400" />
Decision
</button>
<button
onClick={() => { onInsertTemplate(NODE_TEMPLATES.action); setInsertOpen(false) }}
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-white/70 hover:bg-white/10"
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent"
>
<span className="h-2 w-2 rounded-full bg-amber-400" />
Action
</button>
<button
onClick={() => { onInsertTemplate(NODE_TEMPLATES.solution); setInsertOpen(false) }}
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-white/70 hover:bg-white/10"
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent"
>
<span className="h-2 w-2 rounded-full bg-emerald-400" />
Solution
@@ -139,8 +139,8 @@ export function CodeModeToolbar({
<div className="flex items-center gap-1.5 text-xs">
{isValidating ? (
<>
<Loader2 className="h-3 w-3 animate-spin text-white/40" />
<span className="text-white/40">Validating...</span>
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
<span className="text-muted-foreground">Validating...</span>
</>
) : isValid ? (
<>
@@ -173,8 +173,8 @@ export function CodeModeToolbar({
className={cn(
'flex items-center gap-1 rounded-md px-2 py-1 text-xs',
syntaxHelpOpen
? 'bg-white/10 text-white'
: 'text-white/40 hover:bg-white/[0.06] hover:text-white/60'
? 'bg-accent text-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-muted-foreground'
)}
>
<HelpCircle className="h-3 w-3" />

View File

@@ -10,17 +10,17 @@ export function SyntaxHelpPanel({ open, onClose }: SyntaxHelpPanelProps) {
if (!open) return null
return (
<div className="flex h-full flex-col border-l border-white/[0.06] bg-[#0a0a0a]">
<div className="flex items-center justify-between border-b border-white/[0.06] px-3 py-2">
<span className="text-xs font-medium text-white/60">Syntax Reference</span>
<div className="flex h-full flex-col border-l border-border bg-card">
<div className="flex items-center justify-between border-b border-border px-3 py-2">
<span className="text-xs font-medium text-muted-foreground">Syntax Reference</span>
<button
onClick={onClose}
className="rounded p-0.5 text-white/30 hover:bg-white/10 hover:text-white/60"
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-muted-foreground"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
<div className="flex-1 overflow-y-auto px-3 py-3 text-[11px] leading-relaxed text-white/50">
<div className="flex-1 overflow-y-auto px-3 py-3 text-[11px] leading-relaxed text-muted-foreground">
<Section title="Node Structure">
<Code>{`---
id: node_id
@@ -82,7 +82,7 @@ Description paragraph.
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="mb-4">
<h4 className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-white/30">{title}</h4>
<h4 className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">{title}</h4>
{children}
</div>
)
@@ -91,8 +91,8 @@ function Section({ title, children }: { title: string; children: React.ReactNode
function Code({ children }: { children: string }) {
return (
<pre className={cn(
'mb-2 overflow-x-auto rounded-md border border-white/[0.06] bg-black/50 px-2 py-1.5',
'text-[10px] leading-relaxed text-white/50 whitespace-pre'
'mb-2 overflow-x-auto rounded-md border border-border bg-card px-2 py-1.5',
'text-[10px] leading-relaxed text-muted-foreground whitespace-pre'
)}>
{children}
</pre>
@@ -102,8 +102,8 @@ function Code({ children }: { children: string }) {
function Row({ label, code }: { label: string; code: string }) {
return (
<div className="flex items-center justify-between py-0.5">
<span className="text-white/40">{label}</span>
<code className="rounded bg-white/[0.06] px-1 py-0.5 text-[10px] text-white/50">{code}</code>
<span className="text-muted-foreground">{label}</span>
<code className="rounded bg-accent px-1 py-0.5 text-[10px] text-muted-foreground">{code}</code>
</div>
)
}

View File

@@ -154,8 +154,8 @@ export function TreePreviewNode({
<div className="relative">
{/* From option label */}
{fromOption && (
<div className="mb-1 text-xs font-medium text-white/40">
<span className="rounded bg-white/10 px-1.5 py-0.5">{fromOption}</span>
<div className="mb-1 text-xs font-medium text-muted-foreground">
<span className="rounded bg-accent px-1.5 py-0.5">{fromOption}</span>
</div>
)}
@@ -185,7 +185,7 @@ export function TreePreviewNode({
className="absolute -top-1.5 -right-1.5 flex items-center justify-center rounded-full bg-green-500/90 p-0.5 shadow-sm"
title="This branch leads to a solution"
>
<CheckCircle className="h-3 w-3 text-white" />
<CheckCircle className="h-3 w-3 text-foreground" />
</div>
)}
{/* Root node START header */}
@@ -206,12 +206,12 @@ export function TreePreviewNode({
<button
type="button"
onClick={toggleCollapse}
className="mt-0.5 rounded p-0.5 hover:bg-white/10"
className="mt-0.5 rounded p-0.5 hover:bg-accent"
>
{isCollapsed ? (
<ChevronRight className="h-4 w-4 text-white/50" />
<ChevronRight className="h-4 w-4 text-muted-foreground" />
) : (
<ChevronDown className="h-4 w-4 text-white/50" />
<ChevronDown className="h-4 w-4 text-muted-foreground" />
)}
</button>
) : (
@@ -222,14 +222,14 @@ export function TreePreviewNode({
{isRootNode && <HelpCircle className="h-4 w-4 text-blue-500" />}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white leading-tight">
<p className="text-sm font-medium text-foreground leading-tight">
{getNodeLabel()}
</p>
{/* Node ID with copy button */}
<div className="flex items-center gap-1 mt-1">
<span
className="text-xs text-white/40 cursor-help"
className="text-xs text-muted-foreground cursor-help"
title={`Full ID: ${node.id}`}
>
{node.id === 'root' ? 'root' : node.id.slice(0, 8) + '...'}
@@ -237,7 +237,7 @@ export function TreePreviewNode({
<button
type="button"
onClick={handleCopyId}
className="rounded p-0.5 text-white/40 hover:bg-white/10 hover:text-white"
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground"
title="Copy full ID"
>
{copiedId ? (
@@ -252,8 +252,8 @@ export function TreePreviewNode({
{/* Show options for decision nodes */}
{node.type === 'decision' && node.options && node.options.length > 0 && (
<div className="mt-2 space-y-1 border-t border-white/[0.06] pt-2">
<p className="text-[10px] uppercase tracking-wide text-white/40">Options:</p>
<div className="mt-2 space-y-1 border-t border-border pt-2">
<p className="text-[10px] uppercase tracking-wide text-muted-foreground">Options:</p>
{node.options.map((opt, i) => {
const leadsToSolution = nodeLeadsToSolution(opt.next_node_id)
return (
@@ -261,15 +261,15 @@ export function TreePreviewNode({
key={opt.id}
className={cn(
'flex items-center gap-1 text-xs rounded px-1 py-0.5 -mx-1',
opt.next_node_id && 'hover:bg-white/[0.06] cursor-pointer'
opt.next_node_id && 'hover:bg-accent cursor-pointer'
)}
onMouseEnter={() => opt.next_node_id && onHoverNodeId?.(opt.next_node_id)}
onMouseLeave={() => onHoverNodeId?.(null)}
>
<span className="inline-flex h-4 w-4 items-center justify-center rounded bg-white/10 text-[10px] font-medium text-white/50">
<span className="inline-flex h-4 w-4 items-center justify-center rounded bg-accent text-[10px] font-medium text-muted-foreground">
{i + 1}
</span>
<span className="truncate text-white/70">{opt.label || 'Untitled'}</span>
<span className="truncate text-muted-foreground">{opt.label || 'Untitled'}</span>
<span className="ml-auto flex items-center gap-1">
{leadsToSolution && (
<span title="Leads to solution">
@@ -279,7 +279,7 @@ export function TreePreviewNode({
{opt.next_node_id ? (
<span className="text-blue-500"></span>
) : (
<span className="text-white/30 text-[10px]">(no link)</span>
<span className="text-muted-foreground text-[10px]">(no link)</span>
)}
</span>
</div>
@@ -308,12 +308,12 @@ export function TreePreviewNode({
return (
<div
className="mt-2 text-xs border-t border-white/[0.06] pt-2 hover:bg-white/[0.04] cursor-pointer rounded px-1 -mx-1"
className="mt-2 text-xs border-t border-border pt-2 hover:bg-accent/50 cursor-pointer rounded px-1 -mx-1"
onMouseEnter={() => onHoverNodeId?.(node.next_node_id!)}
onMouseLeave={() => onHoverNodeId?.(null)}
>
<div className="flex items-center gap-1.5">
<span className="text-white/40">Next:</span>
<span className="text-muted-foreground">Next:</span>
{isSharedTarget && (
<span title={sharedTooltip} className="flex items-center">
<Users className="h-3 w-3 text-purple-500" />
@@ -321,7 +321,7 @@ export function TreePreviewNode({
)}
<span className={cn(
'truncate',
nextNode?.type === 'solution' ? 'text-green-500 font-medium' : 'text-white/70'
nextNode?.type === 'solution' ? 'text-green-500 font-medium' : 'text-muted-foreground'
)}>
{nextNodeLabel.slice(0, 30)}{nextNodeLabel.length > 30 ? '...' : ''}
</span>
@@ -347,7 +347,7 @@ export function TreePreviewNode({
{/* Children - show as branches */}
{hasChildren && !isCollapsed && (
<div className="relative mt-3 ml-6 pl-6 border-l-2 border-white/[0.06]">
<div className="relative mt-3 ml-6 pl-6 border-l-2 border-border">
<div className="space-y-4">
{node.children!.map((child) => {
const optionLabel = getOptionLabelForChild(child.id)
@@ -355,7 +355,7 @@ export function TreePreviewNode({
return (
<div key={child.id} className="relative">
{/* Horizontal connector line */}
<div className="absolute -left-6 top-6 h-0.5 w-6 bg-white/[0.06]" />
<div className="absolute -left-6 top-6 h-0.5 w-6 bg-border" />
<TreePreviewNode
node={child}
@@ -377,8 +377,8 @@ export function TreePreviewNode({
{/* Show collapsed indicator */}
{hasChildren && isCollapsed && (
<div className="mt-2 ml-6 text-xs text-white/40">
<span className="rounded bg-white/10 px-2 py-1">
<div className="mt-2 ml-6 text-xs text-muted-foreground">
<span className="rounded bg-accent px-2 py-1">
{node.children!.length} child node{node.children!.length !== 1 ? 's' : ''} hidden
</span>
</div>

View File

@@ -56,7 +56,7 @@ export function TreePreviewPanel() {
if (!treeStructure) {
return (
<div className="flex h-full items-center justify-center p-4 text-sm text-white/40">
<div className="flex h-full items-center justify-center p-4 text-sm text-muted-foreground">
No tree structure to preview
</div>
)
@@ -65,11 +65,11 @@ export function TreePreviewPanel() {
return (
<div className="flex h-full flex-col">
{/* Header */}
<div className="border-b border-white/[0.06] px-4 py-2">
<h3 className="text-sm font-semibold text-white">
<div className="border-b border-border px-4 py-2">
<h3 className="text-sm font-semibold text-foreground">
Preview: {name || 'Untitled Tree'}
</h3>
<p className="text-xs text-white/40">
<p className="text-xs text-muted-foreground">
Click a node to select Hover options to highlight targets
</p>
</div>
@@ -91,8 +91,8 @@ export function TreePreviewPanel() {
</div>
{/* Legend */}
<div className="border-t border-white/[0.06] px-4 py-2">
<div className="flex flex-wrap gap-4 text-xs text-white/40">
<div className="border-t border-border px-4 py-2">
<div className="flex flex-wrap gap-4 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<div className="h-3 w-3 rounded bg-blue-500/50" />
<span>Decision</span>

View File

@@ -12,7 +12,7 @@ interface MarkdownContentProps {
*/
export function MarkdownContent({ content, className }: MarkdownContentProps) {
return (
<div className={cn('prose prose-sm dark:prose-invert max-w-none', className)}>
<div className={cn('prose prose-sm prose-invert max-w-none', className)}>
<ReactMarkdown
components={{
// Style paragraphs
@@ -21,7 +21,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
// Style bold text
strong: ({ children }) => (
<strong className="font-semibold text-white">{children}</strong>
<strong className="font-semibold text-foreground">{children}</strong>
),
// Style ordered lists
ol: ({ children }) => (
@@ -33,7 +33,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
// Style list items
li: ({ children }) => (
<li className="text-white/60">{children}</li>
<li className="text-muted-foreground">{children}</li>
),
// Style inline code
code: ({ className, children, ...props }) => {
@@ -43,7 +43,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
return (
<code
className={cn(
'block rounded bg-white/10 p-3 font-mono text-sm overflow-x-auto',
'block rounded bg-accent p-3 font-mono text-sm overflow-x-auto',
className
)}
{...props}
@@ -54,7 +54,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
}
return (
<code
className="rounded bg-white/10 px-1.5 py-0.5 font-mono text-sm"
className="rounded bg-accent px-1.5 py-0.5 font-mono text-sm"
{...props}
>
{children}
@@ -63,25 +63,25 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
},
// Style code blocks (pre)
pre: ({ children }) => (
<pre className="mb-3 overflow-x-auto rounded bg-white/10 p-0 last:mb-0">
<pre className="mb-3 overflow-x-auto rounded bg-accent p-0 last:mb-0">
{children}
</pre>
),
// Style headers
h1: ({ children }) => (
<h1 className="mb-3 text-lg font-bold text-white">{children}</h1>
<h1 className="mb-3 text-lg font-bold text-foreground">{children}</h1>
),
h2: ({ children }) => (
<h2 className="mb-2 text-base font-bold text-white">{children}</h2>
<h2 className="mb-2 text-base font-bold text-foreground">{children}</h2>
),
h3: ({ children }) => (
<h3 className="mb-2 text-sm font-bold text-white">{children}</h3>
<h3 className="mb-2 text-sm font-bold text-foreground">{children}</h3>
),
// Style horizontal rules
hr: () => <hr className="my-4 border-white/[0.06]" />,
hr: () => <hr className="my-4 border-border" />,
// Style blockquotes
blockquote: ({ children }) => (
<blockquote className="mb-3 border-l-4 border-white/20 pl-4 italic text-white/50 last:mb-0">
<blockquote className="mb-3 border-l-4 border-border pl-4 italic text-muted-foreground last:mb-0">
{children}
</blockquote>
),