Eine eigene App bauen
Bu içerik henüz dilinizde mevcut değil.
Eine App auf Real Life Stack stellt drei Dinge: einen Connector Die Implementierung des DataInterface für eine konkrete Datenquelle, ergänzt um unterstützte Capabilities. Die Steckstelle des Stacks nach unten. GlossarConnector The implementation of the DataInterface for one data source, plus the capabilities it supports. The stack's socket downward. Glossary zu ihren Daten, einen Router für die Adresse und den Rahmen aus dem Toolkit. Kopfzeile, Tabs, Panel, Erstellen, Detailansicht und die sieben Module (Feed, Liste, Kanban, Kalender, Karte, Graph, Resonanz) kommen fertig mit. Der ganze Code unten steht als lebendes Beispiel unter examples/first-app/ im Repo und wird in jeder CI gebaut und getestet.
So sieht das Ergebnis aus, mit denselben Bausteinen, im Storybook:
Ein Gemeinschaftsgarten auf dem Rahmen: Space, Module, ein Item im Kalender und auf der Karte. Die Beispieldaten sind fiktiv. Änderungen bleiben nur für diese Sitzung.
1. Pakete
Drei Pakete von Real Life Stack und vier von außen. react-router-dom trägt die Adresse, maplibre-gl zeichnet die Karte.
{ "name": "first-app", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc --noEmit && vite build", "typecheck": "tsc --noEmit", "test": "vitest run" }, "dependencies": { "@real-life/data-interface": "^0.4.0", "@real-life/mock-connector": "^0.2.3", "@real-life/toolkit": "^0.4.0", "maplibre-gl": "^5.24.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.1" }, "devDependencies": { "@tailwindcss/vite": "^4.1.0", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", "jsdom": "^26.1.0", "tailwindcss": "^4.1.0", "typescript": "~5.7.2", "vite": "^6.0.7", "vitest": "^4.1.0" }}Die drei Pakete kommen von npm, auch im Repo; die genaue Version hält die Lockdatei fest. Der Rahmen und die sieben Module sind in @real-life/toolkit enthalten.
2. Die Daten und die Adresse: main.tsx
import { StrictMode } from "react"import { createRoot } from "react-dom/client"import { createBrowserRouter, RouterProvider } from "react-router-dom"import { MockConnector } from "@real-life/mock-connector"
import { App } from "./App"import "./index.css"import "maplibre-gl/dist/maplibre-gl.css"
async function start() { // Die Daten: ein Space, ein Mensch, ein Item — im Speicher, für den Anfang. const connector = new MockConnector({ users: [{ id: "mira", displayName: "Mira" }], groups: [{ id: "garten", name: "Gemeinschaftsgarten", data: { modules: ["calendar", "map", "collection"] } }], groupMembers: { garten: ["mira"] }, items: [{ id: "erntefest", type: "event", createdBy: "mira", createdAt: "2026-09-01T10:00:00Z", data: { title: "Erntefest", content: "Wir teilen unsere Ernte.", start: "2026-09-26T14:00:00+02:00", position: { type: "Point", coordinates: [13.4, 52.5] } }, }], groupItems: { garten: ["erntefest"] }, }) await connector.init() connector.setCurrentGroup("garten")
// Die Adresse: /{space}/{modul}/{item} — der Rahmen liest sie, die App stellt nur den Router. const router = createBrowserRouter([{ path: "*", element: <App connector={connector} /> }])
createRoot(document.getElementById("root")!).render( <StrictMode> <RouterProvider router={router} /> </StrictMode>, )}
void start()Zeile für Zeile:
MockConnectorhält die Daten im Speicher: ein Mensch (users), ein Space Der gemeinsame Arbeits-, Mitgliedschafts- und Sichtbarkeitskontext. Im Datenvertrag heißt er Group. GlossarSpace The shared context for work, membership and visibility. In the data contract it is called Group. Glossary (groups) mit seinen Modulen in Tab-Reihenfolge (data.modules), die Mitgliedschaft (groupMembers) und ein Item (items), das dem Space zugeordnet ist (groupItems). Das Item hat ein Datum (start) und einen Ort (position); deshalb steht es im Kalender und auf der Karte, als dasselbe Objekt.await connector.init()wartet, bis der Connector bereit ist.setCurrentGroupwählt den Space, den die App zuerst zeigt.createBrowserRoutermit einer einzigen Route*: Die Adresse/{space}/{modul}/{item}liest der Rahmen selbst. Die App besitzt nur den Router; das ist der Teil, der bei ihr bleibt (Spec 01, „Was bei der App bleibt“).- Die beiden CSS-Importe: dein Stylesheet und das der Karte.
3. Der Rahmen: App.tsx
import type { DataInterface } from "@real-life/data-interface"import { ConnectorProvider } from "@real-life/toolkit"import { MapLibreAdapterProvider } from "@real-life/toolkit/maplibre"import { RoutedAppFrame } from "@real-life/toolkit/router"
// Die App stellt drei Dinge: den Connector, die Karten-Engine und den Rahmen.// Kopfzeile, Tabs, Panel, Erstellen, Detail und alle Module kommen aus dem Toolkit.export function App({ connector }: { connector: DataInterface }) { return ( <ConnectorProvider connector={connector}> <MapLibreAdapterProvider> <RoutedAppFrame fallbackModule="collection" /> </MapLibreAdapterProvider> </ConnectorProvider> )}ConnectorProvidermacht den Connector für jede Fläche darunter erreichbar. Flächen fragen Hooks, nie den Connector direkt.MapLibreAdapterProviderist die Karten-Engine, die einzige Zeile, die eine App für die Karte schreibt. Ohne sie zeigt das Kartenmodul den Hinweis „keine Karten-Engine“.RoutedAppFrameist der Rahmen: Provider, das geteilte Panel, die Kopfzeile mit Space-Verwaltung, die Tabs aus dem Register des Space, der Fokus in der Adresse und der Schutz vor dem Verlust ungespeicherter Eingaben.fallbackModulesagt, welches Modul ein Item zeigt, wenn keines seiner Felder ein anderes wählt: hier die Liste.
Kein Register, kein Modul, kein Detail wird hier verdrahtet. Das Register enthält ohne weiteres Zutun die Module des Toolkits; ein eigenes Modul kommt später als Ergänzung dazu (Seite „Eigenes Modul“, folgt).
4. Styles
@import "tailwindcss";@import "@real-life/toolkit/styles/globals.css";@source "../node_modules/@real-life/toolkit/dist";@custom-variant dark (&:is(.dark *));Tailwind, die Tokens des Toolkits (globals.css) und die Anweisung, die Klassen im gebauten Toolkit einzusammeln (@source). Ohne die dritte Zeile fehlen dem Rahmen seine Abstände und Farben.
5. Starten
Im Wurzelverzeichnis des Repos:
pnpm installpnpm dev:first-appAußerhalb des Repos ist es pnpm install && pnpm dev in deinem eigenen Verzeichnis; die Pakete kommen in beiden Fällen von npm.
Im Browser: Der Space „Gemeinschaftsgarten“ mit drei Tabs. Im Kalender steht das Erntefest am 26. September, auf der Karte in Berlin, in der Liste als Karte. Klick auf das Item öffnet die Detailansicht im Panel, die Adresse wird /garten/calendar/erntefest. Der Plusknopf unten rechts erstellt ein neues Item, mit allen Typen; im Kalender schlägt er „Termin“ vor. Über die Kopfzeile legst du einen weiteren Space an.
Was die App nicht tut
- Sie speichert nichts: Der
MockConnectorlebt im Arbeitsspeicher. Ein anderer Connector (lokal, Supabase, Web of Trust) tauscht die Datenquelle, ohne dass sich eine Zeile inApp.tsxändert (Seite „Connector wechseln“, folgt). - Sie hat kein eigenes Modul. Eins hinzuzufügen sind drei Schritte: eine Ansicht, ein Registereintrag, ein Hinweis (Seite „Eigenes Modul“, folgt).
- Sie meldet niemanden an. Anmeldung, Rechte und Verschlüsselung bringt der Connector mit, der sie kann; der Rahmen zeigt dann, was der Connector erlaubt.
Wo es weitergeht
- Die Spec zum Rahmen: 01 App-Komposition, „Was bei der App bleibt“.
- Im Storybook: RLS → App zeigt Register, Host, Ladevertrag, Fokus und Erstellen einzeln.
- Alle Hooks, die eine Fläche fragen kann: All hooks.