Self-contained HTML-Kanban als operative Single Source of Truth für alle laufenden Projekte. Visualisiert das Ringsystem. Stack: Vanilla HTML/CSS/JS, localStorage, kein Build, kein Backend Server: ~/dev/kanban/server.sh (Python http.server auf Port 8765) Spec: ~/dev/kanban/SPEC.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
22 KiB
Kanban — Spec & Architektur
Datei: /Users/robinchoice/dev/kanban/index.html (eine Datei, kein Build)
Server: /Users/robinchoice/dev/kanban/server.sh — static-file server aus ~ auf Port 8765
Aufruf: http://localhost:8765/dev/kanban/index.html (Server muss laufen für MD-Viewer; sonst funktioniert das Board trotzdem)
State: Browser localStorage
Zweck
Single Source of Truth für alle laufenden Projekte. Self-contained HTML mit Kanban-Methodik (3 Spalten, Expedite-Swimlane, Class of Service, WIP-Limits, Little's Law, Monte Carlo). Visualisiert das Ringsystem von Robin (Ring 0–3, vier Workspace-Gruppen, ~16 Boards).
Layout
┌──────────────┬──────────────────────────────────────┬──────────────┐
│ SIDEBAR │ MAIN (Overview / Board / Analytics) │ IDEEN │
│ links │ │ rechts │
│ │ │ │
│ Gruppen │ • Header (Titel, Goal, Pills) │ • Input │
│ + Boards │ • Tabs │ • Filter │
│ + Aktionen │ • View-Body │ • Liste │
│ 210 px │ flex:1 │ 280 px │
└──────────────┴──────────────────────────────────────┴──────────────┘
Banner oben (rot/amber) wenn auf file:// statt localhost:8765 — mit Switch-Button.
Drei Views (Tabs in der Reihenfolge: Overview · Board · Analytics)
Overview (Default)
Hero-Block + 5 Sektionen, alle Felder editierbar:
| Element | Quelle | Edit |
|---|---|---|
| Hero-Icon (96×96, rounded) | overview.icon (path) |
bei Bild-Fail Emoji-Fallback in Board-Farbe |
| Hero-Name | board.name |
im Board-Header editierbar |
| Hero-Tagline (lila, kursiv) | overview.tagline |
Click + tippen |
| Hero-Description (2-3 Sätze) | overview.description |
Click + tippen |
| Badges: Ring, Gruppe, Typ | live + overview.type |
type editierbar |
| Launch-Cards Grid | overview.launches[] |
Single-Click öffnet URL (oder MD-Viewer wenn .md). Doppelklick auf Text editiert. × löscht. |
| Was bisher geschah | overview.summary |
Click + tippen |
| Tech-Stack | overview.stack[] |
Pill mit ×, + Tech Input |
| Hosting · IDs · Pfade | overview.info[] |
Label + Value editierbar, Copy-Btn, ×, + Zeile |
| 🔒 Secrets · Keys | overview.secrets[] |
Label + Value editierbar, masked-Toggle (Auge), Copy-Btn, ×, + Secret |
| ⌘ Quick-Commands | overview.commands[] |
Label + Cmd editierbar, Copy-Btn, ×, + Command |
Board
3-Spalten-Kanban + Expedite-Swimlane darüber.
| Spalte | Farbe | WIP-Limit |
|---|---|---|
| Ready | amber | — |
| In Progress | blau | per Board (Pill im Header editierbar) |
| Done | grün | — |
Expedite-Swimlane: horizontale Reihe über dem Board mit 3 Zonen (deckungsgleich mit Spalten). Tasks mit cos:'expedite' erscheinen NUR in der Zone, nicht im normalen col-body. Drag zwischen Zonen behält Expedite. Drag in col-body demotet. Drag von col-body in Zone promoted.
Karten-Aktionen (sichtbar bei Hover, oben rechts):
- ⚑ Blocker togglen (
task.blocked) — Karte rot - ↗ Promote-to-Board — öffnet Add-Board-Modal mit Task-Titel vorbelegt
- × Löschen
Auto-Aging: Karte > SLE-Tage in einer Spalte → roter Glow + "ÜBERFÄLLIG"-Label. Done-Karten werden nicht alt gezeigt.
Add-Card: + Karte… Input am Spalten-Ende. Enter fügt hinzu, Esc blurt.
Analytics
| KPI | Berechnung | Farbe |
|---|---|---|
| WIP | count(wip) gegen wipLimit |
rot/amber/blau |
| Cycle Time ⌀ | WIP / Throughput × 7 (Tage) |
blau |
| Queue ETA | (WIP+Ready) / Throughput × 7 (Tage bis Queue leer) |
teal |
| Flow Efficiency | WIP / (WIP+Ready) × 100% |
grün/amber/rot |
| Remaining | WIP + Ready |
neutral |
Throughput-Quelle: automatisch realThroughput() aus Done-Cards der letzten 14 Tage (mit task.doneAt). Fallback board.throughput wenn keine History. Status-Pill ("↻ Real" grün vs. "⊘ Statisch" grau).
Monte Carlo: 8.000 Runs, Varianz ±60%, gibt P50/P75/P85/P95 Wochen + Datum. Live-Input für Throughput-Override.
Active Blockers: alle Tasks mit task.blocked === true.
SLE: vergleicht Cycle Time mit board.sle.days.
Sidebar (links)
Gruppen-Sections, dynamisch aus GROUPS + BOARDS gerendert. Meta-Gruppe (Ring 0) ist visuell hervorgehoben (lila Gradient, Glow, ◉-Bullet).
| Element | Verhalten |
|---|---|
| Gruppen-Section | Drop-Zone für Board-Drag (board.group ändern) |
Gruppen-Header (s-group) |
draggable — Reihenfolge der Gruppen ändern (persistiert in kanban_groups) |
| Nav-Item (Board) | Klick = aktivieren; draggable in andere Gruppe oder vor andere Nav-Items (Reihenfolge in kanban_board_order) |
| Ring-Badge (R0/R1p/R1w/R2/R3/?) | Klick togglet durch Ring-Werte |
| Badge (Count) | remaining(b) mit Color-Code (rot=Blocker, blau=WIP, grau=neutral) |
× (nur user-erstellt) |
löscht das Board |
Unten:
+ Board→ Modal: Name, Goal, Gruppe (oder + Neue Gruppe), Farb-Picker (8 Optionen)+ Gruppe→prompt()für Namen; leere Gruppen werden gezeigt↺ Reset→ aktuelles Board auf DEFAULTS zurück; bei user-erstellten Boards: löschen
Ideen-Pinnwand (rechts)
| Element | Verhalten |
|---|---|
| Input + Enter | neue Idee am Anfang einfügen, Tag aus aktivem Filter |
| Filter-Buttons (Alle/Projekt/Erkenntnis/Später) | filtern Liste + setzen Default-Tag |
| Idee-Card | hover-Buttons: → (Promote-Modal) und ✕ (löschen); draggable auf col-body → erstellt Task |
| Drop-Zonen (erscheinen beim Card-Drag aus Board) | 📂 Projekt / 💡 Erkenntnis / 🕒 Später / ○ ohne Label — droppen entfernt Task aus Board und legt Idee an |
Seed: 35 Ideen (v1=20 Projekt, v2=15 Erkenntnis, v3=+16 aus _archive). Versionierung via kanban_ideas_seeded Key.
Datenmodell
Task
{
t: 'Titel', // required
blocked: true, // optional — Karte rot, ⚑ aktiv
cos: 'expedite' | 'fixed', // optional — Class of Service
movedAt: 1779580800000, // Timestamp letzter Spaltenwechsel (für Aging)
doneAt: 1779580800000, // Timestamp bei Wechsel nach Done (für Throughput)
age: 14, // optional — manuell überschriebenes Aging (rare)
note: 'Notiz', // optional — graue Sub-Zeile
}
Board
{
name: 'Döner-App',
goal: 'Beschreibung',
group: 'Code', // Sidebar-Gruppe
color: '#f87171', // Dot-Farbe
ring: '0' | '1p' | '1w' | '2' | '3' | '?',
wipLimit: 3, // editierbar via Pill
throughput: 2, // editierbar via Pill (Fallback wenn keine Done-History)
sle: { days: 14, p: 85 }, // days editierbar via Pill
cols: [
{ id: 'ready', label: 'Ready', tasks: [...] },
{ id: 'wip', label: 'In Progress', tasks: [...] },
{ id: 'done', label: 'Done', tasks: [...] },
],
focus: 'Fixstern-Text', // editierbar im Header
overview: { // optional
icon: 'assets/...png',
tagline: '...',
description: '...',
summary: '...',
type: 'ios-app' | 'web' | 'meta' | ...,
launches: [{label, sub, url, icon}],
stack: ['...'],
info: [{label, value}],
secrets: [{label, value, masked}],
commands: [{label, cmd}],
},
userCreated: true, // nur bei UI-erstellten Boards
}
Idea
{
id: 1013,
text: '...',
tag: 'projekt' | 'erkenntnis' | 'spaeter', // optional
created: '17.05.' | 'archive/...' | 'journal/...',
}
State-Keys in localStorage
| Key | Inhalt |
|---|---|
kanban_v2 |
{[boardId]: {cols, focus, [overrides], [userCreated-Meta]}} |
kanban_groups |
['Meta','Code',...] |
kanban_board_order |
{[group]: [id1, id2, ...]} |
kanban_ideas |
[Idea, ...] |
kanban_ideas_seeded |
'v3' |
Default-Boards
| ID | Name | Gruppe | Ring |
|---|---|---|---|
| ringsystem | Ringsystem | Meta | 0 |
| kanban | Kanban (dieses Board) | Meta | 0 |
| doener | Döner-App | Code | 3 |
| musichub | Music Hub | Code | 1w |
| openclaw | OpenClaw / Rob | Code | 1w |
| docpilot | docpilot | Code | 1w |
| k4 | K4 Digital — PM-Mandat | Beruflich | 1w |
| branding | Branding & Außendarstellung | Beruflich | 1w |
| psk | PSK I Zertifizierung | Beruflich | 1w |
| eu | Einzelunternehmen | Beruflich | 1w |
| pleasance | Pleasance | Web | 3 |
| mdim | mydrugismusic Website | Web | 3 |
| privat | Haushalt & Leben | Privat | 1p |
| degoogle | De-Google / FOSS Migration | Privat | 1p |
| bibliothek | Bibliothek-Pipeline | Privat | 1p |
| aikb | AI Engineering KB | Privat | 1w |
Overview gefüllt für: doener, kanban, ringsystem. Andere: nur Skeleton (Pilot-Modus).
Tool-Agnostik
| Datei | Status |
|---|---|
~/.claude/AGENTS.md |
kanonisch |
~/.claude/CLAUDE.md |
Symlink → AGENTS.md |
~/dev/personal-vault/AGENTS.md |
kanonisch |
~/dev/personal-vault/CLAUDE.md |
Symlink → AGENTS.md |
~/dev/robin-work/AGENTS.md |
kanonisch |
~/dev/robin-work/CLAUDE.md |
Symlink → AGENTS.md |
Backup: ~/.claude/CLAUDE.md.bak (alte Datei vor der Symlink-Migration)
Skills tool-agnostisch über ~/.skills/{name}.md mit Symlinks aus tool-spezifischen Verzeichnissen (Claude: ~/.claude/skills/{name}/SKILL.md; OpenCode: ~/.config/opencode/commands/{name}.md).
Local-Server
~/dev/kanban/server.sh
Startet python3 -m http.server 8765 --bind 127.0.0.1 aus ~/.
- Aufruf:
~/dev/kanban/server.sh(Vordergrund) odernohup ~/dev/kanban/server.sh &(Hintergrund persist) - Stop:
pkill -f "http.server 8765" - Logs:
/tmp/kanban-server.log(wenn via nohup gestartet)
Benötigt nur für MD-Viewer und localhost-URLs. Board-Funktion funktioniert auch ohne Server unter file://. Aber dann erscheint ein Warn-Banner oben.
MD-Viewer
Klick auf Launch-Card mit .md-URL → Modal mit gerendertem Markdown.
Mini-Parser inline: Headings (h1-h3), Bold, Italic, inline Code, Code-Blocks, Listen (ul/ol), Tables, Blockquotes, Hr, Links.
Fallback wenn fetch() scheitert (CORS): Anleitung mit drei Optionen (Extension / Server / Tab).
Wichtige JS-Funktionen
Render
| Funktion | Beschreibung |
|---|---|
show(id) |
Board wechseln |
setView(v) |
Tab-Switch (overview/board/analytics) |
renderOverview(id) |
Hero + 5 Sektionen |
renderBoard(id) |
Swimlane + 3 Spalten + col-add-input |
renderAnalytics(id) |
KPIs, Flow, Monte Carlo, Blockers, SLE |
renderSidebar() |
Gruppen + Boards |
renderIdeas() |
gefilterte Ideen mit Drag-Attrs |
initBadges() |
Alias renderSidebar |
renderMarkdown(src) |
Mini-MD-Parser |
State
| Funktion | Beschreibung |
|---|---|
loadState() / saveState() |
BOARDS persist mit Override-Logik für Defaults |
loadGroups() / saveGroups() |
+ Migration: Meta vorne einfügen wenn fehlend |
loadBoardOrder() / saveBoardOrder() |
per-Group-Order |
loadIdeas() / saveIdeas() |
+ Seeding |
migrateCols(b) |
5→3-Spalten-Migration |
getBoardsForGroup(g) |
geordnete Board-IDs |
Mutations (Board)
| Funktion | Beschreibung |
|---|---|
addCard(boardId, colId, text) |
mit movedAt + ggf. doneAt |
deleteCard(boardId, colId, idx) |
|
toggleBlocker(boardId, colId, idx) |
task.blocked |
saveBoardSetting(key, el, min, max) |
wipLimit/throughput/sleDays |
saveBoardName() / saveBoardGoal() |
Header-Edit |
saveFocus() |
Fokus-Text im Header |
resetCurrentBoard() |
Default-Reset oder User-Board-Delete |
deleteBoard(id, e) |
nur user-erstellt |
cycleRing(boardId, e) |
togglet durch Ring-Werte |
promoteToBoard(boardId, colId, idx) |
Task → neues Board |
Mutations (Overview)
| Funktion | Beschreibung |
|---|---|
updateOvFromEl(el) |
aus data-section/idx/prop |
addOvRow(section) |
leere Zeile für info/secrets/launches/commands |
addOvPill(section, value) |
für stack |
deleteOvRow(section, idx) |
|
enableEdit(el) |
Launch-Card-Felder Doppelklick aktiviert |
toggleSecretBtn(btn) |
masked toggle |
copyText(t, btn) / copyFromBtn(btn) |
Clipboard mit visual feedback |
Mutations (Ideen)
| Funktion | Beschreibung |
|---|---|
addIdea() / deleteIdea(idx) |
|
filterIdeas(tag) |
+ setzt Default-Tag |
openPromote(idx) / confirmPromote() / closePromote() |
Modal Idee→Task |
Add-Board-Modal
| Funktion | Beschreibung |
|---|---|
showAddBoard() / closeAddBoard() |
|
selectAbColor(c, el) |
Farb-Picker |
confirmAddBoard() |
+ Promote-Source-Handling |
showAddGroup() |
prompt()-Dialog |
DnD
| Funktion | Beschreibung |
|---|---|
onDragStart/End (Cards) |
+ showIdeaDropZones(on) |
onDragOver/Drop (col-body) |
demotet expedite, handelt auch Ideen-Drops |
onDragOverExp/onDropToExpCol |
promoted expedite |
onBoardDragStart/End |
Board-Drag in Sidebar |
onGroupDragOver/Drop |
Board → Gruppe |
onNavItemDragOver/Drop |
Board-Reorder innerhalb Gruppe |
onIdeaDragStart/End |
Idee aus Pinnwand |
onGroupHeaderDragStart/End |
Gruppe-Reorder |
onGroupSectionDragOver/Drop |
Gruppe-Drop-Target |
onIdeaZoneDragOver / onDropToIdeas |
Card → Idee |
onLaunchClick(e, card) |
Launch-Card öffnet URL/MD |
Helper
| Funktion | Beschreibung |
|---|---|
escapeHtml(s) |
HTML-Escape |
liveAge(task) |
aus movedAt |
ageClass(days) |
fresh/ok/warn/old |
colCount(b, id) |
tasks.length |
remaining(b) |
ready + wip |
hasBlocker(b) |
any task.blocked |
monteCarlo(left, tp, runs) |
{p50,p75,p85,p95} |
realThroughput(b, days) |
aus Done-History |
openMD(url) / closeMD() / openMdExternal() |
MD-Viewer |
enableEdit(el) |
Launch-Card-Edit-Modus aktivieren |
Globals
let curId = 'doener';
let curView = 'overview'; // default
let BOARDS = {}; // runtime, mutiert
let IDEAS = [];
let GROUPS = ['Meta','Code',...];
let BOARD_ORDER = {};
let dragSrc = null; // {boardId, colId, idx} bei Card-Drag im Board
let dragBoard = null; // boardId bei Sidebar-Drag
let dragIdea = null; // idx bei Ideen-Drag
let dragGroup = null; // group bei Gruppe-Drag
let promoteSource = null; // {boardId, colId, idx} beim Task→Board
let abSelColor = '#7c6af7';
let promoteIdx = null;
let mdCurrentUrl = null;
CSS-Variablen (Theme)
--bg:#0d0d0f, --surface:#16161a, --surface2:#1e1e24, --surface3:#26262e
--border:#2a2a35, --border2:#353545
--text:#e8e8f0, --text-muted:#6b6b80, --text-dim:#4a4a5a
--accent:#7c6af7 (lila), --accent-soft:#2d2550
--green:#4ade80, --amber:#fbbf24, --red:#f87171, --blue:#60a5fa
--teal:#2dd4bf, --purple:#c084fc, --orange:#fb923c
--sidebar-w:210px, --col-w:240px
Color-of-State Konvention:
- Ready = amber
- In Progress = blau
- Done = grün
- Blocked = rot (Border + Background-Tint)
- Expedite = rot (border-left + Swimlane-Background)
- Fixed Date = amber (border-left)
- Aging > SLE = roter Glow
Test-Checkliste (für externe Tester-Agents)
Layout & Navigation
- Sidebar links: alle 5 Gruppen sichtbar (Meta · Code · Beruflich · Web · Privat)
- Meta-Gruppe: lila Gradient mit
◉-Bullet - Klick auf jedes Board lädt korrektes Layout, Header zeigt Name, Goal, Pills (WIP-Limit/TP/SLE), Fokus-Stern
- Tab-Reihenfolge: Overview · Board · Analytics
- Default-Tab beim Start: Overview
- Banner oben wenn auf
file://(nicht localhost:8765)
Boards
+ Boardöffnet Modal mit Name (Pflicht), Goal, Gruppe-Select (+ "Neue Gruppe…"), Farb-Picker (8 Farben)- User-Board zeigt
×bei Hover (Default-Boards nicht) + Gruppelegt leere Gruppe an (sichtbar mit "Leer — Board hinzufügen" Placeholder)- Board-Drag in andere Gruppe → Group-Wechsel persistent
- Board-Drag auf konkretes Nav-Item → Reihenfolge innerhalb Gruppe persistent
- Gruppe-Header draggable → Gruppen-Reihenfolge ändert
- Ring-Badge (R0/R1p/R1w/R2/R3) togglet bei Klick durch alle Werte
↺ Reset: bei Default-Board Spalten reset, bei User-Board Löschen-Confirm- Board-Name + Goal per Klick im Header editierbar (Enter speichert, Esc revert)
Settings (Pills im Header)
- WIP-Limit Pill editierbar (1-20)
- Throughput Pill editierbar (0.5-30, Dezimalen erlaubt)
- SLE-Tage Pill editierbar (1-180)
- Alle drei persistent nach Reload
Karten (Board-View)
+ Karte…Input: Enter fügt hinzu, Esc blurt- Hover-Buttons: ⚑ togglet blocked (Karte rot), ↗ öffnet Promote-Modal, × löscht
- Drag col-body → col-body: verschiebt + setzt
movedAt - Drag col-body → exp-zone: verschiebt + setzt cos=expedite
- Drag exp-zone → exp-zone: verschiebt + bleibt expedite
- Drag exp-zone → col-body: demotet (cos entfernt)
- Drag in
donesetzt zusätzlichdoneAt - Karte > SLE-Tage in Spalte (außer done): rot leuchtender Glow + "ÜBERFÄLLIG"-Label
- Drag Card → eine der vier Ideen-Drop-Zonen (rechte Sidebar): erstellt Idee, entfernt Card
Promote-to-Board
- ↗ auf Karte öffnet Modal mit Name = Task-Titel
- Hint zeigt Quell-Board und -Spalte korrekt
- Gruppe + Farbe vorbelegt mit Quell-Board
- Confirm → neues User-Board, Task aus Quelle entfernt
- Cancel → kein Board angelegt, Quell-Task bleibt
Overview
- Hero-Block: Icon (oder Emoji-Fallback in Board-Farbe), Name, Tagline, Description, Badge-Reihe (Ring/Gruppe/Typ)
- Tagline + Description + Type per Single-Click editierbar (Click + tippen)
- Launch-Cards: Single-Click öffnet URL (oder MD-Viewer bei .md-URL); Doppelklick auf Text aktiviert Edit-Modus
× Entfernenlöscht Launch-Card+ Launch-Cardfügt leere Card hinzu- Was bisher geschah: per Click editierbar
- Tech-Stack-Pills:
×löscht,+ TechInput fügt hinzu - Info-Rows: Label + Value editierbar, Copy-Btn (✓ Feedback),
×löscht,+ Zeilefügt hinzu - Secrets: Label + Value editierbar, Auge-Toggle (masked persistent), Copy-Btn,
×,+ Secret - Quick-Commands: Label + Cmd editierbar, Copy-Btn,
×,+ Command
MD-Viewer
- Voraussetzung: Aufruf via
http://localhost:8765/dev/kanban/index.html(Server läuft) - Klick auf
.md-Launch-Card öffnet Modal mit gerendertem Markdown (Headings, Code, Tables, Listen) ↗ Taböffnet Roh-Inhalt in neuer Tab× Schließenoder Klick auf Overlay schließt Modal- Bei
file://-Aufruf: Fallback-Anleitung erscheint mit drei Optionen
Analytics
- Tab-Wechsel ohne State-Verlust
- WIP-KPI färbt rot bei
> wipLimit, amber bei= wipLimit, blau sonst - Cycle Time, Queue ETA, Flow Efficiency mathematisch plausibel (siehe Spec oben)
- Monte Carlo Throughput-Input updated Live
- Status-Pill grün "↻ Real" wenn Done-Cards mit doneAt < 14d existieren, sonst grau "⊘ Statisch"
- Active Blockers Panel listet alle Tasks mit
blocked:true - Flow Distribution: 3 Balken (Ready/WIP/Done) korrekt skaliert
Ideen
- Input + Enter fügt Idee am Anfang ein
- Filter-Buttons: ändern Anzeige UND Default-Tag für neue Ideen
- Hover-Buttons:
→Promote-Modal (Spalten-Default = erste verfügbare, nie 'backlog'),✕löscht - Drag Idee in col-body → Task entsteht, Idee verschwindet
- Drag funktioniert nur auf col-body, nicht in exp-zone
Persistenz
- Reload behält: Boards, Spalten-States, Fokus, Ideen, Gruppen, Group-Order, alle Overrides (name/goal/wipLimit/tp/sle/overview)
localStorage.clear()+ Reload → alle Defaults zurück inkl. Seed-Ideen v3- Alte 5-Spalten-States werden via
migrateColszu 3-Spalten konsolidiert
Tool-Agnostik
~/.claude/CLAUDE.mdist Symlink aufAGENTS.md~/.claude/CLAUDE.md.bakenthält den alten Inhalt vor Migration- Ringsystem-Board zeigt alle relevanten MD-Files als Launch-Cards mit korrekten http://localhost:8765-URLs
Bekannte Einschränkungen
- Karten-Sortierung INNERHALB einer Spalte nicht möglich (immer append)
- Card-Edit (Titel/Note ändern) nicht möglich — nur löschen + neu
- Gruppen können nicht umbenannt oder gelöscht werden
- Kein Card-Detail-View (kein Comments, Attachments, History)
- Kein Export
- Keine Tastatur-Shortcuts
- localStorage nicht synchronisiert — pro Browser/Gerät separat
- Keine Undo-Funktion
- CFD-Chart noch nicht implementiert (Ready-Task)
- Throughput-History akkumuliert sich erst über Zeit — bei frischem Start fallback auf statischen Wert
Datei-Struktur
/Users/robinchoice/dev/kanban/
├── index.html # Die gesamte App
├── SPEC.md # Diese Datei
├── server.sh # Local HTTP Server (chmod +x)
└── assets/
└── doener.png # App-Icon (kopiert aus ~/dev/doener-app/iOS/.../AppIcon.appiconset/icon-1024.png)
Setup (für neue Session / anderen Agent)
# 1. Server starten (für MD-Viewer)
nohup ~/dev/kanban/server.sh > /tmp/kanban-server.log 2>&1 &
# 2. Im Browser öffnen
open http://localhost:8765/dev/kanban/index.html
# 3. Stop wenn nötig
pkill -f "http.server 8765"
Wenn Server nicht laufen soll: einfach open ~/dev/kanban/index.html — Board funktioniert, aber MD-Viewer-Modal zeigt Fallback-Anleitung statt gerenderter Inhalte.