Skip to content

Ref app page phase 1#202

Merged
daun-gatal merged 35 commits into
daun-gatal:mainfrom
abduldjafar:ref-app-page-phase-1
May 18, 2026
Merged

Ref app page phase 1#202
daun-gatal merged 35 commits into
daun-gatal:mainfrom
abduldjafar:ref-app-page-phase-1

Conversation

@abduldjafar
Copy link
Copy Markdown
Collaborator

@abduldjafar abduldjafar commented May 18, 2026

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/brand
  • src/App.tsx: dropped the root radial purple-violet gradient + two bg-purple-500/10 / bg-blue-500/10 blur orbs that bled a violet halo under every authenticated page. Now flat bg-ink-50 — every page stops fighting the haze underneath

Pages

  • Login, Home/Overview, Explorer shell, Monitoring wrapper, 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), Admin wrapper, Preferences (full rewrite)

Workspace

  • WorkspaceTabs, HomeTab empty state, SqlEditor toolbar (header strip, save-status pill, Save/Kill dialogs, Shortcuts dialog), SqlTab Monaco wrapper + result/explain/statistics tabs, EmptyQueryResult, StatisticsDisplay
  • EXPLAIN visualization stack (8 files): ExplainTab, ExplainInfoHeader, ASTView, PipelineView, VisualExplain, SyntaxView, QueryAnalysisView, ExplainPopout page. 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, DataExplorer sidebar 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)" badge
  • RbacRolesTable — unified ROLE_STYLE with 2-letter codes (SA/AD/DV/AN/VW/GS) replacing the previous per-role color map
  • ConnectionManagement + ConnectionUserAccess — hairline tables, brand Default pill, emerald/ink Active/Inactive, brand Direct access + mono Via roles pills
  • ClickHouseUsers full 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 footer
  • AiModels (index + ProvidersTab + BaseModelsTab + ConfigsTab) — Bot/Server/Sliders chips, mono internal tab bar, hairline tables, emerald/ink Active/Inactive, brand Default pill, editorial dialogs
  • CreateUser (1,100 lines) — GlassCard wrappers → hairline cards; ROLE_COLORS emoji map → ROLE_CODES 2-letter; amber generated-password warning panel
  • EditUser (1,007 lines) — User Info 4-cell hairline grid, per-tab colored active states → uniform editorial, brand Save/Reset Password, semantic red Delete dialog
  • UserDataAccess — hairline rules table with emerald/red Allow/Deny pills, brand Save, editorial Add/Edit Rule dialog
  • RoleFormDialog — gradient header strip + glassy purple icon box → editorial chip+title, brand X selected pill, brand checkboxes, semantic red system-role warning
  • RbacAuditLogs — 11-variant ACTION_COLORS map → single uniform ACTION_BADGE, hairline 4-cell stats with emerald/red Success/Failed counts, editorial filters (Select + Calendar popover), emerald/red/ink status pills
  • RbacAuditPruneDialog — destructive red trigger pill, red Confirm Deletion, brand-tinted single-date calendar
  • RbacAuditExportDialog — editorial outline trigger, brand-tinted range calendar, brand Download CSV

Preferences (full rewrite)

  • 4 SettingCards (Identity / ClickHouse node / Data access / Functional access) with uniform paper-muted chips — no per-card color hue; the title carries the identity
  • Profile hero with 80px hairline initials chip + emerald online dot + brand role pills (replaces the 96px gradient avatar + animated ping ring)
  • Data access tree flattened: indigo→transparent separator → flat hairline; per-rule chips emerald/red Allow/Deny; Admin "FULL GLOBAL ACCESS" empty state uses brand chip
  • New StatusFooter helper replaces four differently-colored "Session Active / Operational / Active Policy Engine / Permissions Visualized" tags with one brand|emerald variant
  • Log out button: red-tinted ghost on red hover (semantic, not the old purple→red transition)

Shared 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 rules
  • Sonner Toaster — dropped richColors so 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 overriding toastOptions from callers (main.tsx passed closeButton: true and 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 unchanged

Bug fixes that landed along the way

  • Home.tsx: q.duration.toFixed is not a function crash on /overview after the recent-queries polling tick. ClickHouse occasionally serializes duration as a string; now defensively coerced with Number() + Number.isFinite guard
  • App.tsx dock mode-change race condition: FloatingDock dispatched the event before App.tsx had attached its listener (React child effects run before parent effects). Dispatch now goes through setTimeout(_, 0) so the listener wins the race
  • Explain button missing from SQL editor: in SqlTab.tsx, the Explain entry in both the results-panel action strip and the Shortcuts dialog was gated on canOptimize (= config.features.aiOptimizer && AI_OPTIMIZE permission). 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 PLAN is a standard ClickHouse feature; anyone who can run a SELECT can also EXPLAIN it. Ungated both entries; AI Optimize stays behind canOptimize
  • AiChatBubble input invisible against the new bg-ink-100 panel — bumped to bg-ink-200 for a differentiated surface
  • AiChatBubble thread-empty welcome state was vertically over-centered post-flatten — reflowed to top-aligned like ChatGPT's empty state
  • LiveQueries page formatters: formatBytes returned "102.93 undefined" (sizes array ran off the end at TB), formatNumber produced 17-digit raw concatenated strings with .00B suffix. Hardened to coerce input via Number(), extended unit table to YB, all formatters use 2 decimals, and a centralized fixed2() helper falls back to compact .toExponential(2) for values past 1e15 instead of letting .toFixed() produce 80-character numbers
  • useLiveQueriesStats hook: 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 doing acc + 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 a toFinite() helper before the reduce. The formatter hardening from the previous bullet now becomes defense in depth
  • useQueryLogs hook: same UInt64-as-string root cause, different surface — Query Logs header showed "Avg duration: 9.651382226469024e+58ms" for 50 queries averaging 4ms. event_timestamp was already coerced via Number() but query_duration_ms, read_rows, read_bytes, memory_usage were 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-coerced logs array) inherit the fix
  • docker-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 .env without forking the compose file; the dev placeholders still let the container boot locally

