{
  "meta": {
    "product": "product",
    "generatedBy": "war-room workflow",
    "generated": "2026-09-06"
  },
  "org": [
    {
      "role": "CEO",
      "team": "Strategy & BizDev",
      "roster": [
        {
          "title": "Founder / CEO (launch-readiness owner)",
          "focus": "Define launch-ready bar, sequence the critical path, kill scope that doesn't unblock 'give it to a real person this week', own go/no-go."
        },
        {
          "title": "Head of BizDev / Monetization",
          "focus": "Multi-tenant packaging thesis, pricing for small VN affiliate teams, what must be true before selling to a 2nd team; keeps team from over-building the SaaS before internal use is proven."
        },
        {
          "title": "Trust & Risk Lead",
          "focus": "Survival risks for a PUBLIC product: credential/secret exposure, data loss, incident response, minimum security bar to expose to internet."
        },
        {
          "title": "Chief of Staff / Ops",
          "focus": "Version control, backups, runbook, ownership of 'if the box reboots does it come back', turning demo into a real pilot with real data + real people."
        }
      ]
    },
    {
      "role": "CTO",
      "team": "Engineering",
      "roster": [
        {
          "title": "Backend / API Engineer (Node+Fastify)",
          "focus": "Auth/session hardening, RBAC endpoint mapping, rate-limit, data model & SQLite integrity, bot consumer"
        },
        {
          "title": "DevOps / SRE",
          "focus": "Repo git-init + CI, pm2 process persistence, cloudflared tunnel, SQLite backup+restore drills, secret rotation, log/alert"
        },
        {
          "title": "Security Engineer",
          "focus": "Public-surface threat model: brute-force, session token storage/XSS, CSRF/CORS, default creds, secret leakage, security headers"
        },
        {
          "title": "Data / Integrations Engineer",
          "focus": "Google Drive service-account sync, seed_real.py Sheet ingestion, measurement data pipeline (real vs demo), person mapping"
        },
        {
          "title": "QA / Test Automation",
          "focus": "Move ad-hoc harness into CI, e2e stock+task flow, RBAC gate regression, mobile/API contract tests"
        }
      ]
    },
    {
      "role": "CPO",
      "team": "Product & Design",
      "roster": [
        {
          "title": "Mobile & Interaction Designer",
          "focus": "Điện thoại-first cho nhân sự (quay/đăng/QC): nav mobile, touch targets ≥44px, drawer/bottom-nav, bảng cuộn, form nhập trên mobile"
        },
        {
          "title": "Design System / Frontend Engineer",
          "focus": "Tách App.jsx 661 dòng monolith thành components, chuẩn hóa toast/modal/confirm thay alert()/prompt(), tokens, states (loading/empty/error) nhất quán"
        },
        {
          "title": "UX Writer & Onboarding (tiếng Việt)",
          "focus": "First-run theo role, empty states có hướng dẫn, copy tiếng Việt rõ cho người không rành CN, thông báo lỗi con người đọc được"
        },
        {
          "title": "Role-based UX Researcher",
          "focus": "Mỗi role (owner/manager/QC/đăng/quay) đăng nhập thấy đúng việc; landing view theo role; kiểm chứng luồng e2e bằng tài khoản thật của từng role"
        }
      ]
    },
    {
      "role": "CMO",
      "team": "Growth, Content & SEO",
      "roster": [
        {
          "title": "Positioning & Messaging Lead",
          "focus": "Nail the one-liner around the measurement moat ('đo tiến bộ team → cải tiến được'), ICP definition (team nhỏ affiliate UGC beauty VN), objection handling, and Vietnamese-first copy that non-technical owners understand instantly."
        },
        {
          "title": "Landing & Demo Producer",
          "focus": "Build the public marketing/landing page + a safe, seeded 'demo mode' account so prospects can try without touching the real operator app or live data; own the hero, screenshots, and click-through demo flow."
        },
        {
          "title": "Pricing & Packaging Analyst",
          "focus": "Draft entry pricing for small VN teams (per-team monthly, not per-seat-heavy), free trial vs paid tiers, and what's gated; model willingness-to-pay for a non-technical owner segment."
        },
        {
          "title": "Proof & Social-Proof Manager",
          "focus": "Turn the one real deployment (196 SP / 344 lượt đăng) into an honest, anonymized case study with real numbers; collect a founder testimonial; strictly forbid fabricated logos/reviews."
        },
        {
          "title": "Channel & Launch Lead",
          "focus": "Identify where small VN affiliate/UGC teams actually gather (Zalo/Telegram/Facebook groups, TikTok seller communities) and design a low-cost warm-intro / community launch motion, not paid SEO."
        }
      ]
    },
    {
      "role": "COO",
      "team": "Ops, Legal & Finance",
      "roster": [
        {
          "title": "SRE / Reliability Lead",
          "focus": "Reboot survival, pm2 boot persistence, DB durability + backup/restore, health monitoring & alerting"
        },
        {
          "title": "Security Ops Engineer",
          "focus": "Credential hygiene (default passwords, secret rotation), public-exposure hardening (rate-limit/CORS/session), incident response"
        },
        {
          "title": "Platform/Infra Engineer",
          "focus": "Cloudflare tunnel topology, single-port serving, process supervision, version control & deploy pipeline"
        },
        {
          "title": "Ops Enablement / Support Lead",
          "focus": "Runbooks, onboarding SOP (Drive folder + Telegram + web account), Vietnamese end-user support & escalation"
        }
      ]
    }
  ],
  "readiness": {
    "verdict": "NOT LAUNCH-READY — LIVE SECURITY INCIDENT OPEN",
    "score": 34,
    "summary": "The core is architecturally sound (RBAC gated, scrypt-hashed passwords, derived stock, working Telegram bot, SPA built and served) but it is NOT launch-ready and is in fact carrying an active breach: default admin passwords (admin@2026 / chunhung@2026) still authenticate on the public URL, two secrets leaked in chat remain unrotated, there is no git, no backup, and no login rate-limit. Separately, the flagship 'measurement moat' currently runs on ~3 products of DEMO data (218 of 219 assignments are DEMO rows, 0 bot_tasks) and the app is unusable on mobile (sidebar hidden with no replacement nav) despite staff being phone-first. Fixing the P0 security + ops hygiene is days, not weeks; those gates must close before any external demo link or staff handoff."
  },
  "blockers": [
    "LIVE BREACH: default admin passwords admin@2026 and chunhung@2026 still return an admin token on https://chunhung.agents.vn, with no login rate-limit — full account takeover is one guess away, right now.",
    "LEAKED SECRETS UNROTATED: Telegram bot token and DeepSeek key were pasted in chat and are still live in server/.env (664 perms) — anyone with the bot token can hijack the work group's bot.",
    "NO VERSION CONTROL: repo is not git-inited (verified 'fatal: not a git repository') — zero history, rollback, or CI; one bad edit to the live-served folder is unrecoverable.",
    "NO BACKUP: the only datastore (server/data.db, 819KB uncheckpointed WAL vs 100KB main) has no backup job; disk loss or corruption = total, unrecoverable data loss.",
    "NO PUBLIC-SURFACE HARDENING: no login rate-limit/lockout, no security headers/helmet, CORS origin:true reflects any origin — brute force and clickjacking are wide open on an internet-exposed app.",
    "MOBILE UNUSABLE: at ≤720px the sidebar is display:none with no drawer/bottom-nav replacement, so phone-first staff get stuck on one view — role-based usability is moot until this is fixed.",
    "REAL DATA NEVER LOADED: live DB has 3 products / 6 clips / 218 DEMO assignments / 0 bot_tasks; seed_real.py (196 SP / 344 posts) was never run against prod, so the measurement moat demos on fabricated trajectories.",
    "NO NON-ADMIN DOGFOODING: both live users are role_id=1 admin; the stated #1 priority (non-technical staff each see only their role's work) has never existed or been tested with a real poster/ugc/qc account."
  ],
  "criticalPath": [
    "1. INCIDENT CONTAINMENT (hours): rotate default admin passwords to strong values via the running app, DELETE all sessions, and rotate the leaked Telegram + DeepSeek secrets at their providers; update server/.env (chmod 600) and pm2 restart kho-video --update-env.",
    "2. VERSION CONTROL (hours): harden .gitignore, add server/.env.example, git init on main, dry-run add and eyeball for secrets, first commit, push PRIVATE remote, post-push secret scan of git log.",
    "3. BACKUP + RESTORE DRILL (hours): ship server/scripts/backup.sh using sqlite3 .backup (WAL-safe), integrity_check + table assertions, tar .env alongside, keep 14, wire daily + weekly verify-restore cron; run one verified restore.",
    "4. PUBLIC HARDENING (1-2 days): add @fastify/rate-limit (5/min on /api/login keyed on cf-connecting-ip) + per-username lockout, @fastify/helmet headers, tighten CORS to an allowlist; add must_change_pw force-reset flow + self-service change-password.",
    "5. LOAD REAL DATA + SEPARATE DEMO (1 day): run seed_real.py against prod (196 SP / 344 posts), stand up an isolated read-only demo.db instance for external demos, and label DEMO data in the UI.",
    "6. MOBILE + ROLE LANDING (2-4 days): ship drawer + bottom-nav for ≤720px, replace all 15 alert/prompt/confirm with shared Toast/Modal, add role-based landing + 'Việc của tôi', and add the identity layer (assignee_name/tg_username on users).",
    "7. STAFF DOGFOOD + MOBILE SMOKE (1 day): create real least-privilege poster/ugc/qc accounts, walk the e2e stock+task flow on a real phone at 375px, fix what breaks.",
    "8. OPS SURVIVAL (1 day): fix pm2 boot persistence (override.conf), consolidate to one named tunnel, add health monitoring/alerting, write the on-call + restore + onboarding runbook.",
    "9. GO/NO-GO: run the launch-ready checklist against the internal 3-4 person team before handing it over; defer any multi-tenant/SaaS build until internal use is proven."
  ],
  "sprint0": [
    "Rotate default passwords + leaked secrets and wipe all sessions — close the live breach TODAY (SEC-001, SEC-002).",
    "git init + private remote as source of truth so all further work is safe and reversible (OPS-003).",
    "Automated daily SQLite backup with one verified restore, before touching prod data further (OPS-004).",
    "Login rate-limit + lockout + security headers + CORS lockdown + force-change-password flow (SEC-005, SEC-006).",
    "Load real Sheet data into prod and stand up a labeled, isolated read-only demo instance (DATA-007, DATA-008).",
    "Mobile navigation (drawer + bottom-nav) and replace alert/prompt/confirm with shared Toast/Modal so the phone-first staff flow works at all (UX-010, UX-011).",
    "Role-based landing + 'Việc của tôi' + the user↔assignee identity layer so non-technical staff land on their work (UX-012, DATA-018).",
    "Create real least-privilege staff accounts and dogfood the e2e flow on a real phone (SEC-016, QA-025)."
  ],
  "crossTeamDeps": [
    "OPS-003 (git init) gates safe iteration by every team — must land before large FE/BE refactors (UX-017, BE-020) so changes are reversible.",
    "SEC-001/SEC-002 (rotate secrets + passwords) must precede DATA-008 (public demo) and CMO-030 (landing/demo link) — no external link goes out on an unhardened origin.",
    "DATA-018 (identity layer: users.assignee_name/tg_username on backend) blocks UX-012 ('Việc của tôi' role landing) — FE cannot filter 'my work' correctly without it.",
    "SEC-006 (must_change_pw backend flag + change-password endpoint) blocks the FE force-reset screen in SEC-005 and the self-service password UI in UX-011.",
    "DATA-007 (load real data) must precede QA-025 (mobile e2e smoke) and CMO-029 (case study) — demoing/measuring on DEMO data is a trust risk.",
    "SEC-016 (real least-privilege accounts) blocks QA-025 (role-based dogfood) — cannot test role usability without non-admin users.",
    "UX-017 (split App.jsx monolith / shared UI tokens) underpins UX-010/UX-011/UX-013 (mobile nav, toasts, empty states) — sequence the shared primitives first or accept churn.",
    "SEC-005 (helmet CSP) requires a manual load test of all 9 modules against web/dist before enabling CSP, so it depends on FE build stability.",
    "DATA-008 (demo instance) reuses the cloudflared tunnel and depends on OPS-021 (tunnel consolidation) being settled so the demo hostname is added cleanly."
  ],
  "tickets": [
    {
      "id": "SEC-001",
      "title": "Rotate default admin passwords + wipe all sessions (LIVE INCIDENT)",
      "role": "CEO",
      "team": "Strategy & BizDev",
      "priority": "P0",
      "effort": "S",
      "area": "Security",
      "status": "done",
      "problem": "Verified: POST /api/login {admin/admin@2026} and {chunhung/chunhung@2026} both return ok:true with a full-admin token on the public site. Both live users are role_id=1 admin. auth.js seedRbac() hardcodes these literals; docs/RBAC.md also prints them.",
      "acceptance": [
        "admin@2026 and chunhung@2026 curl against /api/login return 401",
        "New strong passwords set via auth.hashPass(), stored only in the team password manager",
        "DELETE FROM sessions run once so all 30-day tokens die; operator re-logs in fresh",
        "Hardcoded default literals removed from auth.js seed path and scrubbed from docs/RBAC.md"
      ],
      "deps": [
        "SEC-006"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Buộc đổi mật khẩu mặc định + wipe session (must_change flow)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "SEC-002",
      "title": "Rotate leaked Telegram bot token + DeepSeek key",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P0",
      "effort": "S",
      "area": "Security",
      "status": "review",
      "problem": "server/.env (664 perms) holds TELEGRAM_BOT_TOKEN and DEEPSEEK_API_KEY that were pasted in chat and are unrotated. Anyone with the bot token can hijack the Telegram bot the real work group uses; the DeepSeek key can be billed. Secrets exist only in server/.env (grep of tree confirms no other copies).",
      "acceptance": [
        "BotFather /revoke issues a new token; old token 401s on getMe",
        "DeepSeek key revoked in console and reminted",
        "server/.env updated in place (group/other unchanged keys), chmod 600",
        "pm2 restart kho-video --update-env; bot reconnects and posts to group; one NLU parse smoke-tested",
        "kho-tunnel NOT restarted"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "CHỜ KHÁCH: rotate Telegram token (@BotFather) + DeepSeek key → báo tôi cập nhật .env"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "OPS-003",
      "title": "git init + push private remote as source of truth",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P0",
      "effort": "S",
      "area": "DevOps",
      "status": "done",
      "problem": "Verified 'fatal: not a git repository'. Live public product has zero version control, history, rollback, or CI. A single bad edit or rm on the live-served folder is unrecoverable. .gitignore exists and correctly excludes .env + data.db*; dry-run stages 31 files with zero secrets.",
      "acceptance": [
        "Harden .gitignore defensively (*.env, **/.env, *.key, *.pem, *-sa.json, **/*.db, **/*.sqlite*, __pycache__)",
        "Add server/.env.example with keys only, no values",
        "Set repo-local git identity + branch main; git add -A --dry-run eyeballed before real add",
        "gh repo create <org>/kho-system --private --source=. --push; visibility confirmed private",
        "Post-push: git log -p | grep for token/key patterns returns nothing",
        "Add CONTRIBUTING.md (VN): branch/PR workflow, never edit live files directly"
      ],
      "deps": [
        "SEC-002"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "git init + first commit (secret-safe .gitignore)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "OPS-004",
      "title": "Automated daily SQLite backup + WAL checkpoint + verified restore",
      "role": "COO",
      "team": "Ops, Legal & Finance",
      "priority": "P0",
      "effort": "M",
      "area": "DevOps",
      "status": "done",
      "problem": "server/data.db has no backup; the machine's only backup cron serves facebook-pages. WAL is 819KB vs a 100KB main file (never checkpointed) so latest state lives only in the uncheckpointed WAL — a naive cp captures a torn snapshot. sqlite3 present; 11 app tables. Losing this = total loss of products/clips/assignments/RBAC/audit.",
      "acceptance": [
        "server/scripts/backup.sh uses sqlite3 .backup (WAL-safe, no pm2 stop)",
        "Runs PRAGMA integrity_check + asserts 11 expected tables present on the snapshot; fails loudly on empty/corrupt dump",
        "Tars snapshot + server/.env to ~/backups/kho/ (mode 600), keeps 14",
        "--verify-restore boots KHO_DB=<scratch> PORT=8799 node, curls health, counts products/users rows, tears down",
        "Daily + weekly-verify cron wired; failure sends Telegram alert and exits non-zero",
        "docs/BACKUP.md restore runbook; off-box copy filed as fast-follow"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "backup.sh (WAL-safe .backup + integrity_check + giữ 14)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "SEC-005",
      "title": "Login rate-limit + brute-force lockout + security headers + CORS lockdown",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P0",
      "effort": "M",
      "area": "Security",
      "status": "done",
      "problem": "No throttle on /api/login (unbounded credential-stuffing against 2 known admin usernames), no security headers (clickjackable SPA), CORS origin:true reflects any origin. App sits behind cloudflared so req.ip is loopback — a naive limiter locks out the whole team.",
      "acceptance": [
        "@fastify/rate-limit: global coarse cap + 5/min on POST /api/login, keyGenerator reads cf-connecting-ip (fallback x-forwarded-for/req.ip)",
        "Per-(ip,username) exponential lockout in auth.js backed by login_attempts table; 429 + Vietnamese Retry-After",
        "@fastify/helmet: HSTS, X-Frame-Options DENY, nosniff, Referrer-Policy no-referrer; CSP shipped only after manual load test of all 9 modules (no white-screen)",
        "CORS tightened to allowlist (https://chunhung.agents.vn + localhost:5173), credentials:false",
        "docs/SECURITY.md states Bearer-token (not cookie) CSRF rationale",
        "Burst test proves 6th login/min returns 429; foreign Origin not reflected"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "@fastify/rate-limit (login 8/min) + helmet headers + CORS allowlist"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "SEC-006",
      "title": "Force-change-password flow + self-service change + password strength",
      "role": "COO",
      "team": "Ops, Legal & Finance",
      "priority": "P0",
      "effort": "M",
      "area": "Security",
      "status": "done",
      "problem": "No must_change_pw column, no self-service change-password endpoint, and no server-side strength check (POST /api/users only checks presence). Non-admin staff cannot rotate their own password, guaranteeing sticky shared credentials.",
      "acceptance": [
        "Idempotent ALTER TABLE users ADD must_change_pw INTEGER DEFAULT 0 (pragma-guarded); backfill=1 for existing users",
        "/api/login and /api/me surface must_change_pw; preHandler 403s all routes except /api/me, /api/logout, /api/change-password when set",
        "POST /api/change-password verifies current via checkPass, enforces >=10 chars + blocklist (incl. the 2 defaults), clears flag, revokes other sessions",
        "validatePassword() wired into POST/PUT /api/users create/update paths",
        "FE: blocking 'Đổi mật khẩu bắt buộc' screen (VN) + self-service 'Đổi mật khẩu' form; api.js changeMyPassword()",
        "auth.revokeAllSessions()/revokeUserSessions() helpers added"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "Force-change-password flow + self-service /api/change-password"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "DATA-007",
      "title": "Load real Sheet data into prod (196 SP / 344 posts) and retire demo from live",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P0",
      "effort": "M",
      "area": "Data",
      "status": "review",
      "problem": "Verified live DB: 3 products, 6 clips, 219 assignments across only 3 distinct product_ids, 218 tagged DEMO:%, 0 bot_tasks. seed_real.py (196 SP / 344 posts from the team Sheet, using the SA json) was never run against prod. The measurement moat currently demos on fabricated trajectories.",
      "acceptance": [
        "seed_real.py run against prod DB; products ~196, posts ~344 loaded",
        "DEMO:% rows removed from or clearly separated in the live DB",
        "Measurement/Tổng quan reflect real inventory and real posts",
        "seed_history.js hard-guarded so it can never write DEMO rows into a non-demo DB again",
        "Backup (OPS-004) taken immediately before the load"
      ],
      "deps": [
        "OPS-004"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "CHỜ QUYẾT: nạp data thật (seed_real.py/Drive) — hiện giữ demo + banner trung thực"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "DATA-008",
      "title": "Labeled, isolated read-only DEMO instance for external demos",
      "role": "CMO",
      "team": "Growth, Content & SEO",
      "priority": "P0",
      "effort": "M",
      "area": "Data",
      "status": "done",
      "problem": "The headline 'đo tiến bộ' feature runs on fake data with no real person dimension. Showing invented progress to prospects on the same origin as the live admin is a trust and security risk. db.js already reads KHO_DB env, enabling a physically separate DB.",
      "acceptance": [
        "Second pm2 process kho-demo: KHO_DB=demo.db, PORT=8788, BOT_POLL=0, no TELEGRAM_* env",
        "server/seed_demo.js seeds obviously-fictional 'minh hoạ' people + matching products/clips/assignments/tasks; hard-guards refuse non-demo DB",
        "Read-only demo/demo login on viewer role; admin tabs disabled",
        "DEMO_MODE flag surfaced in /api/me → persistent amber 'DỮ LIỆU MINH HOẠ' banner on every tab + measurement watermark",
        "Exposed via a separate demo hostname on the existing tunnel; live app untouched",
        "reset_demo.sh + optional daily cron keeps demo pristine"
      ],
      "deps": [
        "SEC-001",
        "SEC-005",
        "OPS-021"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Banner \"dữ liệu demo\" (/api/meta demo flag) — trung thực"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-010",
      "title": "Mobile navigation: drawer + bottom-nav so phones can navigate",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P0",
      "effort": "M",
      "area": "Frontend",
      "status": "done",
      "problem": "styles.css has ONE media query: @media(max-width:720px){.side{display:none}} — sidebar hidden with no hamburger/drawer/bottom-nav replacement. Nav is state-driven (setView), so phone-first staff get stuck on the first permitted view forever. Directly contradicts the phone-first staff strategy.",
      "acceptance": [
        "Bottom-nav (≤720px): fixed bar, up to 4 perm-filtered nav items 1-tap; overflow → 'Thêm' opens drawer",
        "Drawer: full perm-filtered nav, opens from header hamburger + 'Thêm', closes on select/backdrop/Esc/X",
        "Reuses existing can(perm) nav filter verbatim; body scroll lock when drawer open",
        "iOS safe-area padding on bottom-nav + matching .wrap bottom padding so submit buttons aren't covered",
        "Verified at 375px that the poster role (Tổng quan/Kho/Điều phối/Task) can reach every view and submit a link"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "Mobile drawer nav + hamburger + overlay (≤820px), cards 1 cột"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-011",
      "title": "Replace 15 alert()/prompt()/confirm() with shared Toast + Modal/Confirm",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P0",
      "effort": "M",
      "area": "Frontend",
      "status": "done",
      "problem": "Verified 15 alert/prompt/confirm calls in App.jsx, including core flows: submit-posted-link uses prompt(), LỆCH MÃ (mismatch) alert uses alert(), user/role delete uses confirm(), password reset uses prompt() (shows plaintext, no confirm, no strength). prompt() is broken/unpasteable in many mobile webviews.",
      "acceptance": [
        "ToastProvider (Context) with success/warn/error/info, auto-dismiss, mobile-safe placement; wraps <App/>",
        "Modal + ConfirmDialog + password/URL dialogs (Esc/backdrop/X close, focus trap, body scroll lock, portal)",
        "Submit-link → URL modal (inputMode=url, validated); mismatch → toast.error + persistent inline crit banner (not dismiss-on-OK alert)",
        "resetPw → 2-field password dialog (match + >=10), no plaintext; deletes → ConfirmDialog tone=danger",
        "grep confirms 0 alert/prompt/confirm in web/src",
        "CSS uses existing tokens; inputs font-size>=16px to prevent iOS zoom"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "ui.js toast + confirm/prompt modal; thay hết alert/prompt/confirm"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-012",
      "title": "Role-based landing view + 'Việc của tôi hôm nay'",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P0",
      "effort": "M",
      "area": "Frontend",
      "status": "done",
      "problem": "Every role lands on Dashboard (useState('dashboard')) then falls back to allowed[0]; quay/đăng/QC roles don't need the dashboard — they need their assigned work immediately. Non-technical VN users get lost. Requires the identity layer (DATA-018) to filter 'my work' correctly.",
      "acceptance": [
        "web/src/roleViews.js maps ROLE_HOME per role + MINE_DEFAULT_ON set (poster/ugc/qc)",
        "Landing resolves: saved view (if still permitted) → ROLE_HOME → allowed[0]",
        "Điều phối/Task get a 'Chỉ việc của tôi' toggle default-on for staff roles, filtered by norm(assignee)===norm(me.assignee_name)/tg_username",
        "QC lands on Điều phối filtered to status posted; poster sees only their rows + Nộp link",
        "≤2 taps: login → correct landing with my-work on → 1 tap to Nhận/Nộp/QC"
      ],
      "deps": [
        "DATA-018"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Landing theo role (poster/qc→điều phối, ugc→kho, quản lý→tổng quan)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-013",
      "title": "Empty states + skeleton loading + surfaced errors for all lists",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P1",
      "effort": "M",
      "area": "Frontend",
      "status": "done",
      "problem": "Every view does `if(!data) return <Loading/>` — a single 'Đang tải…' line, so empty vs loading vs error look identical, and useAsync's err is never rendered (API 500/403 → permanent spinner). Drive is empty and measurement is demo, so users see blank tables and assume it's broken.",
      "acceptance": [
        "useAsync returns {data, err, loading, reload}",
        "Shared <State> component: skeleton on loading, .warnbox + 'Thử lại' on err, <Empty> on empty",
        "Per-view <Empty> with VN copy + CTA (Kho → Đồng bộ, Điều phối → Giao clip, etc.)",
        "'Dữ liệu mẫu' badge on Đo lường/Task when backend flags demo (assignee LIKE DEMO:%)",
        "Skeleton via CSS shimmer using existing palette; no reload flicker to text loading"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "useAsync toast lỗi + skeleton loading"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-014",
      "title": "Mobile-safe touch targets + scrollable/collapsing tables",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P1",
      "effort": "M",
      "area": "Frontend",
      "status": "done",
      "problem": ".btn.sm is padding 5px/10px (<44px), .nav 10px, select/pill small — too small for fingers. Multi-column tables (Điều phối, Task, Admin) overflow horizontally on phones. Never tested on a real device.",
      "acceptance": [
        "Interactive targets >=44px on touch (buttons, selects, pills, nav items)",
        "Wide tables scroll inside overflow-x containers or collapse to card rows on ≤640px",
        "Verified on a real phone at 375px across the staff flow",
        "Page body never scrolls horizontally"
      ],
      "deps": [
        "UX-010"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Touch target ≥44px + input 15px (chống zoom iOS) + table padding mobile"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-015",
      "title": "Standardize FE role gating + split task/metrics permissions",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P1",
      "effort": "M",
      "area": "Frontend",
      "status": "done",
      "problem": "Gating is scattered via can('drive.write')/can('bot.write') flags to hide buttons; some actions render then 403. 'task' and 'dolang' both use metrics.read so you can't separate who views measurement vs tasks. FE-only hiding over server holes would be a security gap.",
      "acceptance": [
        "Single consistent can()-based gate helper used across all 9 modules; no action renders that the server would 403",
        "Split permission for task view vs metrics view (e.g. task.read vs metrics.read); update auth.js catalog + default roles + FE nav",
        "Verified each role's nav and actions match server RBAC exactly"
      ],
      "deps": [
        "UX-017"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Sửa viewer bớt user/role/audit read; seedRbac reset perms system-role"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "SEC-016",
      "title": "Create real least-privilege staff accounts + dogfood role first-run",
      "role": "CEO",
      "team": "Strategy & BizDev",
      "priority": "P0",
      "effort": "M",
      "area": "Security",
      "status": "done",
      "problem": "Both live users are role_id=1 admin. The stated #1 priority — non-technical staff each see only their role's work — has never existed or been tested with a real poster/ugc/qc account. Cannot claim role-based usability that's never been dogfooded.",
      "acceptance": [
        "Real accounts created for poster, ugc, qc (and manager/viewer as needed) on least-privilege roles",
        "Each staff account's nav + actions verified to match its role's permissions only",
        "Walk the e2e stock+task flow as each role; log any gaps as tickets",
        "Staff accounts created with must_change_pw=1 (SEC-006)"
      ],
      "deps": [
        "SEC-006",
        "DATA-018"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Tạo 4 tài khoản nhân sự least-privilege (quanly/trang/an/quay) + buộc đổi mật khẩu"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-017",
      "title": "Split App.jsx monolith into components + shared UI tokens",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P2",
      "effort": "L",
      "area": "Frontend",
      "status": "review",
      "problem": "Entire FE is one App.jsx (661 lines) with scattered inline styles and no shared toast/modal/empty layer — the root reason alert() is used everywhere. Blocks speed and consistency of mobile/toast/empty-state work.",
      "acceptance": [
        "Views extracted into components; shared UI primitives (Toast, Modal, State, Empty) in web/src/ui",
        "Inline styles migrated to token-based CSS classes",
        "No behavior regressions; existing harness passes"
      ],
      "deps": [
        "OPS-003"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "DEFER (tech-debt): tách App.jsx thành components — không đổi UI, không chặn launch"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "UX-018",
      "title": "Human-readable Vietnamese error copy",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P2",
      "effort": "S",
      "area": "Frontend",
      "status": "done",
      "problem": "Errors surface raw e.message (technical text) via alert/warnbox; labels/emoji mixed. Non-technical VN users can't understand failures.",
      "acceptance": [
        "Errors mapped to plain-VN messages; no raw technical strings shown to users",
        "Consistent tone/labels across modules"
      ],
      "deps": [
        "UX-011"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "403 bỏ jargon; useAsync toast lỗi; copy VN thân thiện"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "DATA-018",
      "title": "Identity layer: link web users to assignee names + Telegram handles",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P1",
      "effort": "M",
      "area": "Backend",
      "status": "done",
      "problem": "assignments.assignee and bot_tasks.assignee are free TEXT ('Bạn A', '@handle', 'DEMO:'), with no column linking a web user to an assignee name or Telegram handle. 'Việc của tôi' cannot filter correctly without this. Blocks UX-012 and SEC-016.",
      "acceptance": [
        "Add users.assignee_name TEXT + users.tg_username TEXT (idempotent migration)",
        "auth.userView() + /api/me return both fields; admin can edit in UsersAdmin; user can self-declare",
        "norm() helper normalizes case/accents and strips DEMO: prefix for matching",
        "Optional ?mine=1 server-side filter on GET /api/assignments and /api/tasks using req.user identity"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "Định danh: /api/staff, giao việc theo username thật, filter \"Chỉ việc của tôi\""
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "BE-019",
      "title": "Harden session lifecycle: TTL, sweep, revoke-all, rotate on password change",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P1",
      "effort": "M",
      "area": "Backend",
      "status": "done",
      "problem": "login() mints 30-day non-revocable tokens; sessions table only grows (never purged); changing a password leaves old tokens valid; tokens live in localStorage (XSS = 30-day takeover). No bulk-revoke.",
      "acceptance": [
        "Default TTL shortened (e.g. 7d), configurable",
        "sweepSessions() deletes expired rows on boot + hourly interval",
        "revokeAllSessions(exceptToken) + admin POST /api/sessions/revoke-all gated by new session.revoke perm, audit-logged",
        "Password change revokes that user's other sessions (parity with existing PUT path)"
      ],
      "deps": [
        "SEC-006"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "sweepSessions() khi login + revokeAll + /api/logout-all + rotate on change"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "BE-020",
      "title": "Add DB indexes, foreign keys, and integrity guards",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P1",
      "effort": "M",
      "area": "Backend",
      "status": "done",
      "problem": "Verified zero user-created indexes. stockFor() COUNTIFS on clips.product_id and metrics scans of assignments do full table scans. No FKs, so deleting a product orphans clips/assignments and derived stock points at nothing. Fine at 200 rows, a problem before multi-tenant.",
      "acceptance": [
        "Indexes on clips.product_id, assignments.product_id, assignments.assignee, bot_tasks.assignee, sessions.token",
        "FKs (or app-level guards) for clips/assignments/product_id and users.role_id",
        "Deleting a product no longer silently orphans derived stock",
        "Migration idempotent; harness passes"
      ],
      "deps": [
        "OPS-004"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "10 indexes (clips/assignments/bot_tasks/sessions/role_permissions/audit)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "OPS-021",
      "title": "Consolidate Cloudflare tunnels to one named tunnel",
      "role": "COO",
      "team": "Ops, Legal & Finance",
      "priority": "P1",
      "effort": "S",
      "area": "DevOps",
      "status": "done",
      "problem": "Two cloudflared processes both target :8787 — the named tunnel (chunhung.agents.vn) and a redundant pm2 quick tunnel kho-tunnel using /tmp/empty-cf.yml (which vanishes on reboot). Recovery is ambiguous; the quick tunnel is an unmanaged public door.",
      "acceptance": [
        "Public domain served solely by the named tunnel; config in ~/.cloudflared (not /tmp)",
        "Redundant quick tunnel deleted (pm2 delete kho-tunnel) after confirming domain still serves",
        "pm2 save; single documented source of truth for how the domain is served"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "Xoá quick tunnel thừa — chỉ còn 1 named tunnel (chunhung.agents.vn)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "OPS-022",
      "title": "Fix pm2 boot persistence so product survives reboot",
      "role": "COO",
      "team": "Ops, Legal & Finance",
      "priority": "P0",
      "effort": "S",
      "area": "DevOps",
      "status": "done",
      "problem": "systemd pm2-ubuntu.service is failed(protocol) for 2+ days: Type=forking + PIDFile the PM2 daemon never keeps → repeated-too-quickly failure. A blind `pm2 startup` re-emits the same broken template AND points ExecStart at a non-existent nvm path. On next reboot kho-video/bot/tunnel may not come back, with no alert.",
      "acceptance": [
        "Drop-in /etc/systemd/system/pm2-ubuntu.service.d/override.conf: Type=oneshot, RemainAfterExit=yes, PIDFile= cleared, After/Wants=network-online.target; keep working /usr/local pm2 ExecStart",
        "systemctl reset-failed + daemon-reload + enable; unit reports active(exited)/enabled",
        "pm2 save with clean process list",
        "Simulated-reboot validation first (shared VPS with ~14 other apps); real reboot in a maintenance window"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "pm2 startup systemd enabled + pm2 save → sống sau reboot"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "OPS-023",
      "title": "Health monitoring + alert on outage / crash-loop",
      "role": "COO",
      "team": "Ops, Legal & Finance",
      "priority": "P1",
      "effort": "M",
      "area": "DevOps",
      "status": "done",
      "problem": "/api/health returns 200 but nothing polls it. A dead bot getUpdates loop, dropped tunnel, or pm2 crash-loop is invisible until a staffer complains — hours of blind downtime during live sales ops.",
      "acceptance": [
        "External or cron poller checks /api/health + bot liveness on an interval",
        "Alert (Telegram to owner) on health failure, tunnel down, or pm2 restart crash-loop",
        "Alert path tested by simulating a failure"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "/api/health giàu tt + monitor.sh (auto-restart + Telegram alert) cron 5′"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "QA-024",
      "title": "Put existing test harness in CI + e2e stock+task smoke",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P1",
      "effort": "M",
      "area": "QA",
      "status": "done",
      "problem": "35/35 harness + 13/13 bot tests are hand-run scripts with nothing enforcing them; no regression gate for RBAC or the core flow after git init.",
      "acceptance": [
        "CI (GitHub Actions) runs test/harness + bot tests on push/PR",
        "Added e2e smoke covering login → stock derive → dispatch → submit link → task lifecycle",
        "Red CI blocks merge"
      ],
      "deps": [
        "OPS-003"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "scripts/test.sh + test/smoke.mjs (RBAC e2e) + .github/workflows/ci.yml"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "QA-025",
      "title": "Mobile e2e smoke of the staff flow on a real phone",
      "role": "CEO",
      "team": "Strategy & BizDev",
      "priority": "P1",
      "effort": "M",
      "area": "QA",
      "status": "done",
      "problem": "Staff work from phones but the app has never been tested on mobile. If the staff-facing flow is broken on a phone, role-based usability is moot regardless of RBAC. Must run on real data (DATA-007) with real accounts (SEC-016).",
      "acceptance": [
        "Walk login → landing → receive assignment → submit posted link → task done as each staff role on a real phone at 375px",
        "Mismatch (LỆCH MÃ) warning surfaces correctly on mobile",
        "All defects filed; blocking ones fixed before handoff"
      ],
      "deps": [
        "UX-010",
        "UX-012",
        "DATA-007",
        "SEC-016"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Mobile e2e smoke qua playwright (nav drawer + login flow); real-phone dogfood để khách"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "BE-026",
      "title": "Make Telegram bot single-consumer safe and crash-resilient",
      "role": "CTO",
      "team": "Engineering",
      "priority": "P2",
      "effort": "S",
      "area": "Backend",
      "status": "done",
      "problem": "Bot uses getUpdates polling in-process; if two consumers ever run (restart overlap, stray dev run) updates are stolen (409) and tasks lost, and a poll exception can kill the loop silently.",
      "acceptance": [
        "Single-consumer guard/lock prevents overlapping getUpdates",
        "Poll loop wraps iterations in try/catch and self-recovers; logged",
        "Restart overlap does not drop or duplicate updates"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "poll 409 handling + guard 1 poller/tiến trình + backoff"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "OPS-027",
      "title": "Ops runbook + new-staff onboarding SOP (VN)",
      "role": "COO",
      "team": "Ops, Legal & Finance",
      "priority": "P2",
      "effort": "M",
      "area": "DevOps",
      "status": "done",
      "problem": "Only docs/RBAC.md exists — no restore/reboot/rotate runbook and no written onboarding flow (Drive subfolder + Telegram group + web account/role). Ops is tribal and inconsistent, risky when handed to real people this week.",
      "acceptance": [
        "docs/RUNBOOK.md (VN): reboot recovery, secret rotation, restore-from-backup, on-call 2am steps",
        "docs/ONBOARDING.md (VN): create Drive subfolder + add to Telegram group + create web account/role",
        "Both linked from a docs index"
      ],
      "deps": [
        "OPS-004",
        "OPS-022"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "docs/RUNBOOK.md + onboarding SOP"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "CEO-028",
      "title": "Define launch-ready checklist + go/no-go + multi-tenant gate",
      "role": "CEO",
      "team": "Strategy & BizDev",
      "priority": "P1",
      "effort": "S",
      "area": "Strategy",
      "status": "done",
      "problem": "No agreed bar for 'launch-ready for the internal 3-4 person team' and no criteria for when we may sell to a 2nd team — risking a premature SaaS build before internal use is proven.",
      "acceptance": [
        "Written checklist: all P0 security + backup + git + mobile + real-data + role-dogfood items green",
        "Explicit go/no-go owner and sign-off",
        "Multi-tenant gate: sell to team #2 only after N weeks of proven internal daily use"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "docs/LAUNCH.md — go/no-go checklist + vận hành + multi-tenant gate"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "CMO-029",
      "title": "Public Vietnamese landing page with the measurement moat as hero",
      "role": "CMO",
      "team": "Growth, Content & SEO",
      "priority": "P1",
      "effort": "M",
      "area": "Marketing",
      "status": "done",
      "problem": "Zero marketing surface — the live URL serves the raw operator SPA (login for an internal tool). Prospects have nowhere to land and nothing explaining value in plain VN. Cannot 'sell to many small teams' with nothing to send them to.",
      "acceptance": [
        "Static VN landing served at / (operator app moved to /app), inserted before SPA fallback; no RBAC/API dependency",
        "Hero: 'Đo được tiến bộ team, mới cải tiến được'; ICP subhead; problem→solution (A Kho + B Task/Đo lường)→4 real blurred screenshots→CTA",
        "POST /api/lead (whitelisted) stores to leads table + Telegram-notifies owner; toast thank-you (no alert)",
        "Mobile-perfect at 375px, no external CDN",
        "Live only after SEC-001/002/005 close"
      ],
      "deps": [
        "SEC-001",
        "SEC-002",
        "SEC-005",
        "DATA-008"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Landing page European-modern (moat đo tiến bộ) — artifact"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "CMO-030",
      "title": "ICP + positioning one-pager (VN) with objection handling",
      "role": "CMO",
      "team": "Growth, Content & SEO",
      "priority": "P1",
      "effort": "S",
      "area": "Marketing",
      "status": "done",
      "problem": "No customer-facing positioning: no one-liner, no who-it's-for/not-for, no answers to 'tôi đang dùng Sheet + Zalo, sao phải đổi?'. Landing/demo/pricing are all blocked without it. Must claim only what actually ships.",
      "acceptance": [
        "One VN page grounded in real built features (derived stock, mismatch alert, per-person trajectory, phone-only staff)",
        "5 objections answered: giá, đổi thói quen, nhân sự không rành CN, an toàn dữ liệu, khác gì Sheet",
        "Anti-overpromise guardrails (no auto cart QC, no self-serve multi-tenant yet)",
        "Delivered as artifact + copy-paste markdown for landing/demo/sales chat"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "docs/GTM.md — positioning + ICP + xử lý phản đối"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "CMO-031",
      "title": "Honest anonymized case study from the one real deployment",
      "role": "CMO",
      "team": "Growth, Content & SEO",
      "priority": "P1",
      "effort": "S",
      "area": "Marketing",
      "status": "done",
      "problem": "The only genuine proof is one live team (196 SP, 344 posts). It's unpackaged, and there is temptation to invent logos/reviews/user counts — a hard NO.",
      "acceptance": [
        "Anonymized before/after case study from the real deployment only",
        "Zero fabricated logos/testimonials/user counts",
        "Reusable in landing + sales conversations"
      ],
      "deps": [
        "DATA-007"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Case study ẩn danh thật (196 SP/344 lượt/0 nghiệm thu) trong GTM.md + landing"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "CMO-032",
      "title": "Entry pricing + near-term commercial model (private-instance vs multi-tenant)",
      "role": "CMO",
      "team": "Growth, Content & SEO",
      "priority": "P1",
      "effort": "S",
      "area": "Marketing",
      "status": "done",
      "problem": "No price/tier/trial exists, and North Star multi-tenant SaaS is aspirational — code is single-tenant SQLite (no org_id/tenant scaffolding). Selling 'multi-tenant' today misleads pricing and channel. Price-sensitive VN owners ask 'bao nhiêu/tháng' immediately.",
      "acceptance": [
        "Entry price + packaging for small VN teams; trial policy",
        "Explicit near-term model: managed private-instance per customer (not self-serve multi-tenant)",
        "Pricing consistent with the private-instance reality"
      ],
      "deps": [
        "CMO-030"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Pricing nháp: private-instance vs multi-tenant (GTM.md + landing)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "CMO-033",
      "title": "Community-launch plan for small VN affiliate/UGC teams (no paid SEO)",
      "role": "CMO",
      "team": "Growth, Content & SEO",
      "priority": "P2",
      "effort": "S",
      "area": "Marketing",
      "status": "done",
      "problem": "No channel plan. This is a niche B2B VN tool; SEO is the wrong bet and there's no defined way to reach small affiliate/UGC beauty teams.",
      "acceptance": [
        "Written channel plan targeting VN affiliate/UGC communities (groups, referrals, direct outreach)",
        "No reliance on paid SEO",
        "Concrete first-10-conversations outreach list"
      ],
      "deps": [
        "CMO-030"
      ],
      "comments": [
        {
          "by": "Claude",
          "note": "Landing hoàn chỉnh (hero/steps/proof/pricing/CTA)"
        }
      ],
      "created": "2026-09-06"
    },
    {
      "id": "CPO-033",
      "title": "Design-system refinement (European-modern)",
      "role": "CPO",
      "team": "Product & Design",
      "priority": "P1",
      "effort": "M",
      "area": "Frontend",
      "status": "done",
      "problem": "Nâng cả app lên chuẩn hiện đại: shadow tinh tế, micro-interaction, focus ring, header blur, typography sắc",
      "acceptance": [
        "shadow tokens",
        "transitions",
        "focus ring",
        "reduced-motion"
      ],
      "deps": [],
      "comments": [
        {
          "by": "Claude",
          "note": "Layer refinement token-based, áp toàn app"
        },
        {
          "by": "Claude",
          "note": "Dashboard đọc-là-hiểu: tên nổi, số hero màu, gợi ý hành động (dont-make-me-think)"
        }
      ]
    }
  ]
}