feat(dashboard): unified workspace hub — cookie auth, 9-state projects, planning chat
Merges workspace.html + ralph.html into a single unified project hub with: - Cookie-based auth (DASHBOARD_TOKEN, HttpOnly, SameSite=Strict) - 9-state project badge system (running-ralph/manual, planning, approved, pending, blocked, failed, complete, idle) with BUTTONS_FOR_STATE matrix - SSE realtime + polling fallback, version-based optimistic concurrency (If-Match) - Planning chat modal (phase stepper, markdown bubbles, 50s+ wait state, auto-resume) - Propose modal (Variant B: inline Plan-with-Echo checkbox) - 5-type toast taxonomy (success/info/warning/busy/error, 3px colored left-bar) - Inter font self-hosted + shared tokens.css design system + DESIGN.md - src/jsonlock.py (flock helper, sidecar .lock for stable inode) - src/approved_tasks_cli.py (shell-safe wrapper for cron/ralph.sh) - 55 new tests (T#1–T#30) + real jsonlock bug fix caught by T#16/T#28 - No emoji anywhere (enforced by test_dashboard_no_emoji.py) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
30
CLAUDE.md
30
CLAUDE.md
@@ -137,7 +137,19 @@ source .venv/bin/activate && pip install -r requirements.txt
|
||||
|
||||
**Memory** (`src/memory_search.py`): Embeddings Ollama all-minilm (384 dim) + cosine similarity SQLite. Trăiește la `memory/` în acest repo — single source of truth. *Notă istorică:* era symlink la repo-ul legacy Clawdbot; consolidat în echo-core în migrația OpenClaw (2026-04).
|
||||
|
||||
**Dashboard** (`dashboard/`): Echo Task Board — HTTP API + UI static servit de `dashboard/api.py` pe portul 8088, de obicei în spatele unui reverse proxy la `/echo/`. Logica endpoint-urilor împărțită în mixin-uri `dashboard/handlers/*.py`; path-urile centralizate în `dashboard/constants.py`. Template systemd user unit la `dashboard/echo-taskboard.service`.
|
||||
**Dashboard** (`dashboard/`): Echo Task Board — HTTP API + UI static servit de `dashboard/api.py` pe portul 8088, de obicei în spatele unui reverse proxy la `/echo/`. Logica endpoint-urilor împărțită în mixin-uri `dashboard/handlers/*.py`; path-urile centralizate în `dashboard/constants.py`. Template systemd user unit la `dashboard/echo-taskboard.service`. `workspace.html` este hub-ul unificat de proiecte (fostul ralph.html + workspace.html); `/echo/ralph.html` → 302 redirect la `/echo/workspace.html`. Autentificare prin cookie httpOnly `dashboard=<token>`; `DASHBOARD_TOKEN` setat în `dashboard/.env`.
|
||||
|
||||
## Dashboard — Note arhitecturale
|
||||
|
||||
**Cookie auth:** dashboard folosește httpOnly cookie `dashboard=...`; SameSite=Strict; Path=/echo/. EventSource SSE trimite cookie-ul automat. `DASHBOARD_TOKEN` din `dashboard/.env` — setează o dată, restart service. Resetare: schimbă valoarea din .env + restart.
|
||||
|
||||
**jsonlock helper (`src/jsonlock.py`):** folosește `read_locked(path)` / `write_locked(path, mutator)` pentru orice scriere la `approved-tasks.json`, `sessions/*.json`. Lock pe sidecar `<path>.lock` (inode stabil chiar și după os.replace). Ordine canonică lock-uri: alfabetic după filename. Re-entrant (threading.local refcount).
|
||||
|
||||
**Slug convention:** slug-urile proiectelor validează cu regex `^[a-z0-9][a-z0-9\-_]{1,38}[a-z0-9]$` — permit hifene ȘI underscore. Validare centralizată în `dashboard/handlers/_validators.py`.
|
||||
|
||||
**Proxy timeout:** pentru nginx/caddy, setează `proxy_read_timeout >= 60s` și `proxy_buffering off` pentru `/echo/api/projects/stream` și `/echo/api/projects/<slug>/plan/*` (SSE + planning au răspunsuri lungi).
|
||||
|
||||
**Planning fragmentation (known limit):** sesiunile de planning pornite din Discord/Telegram nu se fuzionează cu cele din dashboard. Dashboard afișează sesiunea cea mai recentă per slug indiferent de adapter. P3 follow-up.
|
||||
|
||||
## Ralph — Execuție autonomă de proiecte
|
||||
|
||||
@@ -151,7 +163,7 @@ Marius → /a <slug> (Discord/Telegram/WhatsApp → router.py → statu
|
||||
23:00 night-execute → citește approved, clonează repo dacă lipsește, generează PRD din final-plan.md,
|
||||
lansează ralph.sh; actualizează approved-tasks.json (running, pid: PID)
|
||||
08:30 morning-report → citește approved-tasks.json + prd.json per proiect, raportează stories done/total
|
||||
Live dashboard → /echo/ralph.html (polling 5s) — cards per proiect cu status, iter, ETA, log, stop
|
||||
Live dashboard → /echo/workspace.html — cards per proiect cu status, iter, ETA, log, stop; realtime SSE
|
||||
```
|
||||
|
||||
**Două căi de aprobare**:
|
||||
@@ -203,8 +215,9 @@ Pe **WhatsApp**: text-only — meniu redirect la Discord/Telegram. **Text-keywor
|
||||
| `~/workspace/<name>/scripts/ralph/prd.json` | PRD per proiect cu schema extinsă |
|
||||
| `~/workspace/<name>/scripts/ralph/logs/` | Loguri ralph.sh per rulare |
|
||||
| `dashboard/handlers/ralph.py` | Endpoints `/api/ralph/status`, `/<slug>/log`, `/<slug>/prd`, `/<slug>/stop`, `/<slug>/rollback`, `/usage[?days=N]`, `/stream` (SSE) |
|
||||
| `dashboard/ralph.html` | UI live cards, status badges, ETA, butoane log/prd/stop/rollback. Realtime via EventSource cu fallback la polling 5s; badge 🟢 Live / ⏱ Polling |
|
||||
| `dashboard/.env` | `GITEA_TOKEN` pentru clone HTTPS la `gitea.romfast.ro` |
|
||||
| `dashboard/handlers/projects.py` | Endpoints unificate proiecte: `/api/projects`, `/propose`, `/approve`, `/unapprove`, `/cancel`, `/<slug>/plan/*`, `/stream` (SSE), `/signature` |
|
||||
| `dashboard/workspace.html` | Hub unificat proiecte — cards status/iter/ETA, log, prd, stop/rollback. Realtime SSE cu fallback polling 5s. Înlocuiește ralph.html (care face 302 redirect aici) |
|
||||
| `dashboard/.env` | `GITEA_TOKEN` pentru clone HTTPS la `gitea.romfast.ro`; `DASHBOARD_TOKEN` pentru cookie auth |
|
||||
|
||||
**Status flow:** `pending` → (`planning` →) `approved` → `running` → `complete` / `failed` / `stopped` / `blocked` (DAG)
|
||||
**Story status (în prd.json):** `passes:false` + `retries:N` → `passes:true` SAU `failed:rate_limited|max_retries`
|
||||
@@ -234,9 +247,16 @@ Import-uri absolute via `sys.path.insert(0, PROJECT_ROOT)`: `from src.config imp
|
||||
| `personality/*.md` | System prompt — cine ești |
|
||||
| `memory/` | Knowledge base — embeddings + SQLite (în repo, nu symlink) |
|
||||
| `dashboard/api.py` | Task Board HTTP API (port 8088) |
|
||||
| `dashboard/handlers/` | Mixin-uri endpoints (git, cron, habits, eco, files, pdf, workspace, youtube) |
|
||||
| `dashboard/handlers/` | Mixin-uri endpoints (git, cron, habits, eco, files, pdf, workspace, youtube, projects, ralph, auth) |
|
||||
| `dashboard/handlers/projects.py` | Endpoints unificate proiecte: `/api/projects`, `/propose`, `/approve`, `/unapprove`, `/cancel`, `/<slug>/plan/*`, `/stream` (SSE) |
|
||||
| `dashboard/handlers/auth.py` | Login/logout cu cookie httpOnly `dashboard=<token>`; `DASHBOARD_TOKEN` din `.env` |
|
||||
| `dashboard/handlers/_validators.py` | Validatori slug/descriere partajați. Slug regex: `^[a-z0-9][a-z0-9\-_]{1,38}[a-z0-9]$` (permite hifene ȘI underscore) |
|
||||
| `dashboard/static/tokens.css` | Design tokens CSS (`--color-*`, `--space-*`, etc.) — shared variables pentru toate paginile |
|
||||
| `dashboard/DESIGN.md` | Design system source-of-truth: tokens, componente, regula no-emoji |
|
||||
| `dashboard/constants.py` | Path-uri centralizate + config Gitea pentru dashboard |
|
||||
| `dashboard/echo-taskboard.service` | Template systemd user unit |
|
||||
| `src/jsonlock.py` | Flock helper pentru scrieri concurente: `read_locked(path)`, `write_locked(path, mutator)`, `LockTimeoutError`. Sidecar `<path>.lock` (inode stabil). Re-entrant per thread. Ordine canonică: alfabetic |
|
||||
| `src/approved_tasks_cli.py` | CLI wrapper pentru shell scripts: scrie în `approved-tasks.json` prin jsonlock. Usage: `python3 -m src.approved_tasks_cli set-status --slug X --status Y` |
|
||||
| `cron/jobs.json` | Job-uri APScheduler (schemă plată, Europe/Bucharest) |
|
||||
| `approved-tasks.json` | Fișier coordonare Ralph — status proiecte autonome (extins cu `planning_session_id`, `final_plan_path`) |
|
||||
| `tasks/lessons.md` | Lecții capturate din corectările lui Marius (citit la session start) |
|
||||
|
||||
Reference in New Issue
Block a user