דלגו לתוכן

Eine eigene App bauen

תוכן זה אינו זמין עדיין בשפה שלך.

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:

In Storybook öffnen ↗

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.

package.json
{
"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

src/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:

  • MockConnector hä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. setCurrentGroup wählt den Space, den die App zuerst zeigt.
  • createBrowserRouter mit 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

src/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>
)
}
  • ConnectorProvider macht den Connector für jede Fläche darunter erreichbar. Flächen fragen Hooks, nie den Connector direkt.
  • MapLibreAdapterProvider ist die Karten-Engine, die einzige Zeile, die eine App für die Karte schreibt. Ohne sie zeigt das Kartenmodul den Hinweis „keine Karten-Engine“.
  • RoutedAppFrame ist 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. fallbackModule sagt, 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

src/index.css
@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:

Terminal-Fenster
pnpm install
pnpm dev:first-app

Auß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 MockConnector lebt im Arbeitsspeicher. Ein anderer Connector (lokal, Supabase, Web of Trust) tauscht die Datenquelle, ohne dass sich eine Zeile in App.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