Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
432f563
refactor(app): port editorial tokens, redesign Login
May 18, 2026
4320309
refactor(app): redesign Home / Overview to editorial style
May 18, 2026
13b107b
refactor(app): redesign FloatingDock / Sidebar to editorial style
May 18, 2026
ef54ea5
refactor(app): redesign Explorer shell + WorkspaceTabs + HomeTab
May 18, 2026
7d8c9bd
refactor(app): redesign DataExplorer sidebar tree to editorial style
May 18, 2026
a40af8e
fix(app): dock mode-change event must outlive parent listener attach
May 18, 2026
929decc
refactor(app): redesign SqlTab editor + result panel to editorial
May 18, 2026
24f35c0
refactor(app): redesign SqlEditor toolbar + dialogs to editorial
May 18, 2026
46e9bf1
refactor(app): redesign EmptyQueryResult + StatisticsDisplay editorial
May 18, 2026
313e509
refactor(app): redesign ConnectionSelector + UserMenu in dock
May 18, 2026
4b5cae0
refactor(app): redesign AiChatBubble visible surfaces to editorial
May 18, 2026
0ce8220
fix(app): AiChatBubble input area was invisible against new panel bg
May 18, 2026
a82102e
fix(app): AiChatBubble empty-thread state was vertically over-centered
May 18, 2026
8a7a612
refactor(app): redesign Monitoring page wrapper to editorial
May 18, 2026
e77f360
refactor(app): redesign LiveQueries page to editorial
May 18, 2026
50c2cd3
refactor(app): redesign Logs page to editorial
May 18, 2026
502029c
refactor(app): redesign Metrics page to editorial (large pass)
May 18, 2026
94669ca
refactor(app): redesign Admin page wrapper to editorial
May 18, 2026
aa4411a
refactor(admin): redesign Admin sub-components to editorial style
May 18, 2026
8da7238
refactor(admin): editorial pass on Admin dialogs + Metrics overview f…
May 18, 2026
e3652e2
chore(docker): allow env override for JWT and RBAC secrets in compose
May 18, 2026
df2beb0
refactor(app): redesign Preferences page + drop global purple decor
May 18, 2026
b6bba0d
refactor(common): editorial pass on shared ConfirmationDialog
May 18, 2026
752b45e
refactor(common): editorial toast styling for sonner Toaster
May 18, 2026
4a576cc
refactor(explorer): editorial pass on Saved Queries connection filter
May 18, 2026
8d484a9
refactor(ai-chat): convert AiChatBubble from draggable modal to side-…
May 18, 2026
4ae0514
fix(live-queries): harden stat formatters against bogus/huge values
May 18, 2026
50f3077
fix(live-queries): coerce UInt64 strings before summing stats
May 18, 2026
81d3a76
fix(query-logs): coerce UInt64 fields in useQueryLogs at the boundary
May 18, 2026
f4084d2
docs: v2.13.0 — CHANGELOG + ARCHITECTURE design system section
May 18, 2026
6d9a611
fix(workspace): show Explain button regardless of AI Optimize permission
May 18, 2026
15e7816
refactor(workspace): editorial pass on EXPLAIN feature (8 files)
May 18, 2026
f8fb8b8
refactor(explain): drop decorative Sparkles icon from Analysis tab
May 18, 2026
6389dd3
refactor(ui): editorial sweep — UserManagement card + Explorer dialogs
May 18, 2026
94b4ab6
refactor(ui): editorial pass on AiChatBubble internals + Metrics sub-…
May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,39 @@ The `ApiClient` class (`src/api/client.ts`) is a singleton that handles:
- **Session recovery** — `onSessionExpired` callback in `App.tsx` automatically reconnects ClickHouse sessions
- **Request headers** — Adds `Authorization: Bearer <token>`, `X-Session-Id`, and `X-Requested-With: XMLHttpRequest` to all requests

### Design System ("editorial")

