umbau(boxwart): neue Oberflaeche im Cockpit-Stil (Richtung A), Werkzeuge angehoben
Drei Seiten statt zehn: Start, Updates, Modelle — am PC mit Kopfnavigation, am Handy mit Leiste unten. Umsetzung von Mockup A („Cockpit“), vom User am 23.09. gewaehlt. - Start: Hauptwarnleuchte + 8 Warnlampen, Rundinstrumente mit Live-Werten aus dem Strom (Speicher, Temperatur, Platte) und Laufzeit-Zaehlwerk, Checkliste der Waechter-Hinweise mit ihren Knoepfen, Flugplan (heute gelaufen / geplant), Radar-Kasten. - Updates: Bausteine mit „Laeuft → Neu“ und Zusammenfassung, laufende Auftraege, Verlauf der Sonntagslaeufe (neu: GET /api/updates/verlauf), Sicherungen samt Zurueckspielen mit Rueckfrage. - Modelle: Speicherbalken, Rollen Hirn/Coder/Dritte Rolle, wer die Modelle nutzt (7 Tage + 24 h je Stunde), Modell-Radar, weitere Eintraege, Modelle selbst suchen. - Schubladen: Dienste mit Protokoll und Neustart, Einstellungen (HF-Zugang), Hermes-Link. - Werkzeuge: Vite 8, React 19.3 mit React Compiler 1.0 (Babel), vitest 5, Tailwind 4.3, shadcn 4 (Radix) fuer Dialog/Schublade/Knopf, Schriften Barlow/Barlow Condensed/ JetBrains Mono. Entfernt: recharts, cmdk, Kraftgraph, zustand, Inter, Space Grotesk. - Startbuendel 115 KB gzip (Budget 140); Updates/Modelle/Schubladen laden bei Bedarf. - 16 Oberflaechen-Tests (Instrument-Bogen, Hauptleuchte, Zeitformate, Versionen). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3d2c9549fb
commit
907289d7dc
+18
-185
@@ -1,197 +1,30 @@
|
||||
import { lazy } from "react"
|
||||
import {
|
||||
createRootRoute,
|
||||
createRoute,
|
||||
createRouter,
|
||||
redirect,
|
||||
useNavigate,
|
||||
} from "@tanstack/react-router"
|
||||
import { Shell } from "@/App"
|
||||
import { NAV } from "@/nav"
|
||||
import { RouteFehler } from "@/components/RouteFehler"
|
||||
import { CockpitView } from "@/views/cockpit/CockpitView"
|
||||
import { createRootRoute, createRoute, createRouter, lazyRouteComponent, Navigate } from "@tanstack/react-router"
|
||||
import { Shell } from "./Shell"
|
||||
import { StartSeite } from "@/views/Start"
|
||||
|
||||
// Routenbaum (v3-Umbau P2).
|
||||
//
|
||||
// Bewusst code-based statt dateibasiert: Es sind zwölf Routen, und sie stehen dadurch
|
||||
// alle an EINER Stelle — inklusive dem, was in den Suchparametern erlaubt ist. Eine
|
||||
// Datei-Konvention würde diese Übersicht auf zwölf Dateien verteilen, ohne dass jemand
|
||||
// etwas davon hätte.
|
||||
//
|
||||
// Code-Splitting bleibt exakt wie bisher: die Views hängen an React.lazy, das Cockpit als
|
||||
// Startseite lädt eifrig. Kein Chunk verschiebt sich durch den Router.
|
||||
const ModelsView = lazy(() => import("@/views/ModelsView").then((m) => ({ default: m.ModelsView })))
|
||||
const ConnectView = lazy(() => import("@/views/ConnectView").then((m) => ({ default: m.ConnectView })))
|
||||
const AgentView = lazy(() => import("@/views/AgentView").then((m) => ({ default: m.AgentView })))
|
||||
const KonsoleView = lazy(() => import("@/views/KonsoleView").then((m) => ({ default: m.KonsoleView })))
|
||||
const GuideView = lazy(() => import("@/views/GuideView").then((m) => ({ default: m.GuideView })))
|
||||
const IdeenView = lazy(() => import("@/features/ideen/IdeenAnsicht").then((m) => ({ default: m.IdeenView })))
|
||||
const SkillsView = lazy(() => import("@/views/SkillsView").then((m) => ({ default: m.SkillsView })))
|
||||
const ChronikView = lazy(() => import("@/views/ChronikView").then((m) => ({ default: m.ChronikView })))
|
||||
const WissenView = lazy(() => import("@/views/WissenView").then((m) => ({ default: m.WissenView })))
|
||||
|
||||
// ── Suchparameter ───────────────────────────────────────────────────────────
|
||||
// Regel für das, was hier steht: Alles, was ein Kollege per Link teilen können soll und
|
||||
// was einen Reload überleben muss. Nichts Flüchtiges (Hover, Entwürfe im Eingabefeld).
|
||||
|
||||
/** Wurzel: die System-Schublade. Gehört hierher, weil sie über JEDER Ansicht liegt. */
|
||||
export interface WurzelSuche {
|
||||
system?: "wartung" | "logs"
|
||||
}
|
||||
|
||||
const wurzelSuche = (roh: Record<string, unknown>): WurzelSuche => {
|
||||
const s = roh.system
|
||||
return s === "wartung" || s === "logs" ? { system: s } : {}
|
||||
}
|
||||
|
||||
export const rootRoute = createRootRoute({
|
||||
const wurzel = createRootRoute({
|
||||
component: Shell,
|
||||
validateSearch: wurzelSuche,
|
||||
notFoundComponent: () => (
|
||||
<RouteFehler
|
||||
titel="Diese Seite gibt es nicht"
|
||||
text="Der Link zeigt auf eine Ansicht, die Mission Control nicht kennt. Vielleicht stammt er aus einer älteren Fassung."
|
||||
/>
|
||||
),
|
||||
// Alte Lesezeichen (/ideen, /wissen, /cockpit …) führen einfach zum Start.
|
||||
notFoundComponent: () => <Navigate to="/" />,
|
||||
})
|
||||
|
||||
// Wurzelpfad → Cockpit. `redirect` statt einer Kopie der Startseite: sonst gäbe es
|
||||
// zwei URLs für denselben Inhalt und die aktive Markierung in der Sidebar flackert.
|
||||
const indexRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/",
|
||||
beforeLoad: () => {
|
||||
throw redirect({ to: "/cockpit" })
|
||||
},
|
||||
// Der Start ist der Normalfall und kommt im ersten Bündel mit; Updates und Modelle
|
||||
// werden erst beim ersten Besuch nachgeladen (Budget der CI-Ampel: 140 KB gzip).
|
||||
const start = createRoute({ getParentRoute: () => wurzel, path: "/", component: StartSeite })
|
||||
const updates = createRoute({
|
||||
getParentRoute: () => wurzel,
|
||||
path: "/updates",
|
||||
component: lazyRouteComponent(() => import("@/views/Updates"), "UpdatesSeite"),
|
||||
})
|
||||
|
||||
/** Eine Route je Ansicht — kurze Schreibweise, weil sich nur Pfad und Bauteil ändern.
|
||||
* Generisch über den Pfad, damit TanStack Router den Literal-Typ behält: mit
|
||||
* `path: string` verlöre `navigate({ to: "/cockpit" })` seine Typprüfung. */
|
||||
function ansicht<P extends string>(path: P, component: () => React.ReactNode) {
|
||||
return createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path,
|
||||
component,
|
||||
// Fehlergrenze PRO Route (Befund B-13): Vorher hing eine einzige Grenze in main.tsx
|
||||
// um die ganze App — ein Renderfehler in den Modellen nahm Cockpit, Konsole und
|
||||
// Chronik mit. Jetzt bleiben Schiene, Kopfzeile und Statusanzeige stehen.
|
||||
errorComponent: ({ error, reset }) => (
|
||||
<RouteFehler
|
||||
titel="Diese Ansicht ist abgestürzt"
|
||||
text="Der Rest von Mission Control läuft weiter. Erneut versuchen — oder in eine andere Ansicht wechseln."
|
||||
fehler={error}
|
||||
onErneut={reset}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
// Das Cockpit verlinkt in andere Bereiche und bekam den Sprung bisher als Prop von der
|
||||
// App gereicht. Jetzt kommt er aus dem Router — die Ansicht selbst bleibt unverändert.
|
||||
function CockpitSeite() {
|
||||
const navigate = useNavigate()
|
||||
return <CockpitView onNavigate={(v) => {
|
||||
const ziel = NAV.find((n) => n.id === v)
|
||||
if (ziel) navigate({ to: ziel.pfad })
|
||||
}} />
|
||||
}
|
||||
|
||||
const cockpitRoute = ansicht("/cockpit", () => <CockpitSeite />)
|
||||
const skillsRoute = ansicht("/skills", () => <SkillsView />)
|
||||
/** Chronik: `?q=` filtert den Verlauf — Ziel der „Finden"-Einträge der Palette (P6). */
|
||||
export interface ChronikSuche {
|
||||
q?: string
|
||||
}
|
||||
const chronikRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/chronik",
|
||||
validateSearch: (roh: Record<string, unknown>): ChronikSuche =>
|
||||
typeof roh.q === "string" && roh.q ? { q: roh.q } : {},
|
||||
component: () => <ChronikView />,
|
||||
errorComponent: ({ error, reset }) => (
|
||||
<RouteFehler titel="Die Chronik ist abgestürzt" text="Der Rest läuft weiter." fehler={error} onErneut={reset} />
|
||||
),
|
||||
})
|
||||
const verbindenRoute = ansicht("/verbinden", () => <ConnectView />)
|
||||
const konsoleRoute = ansicht("/konsole", () => <KonsoleView />)
|
||||
const anleitungRoute = ansicht("/anleitung", () => <GuideView />)
|
||||
const agentRoute = ansicht("/agent", () => <AgentView />)
|
||||
|
||||
/** Ideen: `?offen=<id>` hält die aufgeklappte Karte fest (Befund B-05).
|
||||
* Vorher lag das in `useState` — wer während eines laufenden Workers neu lud,
|
||||
* verlor seinen Platz in der Liste. */
|
||||
export interface IdeenSuche {
|
||||
offen?: string
|
||||
}
|
||||
const ideenRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/ideen",
|
||||
validateSearch: (roh: Record<string, unknown>): IdeenSuche =>
|
||||
typeof roh.offen === "string" && roh.offen ? { offen: roh.offen } : {},
|
||||
component: () => <IdeenView />,
|
||||
errorComponent: ({ error, reset }) => (
|
||||
<RouteFehler titel="Die Ideen-Ansicht ist abgestürzt" text="Der Rest läuft weiter." fehler={error} onErneut={reset} />
|
||||
),
|
||||
})
|
||||
|
||||
/** Wissen: `?datei=` macht jede Notiz verlinkbar, `?suche=` trägt das Suchwort
|
||||
* aus der Befehlspalette herein (P6). */
|
||||
export interface WissenSuche {
|
||||
datei?: string
|
||||
suche?: string
|
||||
}
|
||||
const wissenRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/wissen",
|
||||
validateSearch: (roh: Record<string, unknown>): WissenSuche => ({
|
||||
...(typeof roh.datei === "string" && roh.datei ? { datei: roh.datei } : {}),
|
||||
...(typeof roh.suche === "string" && roh.suche ? { suche: roh.suche } : {}),
|
||||
}),
|
||||
component: () => <WissenView />,
|
||||
errorComponent: ({ error, reset }) => (
|
||||
<RouteFehler titel="Die Wissens-Ansicht ist abgestürzt" text="Der Rest läuft weiter." fehler={error} onErneut={reset} />
|
||||
),
|
||||
})
|
||||
|
||||
/** Modelle: `?reiter=` — der Reiter war lokaler State und damit nicht teilbar. */
|
||||
export type ModellReiter = "werkbank" | "routing" | "discover"
|
||||
export interface ModelleSuche {
|
||||
reiter?: ModellReiter
|
||||
}
|
||||
const modelleRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
const modelle = createRoute({
|
||||
getParentRoute: () => wurzel,
|
||||
path: "/modelle",
|
||||
validateSearch: (roh: Record<string, unknown>): ModelleSuche => {
|
||||
const r = roh.reiter
|
||||
return r === "werkbank" || r === "routing" || r === "discover" ? { reiter: r } : {}
|
||||
},
|
||||
component: () => <ModelsView />,
|
||||
errorComponent: ({ error, reset }) => (
|
||||
<RouteFehler titel="Der Modell-Manager ist abgestürzt" text="Der Rest läuft weiter." fehler={error} onErneut={reset} />
|
||||
),
|
||||
component: lazyRouteComponent(() => import("@/views/Modelle"), "ModelleSeite"),
|
||||
})
|
||||
|
||||
export const routeTree = rootRoute.addChildren([
|
||||
indexRoute,
|
||||
cockpitRoute,
|
||||
ideenRoute,
|
||||
skillsRoute,
|
||||
chronikRoute,
|
||||
wissenRoute,
|
||||
verbindenRoute,
|
||||
konsoleRoute,
|
||||
anleitungRoute,
|
||||
modelleRoute,
|
||||
agentRoute,
|
||||
])
|
||||
|
||||
export const router = createRouter({
|
||||
routeTree,
|
||||
// Ein LAN-Gerät: der Verlauf muss nicht scrollen-restaurieren, aber die Position
|
||||
// oben zu haben ist beim Ansichtswechsel richtig.
|
||||
scrollRestoration: true,
|
||||
defaultPreload: false,
|
||||
routeTree: wurzel.addChildren([start, updates, modelle]),
|
||||
defaultPreload: "intent",
|
||||
})
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
|
||||
Reference in New Issue
Block a user