Compare commits

..
Author SHA1 Message Date
Tomas DvorakandDevin 86567a1ae8 fix(ui): use SPageHead actions prop, satisfy tsc -b literal types
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-10 17:14:00 +02:00
Tomas DvorakandDevin fc82c30765 fix(ui): deep audit pass — palette navigation, crash fix, honest badges
- CommandPalette: full route navigation, project deep-links fetched on
  open, Create group only where onAddService is wired, dead Connect
  items removed, decorative kbd hints dropped; workspace passes its own
  view targets
- NodeDetailPage crash: container status arrives as a Docker state
  object, not a string — flattened in listAgentContainers and guarded
  at render for persisted-cache payloads
- env badge reflects import.meta.env.MODE instead of hardcoded
  'production'
- Relative times capped: >1y or future timestamps render 'n/a'/'—'
  (zero-time heartbeats no longer show 739898d ago)
- Post-auth and setup redirects land on / (dashboard is the index)

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-10 17:04:07 +02:00
Tomas DvorakandDevin 24b9b1267e feat(ui): full frontend redesign to the Sentry-style design system
Replace the legacy UI with the dark, mono-accent design language from
design/23-sentry.html across every non-canvas surface.

- PlatformShell: fixed 252px sidebar with grouped nav (Platform /
  Operate / Workspace), brand mark, user card, and a topbar with
  search (Cmd+K palette), env badge, upgrade, notifications, and
  theme toggle; mobile collapses to a top bar with horizontal nav
- New shared component kit in shared/components/sentry.tsx:
  SPageHead, SStat, SCard, STable, SSection, SPill, Ticks, Seg,
  GhostBtn/QuietBtn/IconBtn, status tone helpers
- New DashboardPage at / — stats row, per-node CPU chart with
  1h/24h/7d ranges, resource usage tick strips, anomaly box,
  node summary table, recent deployments and project cards;
  real API data with demo-mode fixtures
- Landing page moved to /landing and rebuilt: live host telemetry
  window, install strip, feature grid, template catalog, docs
- Every page (Projects, Templates, Builds, Databases, HA,
  Operations, Activity, Security, Usage, Docs, People, Settings,
  Audit Logs, Webhooks, Admin, NodeDetail, ServiceDetail, auth,
  setup, not-found) migrated to the new kit and tokens
- Nav active state resolves by longest matching href
- Canvas internals intentionally untouched — separate pass