Since v2.13.0 the entire SPA renders in a single visual language defined in `src/index.css`. Tailwind v4's `@theme` block declares the tokens; shadcn semantic CSS vars (`--background`, `--primary`, `--border`, etc.) are remapped to those tokens so every primitive (Dialog, Select, Tabs, DropdownMenu, Tooltip, Toast) inherits the look without per-component overrides.

**Token surfaces**

| Family | Tokens | Use |
|---|---|---|
| `ink-*` (0/50/100/200/300/500/700/800) | dark canvas → elevated surface → border | `bg-ink-50` canvas, `bg-ink-100` card, `bg-ink-200` nested/elevated, `border-ink-500` hairline divider |
| `paper`, `paper-muted`, `paper-dim`, `paper-faint` | text scale (high → low contrast) | `text-paper` headings, `text-paper-muted` body, `text-paper-dim` meta, `text-paper-faint` mono eyebrows |
| `brand`, `brand-soft`, `brand-dim` | ClickHouse-yellow accent | primary CTA, default/active marker, brand tint for selected state — never decoration |
| Semantic palettes | `emerald-*`, `red-*`, `amber-*` (kept from Tailwind) | only when the color carries meaning (success / destructive / warning) — never for chrome |

**Typography**: Geist Sans + Geist Mono via Google Fonts. Body text is sans; eyebrows, labels, badges, and numeric/code display use mono with `uppercase tracking-[0.14em–0.18em]`.

**Class recipes** (used across the app — see editorial style guide in user memory for full set):
- **Eyebrow**: `inline-flex items-center gap-2 font-mono text-[10px] uppercase tracking-[0.18em] text-paper-dim` (with optional `<span className="h-px w-6 bg-ink-700" />` divider)
- **Page header chip+title**: 9×9 hairline icon chip + 18px semibold title + mono eyebrow subtitle
- **Card**: `rounded-xs border border-ink-500 bg-ink-100` for default surface, `bg-ink-200` for nested
- **Primary button**: `h-9 gap-2 rounded-xs bg-brand px-3 font-mono text-[11px] font-semibold uppercase tracking-[0.14em] text-ink-50 hover:bg-brand-soft`
- **Outline button**: `h-9 gap-2 rounded-xs border-ink-500 bg-ink-100 px-3 font-mono text-[11px] uppercase tracking-[0.14em] text-paper hover:border-ink-700 hover:bg-ink-200`
- **Variant pills**: 1.5px-padding chips with mono uppercase 10px — `border-emerald-900/60 bg-emerald-950/40 text-emerald-300` (success), `border-red-900/60 bg-red-950/40 text-red-300` (destructive), `border-brand/40 text-brand` (default/active), `border-ink-500 bg-ink-200 text-paper-faint` (neutral)
- **Stats grid**: hairline `border-l border-t border-ink-500` wrapper with `border-b border-r border-ink-500` per cell, mono label + `font-mono tabular-nums` numeric value

**Role/category encoding**: per-role color maps (e.g. `ROLE_COLORS`, `ACTION_COLORS` with 11 hues) replaced by 2-letter mono codes (`SA/AD/DV/AN/VW/GS` for RBAC roles) or uniform hairline chips. Identity comes from the label, not the hue.

**Shared infrastructure that cascades the look app-wide**:
- `ConfirmationDialog` (`src/components/common/`) — one component reused by every delete/logout confirmation
- `Toaster` (`src/components/ui/sonner.tsx`) — sonner without `richColors`, with registered classNames per variant, so every `toast.success/error/warning/info` call inherits the editorial pill
- `AiChatBubble` (`src/components/common/`) — right-anchored side-sheet (compact 420 / standard 560 / wide 760 width cycle, full viewport height, slide-in from right) replacing the previous draggable floating modal. Industry-standard pattern (Cursor / Copilot Chat / JetBrains AI). Mobile keeps full-screen slide-up via FAB.

