Commit Graph
279 Commits
Author SHA1 Message Date
Juan DenisandClaude Fable 5 2f66507a0c feat(ui): restyle the Terminal/Logs page to the flush console look
Log Files + Journal tabs -> single bordered console frame (con-rail
target list with mono eyebrow groups, accent-bg active rows, session-head
viewer bar, log well on --bg-code); Processes tab -> same frame with
sk-dtable-spec sortable table + token gauges; Services tab cards re-
tinted with status stripes. Terminal gets its OWN proc-chip filter chips
(no longer depends on _templates.scss .filter-chip); process/service
statuses -> ds Pills; hashColor/priority palettes retinted to the new
palette. Legacy block kept for shared consumers (JournalControls/
ProcessTable/ServiceCard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 00:17:54 -04:00
Juan DenisandClaude Fable 5 b38053d448 feat(ui): restyle the Cron Jobs body to the new look
StatCards -> MetricCard KPI strip (jobs/active/service/platform from real
status), job cards -> dense .sk-dtable (clock tile, mono schedule chip +
readable hint, Pill status, all 4 row actions kept incl. run-now), new
list head with search + All/Enabled/Disabled SegControl (client-side
only), modals reskinned with mono eyebrows + --bg-code run output.
Next-run/history/success-rate omitted (backend gap, map S5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 00:17:53 -04:00
Juan DenisandClaude Fable 5 a16ff1b8f8 feat(ui): restyle ServiceDetail tabs + Templates catalog to the new look
ServiceDetail: header -> ServiceTile gradient avatar + ds Pill + mono
dotsep meta (adds the real domain link); OverviewTab -> MetricCard strip
+ sk-info-row key/values + EnvTag + Gauge bars, deploy statuses -> Pills;
MetricsTab gauges tokenized (no fake history charts - backend gap);
Packages -> sk-dtable; Events -> tone dots + Pills; logs/shell/commands/
gunicorn consoles re-tinted to --bg-code (fixes broken light theme);
settings/git modal polished via SCSS only. Templates: catalog -> tpl-card
grid (icon tile, mono version, clamped desc, tag chips, amber featured
star, hover-reveal Deploy wired to the existing install flow) + cat-chip
category filter; drawer no longer stacks under the install modal. All
tabs, filters, handlers, and deep links kept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:45:28 -04:00
Juan DenisandClaude Fable 5 8a61bd67ab feat(ui): restyle the ServerDetail tab bodies to the new look
Header -> demo detail-header (surface gradient + accent wash, mono dotsep
meta, ds Pill status); overview KPI tiles -> spec-card look with thin ds
Gauges (CPU/RAM/Disk retinted to accent-bright/cyan/green convention);
tab strip -> accent underline; data tables + services/packages tables ->
dense sk-dtable spec (scoped fixes for their shadcn-var partials);
docker/cron/cloudflared tabs re-skinned with Pills and mono eyebrows.
Dead blocks removed (kpi-gauge ring, resource-meters, duplicate
server-detail-tabs, notifications-tab). Servers LIST + ops workspace
styles untouched; all tabs, actions, telemetry, and deep links kept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:45:16 -04:00
Juan DenisandClaude Fable 5 8f46ac8369 feat(ui): restyle the Monitoring body to the new look
Overview: StatStrip -> MetricCard KPI strip, current metrics -> host-card
grid (icon tile + Pill + thin token-colored Gauges + mono host-mini
footer), active alerts -> alert-row pattern with red-wash firing chips.
Alert Rules / Delivery / History tabs re-skinned in place (Pills, mono
eyebrows, surface tokens); all CRUD, scheduler, and channel config kept.
Synthetic uptime monitors / response-time charts deferred (backend gap,
REDESIGN_MAP S5). Also removes the dead .alert-item block that collided
with _servers.scss.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:45:05 -04:00
Juan DenisandClaude Fable 5 6687c429fa feat(ui): restyle the FTP Server body to the new look
Completes the Files group. StatStrip -> MetricCard KPI strip (status/
active server/users/connections), users + connections tables -> .sk-dtable
with Pills and mono cells, mono eyebrow section headers, surface-token
cards, log viewer on --bg-code. Replaced the broken icon-font <span>s with
lucide icons inside the same buttons. Dead pre-topbar blocks dropped.
All capabilities kept (install flow, user CRUD + password modal,
disconnect, logs, deep-linked tabs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:44:55 -04:00
Juan DenisandClaude Fable 5 f31bc89958 feat(ui): restyle the File Manager body to the flush three-pane console
SCSS-only reskin of the FileManager page to the demo's `.fmgr` look:
- page goes full-bleed flush (no floating cards): toolbar / tree rail /
  listing / status bar joined by 1px soft borders
- left rail: flat groups under mono-uppercase eyebrow headers, demo tree
  rows (accent-bg active, amber folders), dense type filters + disk gauges
- list view: sticky mono-uppercase headers, tighter rows, mono size/
  modified/permissions cells
- grid view: transparent cards with surface-2 hover, accent-bg selection
  (type-tinted thumbs kept)
- status bar + breadcrumbs go mono; bulk bar becomes a flush accent strip
- type palette literals aligned to the redesign tokens (amber/violet/
  green/cyan); context menu + preview drawer re-tinted to surface tokens
- dropped dead blocks from the pre-topbar era (.page-header, .sort-popover,
  .filter-chips-row, .quick-access-*, .analysis-*, .toolbar-select)

All capabilities kept (uploads, bulk ops, type filters, disk mounts,
remote targets, shortcuts) — zero JSX changes. Build verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:08:22 -04:00
Juan DenisandClaude Opus 4.8 b758fda2fd feat(ui): restyle the DNS Zones + SSL bodies to the new look
Finish the Domains trio visually:
- DNS Zones: records table -> dense .sk-dtable; record type rendered as a colored .dns-rtype chip (A/CNAME/MX/TXT/NS/SOA…); content cell uses .sk-cell-mono. Drop unused Badge import.
- SSL Certificates: per-cert status Badge -> Pill (green Valid / amber Expiring). Drop unused Badge import.
Both already themed from Phase 0; these add the redesign's chip/pill language.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 00:25:09 -04:00
Juan DenisandClaude Opus 4.8 506945c44b feat(ui): restyle the Services list to a KPI strip + dense table
StatStrip -> MetricCard KPI strip (Running/Stopped/Total/Last Deploy with tone icons + subs); per-row status -> Pill; list header -> mono uppercase dense, list card on surface/border-soft tokens. All existing logic kept: search/type/status/sort filters, multi-select + bulk start/stop/restart, repo pill, per-row action buttons, and wordpress/service navigation. Drop now-unused StatStrip/Stat import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:57:55 -04:00
Juan DenisandClaude Opus 4.8 d18d20ce36 feat(ui): restyle the WordPress site list to a dense table
Convert the .wp-site-card grid into a dense .sk-dtable: Site tile + name/port, Environments, Version (mono), Status (Pill green/gray), tag chips, and Open-Site/WP-Admin links. Add a status SegControl (All/Running/Stopped) alongside the existing tag filter; row click still opens the site detail. Omitted SSL/plugins/visits/uptime/server columns (not in the get_sites payload — no list backend) per restyle-first. Drop an unused Badge import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:11:22 -04:00
Juan DenisandClaude Opus 4.8 e2faf9dc6e feat(ui): restyle the Servers list table to the demo's dense look
Visual restyle of the existing .servers-table (no column/capability changes — bulk-select, group, telemetry, and row actions all kept): mono uppercase headers, and thin token-colored telemetry gauges (CPU=accent-bright, RAM=cyan, Disk=green). Region/Load columns omitted (mock-only, no backend field) per restyle-first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:18:28 -04:00
Juan DenisandClaude Opus 4.8 e2968b2197 feat(ui): migrate the WordPress group to the top-bar layout (sidebar now flat)
Final sidebar sub-menu moved to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared WORDPRESS_TABS (WordPress / Pipeline); render PageTopbar on WordPress (sites list) and WordPressProjects (pipeline).
- Remove the wordpress subItems from the sidebar — NO sidebar item has a sub-menu anymore; the rail is fully flat, matching the demo.
- WordPress list: both headers (loading + main) swapped; Import Site + Create Site actions kept.
- Pipeline list header swapped (title 'Pipeline').
- The Pipeline tab is now always visible (was wpInstalled-gated in the sidebar).
Six infra groups total now use the top-bar layout: Domains, Servers, Services, Files, Monitoring, Marketplace, WordPress. Body restyles still pending per group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:07:35 -04:00
Juan DenisandClaude Opus 4.8 7cc27cb82f feat(ui): migrate Monitoring + Marketplace groups to the top-bar layout
Final two sidebar sub-menus moved to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Monitoring group: shared MONITOR_TABS (Monitoring/Status Pages); PageTopbar on both pages (Refresh + Start/Stop + Create-Page actions kept).
- Marketplace group: shared MARKET_TABS (Marketplace/Downloads); PageTopbar replaces the marketplace hero; Downloads header swapped (Import ZIP + Refresh kept).
- Remove the monitoring + marketplace subItems from the sidebar.
Only WordPress (Pipeline) still has a sidebar sub-menu; everything else is now flat + top-bar. Body restyles still pending per group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:35:21 -04:00
Juan DenisandClaude Opus 4.8 faa6355c75 feat(ui): migrate the Files page group to the top-bar layout
Move Files + FTP to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared FILE_TABS (Files / FTP Server); render PageTopbar on both pages.
- Remove the files subItems from the sidebar (route /ftp unchanged, reachable from the tab).
- FileManager: top bar added as the first child of the full-bleed fullscreen browser (already a flex column; body keeps filling) — the toolbar, tree, preview, upload tray and all file ops are untouched.
- FTPServer: .page-header swapped for PageTopbar; the conditional install/test/start/stop/restart actions moved verbatim into the top bar.
- Fix a pre-existing unescaped-apostrophe lint error in FTPServer.
Follow-up: restyle the FileManager + FTP bodies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:26:55 -04:00
Juan DenisandClaude Opus 4.8 c6d44ac502 feat(ui): restyle New Service mode chooser to the demo's method cards
Restyle .new-service-page__mode-card into the demo's centered method-card look (icon chip on top, title, description, corner check on the active card) and tokenize the old teal/indigo active-state literals. GitHub-OAuth repo picker, manifest detection, and the template flow are all preserved. Follow-up: template-catalog grid + Docker-Image/From-Scratch methods.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:14:08 -04:00
Juan DenisandClaude Opus 4.8 4e95c2257f feat(ui): migrate the Services page group to the top-bar layout
Move Services + its sub-pages to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared SERVICE_TABS (Services/New Service/Templates/Deploy Activity); render PageTopbar on all 4 group pages so the tab group persists.
- Remove the services subItems from the sidebar (routes /services/new, /templates, /deployments unchanged, reachable from the tabs).
- Services: header -> PageTopbar (meta shows total/running; New Service action kept).
- NewService: drop the now-redundant breadcrumb; Connections action kept.
- Templates: search box moved into the top-bar actions.
- Deployments: New Service + Live/Paused + Refresh actions kept.
Follow-up: restyle the bodies (NewService method-cards + template catalog, Services dense table, ServiceDetail tabs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:07:48 -04:00
Juan DenisandClaude Opus 4.8 8e61ad08d8 feat(ui): migrate the Servers page group to the top-bar layout
Move Servers + its 4 sub-pages to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared SERVER_TABS (Servers/Agent Fleet/Fleet Monitor/Cloud Servers/Config Templates) and render PageTopbar on all 5 pages so the tab group persists.
- Remove the servers subItems from the sidebar (routes unchanged, reachable from the tabs).
- Main Servers page: top bar added ABOVE the full-bleed ops workspace (container -> column); the fleet-health rail, status/group filtering, workbar, bulk actions and pairing are fully preserved — no capability removed.
- Sub-pages (AgentFleet/FleetMonitor/CloudProvision/ServerTemplates): .page-header swapped for PageTopbar, action buttons preserved.
- Fix a pre-existing unescaped-quote lint error in FleetMonitor.
Follow-up: restyle the Servers list body to the demo's dense table + ServerDetail tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 04:30:51 -04:00
Juan DenisandClaude Opus 4.8 0eab32096b feat(ui): share the top-bar tab group across Domains/DNS/SSL
Finish the domain page group so the Domains/DNS Zones/SSL tabs persist on all three:
- Extract DOMAIN_TABS to components/domains/domainTabs.jsx; Domains imports it.
- DNSZones + SSLCertificates pages now render the shared <PageTopbar tabs={DOMAIN_TABS}> in place of their old headers (actions preserved).
- Fix a pre-existing unescaped-apostrophe lint error in SSLCertificates.
DNS/SSL page bodies (lists/forms) are not yet restyled — headers only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 04:03:29 -04:00
Juan DenisandClaude Opus 4.8 68695a779c feat(ui): migrate Domains to the demo's top-bar layout
First infra page moved to the demo structure (REDESIGN_MAP §6 decision 3):
- Add shared PageTopbar ds primitive (icon + title + routed sub-nav + actions).
- Domains: top bar with Domains/DNS Zones/SSL tabs (replaces the removed sidebar sub-menu), KPI strip (MetricCard), segmented filter (SegControl), .sk-dtable list, and a detail Drawer with SSL/linked-site/status specs + all actions (visit/verify/SSL/delete). Add-domain + enable-SSL modals preserved.
- Remove the domains subItems from sidebarItems.js (/dns, /ssl routes unchanged, reachable from the top-bar tabs).
- Omit registrar/DNSSEC/nameservers columns (no WHOIS backend yet) per restyle-first.
Follow-up: give DNSZones + SSLCertificates pages the same top bar so the tab group persists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:46:46 -04:00
Juan DenisandClaude Opus 4.8 0b9ee6c61e feat(ui): Phase 1 page restyles (dashboard, settings, databases, docker, marketplace)
- Dashboard: metric tiles -> MetricCard, apps status -> Pill; retint MetricsGraph to the new palette while keeping the recharts chart intact.
- Settings: nav group labels -> mono eyebrow, toggles -> gradient-on, theme-preview swatches repainted.
- Databases: engine status-dot glow, green result count, UTF-8 + Connected status-bar segments.
- Docker: MetricCard KPI strip atop the content area, status filter -> segmented control (kept the rich dx- rail/tabs/inspector).
- Marketplace: mono eyebrows, token card shadow, accent category chips (kept tabs + install-from-source).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:35:04 -04:00
Juan DenisandClaude Opus 4.8 e8b5299415 refactor(styles): tokenize hardcoded colors to design tokens
Replace accidental old-palette literals (old indigo/green/amber/zinc surfaces+borders, white/black washes) with design tokens so they theme correctly in light mode and track a custom accent. Intentional literals (brand colors, categorical log/node/file-type palettes, theme-preview mockups, white-on-color, scrims) were preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:34:49 -04:00
Juan DenisandClaude Opus 4.8 326bc89679 feat(ui): redesign foundation — tokens, self-hosted fonts, primitives, sidebar
Phase 0 of the ServerKit UI redesign (see docs/REDESIGN_MAP.md):
- Remap design tokens to the new infra-console palette behind existing names; add prototype token names (surface ramp, cyan/violet, accent-bright/-bg). Dark/light/system.
- Self-host IBM Plex (woff2 vendored via scripts/fetch-fonts.mjs); no Google Fonts CDN.
- Runtime-derive the full accent ramp in ThemeContext.
- Add shared design-system primitives in components/ds (Pill, EnvTag, SegControl, MetricCard, Gauge, ServiceTile, Sparkline, AreaChart, ScoreGauge, Feed, Drawer).
- Restyle the sidebar (gradient rail, mono section headers, left accent bar) and switch the brand to the demo's gradient-tile logo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:34:28 -04:00
Juan Denis 986909a955 Surface Docker DBs, add base port & migration fixes
Expose and surface containerised databases and introduce managed app base port control. Backend: add endpoint to list databases across all Docker apps, robustly resolve compose container names, include docker DB user when querying, compute migration needs from live heads and self-heal orphaned alembic_version entries by stamping to head, and add the managed_app_base_port system setting. Template service now honours the managed_app_base_port and WordPress template default HTTP port updated to 8300. Frontend: add react-icons brand icons and DatabaseBrands component, show Docker-hosted DBs under their engine nodes with a docker badge, platform-aware console shortcut label, pass DB user to docker table/query API calls, add API client header merge fix, UI for managing base port in settings, enhanced migration history UI (backup/apply/resync flows, confirm dialog, toasts), and various style tweaks and color additions.
2026-06-06 23:29:22 -04:00
Juan DenisandClaude Opus 4.8 2d83440040 feat(workspaces): viewer/editor grant roles + operate-endpoint access (#33)
Completes the chosen access-level ACL design: a grant now lets the user actually
operate the resource, gated by a real role.

- Grants carry a role: editor (view + operate) or viewer (read-only). grant POST
  validates role in {viewer, editor} (default editor); ResourceGrantService gains
  grant_role(); the Sharing UI gains a role selector.
- Two access helpers split the app endpoints: _can_access_app (owner/admin/ANY
  grant) gates reads (get_app, /linked, /logs, /container-logs, /containers,
  /status); _can_edit_app (owner/admin/EDITOR grant) gates mutations (/link,
  /unlink, /environment, PUT, /start, /stop, /restart).
- DELETE stays owner-or-admin (an editor can't delete — tested).
- The same sweep fixed a latent int-vs-str ownership bug across ~14 app endpoints
  (compared app.user_id to the stringified get_jwt_identity(); now user.id).

Verified: 3 more ACL tests (viewer read-only + can't operate, editor operates +
can't delete, role validation/default), full suite 130 passed, lint + build.
Still owner-only: env-vars/databases/domains blueprints (follow-up sweep).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:13:18 -04:00
Juan DenisandClaude Opus 4.8 4dca43b6b0 feat(workspaces): sharing UI for per-site ACL grants (#33)
Completes the per-site ACL feature with UI on top of the backend (d9a5ec7).

- A per-app 'Share' action in the Workspaces Resources modal opens an inline
  sub-view: current grantees with Revoke, plus a grant-access list of users
  (the owner and existing grantees are filtered out).
- Client methods getAppGrants / grantAppAccess / revokeAppAccess.

Verified: frontend lint-clean (no new warnings) + production build. The grant
endpoints are covered by backend tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:03:38 -04:00
Juan DenisandClaude Opus 4.8 35c7a73d8c feat(workspaces): manage servers in the workspace Resources modal (#33)
Completes the reassignment feature symmetrically — the server reassign endpoint
shipped earlier (e89415f) was API-only; now it has UI.

- getServers gains an allWorkspaces bypass (empty X-Workspace-Id header) and a
  setServerWorkspace client method (mirrors apps).
- The per-workspace modal is now a 'Resources' modal with both Applications and
  Servers sections, each with move-in / move-out.

Verified: frontend lint-clean (no new warnings) + production build. The server
endpoint itself is already covered by backend tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 11:18:46 -04:00
Juan DenisandClaude Opus 4.8 e89415f3e0 feat(workspaces): reassign apps/servers between workspaces (#33)
Completes the organizing loop — existing resources were stuck wherever the
backfill put them (Default); now they can be moved into client workspaces.

- PUT /apps/<id>/workspace (owner-or-admin on the app) and
  PUT /servers/<id>/workspace (developer+), both requiring the TARGET be a
  workspace the caller can access (member or admin). A null target moves the
  resource back to Default.
- UI: an 'Apps' management modal on each Workspaces card (mirrors the Members
  modal) with move-in / move-out. It fetches with allWorkspaces (an empty
  X-Workspace-Id header) so it can see apps in other workspaces to move them in,
  sidestepping the ambient scope.
- Fixed a real bug caught by tests: the app ownership check compared app.user_id
  to the stringified get_jwt_identity() (int != str -> spurious 403); now uses
  user.id.

Verified: 2 new endpoint tests (ownership + target-membership + null->Default +
role gates), full suite 122 passed, frontend lint-clean + production build.
Servers reassignment is API-only for now; the modal is apps-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:32:30 -04:00
Juan DenisandClaude Opus 4.8 567d6059f7 feat(workspaces): workspace-scoped brand color recolors the panel (#33)
Selecting a client workspace now recolors the whole panel to that client's brand
— the visible payoff of the workspace effort.

- A workspace's primary_color (already on the model) is now editable via a color
  picker added to the Workspaces create modal.
- WorkspaceSwitcher mirrors the active workspace's color into a workspace_accent
  localStorage key; ThemeContext applies it via the existing applyAccentToDOM with
  precedence over the user's personal accent (workspace_accent || accentColor),
  recoloring the logo gradient, buttons, and highlights.
- Deterministic (no effect-ordering races): switching reloads and the value is
  read once on mount. Cleared on logout and on a stale selection; reverts to the
  user accent on 'All workspaces'. Default color is the app accent, so a workspace
  is unbranded unless a custom color is chosen.

Verified: frontend lint-clean (no new warnings) + production build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:19:57 -04:00
Juan DenisandClaude Opus 4.8 ddff69020b feat(workspaces): active-workspace selector + ambient X-Workspace-Id header (#33)
Makes the workspace scoping foundation user-visible — the research flagged the missing 'current workspace' context as the core gap #33 had to close.

- WorkspaceSwitcher (sidebar): self-contained selector that reads/writes an
  active_workspace_id in localStorage, fetched from the user's workspaces. Hides
  for single-workspace installs and drops a stale selection on load.
- services/api/client.js: sends the active workspace ambiently as X-Workspace-Id
  on every request (endpoints that don't honor it ignore it); cleared on logout.
- Switching reloads so all lists re-fetch under the new scope.

Backend resolve_workspace_id is lenient (prior commit) so a stale header can
never break the apps/servers lists. Verified: frontend lint-clean + production
build; the header->scoping path is covered by the backend API tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 08:54:01 -04:00
Juan DenisandClaude Opus 4.8 1926416f27 feat(wordpress): monthly client reports — persisted per-month rollups (#33)
The self-contained reports slice of the #33 agency-scale milestone, built
without the high-blast-radius workspace_id/ACL/branding rework.

- WpReportsService aggregates one calendar month per site: uptime % + a daily
  series + incidents (re-aggregated from #26 HealthCheck samples for the exact
  month), update runs (#29), DatabaseSnapshot backups, and current vulnerability
  posture (#28) + health/disk — each labelled persisted-history vs point-in-time.
- Persisted (WordPressReport, Alembic 014 off 013_ai_assistant) + synchronous
  upsert per (site, YYYY-MM), so point-in-time sources (vulns, health) are
  captured as-of generation while true-historical sources are re-queried per
  month. Client grouping derives from the client-* tag (#20) — no workspace_id
  FK. Model lives in wordpress_site.py + lazy-imported in the service so the
  concurrent AI agent's models/__init__.py is untouched.
- Routes GET /reports, POST /reports/generate (admin), DELETE /reports/<id>;
  client getReports/generateReport/deleteReport; a Reports tab with a recharts
  daily-uptime chart, summary cards, Print->Save-as-PDF (scoped @media print,
  no PDF dep) and JSON export.
- 9 pytest tests; 3-lens adversarial review (4 of 5 findings fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 03:04:03 -04:00
Juan DenisandClaude Opus 4.8 5ad7a4bc3f refactor(ui): consolidate per-page styles into shared cards
Reworks Git, Services, Firewall, FTP, Deployments, Monitoring, and
Marketplace pages onto the shared StatCard/card system and removes their
now-redundant page-specific SCSS (net large reduction in duplicated
styles).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:50:35 -04:00
Juan DenisandClaude Opus 4.8 53f440abb0 refactor(ui): security page + overview adopt shared cards
Moves Security page and its Overview tab onto the shared StatCard/card
styles and trims the now-redundant per-page SCSS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:50:23 -04:00
Juan DenisandClaude Opus 4.8 5338e2930a refactor(ui): expand shared StatCard + card system
Grows the shared card stylesheet and StatCard component so page-level
surfaces can drop their bespoke card SCSS and reuse one vocabulary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:50:16 -04:00
Juan DenisandClaude Opus 4.8 919300498e feat(databases): rebuild Databases page as a full-screen explorer
Replaces the five-tab Databases page with a DataGrip-style, full-bleed
Database Explorer built on the file-manager shell:
- unified source tree for all engines with inline + right-click actions
  (open console, back up, drop, query, copy name)
- tabbed workspace: click a table to open a paginated data-grid tab,
  open SQL consoles per database, and a Backups tab
- toolbar New menu (console / create database / create user, disabled
  when the engine is not installed) and a live status bar
- frontend-only: table browsing runs SELECT ... LIMIT/OFFSET via the
  existing query endpoints; no API changes

Rewrites _databases.scss into the explorer design system (tree, tabs,
results grid, console, context menu, status bar) using existing tokens;
respects prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:41:35 -04:00
Juan DenisandClaude Opus 4.8 caee882844 feat(databases): add Database Explorer building blocks
Reusable pieces behind the new full-screen database UI:
- dbAdapter: unifies MySQL/PostgreSQL/SQLite/Docker behind one
  listTables/getStructure/runQuery interface, with per-dialect identifier
  quoting and a LIMIT/OFFSET browse-query builder
- SourceTree: lazy, status-aware tree (engine -> database -> tables)
- ConsoleTab: SQL console (Ctrl+Enter, read-only/write toggle,
  per-connection history, CSV export)
- TableDataTab: paginated data grid + Structure view
- ResultsGrid: shared sortable result table (sticky header, NULL handling)
- BackupsTab: backups list with engine filter
- modals: create database / user dialogs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:41:24 -04:00
Juan DenisandClaude Opus 4.8 9cab065dc5 feat(ai): add core AI assistant primitive powered by Prompture
AI is a core ServerKit primitive (not a plugin): an Intercom-style bubble + slide-in drawer on every dashboard page, with a tools+page-context assistant mode and a plain simple chat mode, SSE streaming, conversation persistence, and guarded write actions (the model proposes; the user approves).

Plugins EXTEND the assistant rather than rebuilding it: backend via app.plugins_sdk.ai (reg.tool + register_context_provider, discovered from a plugin.json ai block with install/enable/disable hooks); frontend via useServerkitAI() and a contributions.ai category (suggested_prompts, tool_renderers). The marketplace gains an ai category for discovery.

Backend: ai_service (Prompture Conversation + ProviderEnvironment + guardrails), ai_tool_registry singleton, ai_tools_builtin (core.* tools), the /api/v1/ai blueprint (SSE via a worker-thread + queue bridge, single-gevent-worker safe; in-thread confirmation gate), AiConversation/AiMessage/AiPendingAction models + migration 013, encrypted ai_* settings. Frontend: AIContext + components/ai/*, AI settings tab. Powered by Prompture (installed editable; requirements pinned).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:39:05 -04:00
Juan DenisandClaude Opus 4.8 15fb6ebae0 feat(wordpress): safe update manager — snapshot, rollback, schedule (#29)
Background safe update (Docker-correct, via wp_cli): record pre-update versions
-> wp db export a DB snapshot (600s timeout via a new wp_cli timeout param so a
large DB is never truncated; ABORTS if the snapshot fails) -> apply updates only
to components with an available update minus an exclusion list -> a quiet,
side-effect-free health probe (wp eval + HTTP probe; does NOT fire #27 alerts)
-> if the update regressed a previously-healthy site, AUTO-ROLLBACK: version-pin
each updated component back (--force --skip-plugins --skip-themes, so it runs
even on a fatally-broken site) + re-import the snapshot -> re-check -> persist a
WordPressUpdateRun report. The snapshot is kept for manual restore whenever the
site does not end verified-healthy.

A per-site cron schedule + exclusion list is driven by a new daemon-thread
scheduler bounded by a concurrency semaphore (a shared 3am cron can't stampede
the host). New model WordPressUpdateRun + auto_update_schedule/auto_update_exclude
columns (Alembic 012). New Updates tab (run-now, schedule, exclusions, history).

Also fixes a latent #26/#27 bug: EnvironmentHealthService.check_health returned
'unknown' for every production site (_get_compose_path needs a container_prefix
they lack), making uptime/incidents/alerts inert for the primary site type — now
falls back to the Application root_path compose.

Deferred: staging-first promotion (needs a staging env + env-pipeline
orchestration). Verified: backend py_compile, frontend lint-clean (full build was
blocked only by a concurrent agent's in-progress AI import, not #29 code),
2-lens adversarial review (blockers fixed: production health gating, DB-import
timeout; majors fixed: scheduler stampede, snapshot-abort).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:22:51 -04:00
Juan DenisandClaude Opus 4.8 5e2d4afbda feat(wordpress): per-site security depth — integrity, debug, WP-Cron (#30)
A per-site Security tab (Docker-correct, all via the WP-CLI bridge):

- File integrity: a background wp core verify-checksums + wp plugin
  verify-checksums --all (in-container, reaches the volume; off the request
  thread, UI polls) flagging tampered/unexpected files.
- Debug toggle: WP_DEBUG/SCRIPT_DEBUG with WP_DEBUG_DISPLAY=false and
  WP_DEBUG_LOG=/tmp/wp-debug.log — OUTSIDE the web root, so the log is never
  publicly fetchable (the default wp-content/debug.log is apache-served, a real
  info leak this avoids). The write is gated so a read-only/stopped site reports
  an honest failure.
- WP-Cron: DISABLE_WP_CRON status + due-event list, run-due-now, enable/disable
  (with a clear "needs a real system cron" warning).

Routes mirror the per-site pattern (owner-or-admin GET, @admin_required
mutations). Deferred: the brute-force wp-login/xmlrpc rate-limit jail —
Fail2ban/nginx limit_req both need a host-side per-site access log the
localhost:PORT apache-container model does not expose (same gap as #21/#25).

Verified: backend py_compile, frontend lint + build, focused security review
(debug-log web-exposure caught + fixed). Not runtime-exercised on a real host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:57:43 -04:00
Juan DenisandClaude Opus 4.8 2b5db90f89 feat(wordpress): per-site plugin/theme/core vulnerability scanning (#28)
Cross-reference a site's installed plugin/theme/core slug+version against the
keyless community WPVulnerability API and surface findings per site.

WpVulnerabilityService reads installed versions via the Docker-aware WP-CLI
bridge (wp core version + get_plugins/get_themes directly — not the
host-filesystem-gated get_wordpress_info, which fails for volume-backed Docker
sites), queries https://www.wpvulnerability.net/{plugin,theme,core}/ (no key,
1h in-memory cache, descriptive UA, 8s timeout), matches the installed version
against each advisory operator bounds (core advisories all apply — the feed is
version-scoped), and extracts CVE id / severity (cvss3 -> cvss short-code ->
score) / fixed-in / reference.

Persisted as a new WordPressVulnerability child model (FK -> wordpress_sites.id)
+ a last_vuln_scan_at column (Alembic 011, idempotent). The scan runs in a
background thread (Lynis-style) so the single worker never blocks; a new
Vulnerabilities tab polls for status and renders findings with severity badges,
CVE links, and a summary. On-demand only.

Security (3-lens review): reference_url restricted to http(s) (blocks
javascript: hrefs); feed slug percent-encoded; unrecognized version operator
skips rather than over-flags; the major Docker volume-backed core-read gating
was fixed so normal sites scan. Verified: backend py_compile, frontend lint +
build. Not runtime-exercised on a real host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:45:20 -04:00
Juan DenisandClaude Opus 4.8 6949ceac06 feat(wordpress): selective-push — specific-table selectors in PromoteModal (#31)
PromoteModal already had files-only (code), db-only (database), and
specific-folders (include_plugins/themes/mu_plugins/uploads). The only missing
selective-push control was specific-tables, now added to the database section
as two comma-separated inputs:
- Exclude tables — omitted from the promotion entirely
- Truncate tables — structure promoted, rows dropped

Both split into arrays on submit. The backend already honored them end-to-end
(POST .../promote forwards config.exclude_tables/truncate_tables ->
promote_database/promote_full -> clone_options -> _transform_dump), so this is
pure UI wiring, no backend change. Verified: frontend lint + build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:21:51 -04:00
Juan DenisandClaude Opus 4.8 3dc1943da5 feat(ui): denser activity tab — inline stats + compact audit-log rows
Reworks the Settings > Activity tab away from stat-card tiles and icon'd
log entries toward a tighter, terminal-adjacent readout:
- Activity summary becomes a single inline stats line (active today /
  actions this week / total users) instead of three stat cards.
- Audit log entries render as compact log rows (role="list") with inline
  key=value details, dropping the per-row action icon and detail cards.
- _users.scss sheds the old card/icon styling for the leaner row layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:15:32 -04:00
Juan DenisandClaude Opus 4.8 df047e45c3 fix(ui): brand logo renders blank — unique gradient id per instance
The ServerKitLogo SVG hardcoded id="skBrandGradient" for its fill gradient.
Two instances render at once (sidebar + mobile top bar), so the id collides
in the DOM. url(#skBrandGradient) resolves to the first match, which is the
mobile top bar's logo — hidden at 0x0 on desktop. A gradient with the
default objectBoundingBox units and a zero-size owner has no coordinate
space, so the fill painted nothing and the sidebar logo showed only its
white background square.

Generate a unique gradient id per instance with useId() so each logo binds
to its own gradient. The glyph now paints the --accent-primary master color
again across the sidebar, mobile bar, and auth screens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:11:22 -04:00
Juan DenisandClaude Opus 4.8 484aeb575a feat(wordpress): per-site traffic + error analytics (#25)
A per-site Analytics tab showing traffic and error analytics, computed
on-demand from the site's Apache access log — Docker-correct.

Architectural pivot: managed WP sites are wordpress:*-apache containers on
localhost:PORT with NO per-site nginx, so the roadmap's "nginx access log"
premise does not hold. The official image symlinks Apache's access log to the
container's stdout, so `docker logs` is the source.

New WpAnalyticsService.get_traffic(container_name, hours) pulls
`docker logs --tail 20000 --since <h>` (hard 15s timeout; reads only stdout to
separate the access log from the stderr error_log; returncode-checked so a
stopped/missing/remote-agent container degrades to an accurate note) and parses
the Apache combined log on-demand — no store, collector, or migration. Returns
requests, unique visitors, bandwidth, status distribution (2xx/3xx/4xx/5xx),
404s, bot %, error rate, top URLs (grouped by route; query strings stripped so
visitor tokens are never surfaced), and a continuous hourly requests/errors
series.

Route GET /sites/<id>/analytics?hours= (owner/admin-guarded), client
getSiteAnalytics, and a new Analytics tab on the site detail (stat cards + a
recharts requests/errors AreaChart + status codes + Top URLs) with graceful
empty/timeout/unavailable states. Also supplies the 5xx/error-rate signal
#27's "error spike" rule needs.

Deferred (not in the default access log): PHP fatals (need wp-content/debug.log
via a WP_DEBUG toggle, #30), response time / slow pages (need a %D LogFormat),
cache hit ratio (#22/#23). History is point-in-time over the retained container
log; a persistent time-series collector is a clean follow-up.

Review fixes (3-lens): docker-logs timeout + memory cap (single-worker safety);
accurate unavailable note via returncode; query-string token stripping.
Verified: backend py_compile, frontend lint + build. Not runtime-exercised on a
real Docker host (Windows dev).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:10:04 -04:00
Juan DenisandClaude Opus 4.8 b0f3e8b9ef feat(ui): restore top padding on the Docker page
/docker is a FULL_PAGE_ROUTE, so .main-content drops its padding and the
page manages its own. The workspace shell and the "Docker Not Available"
state both relied on that missing padding, leaving their headers flush
against the viewport top.

- .docker-page (unavailable state): add the standard route padding
  ($space-10, $space-5 on mobile) so the header gets the same breathing
  room as every other page.
- .docker-page-new.dx-page (workspace shell): bump the top from $space-3
  to a $space-6 / $space-5 frame.

Verified in-browser on the unavailable state; build passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 00:57:47 -04:00
Juan DenisandClaude Opus 4.8 6ef998515d feat(wordpress): per-site uptime + auto-incidents (#26)
Make a managed WordPress site appear on a status page with a real uptime %
and auto-incidents, driven by an autonomous server-side health poller.

Scheduler: a single-worker-guarded daemon thread (_start_health_check_
scheduler, 300s) runs EnvironmentHealthService.check_health for every RUNNING
production site (skips archived/stopped so an intentional stop is not an
outage). This keeps health_status fresh autonomously — so #27's transition
alerts fire without the browser health card open — and drives bound
status-page components. HealthCheck samples are pruned to 90 days (once/day)
to bound growth.

Schema (Alembic 010 + the boot-time _fix_missing_columns auto-add):
- status_components.wordpress_site_id  (-> wordpress_sites)
- status_incidents.component_id        (-> status_components)
The existing HealthCheck table is reused as the uptime sample store.

StatusPageService.sync_component_from_health maps the health verdict
(healthy->operational, degraded->degraded, unhealthy->major_outage; unknown
skipped), records a HealthCheck, recomputes a real COUNT-based uptime %
(24h/7d/30d/90d; only fully-healthy checks count), and auto-opens an incident
on ENTERING a major outage / auto-resolves on LEAVING it (operational OR
degraded, so a degraded intermediate never strands the incident).

Routes GET/POST/DELETE /sites/<id>/status-page read the binding + attach
(production-only) / detach (resolves & unlinks the incident first -> no
dangling FK on PostgreSQL, no stale public incident). New Uptime tab on the
site detail (health + uptime % + attach/detach).

Security: internal probe config (check_target, intervals) is stripped from the
unauthenticated public-page projection, and a health-driven component stores
no internal localhost:port target.

Review fixes (3-lens adversarial pass): incident-resolve edge for the
degraded-recovery path; detach FK/orphan cleanup in delete_component; public
check_target leak; production-only attach; uptime-prune; degraded-uptime
semantics documented. Verified: backend py_compile, frontend lint + build.
Not runtime-exercised on a real Docker host (Windows dev).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 00:47:26 -04:00
Juan DenisandClaude Opus 4.8 2e6441e6de feat(ui): extend empty-state + AI-slop cleanup to remaining pages
Second pass, applying the same treatment as the infra pages to the rest of
the app. Migrate hand-rolled empty states across Applications,
ApplicationDetail, Backups, CronJobs, DNSZones, Email, Firewall, FTPServer,
SSLCertificates, Security, FileManager, Marketplace, Templates,
ServerTemplates, Workspaces, StatusPages, Monitoring, CloudProvision,
ServerDetail, ServiceDetail, WordPressProject, and WordPressProjects onto
the shared <EmptyState> component (icon + title + description + optional
action, plus its loading variant for "Loading..." placeholders). Inline
SVGs and ad-hoc divs are replaced with consistent lucide icons.

Strip hard-ban design tells found in the audit:
- Decorative gradients on icon/avatar backgrounds and hero/card surfaces
  (Marketplace hero + rainbow stripe, extension/featured/template cards,
  monitoring hero, status-page background, WordPress project icon) ->
  solid tokens.
- Side-stripe borders (>1px colored left-borders, a named ban) on Firewall/
  FTP status cards, Security event/findings items, status-page incident
  rows + public incidents, template variable items, WordPress env/pipeline
  cards -> full borders tinted with the same status color.
- Decorative backdrop-filter blur on an extension badge; kept it on real
  modal/drawer/drag backdrop overlays where it is purposeful.

Rich "feature not installed / unavailable" full-page states (with help
lists, resource requirements, troubleshooting) left as a deliberate tier.
Frontend build verified. WordPressDetail.jsx intentionally excluded here
(entangled with an in-progress feature on the working tree).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 00:46:30 -04:00
Juan DenisandClaude Opus 4.8 40e590f10c feat(ui): unify empty states + strip AI-slop on infra pages
Standardize empty states across Servers, Domains, Databases, Docker, and
Git onto the shared <EmptyState> component, matching WordPress/Services.
Replaces five divergent idioms (docker-empty, raw empty-state divs, inline
SVGs, servers-empty-workspace) so every "nothing here" layout reads the
same: icon + title + description + optional action.

Remove hard-ban design tells flagged in the audit:
- Decorative gradients on icons (WordPress site/detail icons, Databases
  hero + engine status icons) -> solid brand colors.
- Glassmorphism backdrop-filter blur on the Servers health panel and
  command bar (no-op over opaque surfaces).
- Side-stripe borders (>1px colored left-borders, a named ban) on Servers
  alerts/notifications, Docker container cards, and Git status/deployment
  cards + SSH note -> full borders tinted with the same status color.

No behavior changes; frontend build verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 00:32:21 -04:00
Juan DenisandClaude Opus 4.8 9bd757fa0f feat(ui): label connection status + fix status-badge dot, document text tokens
- Dashboard connection state is no longer color-alone: the green/amber dot
  now pairs with a text label (LIVE / RECONNECTING) in the server readout
  and is announced via role="status". Label uses a readable neutral
  (contrast-safe in both themes); the dot carries the status color.
  Removes the orphaned .status-dot-live rules.
- StatusBadge: destructive status dot was bg-white (off-pattern); now
  bg-red-400 to match the semantic hue of the other status dots.
- _variables.scss: document that the $text-*-raw values feed compile-time
  fade() washes (neutral zinc ramp) and are intentionally distinct from the
  lightened runtime --text-* tokens, so the split reads as deliberate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 00:12:23 -04:00
Juan DenisandClaude Opus 4.8 8bc5cc5512 feat(wordpress): one-click create wizard — PHP + page/object cache (#15)
Make WordPressService.create_site a single one-click orchestrator. It now
accepts php_version / enable_page_cache / enable_object_cache and:

- Bakes the chosen PHP version into the INITIAL image tag (concrete
  6.4-php<x.y>-apache) so the site boots on the right PHP with no
  post-create container recreate, then finalizes + _harden_docker_site,
  then best-effort enables page cache (#22) and Redis object cache (#23)
  AFTER `wp core install` (redis already ships in the stack). Cache
  enablement never fails the create — failures degrade to a non-fatal
  warning; the one-time admin password is still surfaced once.

- The live create modal (inline in WordPress.jsx) gains a PHP version
  selector (Default / 8.1 / 8.2 / 8.3) and page-cache / object-cache
  toggles, threaded through createForm -> POST /sites as phpVersion /
  enablePageCache / enableObjectCache (PHP validated server-side).

Mechanism: declare a hidden VERSION template var and switch the image
line to bare ${VERSION} in wordpress.yaml + wordpress-external-db.yaml so
a concrete tag is written at install. This also fixes a latent #24 bug
where the unresolved ${VERSION:-...} literal made set_php_version drop the
WP-core pin; set_php_version now also falls back to the known core for
legacy compose files. No DB migration — PHP/object-cache/SSL read live;
the page-cache flag rides the existing sync_config column.

Deferred: SSL/TLS + custom-domain at create. Managed sites are
http://localhost:PORT containers with no per-site reverse proxy and no
domain attached at create — the same missing public-domain/routing infra
that blocks #21 and that #8 left frontend-only. TLS remains operable
post-create via SiteSSLPanel (#8) once a domain is routed.

Verified: backend py_compile, frontend lint-clean + production build, a
3-lens adversarial review (no blockers), and Docker Hub confirms all
6.4-php8.{1,2,3}-apache tags exist. Not runtime-exercised on a real
Docker host (Windows dev).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 00:12:23 -04:00
Juan DenisandClaude Opus 4.8 768100462c feat(ui): keyboard a11y hardening + quieter brand chrome
Two refinement passes on the dashboard shell, committed together
because their edits interleave in Sidebar.jsx and _sidebar.scss.

Accessibility hardening:
- App-wide focus-visible baseline in _reset.scss, replacing the blanket
  `* { outline: none }` that suppressed all focus rings. Pointer focus
  stays ringless; keyboard focus gets a consistent accent ring.
- User-menu trigger is now a real <button> (aria-haspopup/expanded,
  aria-controls, Escape-to-close with focus return).
- Nav expand buttons get aria-expanded + labels; theme/view buttons get
  aria-pressed + accessible names; menu items get type="button".
- Dashboard app rows expose a real <Link> for keyboard/screen-reader
  navigation (row stays mouse-clickable).
- Decorative icons marked aria-hidden; per-component focus rings on nav
  items, menu items, and dashboard actions.

Quieter brand chrome:
- Removed gradient-text from the wordmark and white-label text; now
  solid $text-primary (drops the background-clip:text ban).
- Replaced the auto-firing, bounce-eased star celebration (particle
  burst, rings, shine sweep, bg flash) with a calm hover affordance;
  deleted the animation effect/state and 8 dead keyframes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 23:48:55 -04:00