Ref app page phase 1#202
Merged
Merged
Conversation
Bring the editorial design language from the landing page into the main SPA, starting with shared tokens and the Login screen. Foundation (src/index.css) - Import Geist & Geist Mono via Google Fonts - Remove the dead duplicate HSL :root/.dark block left from an earlier shadcn migration; only the oklch block was actually in effect - @theme additions expose editorial tokens as v4 utilities: ink-0..800, paper / paper-muted / dim / faint, brand / brand-soft / brand-dim, font-sans (Geist), font-mono (Geist Mono) - Dark-mode shadcn semantic vars remapped: - --primary now ClickHouse yellow -> all <Button> default = brand CTA - --background/card/popover = ink-50/100, --border = ink-500 hairline - --ring = brand yellow, --sidebar flat + sidebar-primary yellow - Scrollbar: hairline ink with content-box trick - ::selection = brand yellow - .ai-chat-fab rewritten: solid ink-100 with accent border on hover, replacing the purple gradient + glow Login (src/pages/Login.tsx) - Drop radial purple gradient, two blur orbs, glassmorphism card, spring-rotate logo, and white->dim gradient wordmark - Editorial layout: static dot-grid bg, mono CHouse+UI(dim) wordmark, hairline 3-section card (eyebrow "01 - Sign in" + heading + sub, form, footer with "RBAC enforced" + version pill) - Inputs: mono uppercase labels, mono-font fields, focus border brand - Primary button bg-brand text-ink-50 with subtle hover lift - Mono demo-credentials hint below card Global effect Because shadcn semantic tokens shifted, all authenticated pages pick up the new palette automatically (cards/popovers ink-100, default buttons brand yellow, focus ring brand, sidebar flat). Page-level purple gradients on stat/action cards in /overview will be addressed in the next batch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rewrite the authenticated landing page (/overview) to match the editorial language introduced in the Foundation + Login commit. What changed - BentoCard (gradient + blur orb) -> EditorialCard (hairline border, flat ink-100 surface) - StatCard (colored icon bg per metric) -> MetricCell (hairline grid cells, mono uppercase label, mono numeric value) - QuickAction (gradient icon bg) -> ActionCell (hairline cells, mono icon, accent border on hover) - ListItem (colored icon per type, gradient mask) -> flat hairline row with paper-dim icon, semantic status color only for query result (success/error) - SectionHeader (colored icon chip) -> editorial: mono index + dash + uppercase eyebrow, optional sentence-case title - EmptyState (circle icon) -> centered text + mono inline link - TabToggle (pill on white/5) -> editorial: hairline-bordered segmented control with ink-300 active bg - New inline primitives: EditorialCard, MetricCell, ActionCell, ListItem, SectionHeader, EmptyState, TabToggle, InlineLink Sections - Header: avatar chip (user initials, hairline border) + Workspace eyebrow + greeting; connection meta in hairline pill with mono version/uptime separators - Cluster: 6 metric cells in hairline 6-col grid - Start: 4 action cells in hairline 4-col grid - Continue working (conditional): brand-tinted card (border-brand/30 bg-brand/4) with Unsaved tag and unsaved-tab list - Quick access + Saved queries: two-card grid, fixed height, editorial toggle for Favorites/Recent - Recent activity: 3-col hairline grid of query rows with mono SQL preview, status pill, ms metric - ClickHouse resources: 4-col hairline grid linking docs/SQL/best practices/GitHub Functional logic unchanged - All data hooks (useSystemStats, useRecentQueries, useSavedQueries, useDatabases) untouched - All handlers (handleNewQuery, handleImport, handleTableClick, handleSavedQueryClick, handleTabClick, handleRefresh) preserved - formatRelativeTime / formatUptime / getGreeting helpers kept Verified - Geist Sans loaded on h1 (24px / 600 / paper) - Refresh button: hairline ink-500 border, paper-muted text - No console errors - 5 sections rendered in 1546px scrollable container Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The dock is visible on every authenticated page, so this commit is where the editorial language meets persistent navigation. Visual changes - Surfaces: bg-black/40 + backdrop-blur-xl/2xl glassmorphism replaced with flat bg-ink-100 surfaces; sidebar uses bg-ink-50 (page color) for true flat anchoring - Borders: border-white/5..20 transparent borders -> border-ink-500 hairlines; hover state moves to border-ink-700 - Active nav indicator: spring-positioned purple-400 dot replaced with a 2x20px brand-yellow bar (left edge in sidebar, bottom edge in horizontal floating). Same Framer Motion layoutId so the animation still slides between items. - Logo: removed drop-shadow yellow glow; wordmark now reads "CH/UI" with the second half in paper-dim - Version pill: text-purple-400/80 -> font-mono text-paper-faint - Drag state border: purple -> brand yellow - Tooltips: rebuilt as mono uppercase pills with hairline ink-500 border and bg-ink-200 (TOOLTIP_CLASS constant, reused everywhere) - Hover-to-show trigger: lost the rounded-2xl glassmorphism pill and the rotating pulse; now a hairline mono chip with "SHOW DOCK" uppercase label and a small chevron - Settings popover: rebuilt as editorial menu — hairline border, mono eyebrow header, SettingRow primitive with hairline-bordered icon square, label + mono sub-text, brand dot for on-state Functional preservation - Two modes (sidebar / floating) with localStorage + DB sync per device type - All four placements (top/bottom/left/right) with drag-end detection - Auto-hide timing (3.5s) + hover trigger zone untouched - Orientation toggle, fullscreen toggle, reset position untouched - RBAC permission filtering of nav items untouched - Drag handle keeps 44x44px touch targets for mobile/tablet - AnimatePresence enter/exit timings preserved New primitives - TOOLTIP_CLASS constant (shared mono tooltip style) - SettingRow component for popover rows Verified - Sidebar: bg #0a0a0a, border-ink-500, backdrop-filter:none, 56px wide - Active marker: 2x20px bar in rgb(255, 204, 1) (brand yellow) - Nav links: paper-dim text on transparent bg - No console errors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Editorial pass over the Explorer route shell and the workspace shell
on the right side. Sidebar tree (DataExplorer) and SqlTab still pending.
Explorer.tsx (page shell)
- Header bar: bg-ink-50, border-ink-500 hairline; sidebar toggle as
hairline button; mono uppercase "Explorer" label
- Breadcrumbs: mono font, hairline active state (bg-ink-200), icons
paper-dim instead of blue/emerald
- Stats: emerald/blue colored pills with backgrounds -> mono uppercase
inline counts ("4 DB / 164 TBL") separated by hairline divider
- Keyboard hint: rounded white/5 kbds -> ink-200 hairline kbds with
mono uppercase "SEARCH" label
- ResizableHandle: white/10..30 -> ink-500 / ink-700 / brand for active
- Removed framer-motion intro wrapper (not needed for nav route)
WorkspaceTabs.tsx
- Tab bar: bg-black/20 + border-white/10 -> bg-ink-100 + border-ink-500
- Tab triggers: rounded-t-lg + state shadow + per-type colored icons
(purple/amber/emerald/blue) -> rounded-none with hairline border on
active, icons paper-muted (active) / paper-faint (inactive)
- Active indicator: gradient purple->blue line -> solid 1px brand bar
(motion layoutId preserved)
- Tab title font: text-xs -> mono text-[11.5px]
- New-tab button + close-tab "x" use hairline ink-200 hover surfaces
HomeTab.tsx (workspace empty)
- Centered "Query Workspace" title + gradient purple->blue button ->
editorial header (eyebrow 01 - Query workspace + 2-line heading
with dim tail + brand yellow "New query" button)
- Recent / Saved cards: bg-black/40 rounded-xl -> bg-ink-100 + border-
ink-500 hairline, fixed 280px height
- Section header: colored icon (purple/blue) + bold text -> mono icon
paper-muted + sentence-case label + mono count
- List items: rounded white/5 hover white/10 -> flat hairline rows
with bg-ink-200 hover, mono text-[13px], arrow appears on hover
Functional preservation
- All resizable panel logic + DB sync untouched
- Tab DnD, context menus, middle-click close untouched
- All RBAC permission gates (canViewSavedQueries) preserved
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sidebar left panel of /explorer — 900-line DataExplorer plus the TreeNode renderer. Surgical class swaps to preserve all logic (search, filters, RBAC gates, dropdowns, favorites mutation, saved query delete dialog). DataExplorer.tsx (helpers) - QuickAccessItem: rounded mx-1 hover white/5 chip -> flat full-width hairline row with bg-ink-200 hover; opacity-70 icon -> paper-dim - SavedQueryItem: amber icon + purple "All connections" label -> paper-dim icon + paper-dim/faint mono metadata; "·" separator stays - TabButton: bg-white/10 active + colored icons -> ink-200 bg + mono uppercase tracking-[0.14em] + paper-muted icons; count badge loses bg, just color contrast - EmptyState: bg-white/5 rounded-xl icon chip + gray text -> hairline- bordered ink-200 chip + paper-muted title + paper-faint description DataExplorer.tsx (main) - Container bg-slate-900/50 -> bg-ink-50 - Search Input bg-white/5 border-white/10 -> bg-ink-200 border-ink-500 + focus border-brand, mono font - ⌘K kbd hint: white/5 -> hairline ink-500 with mono - Refresh + Plus ghost buttons: hover:bg-white/10 -> hover:bg-ink-200 - Connection filter Select: bg-white/5 border-white/10 trigger -> ink-200/500 mono trigger; Select item icons purple/emerald/blue -> all paper-dim (shape distinguishes) - "Clear filter" X button: gray hover white -> paper-dim hover paper - Recently-viewed mono eyebrow + "Clear all" button restyled to mono uppercase - Skeleton bg-white/5 -> rounded-xs bg-ink-200 - All EmptyState calls drop colored icon tint (amber-400/50 etc) -> monochrome (color comes from EmptyState chip) TreeNode.tsx - Row: rounded-md hover:bg-white/5 -> rounded-xs hover:bg-ink-200 - Search highlight: bg-amber-500/10 -> bg-brand/[0.08] (yellow tint) - Chevron toggle: hover:bg-white/10 -> hover:bg-ink-300 - Node icons: blue (db) / purple (view) / emerald (table) -> all paper-dim; shape continues to distinguish type - Node name: text-gray-300 -> font-mono text-[12px] paper-muted/paper - Metadata tooltip: gray/white pairs -> mono uppercase eyebrow + value - Row metadata badge bg-blue-500/15 -> ink-200 hairline mono - Favorite star: amber fill stays semantic (brand yellow), gray -> paper-faint hover brand - Dropdown menu icons (emerald/purple) -> paper-muted Functional preservation - All hooks (useDatabases, useSavedQueries, useDebounce, mutations) unchanged - Connection filter logic, RBAC permission gates, search debounce unchanged - Tree expand/collapse animation (height + opacity AnimatePresence) unchanged - React.memo equality check unchanged - Drop Table / Alter Table actions and permission guards preserved Verified - Active tab: bg ink-200, color paper, Geist Mono font - No console errors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Symptom
- /explorer page: left panel sidebar (DataExplorer) appeared shifted
~56px to the left so the FloatingDock sidebar visually overlaid the
first column of search input / tab labels ("Search databases" ->
"arch databases", "DATABASES" -> "TABASES")
Root cause
React runs effects in children-then-parent order. FloatingDock's
useEffect (which dispatches "dock:mode-change" on mount) fires BEFORE
App.tsx's useEffect (which attaches the listener). The first event
that tells App.tsx to render the w-14 spacer is therefore lost when
localStorage.chouseui-dock-mode is unset (fresh login, no preference
yet). App.tsx defaults isSidebarMode=false -> no spacer -> main starts
at viewport left=0 -> the position:fixed dock overlays the panel.
Fix
In FloatingDock's mount effect:
- Persist the resolved dock mode into localStorage immediately, so a
full reload afterwards starts with the correct App.tsx initial state
- Defer the event dispatch to a setTimeout(_, 0) microtask so any
parent listener attached in the same render pass has a chance to
register first
- Clean up the timeout on unmount
Verified
- mainLeft 56, spacerExists true, sidebarLeft 56, sidebarW 484
- localStorage.chouseui-dock-mode "sidebar" after first load
- Tabs (DATABASES / PINNED / RECENT / QUERIES) and "Search databases"
placeholder render in full
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SqlTab is the main per-tab view inside Explorer (Monaco editor on top, result/explain/statistics tabs below). Surgical visual swaps, all data flow (runQuery, explain, optimize, debug) preserved. Outer shell - Container bg-white/[0.02] -> bg-ink-50 - ResizableHandle bg-white/5 hover white/10 -> h-px bg-ink-500 with hover ink-700 and active brand Action strip - border-b border-white/5 bg-black/20 backdrop-blur-sm -> border-b border-ink-500 bg-ink-100 (flat, no glass) - Run: blue-400 text + hover blue/10 -> paper text + hover bg-brand text-ink-50, mono uppercase tracking-[0.14em]; kbd hint paper-faint - Stop: red palette preserved (semantic destructive) - Explain / AI Optimize buttons: purple/pink icons -> paper-dim/paper on hover ink-200, mono uppercase labels - Divider w-px h-4 bg-white/10 -> bg-ink-500 Result tabs - TabsList rounded-none bg-black/20 backdrop -> bg-ink-100 hairline - TabsTrigger: data-[state=active]:bg-white/5 -> rounded-none with hairline border on active + bg-ink-50, mono uppercase labels - Popout chevron hover bg-white/10 -> hover bg-ink-300 - TabsContent statistics/explain: bg-background/50 backdrop-blur -> bg-ink-50 flat Result column header - "text-white/60" + lowercase -> mono lowercase paper-muted - Column type badge classes (.type-string etc) preserved for semantic data-type color coding Empty / error states - "Debug with AI" button: bg-red-950/20 + red-800 border + red-200 text -> hairline ink-500/100 with brand hover (matches editorial destructive action elsewhere) Shortcuts dialog - DialogContent rounded ink-100 hairline border - Row buttons: rounded-lg hover white/5 -> rounded-xs hover ink-200 - Icon colors purple/pink in command list -> all paper-dim (shape distinguishes); icons go paper-dim -> paper on hover - kbd chips: bg-white/5 white/10 -> bg-ink-200 border-ink-500 with paper-muted text, mono [10px] - "Editor built-ins" eyebrow: muted-foreground -> font-mono uppercase tracking-[0.18em] paper-faint Functional preservation - handleRunQuery, handleExplain, handleOptimize, handlePopout, kbd() helper, isMac detection, all RBAC permission gates, DOMPurify cell sanitization, TanStack DataTable columns memoization — unchanged - All Dialog modals (Shortcuts, Debug, Optimize) and their state hooks preserved - DownloadDialog trigger preserved (icon class swapped to editorial) Verified - RUN button: paper text, Geist Mono, hover bg-brand - No console errors SqlEditor.tsx (Monaco wrapper toolbar) is a separate file and still ships its old surface chrome — to be addressed in a follow-up. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Monaco wrapper that sits above the SQL editor for each tab.
Visible the moment a query tab is opened. Surgical class swaps only —
Monaco internals, auto-save, RBAC, keybindings, and dialog logic all
preserved.
Toolbar
- Outer container: bg-[#14141a] -> bg-ink-50 (matches workspace)
- Header strip: border-white/5 + bg-white/5 + backdrop-blur-md -> flat
border-ink-500 + bg-ink-100, no blur
- File icon: w-8 h-8 rounded-lg bg-primary/10 text-primary -> 7x7
hairline ink-500/200 chip with paper-muted icon
- "Editor" eyebrow: text-muted-foreground uppercase tracking-wider ->
font-mono text-[10px] uppercase tracking-[0.18em] text-paper-faint
- Tab title: text-sm font-semibold -> font-mono text-[13px] text-paper
- Vertical separator: shadcn Separator -> hairline 1px ink-500 bar
(only shown when save status visible)
- Shortcuts button: hover bg-white/10 -> hover bg-ink-200, paper-dim
Save status pill
- Per-state colored bg chips (blue-500/10, emerald-500/10,
amber-500/10, white/5) -> single editorial chip pattern: hairline
ink-500/200 + mono uppercase + semantic colored dot in front
- "Synced" idle state uses paper-faint dot
- "Unsaved" uses brand yellow dot (consistent with editorial "needs
attention" pattern)
Save dialog
- DialogContent: defaults -> rounded-md border-ink-500 bg-ink-100
- Input: bg/border defaults -> bg-ink-200 + hairline + Geist Mono;
duplicate-name validation uses brand (yellow) border instead of
amber-500 (consistent with brand-as-attention pattern)
- Duplicate-name warning panel: amber text -> brand/30 hairline +
brand/4 bg + paper-muted text + brand AlertTriangle icon
- Cancel: outline default -> hairline ghost editorial
- Primary action: defaults to bg-brand text-ink-50 (editorial)
Kill query AlertDialog
- AlertDialogContent: rounded-md border-ink-500 bg-ink-100
- Title sentence case ("Stop query execution?"); description paper-muted
- Stop button: shadcn bg-destructive (which is editorial red oklch
now) -> explicit bg-red-600 for the irreversible action so it reads
red even outside dark mode
Functional preservation
- Monaco init + dispose lifecycle (Strict Mode abort flag) untouched
- All editor commands (Cmd+Enter, F5, Cmd+S, Cmd+Shift+S, Cmd+Shift+F,
Cmd+Shift+E, Cmd+Shift+I) untouched
- Auto-save with debounce, lastSavedContentRef tracking, save-status
timeouts untouched
- useImperativeHandle (format/comment/find/save/saveAs/stop/getQuery)
untouched
- RBAC checks (canSaveQuery, canUpdateQuery, canOptimizeQuery,
canKillQuery) untouched
- Tab content sync from external (Debugger/Optimizer) untouched
Verified
- Header background rgb(15, 15, 15) = ink-100
- "Editor" eyebrow + "Query 1" title both render in Geist Mono
- 2 tabs rendered (Home + Query 1), no console errors
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "no rows returned" panel + the per-query statistics panel both
used colored icons (Clock blue, Database emerald, HardDrive purple)
inside shadcn Cards — the last visible AI-template surface in the
SqlTab result area.
EmptyQueryResult
- shadcn Card with cyan/emerald/purple icons -> hairline 3-col grid
(border-l + border-t with border-b + border-r per cell) matching the
metric grids on Home / DockerDeploy
- Heading: "No Results" bold + muted description -> editorial pattern
with mono eyebrow "Query result" + 2-line heading ("No rows
returned." paper + "Query ran cleanly." paper-dim tail)
- Per-cell layout: mono uppercase label top-left, paper-dim icon
top-right, mono large value below
- Trailing tip stays as paper-muted body text
StatisticsDisplay
- shadcn Card grid with m-4 + opacity-70 icons -> single hairline grid
matching EmptyQueryResult
- Title font: text-sm font-medium -> font-mono uppercase tracking-
[0.18em] paper-faint
- Value: text-2xl font-bold -> font-mono text-[22px] semibold paper
- Description: muted-foreground -> paper-muted
Functional preservation
- formatBytes / formatTime helpers untouched
- Conditional render (statistics === null) preserved
- All metric values rendered identically
Verified
- After running "SELECT * FROM system.backups" (returns 0 rows), the
EmptyQueryResult shows: "QUERY RESULT" eyebrow + heading + 3-col
hairline grid with mono numbers (Execution Time 1.85 ms / Rows
Read 0 / Data Read)
- No console errors
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both components live inside FloatingDock (visible on every authenticated page). Surgical class swaps; all auth/connection logic preserved. UserMenu - Avatar: 9x9 rounded-full gradient purple/blue with ring + glow -> 8x8 rounded-xs hairline ink-500/200 chip with mono uppercase initials - Online dot: emerald with border-[#121212] -> emerald with ring of ink-50 (theme-aware) - Button hover: bg-white/10 -> bg-ink-200 (with hairline border on expanded mode) - Name + email block: text-sm/text-xs -> mono uppercase metadata for the email (paper-faint tracking-[0.14em]) - Logout icon hover: red-400 + red bg -> red-400 text only (no bg flash, matches editorial restraint) - Tooltip: bg-black/90 backdrop-blur custom panel -> reused TOOLTIP_CLASS pattern; rich content keeps separator + "Click to log out" CTA in mono ConnectionSelector - Loading/connecting chip: bg-white/5 + purple Loader2 -> hairline ink-500/200 chip + paper-dim spinner + mono uppercase status; in collapsed dock mode shows as 9x9 chip (matches surrounding icons) - "No connections" warning: bg-yellow-500/10 + yellow text -> editorial attention pattern bg-brand/[0.04] + border-brand/30 + brand icon & label (matches "Continue working" pattern on /overview) - Single-connection button: bg-white/5 + green/yellow Plug colored -> hairline ink-500/100 + paper-muted icon + small status dot (emerald=connected / paper-faint=idle) anchored to icon corner - Multi-connection trigger: same editorial chrome; arrow icon paper-dim - Dropdown surface: bg-gray-900 border-gray-800 -> bg-ink-100 border-ink-500 with rounded-md - DropdownMenuLabel: text-gray-400 text-xs -> font-mono uppercase tracking-[0.18em] paper-faint with hairline border-b - Per-connection item: bg-purple-500/10 active highlight -> bg-ink-200 hairline highlight; "Default" badge bg-yellow-500/20 -> hairline brand/40 with brand text - Active checkmark: purple-400 -> brand yellow - SSL Lock indicator: kept emerald-400 (semantic security positive) - Refresh item: gray text -> mono uppercase paper-muted hover paper Functional preservation - All hooks (useRbacStore, useAuthStore, useQueryClient) untouched - fetchConnections, connectToClickHouse, handleSelectConnection logic preserved verbatim - localStorage + DB persistence (lastConnectionId via rbacUserPreferencesApi) preserved - Auto-connect on mount, 3s polling for new connections preserved - queryClient.invalidateQueries list preserved - "clickhouse:connected" custom event still dispatched - toast.success / toast.error messages preserved - onConnectionChange callback untouched Verified - Dock renders both components correctly at left=0 width=56px - User menu initials chip font: Geist Mono - No console errors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The chat overlay (trigger pill + panel shell + header + sidebar + welcome screen + model selector dropdown) gets the editorial treatment. The deep internals — message bubbles, tool-call cards, markdown renderer — still use the old violet/glass palette and will land in a follow-up since they are far more numerous and only visible after a chat is actively running. Trigger - Desktop side pill (vertical "Ask AI" at right edge): bg-black/60 backdrop-blur-xl + violet shadow + violet sparkles + pulse dot -> flat bg-ink-100 + hairline ink-500, paper-dim icon + mono "ASK AI" uppercase label; hover swaps border/text to brand yellow and lifts 1px to the left - Mobile FAB already shipped editorial in the index.css refactor Panel shell - bg-black/70 backdrop-blur-2xl + border-white/10 + rounded-2xl -> flat bg-ink-100 + hairline ink-500 + rounded-md, mobile slide-up unchanged - Removed the two background "glow orbs" (violet & indigo blur-3xl pseudo-decor) Header bar - bg-white/[0.03] backdrop-blur-sm + border-white/[0.06] -> bg-ink-200 + hairline ink-500 - Bot icon container: bg-gradient-to-br violet/indigo -> hairline ink-500/100 chip with paper-muted Bot - "CHouse AI · Online" title: violet-tinged emerald label -> sentence case paper + mono uppercase paper-faint "Online" - All header icon buttons (sidebar toggle, model selector, export, new chat, close, resize): hover bg-white/[0.08] -> hover bg-ink-300 in a 7x7 grid container Model selector dropdown - bg-black/40 trigger + bg-[#1a1c24] menu -> bg-ink-100 hairline - Model items: violet-tinged active state -> bg-ink-200 hairline, radio-style indicator switches purple -> brand yellow - "AI Models" label: zinc uppercase -> font-mono tracking-[0.18em] paper-faint with hairline divider Thread sidebar - bg-black/80 backdrop-blur-xl -> bg-ink-100 hairline - "Conversations" eyebrow: zinc-500 semibold -> font-mono uppercase tracking-[0.18em] paper-faint - Thread count: zinc-600 -> paper-faint mono Welcome screen - Big gradient violet/indigo rounded-2xl chip with violet Sparkles + pulse dot -> editorial: hairline ink-500/200 chip with paper-muted Sparkles + emerald online dot - Gradient text "CHouse AI" -> mono eyebrow + 2-line tagline pattern (paper + dim tail) consistent with other empty states - Suggested prompt buttons: violet-tinted hover with shadow -> hairline ink-500/200 with bg-ink-300 hover; icon goes paper-dim -> brand on hover (no per-icon bg chip) - "More suggestions" link: zinc text + white/[0.04] hover -> mono uppercase paper-faint hover paper - "Start New Chat" CTA: bg-gradient violet/indigo + shadow violet/40 -> bg-brand text-ink-50 mono uppercase, hover bg-brand-soft + 1px lift - Thread-selected-but-empty mini empty state: same hairline pattern, paper-muted Bot, hairline prompt buttons Functional preservation - Chat streaming logic, threads CRUD, prompts list, position/size persistence — all untouched - isMobile / isTablet / isDesktop responsive branches preserved - Drag controls, resize handles, pointer events untouched - All RBAC + aiEnabled gates preserved Verification limitation - Cannot screenshot in this environment: features.aiOptimizer is false on the local docker container, so AiChatBubble returns null before rendering. Style swaps are mechanical and verified at the source level; will render correctly once an AI provider is enabled Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reported: "box dibawah tidak kelihatan" — the prompt input strip at the bottom of the chat panel rendered black-on-near-black after the panel surface moved to bg-ink-100 in 4b5cae0. Root cause The input strip kept its pre-editorial chrome: bg-black/40 backdrop- blur-sm + textarea bg-white/[0.05] + border-white/[0.08]. On the new flat ink-100 panel there was no glass to blur, so the strip blended into the panel and looked empty. The send button also kept its violet/indigo gradient which clashed visually. Fix - Input strip container: bg-black/40 backdrop-blur-sm + border- white/[0.06] -> bg-ink-200 + border-t border-ink-500 (one shade darker than the panel ink-100, visibly differentiated) - Textarea: bg-white/[0.05] + border-white/[0.08] + violet focus ring -> bg-ink-100 hairline ink-500 + focus border-brand, Geist Mono font, paper text, paper-faint placeholder - Send button: bg-gradient violet/indigo + shadow violet -> 11x11 square bg-brand text-ink-50, hover lift; disabled state bg-ink-300 paper-faint - Stop button (while streaming): red-500/10 light tint -> editorial destructive red-950/40 with red-900/60 hairline, hover red-700 - Footer hint "Shift+Enter" : zinc-700 (too dark) -> font-mono uppercase tracking-[0.14em] paper-faint - Tool status (while streaming): violet-400/60 -> mono uppercase paper-muted Also: User message avatar (line 1927) - 7x7 rounded-xl gradient indigo/blue + ring white/[0.06] + indigo User icon -> hairline ink-500/200 chip + paper-muted User icon (consistent with assistant Bot avatar already updated in 4b5cae0) Verified - textarea bg rgb(15,15,15) = ink-100, border rgb(38,38,38) = ink-500 - send button bg rgb(26,26,26) = ink-300 (disabled — turns brand yellow when input has text) - Input strip visibly separated from panel Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User reported: in non-fullscreen browser the chat panel (default 1340x840) feels empty/sparse — content sits centered in a tall column with huge vertical gaps above and below. Root cause The "thread selected, messages empty" state used flex justify-center on the whole pane. In dense AI-template chrome this was fine — the gradient orbs / glassmorphism filled the void. After the editorial flatten the centered layout shows actual empty space, which reads as unfinished. Fix Reflow this state to a compact top-aligned column, mirroring how ChatGPT/Claude/Linear handle empty conversations: - items-start gap-5 px-6 pt-10 (was items-center justify-center h-full) - Replace the 12x12 Bot chip with a mono eyebrow "01 — New conversation" pattern consistent with other section headers - Heading drops to text-[15px] medium leading-snug (was text-sm muted body copy), now reads as a real prompt - Prompt grid + "More suggestions" link unchanged structurally; text classes harmonized to paper / paper-muted / paper-faint - Empty space naturally falls between the prompt grid and the input strip at the bottom (the way an empty chat thread is supposed to feel — input pinned, content compact at top) Welcome-screen (no active thread yet) still uses centered layout with the Bot chip + "Start new chat" CTA — that one is an actual landing hero and needs to feel inviting. Verified - Heading top = 221px from panel top (was vertically centered around panel midpoint) - Eyebrow + heading + prompt grid sit at top, input strip at bottom, natural breathing room in between Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /monitoring page is a tab wrapper around three sub-pages (Logs, Metrics, LiveQueries). This commit redesigns only the wrapper — sub-pages still ship their old chrome and will land in follow-ups. Header - Big gradient Activity icon with ping ring + per-tab gradient bg -> small hairline ink-500/100 Activity chip, single neutral color - "Monitoring" + sub-description -> mono eyebrow "Observability" + editorial heading - Info button: white/10 hover -> hairline ghost paper-dim - DataControls + InfoIcon button kept (visual style flows from shadcn tokens which are already editorial) Tab cards (TabCard component) - Per-tab gradient/glow/border/textColor config (amber/purple/cyan) removed entirely from TAB_CONFIG type - Active state: gradient bg + motion glow + gradient line marker -> hairline border-ink-700 + bg-ink-200; icon chip gets brand border + brand text (single accent moment) - Inactive: white/5 -> ink-100 surface, paper-muted text - "Live" badge: red-500 pulse pill -> editorial red-950/40 hairline with mono uppercase + small pulse dot - Description: gray-500 -> font-mono uppercase tracking-[0.14em] paper-faint - Removed motion.div with layoutId="activeTabGlow"/"activeTabLine" — rely on border + bg state for clarity, less motion Content area - Per-tab bg-white/5 + border-white/10 container -> hairline ink-500 + bg-ink-100, rounded-md - Removed AnimatePresence motion wrapper between tabs (the tab change is already informative via active card highlight; over-animating the panel content felt restless) InfoDialog body - Per-tab gradient feature cards -> uniform hairline ink-500/200 cards with hairline icon chip - "Pro tip" amber gradient panel -> editorial brand attention pattern (border-brand/30 + bg-brand/4 + mono "Pro tip" eyebrow + body text) Functional preservation - All RBAC permission gating (canViewLiveQueries / canViewLogs / canViewMetrics) unchanged - Auto-redirect to first available tab if URL has invalid/missing tab segment unchanged - Auto-refresh toggle to on when switching to Live queries unchanged - DataControls integration (lastUpdated/refreshing/timeRange) preserved - All three sub-pages still receive same embedded props (refreshKey, autoRefresh, timeRange, onRefreshChange) — only the wrapping container surface changes Verified - /monitoring/logs renders header + tab cards editorial; active tab shows brand icon chip; "Live" badge on Live queries tab editorial - No console errors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Smallest of the three Monitoring sub-pages. Surgical class swaps, all hooks/RBAC/auto-refresh logic preserved. StatsCard (helper) - bg-gradient gray-900/50 + per-color border ring + colored icon chip + colored text -> uniform hairline grid cell pattern (mono label uppercase + mono numeric value + paper-dim icon), matches EmptyQueryResult / StatisticsDisplay - color prop now optional (typed but ignored) for API compat with the rest of the page Header (non-embedded) - p-2 rounded-lg bg-amber-500/20 chip with amber Zap -> hairline ink chip with paper-muted Zap; mono eyebrow "Observability" + editorial heading Stats grid - gap-4 spacing -> single hairline grid (border-l + border-t with per-cell border-b + border-r), matches Home / DockerDeploy pattern Toolbar - Search Input bg-white/5 + amber focus border -> bg-ink-100 hairline + brand focus border, Geist Mono - Auto-refresh Select trigger: bg-white/5 -> bg-ink-100 hairline - Manual Refresh Button: bg-white/5 hover white/10 -> bg-ink-100 hairline hover ink-200 Table - Outer container border-gray-700/50 rounded-lg -> rounded-md ink-500/100 - Loading state: amber Loader2 + "Loading live queries..." gray -> paper-dim spinner + mono uppercase "Loading live queries…" - Empty state: gray-800/50 rounded-full chip + lucide Database in gray-500 -> hairline ink chip + paper-dim Database; mono-friendly copy - Table header row: bg-gray-800/50 + gray-400 -> bg-ink-200 + mono uppercase tracking paper-faint column labels - Error state: red-500/20 rounded-full chip -> hairline red-950/40 ink chip; outlined Retry button hairline editorial - Expanded query row inner Terminal icon: text-purple-400 -> text-paper-muted - Per-query error toast box: bg-red-500/10 -> red-950/40 hairline Semantic colors preserved - getDurationColor (green/amber/red duration tiers) — intentional performance signal; comment added so future passes don't flatten it - Per-query "killed by user" inline red message keeps red palette Verified - /monitoring/live-queries renders with hairline stat grid (Active queries / Longest running / Total memory / Rows read) and editorial table chrome - No console errors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Middle of the three Monitoring sub-pages. Surgical class swaps; all query/filter/permission logic preserved. StatCard (inline helper) - bg-gradient gray-900/50 + per-color border ring + colored icon chip + colored text -> uniform hairline grid cell pattern matching LiveQueries / EmptyQueryResult / Home metric grids Header (non-embedded) - p-3 rounded-2xl bg-gradient blue-cyan + shadow blue/20 + 7x7 white FileText -> 10x10 hairline ink chip with paper-muted FileText; mono eyebrow "Observability" + editorial h1 - "Your queries only" Badge: blue-500/20 + blue-300 -> hairline mono uppercase paper-muted Summary stats - grid grid-cols-2 lg:grid-cols-4 gap-4 -> single hairline grid (border-l + border-t with per-cell border-b + border-r) - Removed framer-motion intro wrapper (stats are static) Filters bar - bg-gradient white/5 + rounded-2xl + backdrop-blur -> bg-ink-100 hairline rounded-md - Search Input + all Selects: bg-white/5 -> bg-ink-200 hairline + Geist Mono, focus border brand - Filter icons (Search/Filter/User/Shield): gray-400 -> paper-dim - "Clear filters" outline button: white/5 hover white/10 -> bg-ink-200 hairline mono uppercase Error pill - rounded-xl bg-red-500/10 + red-400 text -> rounded-xs editorial destructive (red-950/40 hairline + red-300 icon + red-200 text) Logs content container - rounded-2xl bg-gradient white/5 + backdrop-blur -> rounded-md bg-ink-100 hairline - Loading state: gray-500 RefreshCw + "Loading logs..." text-sm -> paper-dim spinner + mono uppercase paper-faint - Empty state: faded FileText icon + lg text -> hairline ink chip + editorial heading + body - Log row: rounded-xl white/10 hover -> rounded-xs hairline ink-500 hover ink-200; status-change ring offset adjusts to ink-100 - Status ring colors keep emerald-500/red-500/amber-500 (semantic status feedback for query state transitions) Semantic colors preserved - Status icons (CheckCircle2 emerald / XCircle red / Zap amber pulse) in log rows — these encode query result state, kept colored - Exception block (red-500/10 bg-red-500/20 border) — kept destructive palette intentionally Notes - Pre-existing data issue spotted while testing: avgDuration can render in scientific notation (e.g. "1.175...e+115ms") for certain log payloads. Out of scope for this refactor — flag for follow-up in processLogs() avg calculation. Verified - /monitoring/logs renders 4 hairline stat cells (Total queries / Successful / Failed / Avg duration) - Filter bar present at y=331 (input mono, focus brand) - No console errors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Largest of the three Monitoring sub-pages (2488 lines, 199 colored
patterns at start). Surgical class swaps + replace_all batches; all
data hooks, time range, query enabling, RBAC unchanged.
Helpers (StatCard, MetricChartCard)
- StatCard: gradient bg + colored icon chip + colored value text +
blur orb -> uniform hairline grid cell (mono label + mono value +
paper-dim icon). color/bgColor props accepted for API compat,
ignored in layout. trend pill keeps semantic colors (emerald/red/
paper-faint).
- MetricChartCard: gradient + glass chrome + colored icon container
-> hairline ink-500/100 + neutral icon chip. Chart line series
colors preserved (per-series identity is the whole point of a
metric chart). Loading + "no data" states reflowed to editorial
paper-dim spinner + mono labels.
Panel wrappers
- replace_all on the repeated pattern
"rounded-2xl border border-white/10 bg-gradient-to-br from-white/5
to-transparent backdrop-blur-xl"
-> "rounded-md border border-ink-500 bg-ink-100"
(21 occurrences swapped in one batch)
Icon containers
- replace_all on "p-2 rounded-lg bg-{color}-500/20" for blue, amber,
cyan, orange, indigo, purple, emerald, red, plus the white/5
fallback -> single editorial pattern
"p-2 rounded-xs border border-ink-500 bg-ink-200"
Header (non-embedded)
- p-3 rounded-2xl bg-gradient emerald-teal + shadow emerald + "Metrics
Dashboard" big bold -> 10x10 hairline ink chip + mono "Observability"
eyebrow + editorial heading; small emerald pulse dot inline
- Time-range pill + Select trigger: bg-white/5 -> bg-ink-100 hairline
+ mono font, paper-dim icons
Internal sub-tabs (Overview / Performance / Storage / Merges / Errors
/ System / Network)
- replace_all on
"data-[state=active]:bg-emerald-500/20 data-[state=active]:text-emerald-400"
-> "data-[state=active]:bg-ink-200 data-[state=active]:text-brand"
(7 occurrences). Active tab now reads as brand-yellow text on
ink-200 fill, consistent with sidebar Pinned/Recent etc.
Error pill
- p-4 rounded-xl bg-red-500/10 + red-400 -> rounded-xs editorial
destructive (red-950/40 hairline)
Semantic colors deliberately preserved
- Trend up/down (emerald/red) on StatCard
- Resource Utilization progress bars (CPU/Disk/Memory color coding —
near-universal convention in system dashboards)
- Per-chart series colors (multi-line charts need distinct colors)
- Destructive Badge variants in error tables/lists
- Status emerald pulse dot in header (online indicator)
Verified
- /monitoring/metrics renders: editorial header, 8 hairline stat
cells (Active queries / Connections / Failed / Uptime / Databases
/ Tables / Size / Total rows), tab strip with brand active, panels
with hairline ink-500 chrome
- No console errors (filtered pre-existing AiChartRenderer / monaco
noise)
Notes
- ~30 colored badges remain (red Failed counters, purple query-type
pills, etc) — all semantic per-content indicators, not chrome
- Metrics is data-dense; further flattening would compromise
scannability of dashboards
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Same shape as the Monitoring wrapper refactor — flatten the chrome
around the 6 admin sub-areas (Users, Roles, Connections, ClickHouse
users, AI models, Audit logs). Sub-page internals still ship their
old palette and will land in follow-ups.
Header
- Per-tab gradient ShieldCheck chip + animated ping ring + colored
bg pulse -> single hairline ink chip with paper-muted ShieldCheck
- "Administration" + sub-description -> mono eyebrow "Administration"
+ editorial heading "Manage users, roles & access"
- Backdrop-blur sticky bar bg-black/10 -> flat bg-ink-50 with
border-b ink-500 hairline
- Info button: white/10 hover -> hairline ghost paper-dim
ADMIN_TAB_CONFIG type
- Stripped per-tab color/gradient/bgGlow/borderColor/textColor fields
- Reduced to just icon + label + description
- Labels normalized to sentence case ("ClickHouse users", "AI models",
"Audit logs" instead of Title Case)
TabCard component
- motion.button with whileHover scale + per-tab gradient bg + glow +
motion glow/line markers -> plain button with hairline border,
bg-ink-100 default, bg-ink-200 active, brand-yellow icon-chip
border for active (single accent moment, consistent with Monitoring)
- "Live" badge removed (was Monitoring-only; copied here for parity
but admin doesn't have a live tab)
Tab content containers
- bg-white/5 border-white/10 + "glass-effect" class -> hairline
ink-500 + bg-ink-100 + rounded-md, matches Monitoring sub-page
containers
- Removed AnimatePresence motion wrapper between tab switches (the
active tab card already provides feedback; tab content swap is
instant which feels more responsive)
InfoDialog body
- Bullet list with "•" character -> editorial dot pattern (1px round
paper-dim before each li)
- Tip box: bg-blue-500/10 + blue-200 -> brand attention pattern
(border-brand/30 + bg-brand/4 + paper-muted body)
Functional preservation
- All 6 RBAC permission checks (USERS_VIEW / ROLES_VIEW /
CONNECTIONS_VIEW / CH_USERS_VIEW / AI_MODELS_VIEW / AUDIT_VIEW)
preserved
- Auto-redirect to first available tab when URL has invalid/missing
segment preserved
- All 6 sub-components rendered with same props as before
Verified
- /admin/users renders: hairline ShieldCheck chip, mono Administration
eyebrow, h1 "Manage users, roles & access", 6 tab cards in horizontal
scroll, Users tab active with brand icon-chip border
- No console errors
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sweeps the Admin tab's six sub-pages from the AI-template look (gradients, glassmorphism, per-category color coding, multi-color badges) into the editorial system used by Login / Overview / Explorer / Monitoring: hairline ink-500 borders, ink-100/200 surfaces, mono uppercase eyebrows + 10–11px labels, brand-yellow only as the selected/primary accent, semantic colors reserved for status (emerald=success, red=destructive, amber=warning, brand=default). Files: - UserManagement/index.tsx — header chip + mono eyebrow, hairline stats grid (Total / Active / Inactive / Roles), editorial avatar initials chip, brand "(You)" badge, success toast in emerald - RbacRolesTable.tsx — unified ROLE_STYLE with 2-letter role codes (SA/AD/DV/AN/VW/GS) replacing per-role color map; hairline role card with brand accent on system/default badges, mono permission pills - ConnectionManagement/index.tsx — Server icon chip header, brand primary "Add connection", editorial table + Default badge as hairline brand pill, emerald/ink status pills - AiModels/index.tsx — Bot icon header, mono-uppercase internal tab bar (Deployments / SDK models / Providers) - AiModels/ProvidersTab.tsx, BaseModelsTab.tsx, ConfigsTab.tsx — editorial eyebrow headers, brand primary actions, hairline tables, emerald/ink Active/Inactive pills, brand Default pill, editorial dialogs with bg-ink-100 + ink-500 borders, mono form labels - ClickHouseUsers/index.tsx — full editorial rewrite of the 4-step Create User wizard: hairline stepper with brand progress fill, unified ink-500 role cards with 2-letter codes (DV/AN/VW) and brand selection state, editorial database tree (preserving semantic Database / Table icons in paper-faint), emerald/ink password requirement pills, amber DDL preview warning, brand primary footer buttons; main view header + table aligned with rest of Admin - RbacAuditLogs.tsx — replaces 11-variant ACTION_COLORS with single uniform ACTION_BADGE; FileText chip header, hairline 4-cell stats with semantic emerald/red for Success/Failed counts; editorial filters (Select + Calendar popover); editorial table with emerald/red/ink status pills and editorial client-info tooltip; cleaned unused imports (motion, Badge, Input, ScrollArea, toast) Sub-component dialogs (CreateUser, EditUser, RoleFormDialog, RbacAuditPrune/ExportDialog, UserDataAccess, ConnectionUserAccess) remain on the AI-template look; will land in a follow-up commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…latten Completes the Admin tab editorial migration started in aa4411a by sweeping the seven dialog/form sub-components used by Users / Roles / Connections / Audit panels, and tones down the bright iconography on the Metrics → Overview tab so it stops fighting the rest of the dark editorial chrome. Also fixes a runtime crash on /overview triggered after the recent-queries refresh tick. Files: - CreateUser/index.tsx (~130 patterns) — GlassCard wrappers replaced by hairline cards; ROLE_COLORS emoji+per-color map → ROLE_CODES (SA/AD/DV/AN/VW/GS) to match RbacRolesTable; brand-yellow selected state on role cards; amber warning panel for generated-password notice; cleaned Shield/Key/Badge/GlassCard imports - EditUser/index.tsx (~116 patterns) — GlassCard + per-tab colored active states + colored role borders → uniform editorial; User Info 4-cell hairline stats grid; brand-yellow Save / Reset Password; semantic red Delete dialog - RoleFormDialog.tsx (~45 patterns) — gradient header strip + glassy purple icon box → editorial chip+title; System Role badge with Lock; brand pill for "X selected"; brand checkboxes; semantic red for system-role warning + "at least one permission" alerts - UserDataAccess/index.tsx — eyebrow header + hairline rules table with emerald/red Allow/Deny pills + brand Save button; editorial Add/Edit Rule dialog - ConnectionUserAccess.tsx — Users chip + connection-name eyebrow; hairline Add User panel; emerald/ink Active/Inactive + brand Direct access / mono Via roles pills; editorial Note panel - RbacAuditPruneDialog.tsx — destructive red trigger pill; AlertTriangle red chip header; brand-tinted single-date calendar selector; red Confirm Deletion button - RbacAuditExportDialog.tsx — editorial outline trigger + FileDown chip header; brand-tinted range calendar; brand primary Download CSV button - Metrics.tsx (Overview tab only) — Resource Utilization Activity icon flattened from bright blue to paper-muted chip; CPU/Disk/ Memory progress bars no longer use bright cyan/purple at normal load — brand-yellow as default with amber/red preserved as tiered warning; Activity Summary 4 stat tiles converted to hairline grid with mono tabular numerics; Recent Errors panel now uses the established semantic red recipe; Largest Tables purple icon and purple compressed-size pill flattened to neutral hairline - Home.tsx — guard q.duration.toFixed(0) against non-number duration values from the recent-queries API. Crash was reproducible by sitting on /overview through the polling tick when the upstream payload occasionally serialized duration as a string Behavior, props, state, validation, and business logic untouched across all files — only visual layer and one defensive coercion. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switches the three security-sensitive env vars in docker-compose.yml
from hard-coded "dev-secret-change-me" / "dev-key-change-me" /
"dev-salt-change-me" placeholders to ${VAR:-fallback} substitution.
Operators can now drop a real-length JWT_SECRET (≥32 bytes),
RBAC_ENCRYPTION_KEY, and RBAC_ENCRYPTION_SALT into their shell or
.env and have docker compose pick them up — without touching the
compose file. The placeholder values are still used when nothing is
provided, so local "just bring it up" workflows keep working.
Motivation: the container previously refused to start in production
when secrets weren't the correct length, because the placeholders
are intentionally invalid. This change makes both safe-by-default
(local dev still boots) and overridable (operators can swap real
secrets without forking compose).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Preferences (/preferences) was still pure AI-template: GlassCard wrappers, gradient indigo→purple avatar block, animated ping ring on the header chip, per-card colored chip palette (indigo/blue/purple/ emerald), glowing status dots with drop-shadow, and gradient-tinted section separators inside the data-access tree. App.tsx's root container also added a purple-on-blue radial gradient plus two large blur orbs underneath every authenticated page, which is why the editorial pages still rendered with a violet haze. src/pages/Preferences.tsx (full rewrite): - Header: gradient indigo→purple chip + animated ping → editorial chip + mono subtitle eyebrow - Log out button: red-tinted ghost on red hover (semantic, not the old purple→red shift) - Profile hero: 96px gradient avatar → 80px hairline chip with mono initials and a tiny emerald online dot (matches UserManagement); role badges now use uniform brand pill (matches RbacRolesTable) - SettingCard helper: dropped `color` prop entirely. All four cards use the same neutral chip (paper-muted icon, ink-500 border) — the title carries the identity, not the icon hue - InfoRow: glassy `bg-white/[0.03]` → hairline `bg-ink-200`, mono uppercase label, paper value - StatusFooter: introduced helper for the bottom strip of each card (replaces the four different colored "Session Active / Operational / Active Policy Engine / Permissions Visualized" tags with one brand|emerald variant) - Data access tree: indigo→transparent gradient separator → flat ink-500 hairline; purple "All Databases" / indigo connection header → mono uppercase paper; per-rule chips now use uniform ink-500 with emerald/red Allow/Deny pills - Functional access: per-category emerald label → mono paper-dim, permission pills uniform hairline - Empty-state for Admin: indigo Shield ring/glow → brand chip + mono "Full global access" — keeps the meaning (admin override) but flattens the visual weight - Removed unused imports: ExternalLink, Keyboard, GlassCard, Badge src/App.tsx: - Root container `bg-[#0a0a0a]` + radial purple-violet gradient + two blur orbs (purple-500/10 + blue-500/10) → flat `bg-ink-50`. Affects every authenticated page, so Login / Overview / Explorer / Monitoring / Admin / Preferences all stop bleeding a violet halo under the editorial chrome No behavior, routing, auth, or API changes — visual layer only. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ConfirmationDialog is the shared destructive/warning/info/success confirmation used across Preferences (Log out), Users (Delete user), Roles (Delete role), ClickHouse users (Delete CH user), AI Models (Delete provider/base model/config), Connection management (Delete connection), and several other admin flows. It still rendered in pure AI-template style: bright `text-red-500` title, gray-800 border, gray-700 cancel button. Now uses the editorial recipe: - DialogContent → `rounded-xs border-ink-500 bg-ink-100 text-paper` - Title icon → 9×9 chip with per-variant border+bg+text (red-900/60 + red-950/40 + red-300 for danger; amber/emerald/ink variants for warning/success/info), matching the destructive panel recipe used in RbacAuditPruneDialog and ClickHouseUsers - Title text → flat `text-paper` (was bright red — now the chip carries the semantic, title stays neutral) - Description → `text-paper-muted` 13px - Cancel button → editorial outline (ink-500 border, mono uppercase) - Confirm button → variant-tinted but flat (red-600/amber-600/ brand/emerald-600), mono uppercase, smaller h-9 Behavior, props, callbacks, DOMPurify sanitization, and isLoading spinner all preserved. Variant API unchanged so every consumer just inherits the new look. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Toasts (toast.success/error/warning/info from sonner, used app-wide
for connection state, save confirmations, deletion notices, etc.)
were rendering with sonner's bright `richColors` palette — saturated
emerald-green for success, screaming red for error, etc. That clashes
with the rest of the editorial chrome, especially with the
"Connected to ..." banner at the bottom-right of every authenticated
page.
Two changes:
src/main.tsx:
- Drop the `richColors` prop from <Toaster>. With it off, sonner
defers to the classNames we register, so we get full control
instead of having to fight the built-in vivid colors.
src/components/ui/sonner.tsx:
- Register editorial classNames for every part of the toast: shell
(rounded-xs + ink-500 border + ink-100 bg), title (paper 13px),
description (paper-muted 12px), icon (paper-dim default), close
button (ink-500 + ink-200), action button (brand primary mono
uppercase), cancel button (editorial outline mono uppercase).
- Per-variant tints — success/error/warning use the same desaturated
recipe as the destructive/warning panels elsewhere
(border-X-900/60 + bg-X-950/30 + text-X-100, icon X-300). Info
and loading stay neutral.
- Fixed a class-merge bug: previously `{...props}` came after our
`toastOptions` in the JSX, which silently dropped our classNames
the moment any caller passed its own toastOptions (which main.tsx
does, for closeButton + duration). Now we destructure
`toastOptions` out of props and merge it: editorialClassNames as
the base, caller overrides win where present, and the other
toastOptions fields (duration, closeButton) flow through
untouched.
Behavior, durations, and toast API all preserved — only the visual
shell changes. Every existing call site (`toast.success(...)`,
`toast.error(...)`, etc.) automatically inherits the new look.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The connection filter dropdown in the Explorer sidebar's Saved Queries tab still used the AI-template palette: white/5 chrome plus three colored icons per option type (purple Layers for All Connections, emerald Pin for the current connection, blue Database for other connections). Now uses the editorial recipe to match the rest of the Explorer sidebar: - Filter funnel icon → paper-faint - Select trigger → `rounded-xs border-ink-500 bg-ink-200` + mono text, matching other admin/explorer Select fields - Select content → `rounded-xs border-ink-500 bg-ink-100` - Layers icon (All connections) → paper-dim (no purple) - Pin icon (current connection) → brand-yellow (semantic: highlights the active selection, aligned with how brand is used for "Default" and active states elsewhere) - Database icon (other connections) → paper-dim (no blue) - Clear (X) button → editorial ghost with paper-dim → paper hover - Placeholder text "All Connections" → sentence case "All connections" to match the rest of the editorial copy Behavior unchanged — filter logic, connectionFilter state, and hasConnectionFilter detection all preserved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…sheet
User feedback: the chat bubble felt small and awkward — it floated as
a centered modal that covered the editor, came with a zoom system to
fit any viewport, and required dragging/resizing on two axes. The
result was lots of whitespace inside the panel and an interaction
model that fought with the rest of the IDE.
Pivot to the side-sheet pattern that's standard for IDE AI assistants
(Cursor, GitHub Copilot Chat, JetBrains AI):
Outer container & animation:
- `fixed top-0 right-0 bottom-0` with width `${sheetWidth}px` on
desktop and tablet — anchored to the right edge, full viewport
height
- Slide-in animation: `initial={{ x: '100%' }} → animate={{ x: 0 }}`
with cubic-out easing (was opacity+scale on a free-floating panel)
- Mobile behavior unchanged — still slides up full-screen
State simplification:
- Removed: `position` (x/y), `dragControls`, `handleDragEnd`,
draggable header `onPointerDown`, "Drag to move" affordance,
`ZOOM_MIN/MAX` + `zoomFactor`, the corner + left + bottom resize
handles, `DEFAULT_DESKTOP_WIDTH/HEIGHT` 1340×840 constants
- Added: `sheetWidth` state (single number) cycling through
COMPACT 420 / STANDARD 560 / WIDE 760, clamped to 70% of viewport
- Width-only resize: single 6px hit-zone on the sheet's left edge,
drag-left to grow (sheet is right-anchored), brand-yellow tint on
hover/active
- Persistence: kept the existing chat prefs API (position + size)
for compatibility with `getChatPrefsFromWorkspace` /
`mergeChatPrefsIntoWorkspace` and their tests, but always pins
position to {0,0} and stores the sheet width in size.width
(size.height now 0 — no longer meaningful)
Header & toolbar:
- Header no longer doubles as a drag handle. Removed `cursor-grab`,
the pointerDown forwarding to dragControls, and "Drag to move"
title/aria
- Maximize/Minimize button repurposed: now cycles sheet width
(compact → standard → wide → compact). Title reads the next
state ("Standard sheet" / "Wide sheet" / "Compact sheet")
- Logical dimension thresholds tuned for the narrower sheet:
`hideSidebarThreshold` 900 → 720, `singleColPromptThreshold`
600 → 520 — keeps single-column suggestion grid + collapsed
thread sidebar at compact width
Removed unused imports: `useDragControls`, `PanInfo` from
framer-motion. No behavioral changes to threads, messages, streaming,
markdown rendering, model selector, sidebar, or any of the chat
internals — only the shell.
Verified in preview: sheet renders at right edge, editor remains
visible+usable underneath, width cycle persists per-device via
existing prefs flow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User reported the LiveQueries header sometimes rendered nonsense:
TOTAL MEMORY showed "102.93 undefined" (formatBytes ran off the end
of its sizes array) and ROWS READ rendered as "16895277334097478.00B"
(raw UInt64 string getting concatenated with the unit suffix).
The upstream root cause is ClickHouse serializing UInt64 values as
JSON strings (to preserve precision past 2^53), plus occasional sums
that overflow into absurd magnitudes (e.g. summed memory_usage that
clearly exceeds physical memory). Fix the UI so it stops looking
broken regardless of what the backend hands us.
Changes in src/pages/LiveQueries.tsx:
- toFiniteNumber(input): new helper that coerces unknown input to a
number and gates on Number.isFinite. UInt64-as-string from JSON now
becomes a number cleanly; NaN/Infinity/null all map to "—" instead
of leaking through as "NaN undefined" etc.
- fixed2(n): centralized 2-decimal formatter that falls back to
compact .toExponential(2) when |n| >= 1e15. Prevents the old
"1.8795015499046923e+82" with its 16 mantissa digits from leaking
through .toFixed() (toFixed switches to exponential at >= 1e21
anyway, with absurd precision).
- formatBytes:
- Accepts unknown, coerces via toFiniteNumber.
- Sizes table extended B / KB / MB / GB / TB / PB / EB / ZB / YB
so the index always lands in-bounds (was bailing at TB).
- Negative-safe via sign extraction.
- Uses fixed2 for the divided value.
- formatNumber:
- Accepts unknown, coerces via toFiniteNumber.
- New T (trillion) and Q (quadrillion) tiers above B (billion).
- Negative-safe via sign extraction.
- Sub-thousand path now uses fixed2 too — was emitting
`num.toString()` which produced inconsistent decimal precision
next to the tiered values.
- formatDuration:
- Accepts unknown, coerces via toFiniteNumber.
- Sub-minute case now `.toFixed(2)` (was .toFixed(1) — user asked
for 2 decimals everywhere).
- Minute case rounds seconds with fixed2 (was .toFixed(0)).
Verified in /monitoring/live-queries: header cards now read e.g.
"4 / 2m 39.67s / 7.29e+17 YB / 2.33e+20Q" and per-row cells
"82.05 GB / 2.33B / 167.56 MB / 3.29M". No "undefined" units, no
20-digit raw numbers, every value uses 2 decimals.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause of the YB / Q / e+XX values in the LiveQueries header cards. ClickHouse serializes UInt64 fields (memory_usage, read_rows, elapsed_seconds) as JSON strings to preserve precision past 2^53. useLiveQueriesStats was reducing them with `acc + q.memory_usage` — JS sees a string operand and switches `+` from numeric addition to string concatenation. Four queries of ~2.5 GB each were producing a literal "02474233856183456789031425592323672895488" (40-digit string), which then coerced to a number for formatBytes hit ~1e40 bytes and tiered all the way up to YB with .toExponential fallback. Same path for read_rows producing 5e+18Q. Fix: introduce a `toFinite(input: unknown)` helper that runs each field through Number() and Number.isFinite, returning 0 on garbage. Used inside the reduce callbacks for totalMemory, totalReadRows, and also longestRunning's Math.max (defensively — elapsed_seconds is typically a float but no harm coercing). Verified by simulating the buggy reduce in the live preview console: old path returns the 40-digit string, new path returns 11124256466 = 10.36 GB which matches the per-row sums (2.32 + 1.83 + 3.14 + 3.67). This also makes the LiveQueries page formatter changes in 4ae0514 mostly redundant for normal data — but those guards stay in place as defense in depth against any future field that arrives as a string from ClickHouse. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Same root cause as 50f3077 but in a different hook. The Query Logs header card was showing "Avg duration: 9.651382226469024e+58ms" for 50 individual queries that were each 2–11ms. useQueryLogs maps the ClickHouse `system.query_log` rows into a typed JS array. The TypeScript type declares query_duration_ms / read_rows / read_bytes / memory_usage as `number`, but at runtime ClickHouse JSON returns UInt64 / Float64 fields as strings (to preserve precision past 2^53). Only `event_timestamp` was being run through `Number()`; the other four were assigned raw from the API payload. Logs.tsx then did `durations.reduce((sum, d) => sum + d, 0)` to compute avg duration — JS `+` saw a string operand and switched to string concatenation, giving a multi-digit string that, when divided by 50, produced the e+58 nonsense. Fix: introduce a local `num(v)` helper at the boundary that coerces through Number() + Number.isFinite (returning 0 for garbage), and run all four numeric fields through it during the initial map. The two later remaps inside the same hook (lines 825 and 871) already read from the now-coerced `logs` array, so the cascade is automatic. Verified in /monitoring/logs: header now reads "50 / 50 / 0 / 4ms" matching the per-row durations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bumps the workspace and server packages from 2.12.9 → 2.13.0 to match the editorial redesign + ClickHouse UInt64-coercion fixes landing on this branch. Three docs touched: CHANGELOG.md — new v2.13.0 entry above v2.12.10. "Changed" covers the editorial redesign as a single coordinated migration (foundation tokens, every page, workspace, sidebar, all 10 admin sub-components, shared infrastructure that cascades app-wide, AiChatBubble side-sheet pivot, docker-compose env fallback). "Fixed" covers the five runtime bugs that landed alongside: /overview crash on the recent-queries polling tick, LiveQueries header showing EB/Q absurd values (UInt64-as-string string concat in useLiveQueriesStats), Query Logs avg duration showing e+58 (same root cause in useQueryLogs), AiChatBubble input invisibility, AiChatBubble welcome-state vertical over-centering, App.tsx dock mode-change race condition. ARCHITECTURE.md — new "Design System (editorial)" subsection under Frontend Architecture, placed after the API Client block. Documents the token surfaces (ink / paper / brand families and what each is for), Geist typography, the most-used class recipes (eyebrow, page header chip+title, card, primary/outline buttons, variant pills, stats grid), the role/category encoding pivot (per-role color maps → uniform hairline + 2-letter mono codes), the three shared infrastructure components that cascade the look app-wide (ConfirmationDialog, Toaster, AiChatBubble side-sheet), and the ClickHouse UInt64 boundary-coercion pattern that explains why the hooks need an explicit Number() helper to stop downstream reducers from doing string concatenation. package.json + packages/server/package.json — version 2.12.9 → 2.13.0 (minor bump, no breaking API/auth/data changes — visual refactor with backward-compatible bug fixes). README.md left as-is — Screenshots section would need fresh captures from the editorial UI; left for a follow-up that can include real images. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Explain button in the SQL editor's results-panel action strip (and in the Shortcuts dialog) was gated on \`canOptimize\`, which combines \`config.features.aiOptimizer\` with the AI_OPTIMIZE RBAC permission. Users without AI Optimize lost access to Explain entirely — there was no other entry point and Cmd+Shift+E hit a noop too because the kbd binding was inside the same gate. EXPLAIN PLAN is a standard ClickHouse feature; anyone who can run a SELECT can also EXPLAIN that SELECT. It doesn't need an AI flag. Ungate the Explain entry in both the action strip (line ~637) and the Shortcuts dialog (line ~746) so it always renders for editor users. AI Optimize stays behind canOptimize. Editorial styling on both was already in place; this just makes the button reachable. Verified in preview: /explorer with a fresh "New Query" tab — action strip now shows "RUN ⌘↵ · F5 | EXPLAIN ⌘⇧E" instead of just RUN. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sweeps the EXPLAIN visualization stack — previously left "out of scope" — into the editorial system. Affects the in-editor explain tabs (clicked via the "EXPLAIN" button or Cmd+Shift+E in the action strip) and the standalone /explain-popout page. Chrome flatten across all 8 files: - zinc-800/900 panel borders + backgrounds → ink-500 borders + ink-100/200 surfaces - zinc-300/400/500 default text → paper / paper-muted / paper-dim - rounded-lg / rounded-md container chrome → rounded-xs - "text-muted-foreground" empty/loading states → editorial mono uppercase recipe - Section titles → mono uppercase eyebrow with hairline divider - Outline / ghost buttons → editorial outline recipe - "destructive" Alerts → red-900/60 border + red-950/40 bg + red-300/200 text Per-file highlights: - ExplainTab.tsx: top tab selector (Plan / AST / Syntax / Pipeline / Estimate / Analysis) re-themed (ink-200 active, paper-dim inactive, mono uppercase); EstimateView summary stat cards flattened into a 3-cell hairline grid with mono tabular numerals; Per-table breakdown table re-themed; loading / error / empty states use the editorial mono recipe. - ExplainInfoHeader.tsx: removed the per-type `color` field on EXPLAIN_INFO entirely — the chip + arrow indicators that used it now show editorial neutral chrome. The lightbulb "Key insights" icon and arrow now use brand-yellow (semantic: insight = brand). - ExplainPopout.tsx: root container `bg-[#0a0a0a]` → `bg-ink-50`; Tabs styled identically to ExplainTab; same EstimateView pattern; fatal error and initial loading screens re-themed. - QueryAnalysisView.tsx: ComplexityCard COMPLEXITY_COLORS map kept but rewritten as the editorial destructive/warning/active chip recipe (emerald-950/40 + emerald-300 for low, amber for medium, red for high). MetricItem cards flattened with brand-yellow as the "highlight" accent. Optimizer feature pills (LIMIT / WHERE / PREWHERE / No LIMIT) re-themed: LIMIT → emerald, PREWHERE → brand, WHERE → neutral, No LIMIT → amber. RecommendationsCard SEVERITY_CONFIG follows the same pattern (info=neutral, warning=amber, critical=red). Per-agent visualization sub-views (delegated to two parallel sub-agents, both reported clean typecheck and chrome grep): - ASTView.tsx: ReactFlow Handle dots, node card, legend panel, StatsPanel, Copy button, Controls and Background all flattened. AST_STYLES (12-color map: Query/Table/Column/Filter/Sort/ Function/Literal/String/List/Expression/Identifier/Other) is SEMANTIC per node category and preserved verbatim. - PipelineView.tsx: Handles + legend + StatsPanel + Copy button + Controls + canvas all flattened. STAGE_STYLES (read=blue, filter=yellow, aggregate=purple, sort=orange, join=pink, etc.) is SEMANTIC per pipeline-stage type and preserved. Parallel-execution marker (badge + border + edge stroke) desaturated from green-500 to emerald-400 to match the editorial active-state recipe. - VisualExplain.tsx: Handles, step badge border, SQL content box, metric pill row, legend, Controls, canvas all flattened. getNodeStyle's per-node-type color map (Source=blue, Aggregate= purple, Filter=cyan, Sort=yellow, etc.) is SEMANTIC and preserved. Metric pill values (Rows=blue, Bytes=purple, Parts=green, Marks=yellow, Granules=cyan) preserved as semantic. - SyntaxView.tsx: Code block container, gutter, copy strip, legend all flattened. TOKEN_STYLES (12-color SQL token palette: keyword/function/string/number/operator/comment/identifier/ punctuation/etc.) is SEMANTIC syntax highlighting and preserved verbatim including the zinc shades for comment / identifier / punctuation that are the only intentional zinc leftovers in the codebase after this pass. Behavior, props, parsing, ReactFlow / D3 layout algorithms, and syntax-highlighter tokenization all unchanged — only visual className/CSS swapped. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User flagged the EXPLAIN tab bar as "still colorful" — the Analysis tab carried a Sparkles icon while every other tab (PLAN / AST / SYNTAX / PIPELINE / ESTIMATE) was text-only. The icon visually popped against the otherwise uniform mono uppercase strip even though it inherited currentColor, and it broke the editorial principle that identity should come from the label, not decoration. Removed the Sparkles render from the TabsTrigger in both consumers (ExplainTab.tsx in-editor + ExplainPopout.tsx standalone page) and dropped the `key === 'analysis' && "flex items-center gap-1.5"` conditional class that was only there to host the icon. All six tabs now render identically — uniform mono uppercase pills, ink-200 background when active, paper / paper-dim text per state. Cleaned the no-longer-used Sparkles + AlertCircle imports. No behavior change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
+ AI dialogs + Info/Schema/Sample + StepPreview + AiChartRenderer
+ ErrorBoundary, drop unused glass-card and old Sidebar
Closes the visual-debt remnants flagged after the previous editorial
batches. Thirteen files in total: eleven migrated, two deleted
because nothing imported them anymore (glass-card.tsx was already
replaced by native hairline cards everywhere; Sidebar.tsx was a
legacy artifact superseded by FloatingDock).
Files migrated:
- `UserManagement/index.tsx` — user-card grid was missed in the
earlier UserManagement pass (only the table chrome was done).
Cards now use `rounded-xs border border-ink-500 bg-ink-100` with
hairline emerald/paper-faint status dot, 2-letter role code chips
matching the rest of the admin (SA/AD/DV/AN/VW/GS), editorial
Edit + DropdownMenu actions. Filter-summary chips re-themed. Empty
state and pagination controls (page-size select + nav buttons +
page number pills) all editorial with brand-yellow active page.
Reset-password dialog re-themed with amber Key chip + editorial
body + brand "Done". ROLE_COLORS map removed entirely, replaced
by ROLE_CODES + getRoleDisplay() returning { displayName, code }.
- `ErrorBoundary.tsx` — fallback page (visible on global React error)
dropped the purple radial gradient + glass card chrome. Now a flat
`bg-ink-50` canvas with a `rounded-xs border-ink-500 bg-ink-100`
card, red destructive chip for the AlertTriangle, editorial mono
error panel (border-red-900/60), outline "Try again" + brand
primary "Go home". QueryErrorFallback (used by data-fetching) gets
the same red destructive recipe.
- `CreateTable.tsx` + `AlterTable.tsx` (Explorer schema dialogs) —
full chrome flatten via sub-agent. CreateTable swaps emoji
type-picker icons for 2-letter mono codes (ST/I3/DT/...), unifies
per-type quick-pick pills (toYYYYMM blue + toYYYYMMDD green) to
brand. AlterTable tabs unified (was tab-tinted green/blue/orange
per ALTER operation), drop-column confirmation kept semantic red,
modify-type warning kept semantic amber.
- `DebugQueryDialog.tsx` + `OptimizeQueryDialog.tsx` (AI dialogs)
— full chrome flatten via sub-agent. Indigo/purple AI-template
accents (header icon, status pills, model badge, error chip,
markdown headings, list markers, code blocks, "Apply Changes"
button) all consolidated under brand-yellow as the single AI
accent. Diagnosis / success / warning panels use the editorial
destructive/emerald/amber recipe. DiffEditor wrapper hairlined.
Markdown renderer color overrides updated (h1/h2/h3 → paper, li
bullet → brand). Streaming/abort logic untouched.
- `InfoTab.tsx` + `SchemaSection.tsx` + `DataSampleSection.tsx`
(table-info panel from sidebar tree click) — full chrome flatten
via sub-agent. Schema per-type color dots (`#ce9178` etc.) and
type-class CSS preserved as semantic data-type indicators (these
use the VSCode-style palette for at-a-glance distinguishing of
String / Number / Date / etc.). Other chrome — backdrop blur,
white/[0.02] panels, lowercase headers — all flattened to mono
uppercase + hairline ink-100.
- `StepPreview.tsx` (Import Wizard schema preview) — full chrome
flatten via sub-agent. Destination mode toggle, all inputs/selects/
checkboxes, mobile + desktop column rows, Advanced collapsible,
data-preview table — all editorial. `CLICKHOUSE_TYPES` color map
preserved as semantic (distinguishes ClickHouse types in the
type-picker dropdown). Nullable/sort-key indicator state pivoted
from emerald-500 to brand-yellow.
- `AiChartRenderer.tsx` (AI chat chart rendering) — full chrome
flatten via sub-agent. Card wrapper, title border, download
button, DropdownMenu content, footer caption, empty state, and
HeatmapTable shell. Recharts per-series palette and tooltip
styles preserved as semantic (multi-series distinguisher);
HeatmapTable per-cell intensity shading preserved (value
encoding). Two pre-existing recharts Formatter type errors
unchanged — these are pre-existing and flagged ignorable in the
redesign progress notes.
Files deleted:
- `src/components/ui/glass-card.tsx` (85 lines) — `GlassCard`
primitive only existed to render the AI-template glass cards; all
call sites already migrated to native `<div className="rounded-xs
border border-ink-500 bg-ink-100">` patterns.
- `src/components/common/Sidebar.tsx` (292 lines) — legacy sidebar
component; the app uses `FloatingDock` everywhere. Verified zero
imports across the codebase before deletion.
Behavior, validation, form state machines, DDL builders, streaming,
markdown rendering, chart layout, error tracking, and all API/state
contracts untouched across all eleven migrated files.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tabs Closes the last two scoped-out items from the editorial redesign backlog. Both files are large (AiChatBubble ~2000 lines, Metrics ~2500 lines) and were handled by sub-agents with the same recipes that drove the earlier batches. src/components/common/AiChatBubble.tsx — ~75 swaps across the deep chat internals (the outer side-sheet shell was already done in 8d484a9). Affected: - Sidebar thread buttons (active state, edit input, timestamps, rename/delete) — violet accents → brand for AI signal, hairline ink/paper chrome - CollapsibleThreadGroup sticky group header — dropped backdrop-blur, mono editorial eyebrow - ThinkingPanel — container, running pulse bar, args pre block, summary checkmark (kept emerald-400/80 for the "done" checkmark as semantic success) - Markdown renderer overrides — h1/h2/h3 sized 18/16/14 per spec, code block + inline code on hairline ink-200, table/thead/th/td with mono uppercase headers, p/ul/ol bullets in brand, blockquote with brand border, links in brand - Message bubbles — assistant avatar (brand-tinted AI signal chip), user bubble (ink-200), assistant bubble (ink-100), error bubble (red-950/40 destructive recipe), tool-status indicator (mono text-brand) - Mobile FAB live dot — violet ring → ink-100 ring, kept emerald live indicator as semantic "online" - Active thread row keeps a left brand-bar marker (`border-l-brand border-l-2`) — editorial convention for navigation actives src/pages/Metrics.tsx — ~95 sub-agent swaps + ~15 manual chrome fixes I added on top (header Select trigger, refresh button, the TabsList strip + 7 tab triggers, footer Quick Actions buttons). Affected sub-tabs: Performance, Storage, Merges, Errors, System, Network. The Overview tab (done previously in 8da7238) and the StatCard / MetricChartCard helpers were skipped. - 17 section header chip+title blocks: dropped per-section icon tints (blue/indigo/cyan/purple/orange/emerald/red); now uniform paper-muted chip with the canonical Overview-style chip+title composition - 30+ <StatCard color=... bgColor=... /> invocations: dropped decorative color props (StatCard's helper ignores them — source now matches reality) - Replaced ad-hoc StatCard gap-3 grids with hairline `grid border-l border-t border-ink-500` wrappers so the inner cells form a proper editorial grid - Storage Detailed Breakdown: flattened sub-headers, rebuilt the disk-usage Progress + Top Tables table with editorial tokens - Merges Replication table: editorial chrome, READONLY/HEALTHY status pills with red/emerald destructive recipe - Errors Exception Log: editorial card chrome, semantic red Code: pills - Errors "No errors" empty state: green pulse → emerald hairline chip - Header time-range select, refresh, and footer Quick Actions (15min / 1 hour / 24 hours / Auto-refresh toggle) — editorial outline buttons with mono uppercase labels; auto-on state uses the emerald-950/40 active chip recipe - TabsList: glass white/10 backdrop-blur strip → hairline ink-100 bordered strip; all seven triggers (Overview + 6 sub-tabs) use uniform mono uppercase pills with `data-[state=active]:bg-ink-200 data-[state=active]:text-paper`. Active glow drop-shadow removed Semantic preserved across both files: - AiChatBubble: emerald for success/online, brand for AI signal, semantic red destructive for error bubbles - Metrics: tiered colors on disk-usage / replication-delay / parts count, READONLY/HEALTHY badges, MetricChartCard color props (drives chart line color, semantic chart consistency), chart palette arrays (per-series distinguisher) Behavior, streaming, tool-call execution, virtualizer, markdown preprocessing, retry, data fetching, chart rendering, prodMetrics access — all untouched. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR migrates the entire main app SPA from the inherited "AI-template" look (purple→blue gradients, glassmorphism, animated rings, drop-shadow glow, emoji role icons, vivid status pills, drag-to-move floating panels, bright richColors toasts) to the editorial style we already shipped on the landing page: dark monochrome canvas, ClickHouse-yellow as the sole brand accent, Geist typography, hairline borders, mono uppercase eyebrows for chrome.
Versioned as v2.13.0 (minor bump — visual refactor + backward-compatible bug fixes, no API/auth/data contract changes). 35 commits on top of
main; ~6.6k lines added / ~7.3k deleted across 69 files (net code reduction — the editorial recipes are simpler than the patterns they replace, the AiChatBubble side-sheet pivot deleted ~100 lines of drag/zoom/resize state, and two unused legacy components were dropped entirely).What changed
Foundation
src/index.css: editorial design tokens (ink-0…ink-700 scale, paper / paper-muted / dim / faint text scale, brand / brand-soft / brand-dim ClickHouse-yellow), Geist Sans + Geist Mono, dark shadcn vars remapped to ink/paper/brandsrc/App.tsx: dropped the root radial purple-violet gradient + twobg-purple-500/10/bg-blue-500/10blur orbs that bled a violet halo under every authenticated page. Now flatbg-ink-50— every page stops fighting the haze underneathPages
Login,Home/Overview,Explorershell,Monitoringwrapper,LiveQueries,Logs,Metrics(StatCard + chart wrappers + 7 sub-tabs; Overview tab progress bars flattened to brand-yellow default with amber 60–80% / red >80% tier),Adminwrapper,Preferences(full rewrite)Workspace
WorkspaceTabs,HomeTabempty state,SqlEditortoolbar (header strip, save-status pill, Save/Kill dialogs, Shortcuts dialog),SqlTabMonaco wrapper + result/explain/statistics tabs,EmptyQueryResult,StatisticsDisplayExplainTab,ExplainInfoHeader,ASTView,PipelineView,VisualExplain,SyntaxView,QueryAnalysisView,ExplainPopoutpage. Chrome flattened (zinc/gray panels → ink + paper, rounded-lg → rounded-xs, "muted-foreground" empty states → editorial mono uppercase). EstimateView stat cards + per-table breakdown rebuilt as hairline grids with mono tabular numerals. ComplexityCard / RecommendationsCard severity recipe (emerald/amber/red 950/40 chips). Optimizer feature pills (LIMIT/WHERE/PREWHERE/No LIMIT) re-themed with the right semantic accent each. Decorative Sparkles icon dropped from the Analysis tab — every tab now renders identically as mono uppercase pills; identity comes from the label, not from a per-tab badge. Semantic colors preserved on purpose: AST_STYLES 12-color node-category map, PipelineView STAGE_STYLES per-stage palette, VisualExplain per-node-type colors + metric pill values, SyntaxView TOKEN_STYLES 12-color SQL token palette — all of these encode meaning, not decoration. Parallel-execution marker in PipelineView desaturated from green-500 to emerald-400 to match the editorial active-state recipe.Sidebar / dock
FloatingDock,DataExplorersidebar tree +TreeNode,ConnectionSelector(4 states: loading / no-conn / single / multi),UserMenu, Saved Queries connection filter dropdown (Pin icon brand-yellow for active connection, Layers + Database icons paper-dim for other options)Admin sub-components
UserManagement— hairline 4-cell stats grid, editorial avatar initials chip, brand "(You)" badgeRbacRolesTable— unifiedROLE_STYLEwith 2-letter codes (SA/AD/DV/AN/VW/GS) replacing the previous per-role color mapConnectionManagement+ConnectionUserAccess— hairline tables, brandDefaultpill, emerald/ink Active/Inactive, brandDirect access+ monoVia rolespillsClickHouseUsersfull 4-step wizard (1,943-line file): stepper with brand progress fill, unified DV/AN/VW role cards with brand selection state, editorial database/table tree (Database & Table icons kept in paper-faint as semantic), emerald/ink password requirement pills, amber DDL preview warning, brand primary footerAiModels(index + ProvidersTab + BaseModelsTab + ConfigsTab) — Bot/Server/Sliders chips, mono internal tab bar, hairline tables, emerald/ink Active/Inactive, brand Default pill, editorial dialogsCreateUser(1,100 lines) — GlassCard wrappers → hairline cards; ROLE_COLORS emoji map → ROLE_CODES 2-letter; amber generated-password warning panelEditUser(1,007 lines) — User Info 4-cell hairline grid, per-tab colored active states → uniform editorial, brand Save/Reset Password, semantic red Delete dialogUserDataAccess— hairline rules table with emerald/red Allow/Deny pills, brand Save, editorial Add/Edit Rule dialogRoleFormDialog— gradient header strip + glassy purple icon box → editorial chip+title, brandX selectedpill, brand checkboxes, semantic red system-role warningRbacAuditLogs— 11-variantACTION_COLORSmap → single uniformACTION_BADGE, hairline 4-cell stats with emerald/red Success/Failed counts, editorial filters (Select + Calendar popover), emerald/red/ink status pillsRbacAuditPruneDialog— destructive red trigger pill, red Confirm Deletion, brand-tinted single-date calendarRbacAuditExportDialog— editorial outline trigger, brand-tinted range calendar, brand Download CSVPreferences (full rewrite)
StatusFooterhelper replaces four differently-colored "Session Active / Operational / Active Policy Engine / Permissions Visualized" tags with one brand|emerald variantShared infrastructure (single edits that cascade app-wide)
ConfirmationDialog— variant chip (red destructive / amber warning / brand info / emerald success) carries the semantic, title stays neutral. Cascades to every delete/logout confirmation in the app: Preferences logout, Admin → Users/Roles/CH Users/AI Models/Connections/Data access rulesToaster— droppedrichColorsso we can register editorial classNames per variant (success emerald-950/30 + emerald-300 icon, error red-950/30 + red-300, warning amber-950/30 + amber-300, info ink-100). Also fixed a quiet bug where{...props}in the wrapper component was overridingtoastOptionsfrom callers (main.tsx passedcloseButton: trueand accidentally dropped our classNames)AiChatBubble— converted from a draggable floating modal (1340×840 default, zoom system, corner/bottom/left resize handles, position state, drag-to-move header) to a right-anchored side-sheet (compact 420 / standard 560 / wide 760 width cycle, full viewport height, slide-in from right edge). Net −97 lines. Editor and chat now coexist instead of fighting. Mobile FAB behavior unchangedBug fixes that landed along the way
Home.tsx:q.duration.toFixed is not a functioncrash on/overviewafter the recent-queries polling tick. ClickHouse occasionally serializesdurationas a string; now defensively coerced withNumber()+Number.isFiniteguardApp.tsxdock mode-change race condition:FloatingDockdispatched the event beforeApp.tsxhad attached its listener (React child effects run before parent effects). Dispatch now goes throughsetTimeout(_, 0)so the listener wins the raceExplainbutton missing from SQL editor: inSqlTab.tsx, the Explain entry in both the results-panel action strip and the Shortcuts dialog was gated oncanOptimize(=config.features.aiOptimizer && AI_OPTIMIZEpermission). Users without AI Optimize lost access to Explain entirely — the keyboard shortcut Cmd+Shift+E was inside the same gate too, so it was a noop.EXPLAIN PLANis a standard ClickHouse feature; anyone who can run a SELECT can also EXPLAIN it. Ungated both entries; AI Optimize stays behindcanOptimizeAiChatBubbleinput invisible against the newbg-ink-100panel — bumped tobg-ink-200for a differentiated surfaceAiChatBubblethread-empty welcome state was vertically over-centered post-flatten — reflowed to top-aligned like ChatGPT's empty stateLiveQueriespage formatters:formatBytesreturned"102.93 undefined"(sizes array ran off the end at TB),formatNumberproduced 17-digit raw concatenated strings with.00Bsuffix. Hardened to coerce input viaNumber(), extended unit table to YB, all formatters use 2 decimals, and a centralizedfixed2()helper falls back to compact.toExponential(2)for values past 1e15 instead of letting.toFixed()produce 80-character numbersuseLiveQueriesStatshook: actual root cause of the LiveQueries header showing "16.47 EB" / "700.15Q" for 2 queries summing 5 GB / 1.37B rows. ClickHouse JSON serializes UInt64 fields as strings to preserve precision past 2^53. The reduce was doingacc + q.memory_usage— with a string operand, JS+switches from numeric addition to string concatenation, producing e.g."01900000000350000000"for two ~2 GB queries, which then coerced to ~1.9e19 bytes. Fixed by running each summed field through atoFinite()helper before the reduce. The formatter hardening from the previous bullet now becomes defense in depthuseQueryLogshook: same UInt64-as-string root cause, different surface — Query Logs header showed "Avg duration: 9.651382226469024e+58ms" for 50 queries averaging 4ms.event_timestampwas already coerced viaNumber()butquery_duration_ms,read_rows,read_bytes,memory_usagewere assigned raw from the API. Fixed at the boundary so all consumers (including two later remaps in the same file that read from the now-coercedlogsarray) inherit the fixdocker-compose.yml: switched the JWT_SECRET / RBAC_ENCRYPTION_KEY / RBAC_ENCRYPTION_SALT entries to${VAR:-fallback}substitution. Operators can now drop real-length secrets via shell or.envwithout forking the compose file; the dev placeholders still let the container boot locallyDocs
CHANGELOG.md— new v2.13.0 entry above v2.12.10 with the full Changed/Fixed breakdownARCHITECTURE.md— new "Design System (editorial)" subsection under Frontend Architecture: token surfaces, typography, class recipes, role/category encoding pivot, the three shared infrastructure components (ConfirmationDialog / Toaster / AiChatBubble side-sheet), and the ClickHouse UInt64 boundary-coercion patternpackage.json+packages/server/package.json— 2.12.9 → 2.13.0README.mdintentionally left as-is — Screenshots section would need fresh captures from the editorial UI; left for a follow-upFinal sweep — backlog cleanup
Three batches of follow-up work closed every "out of scope" item from earlier iterations:
ErrorBoundary(visible on any unhandled React crash) — purple radial gradient + glass card → flat ink-50 canvas with red destructive chip, editorial mono error panel, brand "Go home" button.QueryErrorFallbackmirrors the same recipe.CreateTable.tsx(emoji type-picker → 2-letter mono codes ST/I3/DT/...) andAlterTable.tsx(per-tab green/blue/orange CTAs unified to brand-yellow).DebugQueryDialog.tsxandOptimizeQueryDialog.tsx: indigo/purple AI-template accents consolidated under brand-yellow. Markdown renderer overrides updated (h1/h2/h3 → paper, list markers → brand, code blocks hairline). Diagnosis/error/warning panels follow the destructive/emerald/amber recipe.InfoTab.tsx+SchemaSection.tsx+DataSampleSection.tsx(shown when clicking a table from the sidebar tree). VSCode-style per-data-type color dots preserved as semantic; everything else flattened.CLICKHOUSE_TYPESper-type color map preserved as semantic for the type-picker dropdown; nullable/sort-key state pivoted from emerald-500 to brand-yellow.src/components/ui/glass-card.tsx(85 lines, no imports remained) andsrc/components/common/Sidebar.tsx(292 lines, superseded by FloatingDock).Out of scope (intentional follow-up PRs)
NotFound(rare, single-page surface)Behavioral guarantees
No props, state shape, callback signature, validation rule, route, auth flow, RBAC permission check, or API contract was changed. Where semantic colors carry meaning they're preserved:
Test plan
/overviewstability — sit on the page for ~2 minutes through the recent-queries polling tick. The page used to crash withq.duration.toFixed is not a function; should no longer reproduce.bg-ink-50canvas. No violet/purple haze under cards./monitoring/live-queries. When queries are actively running, the TOTAL MEMORY / ROWS READ cards should show plausible values (GB / millions or billions) — NOT "16.47 EB" / "700.15Q" / "1.879e+82 EB" or similar. Per-query cells and aggregate totals should be in the same order of magnitude./monitoring/logs. AVG DURATION should be a small number of ms (typically 1–500ms) — NOT exponential like "9.65e+58ms"./monitoring/metrics. Resource Utilization progress bars are brand-yellow under normal load, amber 60–80%, red >80%. Stat tiles + Recent Errors + Largest Tables all hairline editorial (no bright purple / blue / cyan)./admin→ click each tab in turn (Users, Roles, Connections, ClickHouse users, AI models, Audit logs). Every table, badge, and dialog should match the editorial chrome. Click "Create user" and walk to step 2 — role cards show 2-letter code chips with brand-yellow selection state./preferences. Profile hero has initials chip + emerald online dot + brand role pills. Four cards (Identity / ClickHouse node / Data access / Functional access) all use neutral paper-muted chips. Admin sees "FULL GLOBAL ACCESS" with brand chip./explorer, click "New query". The results-panel action strip should showRUN ⌘↵ · F5ANDEXPLAIN ⌘⇧E. Open the Shortcuts dialog (button in editor toolbar) — "Explain query plan" should be listed. Both should be visible regardless of whether the user has AI Optimize permission.SELECT 1and click EXPLAIN (or Cmd+Shift+E). Walk through every tab in turn: Plan (VisualExplain), AST, Syntax, Pipeline, Estimate (and Analysis if AI Optimizer is disabled). Every tab should have editorial chrome (ink/paper, hairline borders, mono uppercase labels). Per-node-type colors in Plan/Pipeline/AST, per-token colors in Syntax, and severity tier colors in Analysis (emerald/amber/red) should remain — they encode meaning. Pop out to the standalone page via the popout button — same chrome there too.