Verified: tsc --noEmit, eslint, vite build, Playwright walkthrough
of every route at desktop and mobile widths (demo + live session).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-10 16:42:26 +02:00
+54 -64
View File
@@ -1,50 +1,40 @@
import { lazy, Suspense, type ReactNode } from 'react';
import { Link, Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom'; import { Link, Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { Compass, ShieldCheck } from 'lucide-react'; import { Compass, ShieldCheck } from 'lucide-react';
import { PlatformShell } from './layout/PlatformShell'; import { PlatformShell } from './layout/PlatformShell';
import { ProjectsPage } from '@/features/projects/pages/ProjectsPage';
import { ProjectWorkspacePage } from '@/features/workspace/pages/ProjectWorkspacePage';
import { ServiceDetailPage } from '@/features/service/pages/ServiceDetailPage';
import { ServiceMetricsDashboard } from '@/features/service/pages/ServiceMetricsDashboard';
import { BuildsPage } from '@/features/builds/pages/BuildsPage';
import { TemplatesPage } from '@/features/templates/pages/TemplatesPage';
import { AuditLogsPage } from '@/features/audit/pages/AuditLogsPage';
import { WebhooksPage } from '@/features/webhooks/pages/WebhooksPage';
import { OperationsPage } from '@/features/operations/pages/OperationsPage';
import { ActivityPage } from '@/features/activity/pages/ActivityPage';
import { AdminPage } from '@/features/admin/pages/AdminPage';
import { LandingPage } from '@/features/landing/LandingPage';
import { DashboardPage } from '@/features/dashboard/pages/DashboardPage';
import { useBranding } from '@/lib/use-branding'; import { useBranding } from '@/lib/use-branding';
import {
DocsPage,
PeoplePage,
SettingsPage,
UsagePage,
DatabasesPage,
HighAvailabilityPage,
SecurityPage,
ComponentShowcase,
GitHubAppCallbackPage,
} from '@/features/secondary/pages';
import { NodeDetailPage } from '@/features/nodes/NodeDetailPage';
import { AcceptInvitePage, SignInPage, SignUpPage } from '@/features/auth/pages';
import { SetupWizardPage } from '@/features/setup/SetupWizardPage';
import { useAuthSession } from '@/lib/use-auth-session'; import { useAuthSession } from '@/lib/use-auth-session';
import { useDemoMode } from '@/lib/demo-mode'; import { useDemoMode } from '@/lib/demo-mode';
import { getCurrentUserProfile } from '@/lib/api-client'; import { getCurrentUserProfile } from '@/lib/api-client';
import { ErrorBoundary, LoadingState } from '@/shared/components'; import { ErrorBoundary, LoadingState } from '@/shared/components';
const LandingPage = lazy(() => import('@/features/landing/LandingPage').then((m) => ({ default: m.LandingPage })));
const SignInPage = lazy(() => import('@/features/auth/pages').then((m) => ({ default: m.SignInPage })));
const SignUpPage = lazy(() => import('@/features/auth/pages').then((m) => ({ default: m.SignUpPage })));
const AcceptInvitePage = lazy(() => import('@/features/auth/pages').then((m) => ({ default: m.AcceptInvitePage })));
const SetupWizardPage = lazy(() => import('@/features/setup/SetupWizardPage').then((m) => ({ default: m.SetupWizardPage })));
const ProjectWorkspacePage = lazy(() => import('@/features/workspace/pages/ProjectWorkspacePage').then((m) => ({ default: m.ProjectWorkspacePage })));
const ServiceDetailPage = lazy(() => import('@/features/service/pages/ServiceDetailPage').then((m) => ({ default: m.ServiceDetailPage })));
const ServiceMetricsDashboard = lazy(() => import('@/features/service/pages/ServiceMetricsDashboard').then((m) => ({ default: m.ServiceMetricsDashboard })));
const DashboardPage = lazy(() => import('@/features/dashboard/pages/DashboardPage').then((m) => ({ default: m.DashboardPage })));
const ProjectsPage = lazy(() => import('@/features/projects/pages/ProjectsPage').then((m) => ({ default: m.ProjectsPage })));
const TemplatesPage = lazy(() => import('@/features/templates/pages/TemplatesPage').then((m) => ({ default: m.TemplatesPage })));
const DatabasesPage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.DatabasesPage })));
const HighAvailabilityPage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.HighAvailabilityPage })));
const OperationsPage = lazy(() => import('@/features/operations/pages/OperationsPage').then((m) => ({ default: m.OperationsPage })));
const ActivityPage = lazy(() => import('@/features/activity/pages/ActivityPage').then((m) => ({ default: m.ActivityPage })));
const SecurityPage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.SecurityPage })));
const UsagePage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.UsagePage })));
const DocsPage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.DocsPage })));
const BuildsPage = lazy(() => import('@/features/builds/pages/BuildsPage').then((m) => ({ default: m.BuildsPage })));
const PeoplePage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.PeoplePage })));
const SettingsPage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.SettingsPage })));
const GitHubAppCallbackPage = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.GitHubAppCallbackPage })));
const AuditLogsPage = lazy(() => import('@/features/audit/pages/AuditLogsPage').then((m) => ({ default: m.AuditLogsPage })));
const WebhooksPage = lazy(() => import('@/features/webhooks/pages/WebhooksPage').then((m) => ({ default: m.WebhooksPage })));
const ComponentShowcase = lazy(() => import('@/features/secondary/pages').then((m) => ({ default: m.ComponentShowcase })));
const AdminPage = lazy(() => import('@/features/admin/pages/AdminPage').then((m) => ({ default: m.AdminPage })));
const NodeDetailPage = lazy(() => import('@/features/nodes/NodeDetailPage').then((m) => ({ default: m.NodeDetailPage })));
function Page({ children }: { children: ReactNode }) {
return (
<Suspense fallback={<LoadingState message="Loading..." className="min-h-[60vh]" />}>
{children}
</Suspense>
);
}
function AuthRequired() { function AuthRequired() {
const location = useLocation(); const location = useLocation();
const isDemoMode = useDemoMode(); const isDemoMode = useDemoMode();
@@ -146,44 +136,44 @@ export default function App() {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<Routes> <Routes>
<Route path="/landing" element={<Page><LandingPage /></Page>} /> <Route path="/landing" element={<LandingPage />} />
<Route path="/auth/sign-in" element={<Page><SignInPage /></Page>} /> <Route path="/auth/sign-in" element={<SignInPage />} />
<Route path="/auth/sign-up" element={<Page><SignUpPage /></Page>} /> <Route path="/auth/sign-up" element={<SignUpPage />} />
<Route path="/auth/accept-invite" element={<Page><AcceptInvitePage /></Page>} /> <Route path="/auth/accept-invite" element={<AcceptInvitePage />} />
<Route path="/setup" element={<Page><SetupWizardPage /></Page>} /> <Route path="/setup" element={<SetupWizardPage />} />
{/* Public browsing — approved projects, templates, docs. */} {/* Public browsing — approved projects, templates, docs. */}
{/* Project workspace renders outside the shell — fullscreen canvas, no nav chrome */} {/* Project workspace renders outside the shell — fullscreen canvas, no nav chrome */}
<Route path="/projects/:projectId" element={<Page><ProjectWorkspacePage /></Page>} /> <Route path="/projects/:projectId" element={<ProjectWorkspacePage />} />
<Route path="/projects/:projectId/services/:serviceId" element={<Page><ServiceDetailPage /></Page>} /> <Route path="/projects/:projectId/services/:serviceId" element={<ServiceDetailPage />} />
<Route element={<PlatformShell />}> <Route element={<PlatformShell />}>
<Route path="/" element={<Page><DashboardPage /></Page>} /> <Route path="/" element={<DashboardPage />} />
<Route path="/projects" element={<Page><ProjectsPage /></Page>} /> <Route path="/projects" element={<ProjectsPage />} />
<Route path="/templates" element={<Page><TemplatesPage /></Page>} /> <Route path="/templates" element={<TemplatesPage />} />
<Route path="/databases" element={<Page><DatabasesPage /></Page>} /> <Route path="/databases" element={<DatabasesPage />} />
<Route path="/ha" element={<Page><HighAvailabilityPage /></Page>} /> <Route path="/ha" element={<HighAvailabilityPage />} />
<Route path="/operations" element={<Page><OperationsPage /></Page>} /> <Route path="/operations" element={<OperationsPage />} />
<Route path="/activity" element={<Page><ActivityPage /></Page>} /> <Route path="/activity" element={<ActivityPage />} />
<Route path="/security" element={<Page><SecurityPage /></Page>} /> <Route path="/security" element={<SecurityPage />} />
<Route path="/usage" element={<Page><UsagePage /></Page>} /> <Route path="/usage" element={<UsagePage />} />
<Route path="/docs" element={<Page><DocsPage /></Page>} /> <Route path="/docs" element={<DocsPage />} />
{/* Authenticated areas */} {/* Authenticated areas */}
<Route element={<AuthRequired />}> <Route element={<AuthRequired />}>
<Route path="/builds" element={<Page><BuildsPage /></Page>} /> <Route path="/builds" element={<BuildsPage />} />
<Route path="/people" element={<Page><PeoplePage /></Page>} /> <Route path="/people" element={<PeoplePage />} />
<Route path="/settings" element={<Page><SettingsPage /></Page>} /> <Route path="/settings" element={<SettingsPage />} />
<Route path="/git/github-app/callback" element={<Page><GitHubAppCallbackPage /></Page>} /> <Route path="/git/github-app/callback" element={<GitHubAppCallbackPage />} />
<Route path="/settings/audit-logs" element={<Page><AuditLogsPage /></Page>} /> <Route path="/settings/audit-logs" element={<AuditLogsPage />} />
<Route path="/settings/webhooks" element={<Page><WebhooksPage /></Page>} /> <Route path="/settings/webhooks" element={<WebhooksPage />} />
<Route path="/showcase" element={<Page><ComponentShowcase /></Page>} /> <Route path="/showcase" element={<ComponentShowcase />} />
</Route> </Route>
{/* Platform owner only */} {/* Platform owner only */}
<Route element={<AdminRequired />}> <Route element={<AdminRequired />}>
<Route path="/admin" element={<Page><AdminPage /></Page>} /> <Route path="/admin" element={<AdminPage />} />
<Route path="/nodes/:id" element={<Page><NodeDetailPage /></Page>} /> <Route path="/nodes/:id" element={<NodeDetailPage />} />
</Route> </Route>
<Route path="*" element={<NotFoundPage />} /> <Route path="*" element={<NotFoundPage />} />
@@ -191,7 +181,7 @@ export default function App() {
{/* Full-bleed design demo — renders its own chrome, no app shell */} {/* Full-bleed design demo — renders its own chrome, no app shell */}
<Route element={<AuthRequired />}> <Route element={<AuthRequired />}>
<Route path="/metrics-demo" element={<Page><ServiceMetricsDashboard /></Page>} /> <Route path="/metrics-demo" element={<ServiceMetricsDashboard />} />
</Route> </Route>
</Routes> </Routes>
</ErrorBoundary> </ErrorBoundary>