feat(web): unify console + docs on @unom/ui; host OpenAPI via Scalar

Move the management console (web/) off shadcn/ui to the shared @unom/ui
design system the marketing site + docs are built on, on the punktfunk
violet brand over dark chrome:

- Add @unom/ui/@unom/style/motion/radix-ui/zod + Geist; web/.npmrc maps the
  @unom scope (packages are public-read, so CI needs no npm auth).
- styles.css: one dark-violet palette (#141019/#1c1530, brand #6c5bf3 ->
  #a79ff8) exposed under BOTH the shadcn token names the routes use and
  @unom/ui's contract, so routes + components both resolve; pulls in
  @unom/ui's material gloss + easings.
- components/ui/* now back onto @unom/ui (AnimatedButton/InputText/Label/
  AnimatedCard); brand-mark/wordmark/logo replace the generic Radio icon in
  the shell + login.
- MaterialProvider (specular gloss) at the root. No UI sounds, like the site.

docs-site: new /api route renders the host management REST API as an
interactive Scalar reference (reads public/openapi.json, a snapshot of
docs/api/openapi.json), branded violet and linked from the top nav, the
docs sidebar, the landing page, and host-cli.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 12:00:46 +00:00
parent 43b480ada3
commit 56f724815e
26 changed files with 3409 additions and 155 deletions
+10 -6
View File
@@ -7,7 +7,9 @@ import {
useRouterState,
} from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
import '@fontsource-variable/geist'
import { AppShell } from '@/components/app-shell'
import { UnomProviders } from '@/components/unom-providers'
import appCss from '@/styles.css?url'
export interface RouterContext {
@@ -36,13 +38,15 @@ function RootComponent() {
<HeadContent />
</head>
<body className="min-h-screen">
{isLogin ? (
<Outlet />
) : (
<AppShell>
<UnomProviders>
{isLogin ? (
<Outlet />
</AppShell>
)}
) : (
<AppShell>
<Outlet />
</AppShell>
)}
</UnomProviders>
<Scripts />
</body>
</html>
+2 -2
View File
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { Radio } from 'lucide-react'
import { BrandMark } from '@/components/brand-mark'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@@ -54,7 +54,7 @@ function LoginPage() {
<Card className="w-full max-w-sm">
<CardHeader className="items-center text-center">
<div className="mb-2 flex items-center gap-2">
<Radio className="size-5 text-[var(--success)]" />
<BrandMark className="size-6 drop-shadow-[0_2px_12px_rgba(108,91,243,0.45)]" />
<span className="font-semibold">{m.app_name()}</span>
</div>
<CardTitle>{m.login_title()}</CardTitle>