324 lines
18 KiB
Markdown
324 lines
18 KiB
Markdown
---
|
||
title: "Kanshō – Technische Umsetzung des MVP-Journal-Slices"
|
||
status: "Arbeitsstand"
|
||
date: "2026-08-25"
|
||
product_family: "Jinkendo"
|
||
document_role: "Technical Chapter / MVP Implementation How-To / Module and Runtime Map"
|
||
parent_document: "technische_zielarchitektur.md"
|
||
---
|
||
|
||
# Kanshō – Technische Umsetzung des MVP-Journal-Slices
|
||
|
||
**Kanonisches Home** dafür, *wie* der erste vertikale Slice gebaut ist: Laufzeit, Module, Daten, API, Pfade.
|
||
|
||
Nicht dieses Dokument:
|
||
|
||
- Fachlicher Lieferumfang → `../functional/mvp.md`
|
||
- Foundation-Regeln → `../functional/implementation_foundation.md`
|
||
- Fit-Gap (Spec, Abweichungen, offene Punkte gegen Slice und Gesamtziel) → `../functional/mvp_stand_und_abgleich.md`
|
||
|
||
Kapiteldetails (Auth, Gateway-Verfahren, Shell-Muster) bleiben in den jeweiligen technischen Homes. Hier die zusammenhängende Umsetzung.
|
||
|
||
**Stand:** 2026-08-25, Code unter `frontend/` und `backend/`. Architecture-Correction-Pass am selben Tag (relationale Source-Refs, Selection-Specs, begrenztes Space-Source-Retrieval, Detect-Env, Response Validation, Konsolidierungssignale, `/dialog` als Admin-Harness).
|
||
|
||
---
|
||
|
||
# 1. Laufzeit
|
||
|
||
Lokal, eine Instanz, ein Profil-Nutzer nach Setup:
|
||
|
||
| Prozess | Stack | Port | Einstieg |
|
||
|---|---|---|---|
|
||
| Frontend | React 18, Vite, React Router 6, PWA-Plugin | **5188** (`strictPort`) | `frontend/` → `npm run dev` |
|
||
| Backend | FastAPI, Python 3.12, Uvicorn | **8018** | `backend/` |
|
||
| Persistenz | **SQLite** Datei | — | `backend/data/kansho.sqlite` |
|
||
| Medien | lokale Dateien | — | `backend/data/media/<profile_id>/` |
|
||
| Secrets | Env | — | `backend/.env` (Keys nie in der DB) |
|
||
|
||
Proxy: Vite leitet `/api` an 8018. CORS erlaubt `localhost:5188`.
|
||
|
||
**Abweichung vom technischen Zielrahmen:** `product_frame_and_stack.md` nennt PostgreSQL. Der Slice läuft auf SQLite. Lokal zulässig; Prod-Pfad offen. Siehe Fit-Gap.
|
||
|
||
Health: `GET /api/health`.
|
||
|
||
---
|
||
|
||
# 2. Schichten und Invariante
|
||
|
||
```text
|
||
PWA (React)
|
||
→ /api Session-Cookie
|
||
FastAPI-Router (kein Prompt-Bau im Frontend)
|
||
→ journal_policy / stores
|
||
→ context_builder + retrieval (intern, Klartext, nie Egress)
|
||
→ privacy_gateway (einziger generativer Egress)
|
||
Policy → Maskierung → Detect (Testphase) → Generate-Call → Validierung → Demask
|
||
→ SQLite + media/
|
||
```
|
||
|
||
Persönlicher Kontext geht nicht direkt an OpenRouter. Router rufen kein LLM. Fail Closed: kein unsicherer Generate-Fallback.
|
||
|
||
Zwei Provider-Rollen (`provider_settings`): `detect` (Maskierung) und `generate` (Dialogzug und Journalentwurf). URL/Modell/Flags in der DB, Key in `.env`. Admin: Schnittstellen.
|
||
|
||
---
|
||
|
||
# 3. Backend-Module
|
||
|
||
Einstieg: `backend/main.py` (Router: auth, users, dialogue, journal, prompts, placeholders, subscription, admin).
|
||
|
||
| Modul | Verantwortung |
|
||
|---|---|
|
||
| `routers/journal.py` | Produkt-API `/api/journal` |
|
||
| `routers/dialogue.py` | Low-Level-Source `/api/dialogue`; derselbe Turn-Pfad |
|
||
| `dialogue_turn.py` | Ein generativer Call, JSON `{operation, impulse}`, lokale Register-/Wächter, optional Repair |
|
||
| `journal_generate.py` | Expliziter Draft-Call |
|
||
| `journal_store.py` | Days, Drafts, Entries, Versionen, Scratch |
|
||
| `journal_policy.py` | Explizites Generate, Origins, Konsolidierungsangebot aus lokalen Signalen |
|
||
| `journal_body.py` / `journal_shape.py` | Titel, Markdown-Body, lokale Nachformung |
|
||
| `context_builder.py` | Interner Prompt-Kontext über Selection-Specs, keine Recency-API |
|
||
| `retrieval.py` | Austauschbare Specs: `day_messages`, `space_entries`, `space_recent_sources`, `writing_profile`. Recency/SQL-Kappen sind Implementierung. |
|
||
| `conversation_signals.py` | Lokale operative Metadaten (`narrative_mode`, `reflection_depth`, `current_focus`), kein Extra-LLM |
|
||
| `privacy_gateway.py` | Egress; Response Validation vor Demask |
|
||
| `entity_detect.py` / `pronoun_bind.py` / `identity_store.py` | Detection, Dialog-Pronomen, Mapping (Klasse A) |
|
||
| `writing_profile_store.py` | Lokaler Journal-Stilbrief, Facets, Governance, kein Extra-LLM, keine Dialogsteuerung |
|
||
| `profile_review.py` | Lokale Evidence-/Drift-Erkennung, Review-Pakete, API- und Paste-Kanal; kein Call nach jedem Dialog |
|
||
| `writing_profile_infer.py` | Lokale Heuristik für Facets, kein LLM |
|
||
| `media_store.py` | Upload, Datei, Token im Body |
|
||
| `providers.py` / `provider_settings.py` | Provider-Rollen |
|
||
| `db.py` + `schema.sql` | SQLite, Migrationen |
|
||
|
||
Prompts in der DB, nicht im Anwendungscode: `mvp.dialogue_turn`, `mvp.journal_generate`, `mvp.entity_detect`, `mvp.profile_review`. Seed: `backend/config/prompts.seed.json`. Unangetastete Templates aktualisiert `init_db`.
|
||
|
||
---
|
||
|
||
# 4. Daten (Ist, nicht Ziel-PostgreSQL)
|
||
|
||
Schema: `backend/schema.sql`. Isolation über `profile_id`.
|
||
|
||
**Rahmen / Layer 0:** `profiles`, Auth-`sessions`, `usage_sessions`, `conversations`, `messages`, Thread-/Space-Hüllen, `derived_records` (ungenutzt für Journal-Writes), `identity_mappings`, Prompt-/Feature-/Provider-Tabellen.
|
||
|
||
**Journal-Slice:**
|
||
|
||
| Tabelle | Rolle |
|
||
|---|---|
|
||
| `spaces` | Sichtbarer Reflection Space (`visibility='user'`) |
|
||
| `journal_days` | `(space_id, calendar_date)`; `scratch_json` lokal |
|
||
| `journal_drafts` | Derived; neue Generierung historisiert den Draft |
|
||
| `journal_entries` | Current über `current_version_id`; Soft-Delete `deleted_at` |
|
||
| `journal_entry_versions` | History; Restore hängt eine Version an |
|
||
| `journal_draft_source_refs` / `journal_entry_version_source_refs` | Relationale Provenance (Conversation/Message-IDs). JSON-Listen sind Altbestand und werden beim Start migriert. |
|
||
| `media_assets` | Datei + Kind; Referenz nur im Body `` |
|
||
| `writing_profiles` / `writing_profile_sources` | Current Brief |
|
||
| `writing_profile_evidence` / `writing_profile_reviews` / `writing_profile_versions` | Review-Pipeline: lokale Evidenz, Paket/Ergebnis, Versionen |
|
||
|
||
Assignment: `conversations.space_id` / `journal_day_id` sind Felder, nicht Identität. `space_id` ohne `journal_day_id` ist zulässig (Space ≠ Journal-Container). `conversations.space_id` ohne FK. Kalendertag ≠ `messages.created`. Operative Dialogsignale liegen als Felder auf `conversations`, nicht als Writing Profile.
|
||
|
||
Details: `memory_storage_and_offline.md` §6.1–6.2.
|
||
|
||
---
|
||
|
||
# 5. API (Produkt)
|
||
|
||
Prefix `/api/journal`, Session-Auth, Router `backend/routers/journal.py`. Vollständige Liste: `backend_and_api.md` §5.1.
|
||
|
||
Kernpfade:
|
||
|
||
1. **Dialogzug** `POST /days/{id}/conversations/{cid}/turn` und `POST /api/dialogue/conversations/{id}/turn`
|
||
Context Builder → Gateway (`purpose=dialogue_turn`) → ein Generate-Call → Impuls speichern. Pronomenbindung nur hier in `user:`-Zeilen.
|
||
|
||
2. **Journalentwurf** `POST /days/{id}/generate`
|
||
Nur explizit. Optional `conversation_ids`. Ohne Auswahl kein stilles Mergen. Gateway `purpose=journal_generate`. Pronomen ungebunden. Entry-`current_version_id` bleibt unangetastet. UI öffnet `?draft=1`.
|
||
|
||
3. **Speichern** `POST /entries`
|
||
Mit `entry_id` + `origin=accepted_draft`: neue Version desselben Entry. Sonst neuer Entry oder `user_edit`.
|
||
|
||
4. **Restore** `POST /entries/{id}/restore` → neue Version, Origin `restore`.
|
||
|
||
5. **Scratch** `PATCH /days/{id}/scratch` → nicht im Context Builder, Test: Token nicht im Egress.
|
||
|
||
6. **Media** Upload/GET/DELETE unter dem Entry.
|
||
|
||
7. **Profile Review** `GET/POST /writing-profile/review…` → lokale Evidenz, explizite API- oder Paste-Review, kein Call am Dialogzug.
|
||
|
||
`include_existing` existiert in der API, die UI setzt es nicht.
|
||
|
||
Fehlerkörper oft `{detail: {code, message}}`, Rahmen will `{detail: string}` — Abweichung, Fit-Gap.
|
||
|
||
---
|
||
|
||
# 6. Frontend-Umsetzung
|
||
|
||
Shell: `frontend/src/App.jsx`, Nav `config/appNav.js` (Start / Journal / Einstellungen; Admin extra). `/dialog` ist nicht in der Produkt-Nav. Breakpoint 1024px: Bottom-Nav vs Sidebar. Details: `frontend_pwa_shell.md` §6.1.
|
||
|
||
| Route | Seite | Technik |
|
||
|---|---|---|
|
||
| `/` | `HomePage.jsx` | Stub, Link zum Journal |
|
||
| `/dialog` | `DialoguePage.jsx` | **Admin-only Dev/Test-Harness**, nicht in der Produkt-Nav. Unzugeordnete Conversations. |
|
||
| `/journal` … `/journal/:spaceId/:dayId` | Spaces, Space, `JournalDayPage.jsx` | Chat, Generate, `DayScratch.jsx` |
|
||
| `…/entry` | `JournalEditorPage.jsx` | TipTap, Markdown in `journal/document.js`, Dirty-Flag `context/UnsavedChanges.jsx` |
|
||
| `…/source/:conversationId` | `JournalSourcePage.jsx` | Quelldialog |
|
||
| `/settings` | `SettingsPage.jsx` | Writing/Interaction, Review-Paket (API oder Copy/Paste) |
|
||
|
||
Editor speichert Markdown, nicht HTML. Medien-Token bleiben lokal. Dirty: In-App-Links, Abmelden, `beforeunload`; Browser-Zurück nicht hart blockiert.
|
||
|
||
---
|
||
|
||
# 7. Zwei fachliche Calls, drei technische Schichten
|
||
|
||
| Schicht | Rolle | Call |
|
||
|---|---|---|
|
||
| Maskierung | `detect` | Muster immer. Externes Klartext-Detect nur Development/Test (`KANSHO_ENV`). Produktiv: Muster oder lokale Detect-URL. |
|
||
| Dialogzug | `generate` | Operation + Impuls |
|
||
| Journalentwurf | `generate` | Explizit, getrennt |
|
||
|
||
Explizite Writing-Profile-Review ist ein weiterer Generate-Zweck (`profile_review`), nicht der Dialogzug und nicht nach jedem Turn.
|
||
|
||
Interner Context-Builder (Klartext, lokal): fordert Selection-Specs an (`day_messages`, `space_entries`, `space_recent_sources`, `writing_profile`). Recency-Kappen (Day 80 Messages; 5 Space-Entries × 400 Zeichen; 3 Space-Source-Conversations × 400 Zeichen User-Ausschnitt) liegen in `retrieval.py`, nicht in der Context-Builder-API. Dialogzug: aktuelle Conversation primär, plus begrenzte frühere Original-Conversations desselben Space (ohne denselben Journal Day voll zu laden). Generate ohne Prior-Entries, mit Writing Profile. `WritingProfile` steuert Journaltext, nicht den Dialogue Kernel; der Turn hat einen leeren `interaction_hint`-Slot. Retrieval austauschbar, keine Embeddings.
|
||
|
||
Gateway-Verfahren und offener Security Layer: `privacy_gateway.md` §9.1–9.2.
|
||
|
||
---
|
||
|
||
# 8. Tests (technisch)
|
||
|
||
| Suite | Ort |
|
||
|---|---|
|
||
| Rahmen, Auth | `backend/tests/test_frame.py` |
|
||
| Journal-Slice, Scratch-Egress, Versionsschutz | `backend/tests/test_mvp_journal.py` |
|
||
| Architecture Correction (Provenance, Space-Source, Detect-Env, Kernel-Grenzen) | `backend/tests/test_architecture_correction.py` |
|
||
| Detect, Pronomen Dialog vs Journal, Response Validation | `backend/tests/test_privacy_detect.py` |
|
||
| Shape / Namen | `backend/tests/test_journal_shape.py` |
|
||
| Writing Profile | `backend/tests/test_writing_profile.py` |
|
||
| Profile Governance | `backend/tests/test_profile_governance.py` |
|
||
| Profile Review Pipeline | `backend/tests/test_profile_review.py` |
|
||
| Writing-Profile-Hülle / Initial Build | `backend/tests/test_profile_review.py`, `backend/tests/test_profile_governance.py` |
|
||
| Markdown-Runde | `frontend/src/journal/document.test.js` |
|
||
|
||
Tests beweisen API-Verträge und Invarianten, nicht Dialogqualität.
|
||
|
||
---
|
||
|
||
# 9. Technische Abweichungen und nicht gebaut
|
||
|
||
Nur die **technische** Lage. Bewertung gegen Spec und Vision: Fit-Gap.
|
||
|
||
| Thema | Ist | Zielrahmen / Produktiv |
|
||
|---|---|---|
|
||
| DB | SQLite Datei | PostgreSQL |
|
||
| Detect | Muster immer; externes Klartext-Detect nur Development/Test; Produktiv fällt auf Muster zurück. Lokales HTTP-Detect bleibt Provider-Rolle. | lokales Modell (Ollama) als Ziel |
|
||
| Fehlerkörper | oft Objekt | `{detail: string}` |
|
||
| `/dialog` | Admin-Harness, aus der Produkt-Nav entfernt | nicht MVP-Screen |
|
||
| Response Validation | blockiert Klartext-Identität in der Rohantwort vor Demask; keine Quasi-Identifikatoren | `privacy_gateway.md` §9.2 voll |
|
||
| Security Layer | Pfad da; Minimierung/Audit/Encryption fehlen weiter | `privacy_gateway.md` §9.2 |
|
||
| Offline-Sync, Voice-Egress, Compose-Prod | ungebaut | jeweiliges Kapitel |
|
||
|
||
Nicht im Code: Threads-UI, semantisches Retrieval, Session-Summaries, Related-Space-UI, Papierkorb-UI, lokales Detect-Modell, Obsidian/mindnet/Kairo-Handoff.
|
||
|
||
---
|
||
|
||
# 11. Korrigierter Ist-Stand vs. bewusst offene Target-Fähigkeiten
|
||
|
||
Additiv zum Slice, 2026-08-25. Kein Target-Model-Vorbau.
|
||
|
||
**Korrigiert im Ist:**
|
||
|
||
- Provenance von Draft und Entry-Version ist relational (`journal_*_source_refs`); JSON-ID-Listen werden verlustfrei migriert. Source-Messages bleiben unangetastet.
|
||
- Context Builder spricht Selection-Specs; Recency bleibt die aktuelle Retrieval-Heuristik, nicht die fachliche API.
|
||
- Dialogzug kann begrenzte jüngere Original-Conversations desselben Space sehen, ohne denselben Journal Day voll zu laden.
|
||
- Externes Klartext-Detect ist kein Produktmodus (`KANSHO_ENV=production` fällt auf Muster zurück). Lokales Detect bleibt austauschbare Rolle.
|
||
- Response Validation blockiert Klartext-Identität vor Demask.
|
||
- Konsolidierung nutzt lokale Signale, nicht `count >= 2`.
|
||
- `/dialog` ist Admin-Harness, nicht Produkt-IA.
|
||
- `WritingProfile` hängt nicht am Dialogue Kernel; `interaction_hint` ist ein leerer Slot.
|
||
- Conversation kann einem Space ohne Journal Day zugeordnet werden.
|
||
|
||
**Bewusst offen (nicht in diesem Pass gebaut):**
|
||
|
||
- Semantisches/globales Retrieval, Threads, Self Model, Related Spaces, mindnet
|
||
- Interaction-Profile-Engine, UI und Persistenz
|
||
- Reflection-Space-Typen, Journal-Default am Gesprächsabschluss
|
||
- Lokales Detect-Modell, Audit ohne Prompt, Encryption, inhaltliche Minimierung
|
||
- Stub-Home / Contextual Continuation als Start-IA
|
||
|
||
---
|
||
|
||
# 12. Profile Governance (2026-08-25)
|
||
|
||
Additiv zu §11. Kein Self Model, keine Intent-Engine, kein ML-Training.
|
||
|
||
**Writing Profile**
|
||
|
||
- Ein Profil als Hülle: Core, optionale Facets, dynamische Traits. Die früheren Stilschlüssel sind Seed, nicht Schema. `compiled_brief` bleibt abgeleitete Sicht aus Traits und zeitgestempelten Quellen.
|
||
- Governance `learning` | `advising` | `frozen`. Automatische Pfade (Turn, Save, Import, Bootstrap) überschreiben keine Locks und keine manuellen/angenommenen Facets. `frozen` hält Facet-Werte. Explizites Rebuild ehrt Locks.
|
||
- Rückwirkender Bootstrap aus gespeicherten Current-Entries. Unredigierte Drafts bleiben keine Stilquelle. Dialog ist schwächer und entfällt als Stimme, sobald Entries existieren.
|
||
|
||
**Interaction Profile**
|
||
|
||
- Getrennt, Default `advising`. Produktdefaults (Antwortlänge, ein Anschlussimpuls, Zuhören bei Erzählung) sind gelabelt und überschreibbar.
|
||
- `{{interaction_hint}}` füllt sich aus Defaults + persönlichen Prefs + aktuellem Dialogue State. Nicht-Widersprechen ist kein Lernsignal. `propose_observation` legt höchstens Suggestions an und wird nicht aus `run_turn` aufgerufen.
|
||
- Keine Intent-Taxonomie. Reflection Intent bleibt ungebaut. Dialogue State (`narrative_mode`, `reflection_depth`, `current_focus`, `emotional_intensity`, `long_story`) bleibt automatisch.
|
||
|
||
Kleine Settings-Erweiterung: Lifecycle/Initial Build, dynamische Traits statt festem Facet-Raster, Prefs, Vorschläge. Keine Profile-Management-App.
|
||
|
||
JSON-Portabilität (additiv): `kind` `kansho.writing_profile` / `kansho.interaction_profile` / `kansho.profiles`, `format_version` 1. Restore ersetzt Facets/Prefs und Governance ausdrücklich; Journalquellen, Dialoge und Identity-Mapping gehören nicht ins Dokument.
|
||
|
||
---
|
||
|
||
# 13. Profile Review Pipeline (2026-08-25)
|
||
|
||
Additiv zu §12. Keine neue Profil-Engine.
|
||
|
||
Nach Save/Import/Turn werden semantische Traits **nicht** neu berechnet. Der frühere Bootstrap-Infer über Zählfacetten entfällt; Quellen werden nur assembliert. Kontinuierliche Queue erst nach bestätigtem Initial Build (§14). Danach:
|
||
|
||
1. lokale Evidence-/Drift-Erkennung (billig, ohne LLM)
|
||
2. Review Candidates in `writing_profile_evidence`
|
||
3. semantische AI Review nur explizit, Evidenzen gebündelt
|
||
4. Governance entscheidet die Übernahme (`learning` wendet unlocked an, `advising` queued Suggestions, `frozen` nicht)
|
||
5. `writing_profile_versions` hält den Stand nachvollziehbar
|
||
|
||
Writing-Trigger: starke User-Änderung eines AI-Drafts, heuristische Abweichung, Stichprobe längerer Dialogtexte (Cooldown, kein LLM), periodische Bereitschaft nach Zeit/Volumen, explizite Nutzer-Review.
|
||
|
||
AI Review vergleicht Evidenz mit dem bestehenden Profil und liefert Änderungsvorschläge (`keep`/`update`), kein komplettes neues Profil. Paket unterscheidet `core` und `facet` und darf repräsentative Source-Beispiele an Merkmalen führen.
|
||
|
||
API (`POST /writing-profile/review/api`) und Copy/Paste (`POST .../paste` plus `.../import`) nutzen denselben Vertrag: `kansho.profile_review_package` / `kansho.profile_review_result`. Paste erzeugt lokal einen kopierbaren Prompt; das Ergebnis kommt strukturiert zurück. Interaction-Präferenzen entstehen nicht aus fehlendem Widerspruch oder normalem Gesprächsverlauf.
|
||
|
||
Das statische Facet-Raster aus §12 ist **nicht** die Profilstruktur. Semantische Traits und der Initial Profile Build: §14.
|
||
|
||
---
|
||
|
||
# 14. Writing-Profile-Hülle und Initial Build (2026-08-25)
|
||
|
||
Additiv zu §12 und §13. Keine neue große Engine.
|
||
|
||
Stabile Hülle, nicht ein geschlossenes Merkmalsraster:
|
||
|
||
1. Core
|
||
2. optionale context/output-Facets
|
||
3. darin dynamische semantische Traits (datengetriebene Slugs)
|
||
4. Evidence References
|
||
5. Representative Exemplars
|
||
6. Governance
|
||
7. Versioning
|
||
|
||
`rhythm`, `humor`, `detail`, `chronology` und verwandte Keys bleiben **Seed-/Ordnungshilfe** (`seed_catalog`). UI, Persistenz und Review binden sich nicht an diese Liste. Lokale Heuristiken (`writing_profile_infer`) sind Mess- und Triggerhilfe; sie schreiben keine Traits.
|
||
|
||
Lifecycle: `uninitialized` → `initial_pending` → `confirmed`. Quellen (Journal und Import) tragen `occurred_at` und `context_hint`. Neuere Texte wiegen stärker für die aktuelle Ausprägung, ältere belegen Langzeitmerkmale. Urlaubstagebücher mappen auf `autobiographical_journal`. Ein globaler Core wird nur geschrieben, wenn der Korpus unterschiedliche Quellenarten trägt.
|
||
|
||
Initial Profile Build liest den historischen Korpus (nicht nur zwei Entries). Review-Aktionen folgen Existing-before-New: bestätigen, präzisieren, Scope ändern, zusammenführen/aufteilen, erst dann neu anlegen. Kontinuierliches Learning/Advising (Queue, Drift, Dialog-Sample) startet erst nach bestätigtem Build.
|
||
|
||
API additiv: `POST /writing-profile/corpus`, `GET /writing-profile/corpus`, `PATCH /writing-profile/traits/{slug}`, `POST /writing-profile/initial-build/paste`, `POST /writing-profile/initial-build/api`. Paketvertrag bleibt `format_version` 1 mit zusätzlichen Feldern `mode`, `corpus`, `existing_before_new`, `seed_catalog`.
|
||
|
||
Tests: `backend/tests/test_profile_governance.py`, `backend/tests/test_profile_review.py`.
|
||
|
||
---
|
||
|
||
# 10. Querverweise
|
||
|
||
- Fit-Gap: `../functional/mvp_stand_und_abgleich.md`
|
||
- API-Liste: `backend_and_api.md` §5.1
|
||
- Tabellen: `memory_storage_and_offline.md` §6
|
||
- Egress: `privacy_gateway.md` §9
|
||
- Routen: `frontend_pwa_shell.md` §6.1
|
||
- Stack-Ziel: `product_frame_and_stack.md`
|
||
- Ports: `runtime_and_deploy.md`
|