Ich habe meiner Familie eine Reiseplan-App gebaut — mit einem KI-Assistenten darin

Veröffentlicht
11 Juli 2026
Von
Jacob Lloyd — mit KI-Unterstützung geschrieben, im Nachhinein
Lesezeit
10 Min. Lesezeit

Kurz gesagt: Ich habe eine kleine Website nur für die Japanreise meiner Familie gebaut — sie zeigt den Plan für jeden Tag, das Wetter, Karten und funktioniert auf dem Smartphone auch ganz ohne Empfang. Außerdem gibt es einen kleinen KI-Helfer-Button, der Fragen zur Reise beantworten und den Plan leicht anpassen kann. Dieser Artikel erzählt ehrlich, wie ich das mit viel KI-Unterstützung gebaut habe: was funktioniert hat, was kaputtging, und wie aus einem zerbrechlichen Design-Tool-Export etwas wurde, das sicher zu bearbeiten und sicher ins Netz zu stellen war. Eine bereinigte Beispieldatei lässt sich herunterladen und in jedem Browser öffnen.

Meine Familie ist nach Japan gereist, und ich wollte, dass jeder den Plan in der
Tasche hat — jeder Tag ausgebreitet, Karten einen Fingertipp entfernt, das Wetter
und die kleinen Details, die man im Jetlag am Bahnhof leicht vergisst. Also habe
ich eine kleine Webapp nur für uns gebaut: einen privaten, PIN-gesicherten
Reiseplan
mit einem Tag-für-Tag-Zeitplan, Live-Wetter, Offline-Unterstützung
und — weil ich nun mal so bin — einem eingebauten KI-Assistenten, der Fragen
zur Reise beantworten und sogar kleine Änderungen vornehmen kann.

Das hier ist die ehrliche Baugeschichte: die Teile, die funktioniert haben, die
Teile, die auf unerwartete Weise kaputtgingen, und wie aus einem zerbrechlichen
Design-Tool-Export etwas wurde, das ich guten Gewissens ins öffentliche Netz
stellen konnte. Ganz unten gibt es eine bereinigte Beispieldatei zum
Herunterladen und Öffnen
.

Kurzfassung

  • Was es ist: eine mobile-first Reiseplan-Webapp — Tage, Stopps, Live-Wetter, Währungs- und Zeitzonen-Helfer, offline/installierbar, mit einem KI-Assistenten-Tab.
  • Die Kernidee: ein schöner, aber zerbrechlicher Design-Tool-Export wurde sicher editierbar, sobald der gesamte Zeitplan von einer kleinen Datendatei angetrieben wurde und ein Build-Schritt die App neu generierte.
  • Das Gate: eine clientseitige PIN ist reine Show — das echte Gate ist eine serverseitige Session mit ratenbegrenzter Sperre.
  • Die KI: ein selbstgehostetes lokales Modell, an kurzer Leine gehalten (Fragen beantworten; nur kleine, abgesicherte Änderungen).
  • Die Lehre, die sich ständig wiederholte: Die einzige Möglichkeit zu wissen, ob eine Änderung funktioniert hat, war, die App in einem echten Browser darzustellen und hinzuschauen — nie darauf vertrauen, dass die Datei "eigentlich" in Ordnung sein müsste.

Wo es anfing: eine wunderschöne Blackbox

Ich habe das Aussehen in einem KI-Design-Tool entworfen und exportiert. Herausgekommen
ist eine einzige, in sich geschlossene HTML-Datei — eine komplette React-App mit
eingebetteten Schriften und Bildern — knapp 14 MB in einer Datei. Sie sah
fantastisch aus und funktionierte beim ersten Doppelklick.

Sie war aber auch eine Blackbox. Die ganze App, minifiziert, steckte in einem
einzigen riesigen Klumpen, und der Zeitplan lag tief darin vergraben. Die Reise
von Hand zu bearbeiten bedeutete Chirurgie an maschinengeneriertem Code. Das ist
einmalig okay; es ist ein Albtraum jedes Mal, wenn sich die Pläne ändern — und
auf einer Familienreise ändern sich die Pläne täglich.

Die erste echte Entscheidung war also architektonisch: Aufhören, den Export zu
bearbeiten.

Die eine Idee, die alles andere möglich machte

Statt das 14-MB-Bundle anzufassen, habe ich den gesamten Zeitplan in eine kleine,
lesbare Datendatei verschoben — eine einzige content.json mit den Tagen,
den Stopps, den Wetternotizen, den Fotos und Kosten pro Stopp. Ein kurzer
Build-Schritt liest dann diese Datei und generiert die App aus dem
unangetasteten Export neu.

Das hat den ganzen Charakter des Projekts verändert. “Einen Stopp hinzufügen”
oder “eine Uhrzeit korrigieren” ist jetzt eine Zwei-Zeilen-Änderung an einer
Datendatei, die ein Mensch — oder ein KI-Assistent — sicher vornehmen kann,
gefolgt von einem Rebuild. Der zerbrechliche Teil (die 14-MB-App) wird nie von
Hand angefasst. Jedes Widget, das ich später hinzugefügt habe — eine
Live-Wetter-Karte, eine doppelte Uhr für eigene Zeit ↔ Tokio, ein
zweiseitiger Währungsrechner, aufklappbare Stopps mit Fotos und Parknotizen —
wird aus demselben datengetriebenen Build erzeugt.

Der Fallstrick, der jedes Widget fraß: Die App baut sich beim Start selbst neu

Hier ist die Falle, die mich am meisten Zeit gekostet hat, aufgeschrieben, damit
sie dich nichts kostet.

Die exportierte App baut die gesamte Seite beim Hochfahren neu. Fügt man
eigenes Markup in das statische HTML ein — eine Uhr, ein Banner, irgendetwas —
wird es in dem Moment gelöscht, in dem die App mountet. Die eigenen
Element-IDs verschwinden, getElementById liefert für immer null, und das
Widget erscheint stillschweigend nie. Schlimmer noch: Wenn man durch Durchsuchen
der gespeicherten HTML-Datei “verifiziert”, ist das eigene Markup genau dort zu
finden, also sieht es in Ordnung aus. Es überlebt nur nie bis zum Bildschirm.

Zwei Regeln haben das dauerhaft gelöst:

  1. In die eigene Vorlage der App einfügen, nicht in die Seite. Das Widget
    dort hinzufügen, wo die App ihre UI baut, damit es als Teil der App
    gerendert wird und den Start überlebt.
  2. Verhalten per Event-Delegation auf document verdrahten. Die App
    remountet ganze Abschnitte beim Tab-Wechsel und zerstört und erzeugt ihre
    Knoten dabei neu. Ein Listener, der direkt an einem Button hängt, stirbt
    beim nächsten Re-Render; ein einziger delegierter Listener auf document
    (der prüft, was angeklickt wurde) überlebt alles.

Das Nebenprodukt ist eine Testregel, die ich inzwischen als Gesetz behandle:
eine Web-UI nicht dadurch verifizieren, dass man die Datei grept oder auf
HTTP 200 prüft.
In einem echten (Headless-)Browser rendern, nachdem er
hochgefahren ist
, und sich das Ergebnis tatsächlich ansehen. Das ist die
einzige Prüfung, die diese Fehlerklasse erwischt — und ich habe damit mehrere
gefangen, darunter einen Währungsrechner, der unsichtbar blieb, bis ich ihn in
die Vorlage verschoben habe.

“In sich geschlossen” war es nicht ganz

Der Export behauptete, eine einzige, in sich geschlossene Datei zu sein. Das
stimmte nicht ganz. Er lud sein UI-Framework zur Laufzeit heimlich von einem
öffentlichen CDN nach — zusammengesetzt aus URL-Fragmenten im Code, sodass eine
einfache Textsuche nach dem CDN nichts fand und die Datei aussah, als sei sie
offline-tauglich, obwohl sie es überhaupt nicht war. Auf einem Webhost mit einer
strikten Sicherheitsrichtlinie wurde dieser versteckte Abruf blockiert, und die
ganze App startete nicht. Auf meinem Laptop, ohne eine solche Richtlinie,
funktionierte es — das Problem blieb also unsichtbar, bis es auf den echten
Server traf.

Die Lösung war, das Framework selbst zu hosten (es zusammen mit der App zu
bündeln, sodass kein externer Abruf nötig ist), und für die herunterladbare
Beispieldatei, es direkt einzubetten, sodass das Ganze wirklich eine
einzige Datei ist, die sich ohne jegliches Internet von der Festplatte öffnen
lässt. Wenn du aus diesem Abschnitt eine praktische Sache mitnimmst: “Es
funktioniert auf meinem Rechner” und “Es funktioniert hinter den
Sicherheitsheadern eines echten Webservers” sind zwei verschiedene Aussagen —
teste die zweite.

Das Gate: Show vs. echtes Schloss

Die erste Version hat die Website mit einer PIN im JavaScript der Seite
abgesichert. Das ist Sicherheits-Theater: Der gesamte Reiseplan liegt im
Quelltext der Seite, sodass jeder, der die Entwicklertools öffnen kann, ihn
lesen kann, ohne den Code je einzutippen. Ausreichend, um eine Überraschungsparty
vor einem flüchtigen Blick zu verstecken; nicht ausreichend für alles, was einem
tatsächlich wichtig ist.

Die echte Version hat das Gate auf den Server verlegt: Man sendet die PIN,
der Server prüft sie und startet eine Session, und erst dann gibt er den
Reiseplan heraus. Zusätzlich gibt es eine eskalierende Sperre pro IP — nach
ein paar falschen Versuchen wird diese Adresse für zunehmend längere Zeit
gesperrt, sodass niemand jeden sechsstelligen Code durchprobieren kann.

Ich bin ehrlich zu den Grenzen, weil man sich sonst die Finger verbrennt: Eine
sechsstellige PIN, die nur ratenbegrenzt ist, reicht für einen risikoarmen
Familien-Reiseplan, ist aber nicht bankentauglich. Wer etwas Sensibles absichern
will, sollte eine lange Passphrase und ein richtiges Login verwenden. Das Schloss
sollte zu dem passen, was hinter der Tür liegt.

Der KI-Assistent, an kurzer Leine

Der spaßige Teil: ein kleiner Assistent — ich habe ihn Yoshi genannt — der in
seinem eigenen Tab lebt. In der privaten Familienversion kann er Fragen zur Reise
beantworten (“Was ist der Plan für den Aquarium-Tag?”, “Was sollen wir
einpacken?”) und kleine, abgesicherte Änderungen am Zeitplan vornehmen. Der
springende Punkt ist, dass er dieselbe sichere Datendatei bearbeitet, nie die
zerbrechliche App — und er ist eingezäunt: Er kann hinzufügen oder anpassen, aber
keine Tage löschen, und Flüge und Reservierungen sind fest verriegelt, sodass
keine Chat-Nachricht sie anfassen kann.

Entscheidend ist, dass Yoshi auf einem selbstgehosteten lokalen Modell läuft
— einer kleinen KI auf meiner eigenen Hardware, keiner bezahlten Cloud-API —, die
über ein abgesichertes Relay erreicht wird. Das ist ein eigenes großes Thema
(einen lokal gehosteten Chatbot sicher ins öffentliche Netz zu stellen, und wie
langsam das auf Consumer-Hardware ist), also habe ich es separat aufgeschrieben:
Einen Chatbot ins öffentliche Netz stellen — auf meiner eigenen Hardware.

Hinweis zur unten herunterladbaren Beispieldatei: Der Assistenten-Tab ist da,
damit du siehst, wo er lebt, aber in der Beispieldatei ist er absichtlich
abgeschaltet
— er ist mit keiner KI verbunden und antwortet nicht. Um ihn in
der eigenen Kopie zum Laufen zu bringen, müsste man ihn auf das eigene lokale
Modell zeigen lassen. Das ist Absicht: Ich werde keine öffentliche Datei
ausliefern, die heimlich mit einer laufenden KI telefoniert.

Offline, installierbar und reisetauglich

Der letzte Feinschliff drehte sich um die Realität, das Ganze auf einer Reise zu
nutzen: durchwachsenes Hotel-WLAN, keine Daten im Flugzeug, Roaming, das kommt
und geht. Also wurde die App zu einer installierbaren PWA — zum
Startbildschirm hinzufügen, und sie öffnet sich wie eine echte App — und sie
funktioniert offline, sie liefert den gesamten Reiseplan vom Gerät, sobald
er einmal geladen wurde. Die eine Regel, auf die ich geachtet habe: Sie
cached den Reiseplan nur nach einem erfolgreichen Entsperren, sodass das Gate
nie durch den Offline-Cache umgangen wird. Online prüft sie das Gate immer
erneut; offline vertraut sie der Kopie, die bereits auf dem eigenen Smartphone
liegt. Für einen Familienreiseplan ist das der richtige Kompromiss.

(Service Worker haben scharfe Kanten — eine alte gecachte Version kann noch
lange nach einem ausgelieferten Fix eine veraltete Seite weiter anzeigen. Ich
habe gelernt, immer einen aktualisierten Worker auszuliefern, nie einfach nur
die alte Datei zu löschen, damit Browser die Änderung wirklich übernehmen.)

Es wie echt testen, weil es echt ist

Weil die Familie das während der Reise tatsächlich benutzt hat, habe ich jede
Änderung wie ein Deployment behandelt: bauen, die echte Seite nach dem
Hochfahren in einem Headless-Browser rendern, die tatsächlichen Tabs und
Buttons bedienen, und erst dann ausliefern. Mitten in der Reise habe ich auf
diese Weise kleine Live-Fixes eingespielt — ein “HEUTE”-Badge, das den
aktuellen Tag hervorhebt (verankert an der Tokioter Zeit, nicht am Smartphone
des Betrachters), aktualisiertes Wetter, ein paar Layout-Kleinigkeiten — jede
davon zuerst in einem echten Browser verifiziert. Dieselbe Lehre wie beim
Neuaufbau-beim-Start-Fallstrick, erneut gelernt: Dass die Datei existiert, ist
nicht dasselbe, wie dass das Feature funktioniert.

Ausprobieren — die Beispieldatei herunterladen

Der Download unten ist eine bereinigte, öffentliche Beispieldatei der App.
Jeder Name, jedes Datum, jede Adresse und jedes Detail ist frei erfunden — es
ist ein fiktiver 7-Tage-Tokio-Plan. index.html in jedem beliebigen Browser
öffnen — keine Installation, kein Server, kein Konto nötig. Sie funktioniert
offline; nur die Live-Wetter-Karte und die Karten-Links greifen aufs Internet zu.

Zwei ehrliche Einschränkungen. Erstens ist es eine einzige ~14-MB-Datei,
weil die ganze App, Schriften und Bilder eingebettet sind, damit sie mit null
Abhängigkeiten läuft — das macht sie beim ersten Öffnen langsam; ein
Produktions-Build würde all das aufteilen und komprimieren. Zweitens ist, wie
erwähnt, der KI-Assistent in der Beispieldatei abgeschaltet — der Tab öffnet
sich und sagt das auch.

Selbst eine bauen

Die “Selbst umsetzen”-Box ganz oben ist keine Floskel — dieser Artikel ist dafür
gedacht, direkt an einen KI-Assistenten weitergereicht zu werden. Das Rezept,
verdichtet:

  1. Den Plan in eine Datendatei packen. Tage, Stopps, Notizen, Wetter — eine
    lesbare Datei, die die einzige Quelle der Wahrheit ist.
  2. Die App daraus generieren. Ein deterministischer Build-Schritt verwandelt
    die Daten in die UI, sodass Änderungen immer an den Daten stattfinden, nie am
    generierten Code.
  3. Falls du von einem Design-Tool-Export ausgehst, respektiere seinen Start.
    In die Vorlage der App einfügen, delegierte Events verwenden, und alles, was
    sie nachlädt, selbst hosten.
  4. Auf dem Server absichern, nicht auf der Seite. Eine Session plus
    ratenbegrenzte Sperre schlägt jedes Mal eine JavaScript-PIN.
  5. Offline-first machen, wenn es auf Reisen mitgeht — nur nach einem echten
    Entsperren cachen.
  6. Im Browser verifizieren, nach dem Hochfahren. Nicht “die Datei existiert”.
    Der Bildschirm.

Fallstricke, gesammelt

  • Der Export baut das DOM beim Start neu auf — alles, was in die Seite
    eingefügt wird, wird gelöscht; stattdessen in die Vorlage der App einfügen und
    Events auf document delegieren.
  • “In sich geschlossen” muss es nicht sein — ein Export kann sein Framework
    zur Laufzeit nachladen; selbst hosten und hinter den echten
    Sicherheitsheadern des Servers testen, nicht nur lokal.
  • Eine clientseitige PIN ist reine Show — der Inhalt steht in der Seite;
    auf dem Server absichern.
  • Eine sechsstellige PIN ist nur ratenbegrenzt, nicht unknackbar — für
    geringes Risiko okay, für alles Sensible falsch.
  • Service Worker cachen aggressiv — einen aktualisierten Worker ausliefern,
    um einen Fix auszurollen; die alte Datei zu löschen, meldet ihn nicht ab.
  • “Die Datei existiert” ≠ “das Feature funktioniert” — jedes Mal rendern und
    hinschauen.

Downloads

Kostenlos für die private Nutzung. Wenn es dir einen Nachmittag erspart, ist der Kaffee-Button gleich in der Nähe.


← Mehr KI & lokale LLMs