**Boundary coercion for ClickHouse numerics**: ClickHouse JSON serializes UInt64 / Float64 fields as strings to preserve precision past 2^53. Hooks that expose these fields (`useQueryLogs`, `useLiveQueriesStats`) run them through a local `toFinite()` / `num()` helper at the API boundary so that downstream reducers can safely use `+` for numeric addition (without the helper, JS coerces to string concatenation, producing the absurd e+58 / e+82 stat displays seen pre-v2.13.0).

---

## Backend Architecture
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [v2.13.0] - 2026-05-18

### Changed

- **Editorial redesign of the main app SPA**: migrated every authenticated page 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 already shipped on the landing page. The new system uses a dark monochrome canvas (`bg-ink-50`), ClickHouse-yellow as the sole brand accent, Geist Sans + Geist Mono typography, hairline `border-ink-500` borders, and mono uppercase eyebrows for chrome.
- **Foundation**: `src/index.css` ports the editorial design tokens (ink-0…ink-700 scale, paper/paper-muted/dim/faint text scale, brand/brand-soft/brand-dim ClickHouse-yellow). Dark shadcn vars are remapped to ink/paper/brand. `src/App.tsx` drops the root radial purple-violet gradient and the two `bg-purple-500/10` / `bg-blue-500/10` blur orbs that bled a violet halo under every authenticated page.
- **Pages**: Login, Home/Overview, Explorer shell, Monitoring wrapper, LiveQueries, Logs, Metrics (StatCard + chart wrappers + 7 sub-tabs; Overview tab progress bars use brand-yellow default with amber 60–80% / red >80% tier), Admin wrapper, Preferences (full rewrite with 4 SettingCards, profile hero with initials chip + emerald online dot + brand role pills, `StatusFooter` helper).
- **Workspace**: WorkspaceTabs, HomeTab empty state, SqlEditor toolbar (header strip, save-status pill, Save/Kill/Shortcuts dialogs), SqlTab Monaco wrapper + result/explain/statistics tabs, EmptyQueryResult, StatisticsDisplay.
- **Sidebar / dock**: FloatingDock, DataExplorer sidebar tree + TreeNode, ConnectionSelector (4 states), UserMenu, Saved Queries connection filter dropdown.
- **Admin sub-components**: UserManagement, RbacRolesTable (unified `ROLE_STYLE` with 2-letter codes SA/AD/DV/AN/VW/GS replacing the per-role color map), ConnectionManagement + ConnectionUserAccess, ClickHouseUsers full 4-step wizard (stepper with brand progress fill, unified DV/AN/VW role cards, editorial database tree, emerald/ink password requirement pills, amber DDL preview warning), AiModels (index + Providers/BaseModels/Configs tabs), CreateUser, EditUser, UserDataAccess, RoleFormDialog, RbacAuditLogs (11-variant `ACTION_COLORS` → single uniform `ACTION_BADGE`), RbacAuditPruneDialog, RbacAuditExportDialog.
- **Shared infrastructure cascading edits**:
- `ConfirmationDialog` (`src/components/common/`) — variant chip (red destructive / amber warning / brand info / emerald success) carries the semantic, title stays neutral. Cascades to every delete/logout confirmation across the app (Preferences logout, Admin → Users/Roles/CH Users/AI Models/Connections/Data access rules).
- Sonner `Toaster` (`src/components/ui/sonner.tsx` + `src/main.tsx`) — dropped `richColors`, registered 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 draggable modal to side-sheet**: removed the 1340×840 floating modal with zoom system, corner/bottom/left resize handles, and free position state. Replaced with a right-anchored side-sheet (compact 420 / standard 560 / wide 760 width cycle, full viewport height, slide-in from right edge with cubic-out easing). Persistence kept compatible with `getChatPrefsFromWorkspace` / `mergeChatPrefsIntoWorkspace` — position pinned to {0,0}, sheet width stored in `size.width`. Net −97 lines after dropping `useDragControls`, `PanInfo`, and three resize handlers. Mobile FAB behavior unchanged. Industry-standard pattern matching Cursor / Copilot Chat / JetBrains AI.
- **`docker-compose.yml`**: JWT_SECRET / RBAC_ENCRYPTION_KEY / RBAC_ENCRYPTION_SALT switched to `${VAR:-fallback}` substitution. Operators can drop real-length secrets via shell or `.env` without forking the compose file; dev placeholders still allow local boot.