Docs

  • CHANGELOG.md — new v2.13.0 entry above v2.12.10 with the full Changed/Fixed breakdown
  • ARCHITECTURE.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 pattern
  • package.json + packages/server/package.json — 2.12.9 → 2.13.0
  • README.md intentionally left as-is — Screenshots section would need fresh captures from the editorial UI; left for a follow-up

Final sweep — backlog cleanup

Three batches of follow-up work closed every "out of scope" item from earlier iterations:

  • UserManagement card grid — was missed in the original Users pass (only the table view was migrated). Cards now use 2-letter role codes, hairline ink-100 surfaces, emerald/paper-faint status dot. Pagination + reset-password dialog re-themed.
  • 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. QueryErrorFallback mirrors the same recipe.
  • Schema dialogsCreateTable.tsx (emoji type-picker → 2-letter mono codes ST/I3/DT/...) and AlterTable.tsx (per-tab green/blue/orange CTAs unified to brand-yellow).
  • AI dialogsDebugQueryDialog.tsx and OptimizeQueryDialog.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 familyInfoTab.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.
  • ImportWizard StepPreview — full chrome flatten; CLICKHOUSE_TYPES per-type color map preserved as semantic for the type-picker dropdown; nullable/sort-key state pivoted from emerald-500 to brand-yellow.
  • AiChartRenderer — outer card, title border, download button, DropdownMenu, footer caption, empty state, HeatmapTable shell. Recharts per-series palette + cell-intensity shading preserved as semantic.
  • AiChatBubble deep internals — message bubbles (user/assistant), tool-call cards, ThinkingPanel, markdown renderer overrides, sidebar thread buttons, mobile FAB live dot. Violet AI accents → brand-yellow. Active thread row keeps a left brand-bar marker.
  • Metrics sub-tabs (Performance / Storage / Merges / Errors / System / Network) — 17 section header chip+title blocks + 30+ StatCard invocations dropped per-card decorative tints (StatCard helper was already ignoring them). TabsList re-themed from glass strip to hairline ink-100 with mono uppercase triggers. Footer Quick Actions (15min / 1h / 24h / Auto toggle) editorial. Tiered colors on disk-usage / replication-delay / parts-count preserved as semantic; MetricChartCard color props preserved for chart-line consistency.
  • Dead code removedsrc/components/ui/glass-card.tsx (85 lines, no imports remained) and src/components/common/Sidebar.tsx (292 lines, superseded by FloatingDock).

Out of scope (intentional follow-up PRs)

  • Pages: 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:

  • red for destructive / required / errors
  • emerald for success / active / validation pass
  • amber for warnings (e.g., DDL preview, generated password notice)
  • brand-yellow for default / active / primary CTA

Test plan

  1. Toasts — log in. Bottom-right toast should be a dark editorial pill (not bright green); the "Connected to {name} (v{ver})" pair shows up after the first connection. Trigger any save/error elsewhere — same shell with emerald/red/amber tint per variant.
  2. /overview stability — sit on the page for ~2 minutes through the recent-queries polling tick. The page used to crash with q.duration.toFixed is not a function; should no longer reproduce.
  3. Background — every authenticated page should have a flat bg-ink-50 canvas. No violet/purple haze under cards.
  4. Explorer sidebar — open the tree (DATABASES / PINNED / RECENT / QUERIES). Switch to QUERIES, check the connection filter dropdown: Pin icon is brand-yellow for the active connection, Layers + Database icons paper-dim for other options.
  5. AI chat side-sheet — click the "Ask AI" pill on the right edge. Sheet slides in from the right, anchored to the right edge, full viewport height. Click the maximize toggle in the header — width cycles 420 → 560 → 760. Drag the sheet's left edge — width adjusts and persists per device.
  6. Live queries stats — open /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.
  7. Query logs stats — open /monitoring/logs. AVG DURATION should be a small number of ms (typically 1–500ms) — NOT exponential like "9.65e+58ms".
  8. Metrics → Overview/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).
  9. Admin sweep/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.
  10. Delete / logout confirmation — trigger any delete (e.g., Admin → Users → delete row) and a logout (Preferences → Log out). Same dialog pattern in both: red destructive chip + flat title + editorial Cancel + red Confirm.
  11. Preferences/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.
  12. Explain button visibility — open /explorer, click "New query". The results-panel action strip should show RUN ⌘↵ · F5 AND EXPLAIN ⌘⇧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.
  13. EXPLAIN visualizations — write a simple SELECT 1 and 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.
  14. Mobile FAB — narrow the browser to mobile width. AI chat trigger should become a floating action button (not the side pill); opening it should slide up full-screen as before.

Abdul Haris Djafar and others added 26 commits May 18, 2026 12:39
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>
@abduldjafar abduldjafar marked this pull request as draft May 18, 2026 09:39
Abdul Haris Djafar and others added 3 commits May 18, 2026 16:41
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>
@abduldjafar abduldjafar marked this pull request as ready for review May 18, 2026 10:06
Abdul Haris Djafar and others added 6 commits May 18, 2026 17:12
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>
@daun-gatal daun-gatal self-requested a review May 18, 2026 21:45
@daun-gatal daun-gatal merged commit ea5adfb into daun-gatal:main May 18, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants