Dev-only Prototyping-Schicht fuer Next.js-Apps: TODOs, Review-Gate, Git-/Deploy-/Release-Panel
  • TypeScript 90%
  • JavaScript 9.8%
  • CSS 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
André Böhlke ce9fc3ec61
Some checks failed
QA / qa (push) Has been cancelled
Plan-Reiter: TODO verwerfen, solange es noch geplant wird
Neue Aktion "verwerfen" an /api/prototype-plan: setzt das TODO ueber
cancelTodo auf cancelled und schliesst die Planung. Erlaubt in offen,
laeuft, fragen und vorgelegt; eine freigegebene Planung gibt 409.
Im Reiter ein zweistufiger Knopf "Verwerfen".
2026-10-12 19:13:47 +00:00
.forgejo/workflows Prototyping-Schicht als zentrales Paket: TODOs, Review-Gate, Git-/Deploy-/Release-Panel 2026-09-18 13:49:47 +00:00
bin Aufraeum-Test deterministisch: erstelltAm setzen statt auf die Uhr hoffen 2026-10-09 07:13:31 +00:00
src Plan-Reiter: TODO verwerfen, solange es noch geplant wird 2026-10-12 19:13:47 +00:00
tooling Client-Einstieg als .ts-Barrel: TypeScript unterstuetzt .tsx als exports-Ziel nicht 2026-09-18 13:55:24 +00:00
.gitignore Prototyping-Schicht als zentrales Paket: TODOs, Review-Gate, Git-/Deploy-/Release-Panel 2026-09-18 13:49:47 +00:00
node_modules Prototyping-Schicht als zentrales Paket: TODOs, Review-Gate, Git-/Deploy-/Release-Panel 2026-09-18 13:49:47 +00:00
package.json Plan-Kanal: Modell, Dateiformat und Routen 2026-10-09 06:47:07 +00:00
README.md README: der Plan-Kanal, und wer welchen Status schreiben darf 2026-10-09 06:52:22 +00:00
tsconfig.json noUncheckedIndexedAccess standhalten (strengste Konsumenten-Konfiguration) 2026-09-18 14:08:59 +00:00

@kryptance/prototyping

Die Prototyping-Schicht für Next.js-Apps: Shift+Klick auf dem Desktop, Long-Press (500 ms halten) auf dem Handy auf irgendein Element der Oberfläche legt ein TODO im Repo an; eine responsive Sidebar (vollbreites Blatt auf dem Handy, 420px ab sm) zeigt TODOs, Commits, Review-Zustand und Deploy; ein Release-Panel rollt per helm zurück. Alles gemeinsam trägt eine zwei-Parteien-Disziplin: ohne fertigen Review ohne Blocker geht nichts live — erzwungen in der Route, nicht nur im Knopf.

Die Schicht ist rein entwicklungsseitig: unter NODE_ENV=production rendert keine Komponente etwas und jede Route antwortet 404.

Entstanden in drannamariabeck, seither hier zentral — beide Apps (drannamariabeck, SpielerNet) binden sie als Git-Dependency ein, damit Verbesserungen überall ankommen.

Einbau in eine App

pnpm add @kryptance/prototyping@git+https://git.kryptance.io/kryptance/prototyping.git#<sha>
# bzw. npm i @kryptance/prototyping@git+https://git.kryptance.io/kryptance/prototyping.git#<sha>

Das Paket hat keine Abhängigkeiten (nur peer: react, react-dom, optional next) und liefert TypeScript-Quellen — die App braucht transpilePackages: ["@kryptance/prototyping"] in ihrer Next-Konfiguration.

1. Konfiguration (eine Datei, z. B. src/lib/prototyping.ts)

import type { PrototypingConfig } from "@kryptance/prototyping";

export const prototypingConfig: PrototypingConfig = {
  deploy: { mode: "push" },          // oder { mode: "script", command: "node", args: ["scripts/deploy.mjs"] }
  release: { release: "meine-app" }, // helm; namespaceDefault "apps", kubeconfigDefault ~/.kube/dev3-config
  tour: { file: "src/lib/onboarding-tours.json" }, // nur wenn die App Touren hat
};

2. Routen (dünn, jede Datei nur Re-Exports)

// src/app/api/prototype-todo/route.ts
import { createTodoRoutes } from "@kryptance/prototyping";
import { prototypingConfig } from "@/lib/prototyping";

export const { GET, POST } = createTodoRoutes(prototypingConfig);

Genauso: createClarifyRoute (prototype-todo/[id]/clarify), createUploadsRoute (prototype-todo/uploads/[filename]), createFrageRoutes (prototype-frage), createPlanRoutes (prototype-plan), createGitRoutes (prototype-git), createReleaseRoutes (prototype-release), createEventsRoute (prototype-events), optional createTourRoutes (prototype-tour).

prototype-frage und prototype-plan sind optional: fehlt die Route, antwortet sie 404, und der zugehörige Reiter verschwindet statt ins Leere zu führen. Beide brauchen aber einen eigenen Daemon neben der App — ohne ihn wartet die Sidebar ewig.

prototype-events ist die Live-Verbindung und sollte nicht fehlen: ohne sie fällt die Sidebar auf Abfragen alle 10 Sekunden zurück. Sie schickt Server-Sent Events, sobald sich in todo/ oder .prototype-review/ etwas rührt — oder in .prototype-frage/ und .prototype-plan/. Eine Richtung genügt, es läuft durch jeden Proxy, und EventSource bringt die Wiederverbindung mit. Steht die App hinter nginx, gehört proxy_buffering off für diesen Pfad dazu.

3. Layout

import { PrototypeLayer } from "@kryptance/prototyping/client";

// im Rootlayout:
<PrototypeLayer />

extras reicht App-eigenes in den TODO-Reiter (z. B. einen „Tutorials zurücksetzen“-Knopf). tabs hängt ganze eigene Reiter hinter die festen — Beschriftung und Inhalt kommen von der App, die Schicht rendert sie nur:

<PrototypeLayer tabs={[{ id: "demo", label: "Demo", inhalt: <DemoReiter /> }]} />

Mit vorne: true steht ein eigener Reiter vor den festen, und der erste solche Reiter ist beim Aufklappen der Sidebar offen statt „TODOs“.

Wer Touren hat, zusätzlich:

import { PrototypeTourEditor } from "@kryptance/prototyping/tour";

<PrototypeTourEditor onPlayTour={startTour} />

4. .gitignore der App

/todo-uploads/
.prototype-review/        # Review-Zustand, deploy.log, deploy.lock
.prototype-frage/         # Gespräche — erklären nur, ändern nichts
.prototype-plan/          # Plan-Dialoge; der fertige Plan steht im TODO

todo/ selbst wird committet — die TODOs sind das Arbeitsmaterial.

TODO-Status

status im Frontmatter in der Sidebar
pending (oder fehlend) Offen — noch nicht einmal geplant
planning Plant, mit hochzählender Laufzeit
plan-dialog Wartet auf dich — Rückfragen oder Freigabe, bedient im Reiter Plan
freigegeben Freigegeben — Plan steht, wartet auf die Umsetzung
in-progress In Arbeit, mit hochzählender Laufzeit
testing Im Test — umgesetzt, Testlauf entscheidet noch
needs-clarification Braucht Klärung, mit Antwortfeld
done Reiter Erledigt
cancelled Reiter Erledigt, als „Abgebrochen“ gekennzeichnet

Ein TODO in Klärung lässt sich in der Sidebar verwerfen (Knopf „Abbrechen“, zweistufig). Das geht über denselben Endpunkt wie die Klärung — POST /api/prototype-todo/<datei>/clarify mit { action: "abbrechen" } —, damit der Knopf allein mit einer neuen Paketversion in jeder App ankommt. Die Datei bleibt liegen: die Frage, die niemand mehr beantworten will, ist oft aufschlussreicher als ihr Verschwinden.

Die drei plan-Status stehen vor der Umsetzung, siehe Plan-Kanal weiter unten. Wichtig für Apps mit eigener Schleife: seit dem Plan-Schritt gehört pending dem Plan-Daemon, und die Umsetzung greift nach freigegeben. Eine App ohne Plan-Kanal bleibt davon unberührt — ohne die Route gibt es den Reiter nicht, und ihre Schleife darf weiter pending nehmen.

in-progress braucht zusätzlich startedAt: <ISO-Zeit> — daraus läuft die Uhr. Gezählt wird im Browser, verankert an der Server-Zeit: GET /api/prototype-todo liefert sein eigenes now mit, die Sidebar rechnet daraus den Versatz beider Uhren heraus. Gesetzt wird der Status von dem, der das TODO anfasst — bei einer Schleife von ihrem Abschluss-Skript. Bleibt eine Sitzung auf halbem Weg liegen, gehört das TODO zurück auf pending, sonst läuft die Uhr ewig weiter.

Deploy-Modi

  • push — der Knopf pusht den Branch; Bauen und Ausrollen macht die CI.
  • script — der Knopf startet ein App-eigenes Skript (feste Argumentliste, keine Shell): pushen, Images bauen, helm upgrade. Das Protokoll steht in .prototype-review/deploy.log, ein Lock verhindert Parallel-Deploys, und der Review-Zwang greift vorher in der Route.

Review-Schleife (CLI)

Die Gegenseite des Prüfschritts, lauffähig ohne Abhängigkeiten im Repo der App:

node node_modules/@kryptance/prototyping/bin/review.mjs --pending
node node_modules/@kryptance/prototyping/bin/review.mjs --start <head>
node node_modules/@kryptance/prototyping/bin/review.mjs --finish <head> --result <datei.json>

Ergebnisdatei: { "note": "…", "findings": [{ "file": "…", "line": 12, "severity": "blocker" | "hinweis", "summary": "…" }] }.

Plan-Kanal (CLI)

Vor der Umsetzung plant ein eigener Daemon. Er darf zurückfragen — und das ist der ganze Punkt: eine unbeaufsichtigte Umsetzungs-Sitzung kann es nicht, sie hat nur „umgesetzt" oder „in die Klärung geschoben", und die Klärung beginnt ohne Kontext von vorn.

Der Dialog liegt als JSON in .prototype-plan/<todo-ohne-.md>.json (gitignoriert): Sitzungs-Kennung, offene Fragen, vorgelegter Plan, Verlauf. Der freigegebene Plan gehört dagegen als Abschnitt ## Plan in die committete TODO-Datei — die Umsetzung ist eine frische Sitzung und liest ihren Auftrag aus der Datei, nicht aus dem Gedächtnis des Plan-Agenten.

zustand der Planung status des TODOs wer ist am Zug
offen pending der Daemon (noch nicht angefasst)
laeuft planning der Agent
fragen plan-dialog du — Rückfragen beantworten
vorgelegt plan-dialog du — freigeben oder nachbessern lassen
freigegeben freigegeben die Umsetzungsschleife
fehler needs-clarification du

Die Aufteilung der Schreibrechte ist Absicht und sollte so bleiben:

  • src/lib/plan.ts ist die Seite des Menschen — sie liest, und sie schreibt, was aus der Oberfläche kommt (Antworten, Freigabe, Ablehnung).
  • bin/plan.mjs ist die Seite des Agenten, als gewöhnliches JavaScript, weil der Daemon ein node-Prozess ist und .ts nicht laden kann. Anders als bin/frage.mjs ist es in erster Linie eine Bibliothek: der Daemon importiert die Funktionen, statt sich je Schritt einen Unterprozess zu starten.
  • Den status im TODO schreibt keines von beiden, sondern allein das Abschluss-Skript der App. Zwei Schreiber auf derselben Zeile wären genau die Art Fehler, die man erst bemerkt, wenn ein TODO zweimal umgesetzt wurde.
node bin/plan.mjs --pending          # TODOs, die auf einen Plan warten
node bin/plan.mjs --state <todo.md>  # die Planung als JSON
node bin/plan.mjs --reset            # hängende Planungen zurück in die Schlange

--reset holt nur laeuft zurück — was bei einem Menschen liegt, bleibt liegen. Die Sitzungs-Kennung bleibt dabei erhalten, damit der nächste Lauf das Gespräch per resume fortsetzt statt von vorn zu planen.

Selbstfreigabe. Ein TODO, das ohne Zutun eines Menschen entstanden ist (pfad: konsolenfehler, oder eine jira:-Zeile ohne jira-art: review), gibt sich seinen Plan selbst frei, sofern der Agent keine Rückfragen hat. Sonst stünde die Schlange bis zum nächsten Morgen — diese TODOs entstehen nachts. Der Plan steht trotzdem in der Datei.

Die Rückfragen kommen genau in der Form, in der das Werkzeug AskUserQuestion des Agent-SDK sie stellt (question, header, options, multiSelect), und gehen genau so als answers zurück. Bewusst nicht umgeformt: jede eigene Zwischenform wäre eine zweite Stelle, die bei einer SDK-Änderung bricht.

Frage-Kanal (CLI)

Der Shift-Klick-Dialog kann zweierlei: „Als TODO speichern" beauftragt eine Änderung, „Nur fragen" stellt eine Frage zum angeklickten Element — der Agent liest mit, antwortet und ändert nichts. Die Gespräche liegen als JSON in .prototype-frage/ (gitignored, sie gehören nicht in die Projektgeschichte) und erscheinen in der Sidebar im Reiter „Fragen" als Faden mit Rückfrage-Feld.

Der Reiter zeigt sich nur, wenn die App die Route angelegt hat:

// src/app/api/prototype-frage/route.ts
export const { GET, POST } = createFrageRoutes(prototypingConfig);

Den Datensatz hinter dem Klick steuert die App bei — das Paket kennt ihre Routen nicht:

frageKontext: async (pfad, stelle) => {
  const id = stelle.datensaetze.find((d) => d.entitaet === 'gesuch')?.id;
  return id ? beschreibeGesuch(id) : null;
},

Gerufen wird der Haken von beiden Kanälen, Frage wie TODO. Das zweite Argument (selector, pfad, heading und die Datensatz-Kette) ist neu und optional: Eine Funktion mit weniger Parametern bleibt zuweisbar, bestehende Apps ändern nichts.

Was das Paket von der Oberfläche erwartet

Zwei Attribute, mehr nicht — beides wird beim Klick ausgelesen:

Attribut Wofür
data-testid="gesuch-karte" benennt das Bedienelement. Steht eines da (oder eine id), verschwinden die Klassen aus dem Selektor.
data-<entität>-id="abc123" markiert einen Datensatz-Container, z.B. data-gesuch-id.

Die Datensatz-Kette wird vom angeklickten Element bis zum body gesammelt, Wurzel zuerst (gesuch abc123 > bewerbung def456), und landet im Frontmatter des TODOs sowie strukturiert im Frage-Faden. Ohne die Attribute funktioniert alles weiter — der Selektor fällt dann auf zwei Klassen je Ebene zurück, und das ist genau der wenig aussagekräftige Zustand, den die Attribute beheben.

Der Bindestrich vor id ist wesentlich: data-testid endet auf testid, nicht auf -id, und gilt deshalb richtigerweise nicht als Datensatz.

Beantwortet wird ausserhalb der Anwendung, von einem eigenen Daemon. Sein Griff:

node node_modules/@kryptance/prototyping/bin/frage.mjs --pending
node node_modules/@kryptance/prototyping/bin/frage.mjs --start  <id>
node node_modules/@kryptance/prototyping/bin/frage.mjs --answer <id> --text <datei>
node node_modules/@kryptance/prototyping/bin/frage.mjs --fail   <id> --text "…"
node node_modules/@kryptance/prototyping/bin/frage.mjs --reset

Die Antwort kommt aus einer Datei, nicht als Argument: Antworten sind mehrzeilig und enthalten Code-Fences — als Shell-Argument geht das früher oder später schief. Die Faden-Kennung ist zugleich die Sitzungs-Kennung des Agenten, damit eine Rückfrage dasselbe Gespräch fortsetzt (claude -p --resume <id>).

Angehängte Bilder (Frage wie Rückfrage) landen wie beim TODO in todo-uploads/; der Beitrag im Faden trägt nur ihre Dateinamen (verlauf[].images). Der Daemon reicht sie dem Agenten als Pfade weiter — ohne das beantwortet er „warum sieht das nicht mehr aus wie im Anhang?" ohne den Anhang.

ENV

Variable Wirkung
FORGEJO_TOKEN Pipeline-Anzeige im Deploy-Reiter (Forgejo-API, privat braucht Token)
HELM_KUBECONFIG übersteuert release.kubeconfig
HELM_NAMESPACE übersteuert release.namespace
HELM_RELEASE übersteuert release.release

Sicherheit

Alles Subprocess-artige läuft über execFile mit festen Argumentlisten, nie über eine Shell; Commit-Hashes werden gegen /^[0-9a-f]{7,40}$/ geprüft; Upload-Dateinamen weisen Verzeichnisbestandteile zurück; das Rollback-Ziel wählt der Server aus helm history, nie die Anfrage. Und: In Produktion (next build) existiert nichts hiervon — 404 statt Oberfläche.