### Fixed

- **`/overview` crash after polling tick**: `q.duration.toFixed is not a function` thrown by `Home.tsx:738` when the recent-queries API serialized `duration` as a JSON string (ClickHouse UInt64 / Float64 fields arrive as strings to preserve precision past 2^53). Now defensively coerced through `Number()` + `Number.isFinite` guard.
- **Live Queries header stats showed absurd values (`16.47 EB`, `700.15Q`, `1.879e+82 EB`)**: root cause was `useLiveQueriesStats` doing `acc + q.memory_usage` in a reduce. With a string operand, JS `+` switches from numeric addition to string concatenation — 4 queries of ~2.5 GB became literal "0247423385618345678903142559232367289..." which coerced to ~1e40 bytes. Fixed by introducing a `toFinite()` helper at the boundary and running every summed field through `Number()` before `+` or `Math.max`. Defense-in-depth in the `LiveQueries.tsx` formatters: extended bytes units B → YB so the index always lands in-bounds, all formatters use 2 decimals via a centralized `fixed2()` helper that falls back to compact `.toExponential(2)` for values past 1e15, formatters now accept `unknown` and coerce internally.
- **Query Logs avg duration showed `9.65e+58ms`**: same UInt64-as-string root cause in `useQueryLogs`. Only `event_timestamp` was being run through `Number()`; `query_duration_ms`, `read_rows`, `read_bytes`, `memory_usage` were assigned raw from the API. The `durations.reduce((sum, d) => sum + d, 0)` consumer in `Logs.tsx` was doing string concatenation, then dividing by 50. Fixed at the boundary in `useQueryLogs` — the two later remaps in the same hook read from the now-coerced `logs` array and inherit the fix automatically.
- **AiChatBubble input invisible against new `bg-ink-100` panel**: bumped to `bg-ink-200` for a differentiated surface.
- **AiChatBubble thread-empty welcome state vertically over-centered** post-flatten: reflowed to top-aligned, matching the ChatGPT empty-state pattern.
- **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.


## [v2.12.10] - 2026-04-01

### Fixed
Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ services:
- "5521:5521"
environment:
NODE_ENV: production
JWT_SECRET: dev-secret-change-me
RBAC_ENCRYPTION_KEY: dev-key-change-me
RBAC_ENCRYPTION_SALT: dev-salt-change-me
JWT_SECRET: ${JWT_SECRET:-dev-secret-change-me}
RBAC_ENCRYPTION_KEY: ${RBAC_ENCRYPTION_KEY:-dev-key-change-me}
RBAC_ENCRYPTION_SALT: ${RBAC_ENCRYPTION_SALT:-dev-salt-change-me}
# Uncomment and provide a config.yaml to use hierarchical configuration
# CHOUSE_CONFIG_PATH: /app/config.yaml
volumes:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "chouseui",
"private": true,
"author": "daun-gatal",
"version": "2.12.9",
"version": "2.13.0",
"license": "Apache-2.0",
"type": "module",
"workspaces": [
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chouseui/server",
"version": "2.12.9",
"version": "2.13.0",
"type": "module",
"scripts": {
"dev": "bun run --watch src/index.ts",
Expand Down
8 changes: 1 addition & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,7 @@ const MainLayout = () => {
}, []);

return (
<div className="h-screen w-full overflow-hidden bg-[#0a0a0a] bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))] relative flex">
{/* Background Decor */}
<div className="absolute inset-0 z-0 pointer-events-none">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl opacity-50" />
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl opacity-50" />
</div>

<div className="relative flex h-screen w-full overflow-hidden bg-ink-50">
{/* Spacer for sidebar mode (hidden during fullscreen) */}
{isSidebarMode && !isFullscreen && <div className="w-14 flex-shrink-0" />}

Expand Down
Loading
Loading