Phase 4: Vite-React-Geruest und Workspace-Anbindung

npm-Workspace im Repo-Root verbindet companion-app mit @audi-dash/ui, so
dass die App die Komponentenbibliothek als echte Abhaengigkeit nutzt statt
nur in der Doku. Dazu Vite, React 18, Einstiegsdateien und eine schlanke
Grundlage-CSS, die nur enthaelt was eine App braucht und eine Bibliothek
nicht mitbringt: Vollbild, sichere Bereiche, Scrollverhalten.

Zwei Audit-Befunde des alten Panels sind hier von Anfang an beruecksichtigt:
Formularfelder mit 16px (sonst zoomt iOS Safari beim Fokus) und ein
globaler :focus-visible-Ring.

Nebenbei im design-system den Kontrastwert --fg3 nachgezogen: dort stand
noch #657081 mit 3,0:1 auf --tile, das Panel hatte den Wert laengst auf
#8a94a3 korrigiert. Ohne das haette die neue App den bereits behobenen
WCAG-Verstoss geerbt.
This commit is contained in:
Paul Nothaft
2026-08-11 10:50:51 +02:00
parent 8e268f044d
commit 2fe3576682
10 changed files with 4426 additions and 5 deletions
+17
View File
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
<meta name="color-scheme" content="dark light" />
<meta name="theme-color" content="#161b23" />
<title>DataMetric360</title>
</head>
<body>
<div id="wurzel"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+20 -3
View File
@@ -2,14 +2,31 @@
"name": "datametric360", "name": "datametric360",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"description": "DataMetric360 — private Fahrzeug-App (iOS/Android via Capacitor, zusätzlich als Iframe im Home-Assistant-Dashboard). Dieses Paket enthält vorerst nur die Datenschicht; die Oberfläche folgt aus dem Claude-Design-Entwurf.", "description": "DataMetric360 — private Fahrzeug-App (iOS/Android via Capacitor, zusätzlich als Iframe im Home-Assistant-Dashboard). Datenschicht plus Oberfläche auf Basis von @audi-dash/ui.",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"smoke": "node --experimental-strip-types scripts/smoke.ts" "smoke": "node --experimental-strip-types scripts/smoke.ts",
"smoke:auth": "node --experimental-strip-types scripts/smoke-auth.ts",
"test": "vitest run"
},
"dependencies": {
"@audi-dash/ui": "*",
"leaflet": "^1.9.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/leaflet": "^1.9.12",
"@types/node": "^24.13.3", "@types/node": "^24.13.3",
"typescript": "^5.7.2" "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.7.2",
"vite": "^6.0.7",
"vitest": "^2.1.8"
} }
} }
+19
View File
@@ -0,0 +1,19 @@
import { Tile } from "@audi-dash/ui"
/**
* Vorläufiger Einstieg — wird in Phase 5 durch die App-Shell ersetzt.
* Das äußerste Element trägt immer `ads-root` und `data-theme`, sonst greifen
* die Design-Tokens nicht (siehe design-system/.design-sync/conventions.md).
*/
export function App() {
return (
<div className="ads-root dm-wurzel" data-theme="nacht">
<div className="dm-scroll" style={{ flex: 1, padding: 22 }}>
<span className="ads-eyebrow">DataMetric360</span>
<Tile>
<div style={{ fontWeight: 300, fontSize: 20 }}>Gerüst steht</div>
</Tile>
</div>
</div>
)
}
+15
View File
@@ -0,0 +1,15 @@
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import "@audi-dash/ui/styles.css"
import "./stile/grundlage.css"
import { App } from "./App"
const wurzel = document.getElementById("wurzel")
if (!wurzel) throw new Error("Wurzelelement #wurzel fehlt in index.html")
createRoot(wurzel).render(
<StrictMode>
<App />
</StrictMode>,
)
+78
View File
@@ -0,0 +1,78 @@
/*
* Grundlayout der App. Alles Gestalterische kommt aus @audi-dash/ui
* (Tokens + Komponenten) — hier steht nur, was eine App braucht und eine
* Komponentenbibliothek nicht mitbringen kann: Vollbild, sichere Bereiche,
* Scrollverhalten, Fokusring.
*/
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
overscroll-behavior-y: none;
}
body {
background: var(--canvas);
}
#wurzel {
height: 100%;
}
/* Wurzel der App: setzt Schrift, Farben und Zahlendarstellung (.ads-root aus
der Bibliothek) und spannt sich über die volle Höhe. */
.dm-wurzel {
height: 100%;
display: flex;
overflow: hidden;
font-weight: 400;
}
/* Tastaturbedienung: im alten Panel als Audit-Befund nachgerüstet, hier von
Anfang an. :focus-visible statt :focus lässt Maus und Touch unberührt. */
:focus-visible {
outline: 2px solid var(--red);
outline-offset: 2px;
}
.dm-scroll {
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.dm-scroll::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.dm-scroll::-webkit-scrollbar-thumb {
background: var(--line-strong);
border-radius: var(--r-pill);
}
/* Formularfelder: mindestens 16px, sonst zoomt iOS Safari beim Fokus die
ganze Seite (Audit-Befund aus dem alten Panel). */
input,
select,
textarea,
button {
font-family: inherit;
font-size: 16px;
color: inherit;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
+3 -1
View File
@@ -4,6 +4,8 @@
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"jsx": "react-jsx",
"types": ["vite/client"],
"strict": true, "strict": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true, "exactOptionalPropertyTypes": true,
@@ -16,5 +18,5 @@
"skipLibCheck": true, "skipLibCheck": true,
"isolatedModules": true "isolatedModules": true
}, },
"include": ["src/**/*.ts", "scripts/**/*.ts"] "include": ["src/**/*.ts", "src/**/*.tsx", "scripts/**/*.ts", "vite.config.ts"]
} }
+16
View File
@@ -0,0 +1,16 @@
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
// Basispfad relativ: die App wird sowohl unter einer eigenen Domain als auch
// aus einem Unterordner heraus ausgeliefert (Home Assistant: /local/dm360/),
// und in der Capacitor-Hülle direkt vom Dateisystem.
export default defineConfig({
base: "./",
plugins: [react()],
server: { port: 5173 },
build: {
outDir: "dist",
target: "es2022",
sourcemap: true,
},
})
+4 -1
View File
@@ -29,7 +29,10 @@
--line-strong: rgba(255, 255, 255, .20); --line-strong: rgba(255, 255, 255, .20);
--fg: #FFFFFF; --fg: #FFFFFF;
--fg2: #9aa1ad; --fg2: #9aa1ad;
--fg3: #657081; /* Design-Audit A2: von #657081 (nur 3,0:1 auf --tile, WCAG AA verfehlt) auf
4,9:1 angehoben. Das Panel hat diesen Wert bereits, hier war er noch der
alte — nachgezogen, damit beide dasselbe zeigen. */
--fg3: #8a94a3;
--ok: #15da15; --ok: #15da15;
--warn: #ffaa00; --warn: #ffaa00;
--bad: #fd2c4e; --bad: #fd2c4e;
+4237
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
{
"name": "audi-app",
"version": "0.1.0",
"private": true,
"description": "Werkstatt für DataMetric360: Design-System, Companion-App und das Home-Assistant-Panel.",
"workspaces": [
"design-system",
"companion-app"
],
"scripts": {
"build:ds": "npm run build --workspace @audi-dash/ui",
"dev": "npm run dev --workspace datametric360",
"build": "npm run build:ds && npm run build --workspace datametric360",
"typecheck": "npm run typecheck --workspaces --if-present",
"test": "npm run test --workspace datametric360"
}
}