hermes-kanban-miniapp/docs/architecture-adr.md
kisskin ade88808fb
Some checks failed
CI / backend (push) Failing after 37s
CI / frontend (push) Failing after 21s
feat: M4 docker deployment - bridge + frontend containers
- packages/bridge/Dockerfile: multi-stage build (node:22-alpine)
- frontend/Dockerfile: multi-stage build + nginx:alpine
- frontend/nginx.conf: proxy /bridge to bridge service
- docker-compose.yml: bridge (3456) + frontend (8080)
- .dockerignore: exclude backend/node_modules/dist
- Fix vite proxy target to port 3456
- .env with bridge vars (AUTH_MODE=dev)
2026-07-10 14:58:20 +03:00

27 KiB
Raw Blame History

ADR-001: Community architecture for Hermes Kanban Telegram Mini App

Status: Proposed for implementation Date: 2026-07-10 Decision owner: project maintainer Scope: open-source community release, before rewriting the current local FastAPI prototype

1. Context

The current repository contains an uncommitted prototype with a Vite/React frontend and a Python/FastAPI backend that shells out to hermes kanban. The target changed: the public release should be best-practice, secure, easy to install, documented in English and Russian, pleasant as a Telegram Mini App and normal browser/mobile app, and work well on Vercel.

Hard constraint: a static Vercel frontend cannot safely execute a user's local hermes CLI or read local ~/.hermes/kanban.db. Any architecture that claims otherwise is untrustworthy. A remote browser UI must talk to a trusted server-side component that is already authorized to reach the user's Hermes instance.

2. Verified facts used for this ADR

Hermes Agent

From the official Hermes docs and local checked source at /home/kisskin/.hermes/hermes-agent:

  • Hermes API Server exposes Hermes as an OpenAI-compatible HTTP endpoint for external frontends (POST /v1/chat/completions, POST /v1/responses, POST /v1/runs, sessions, jobs, skills/toolsets discovery, health). Source: /home/kisskin/.hermes/hermes-agent/website/docs/user-guide/features/api-server.md.
  • API Server is enabled with env vars including API_SERVER_ENABLED=true, API_SERVER_KEY, API_SERVER_HOST, API_SERVER_PORT, and optional API_SERVER_CORS_ORIGINS. Default bind host is 127.0.0.1; bearer auth is required. Source: same file, Configuration and Authentication sections.
  • Hermes API Server is powerful: it gives access to the agent's toolset including terminal commands, so the key must never be exposed to a browser. Source: same file, Security warning.
  • Current API Server capabilities do not advertise first-class Kanban CRUD endpoints. The capabilities list includes chat/responses/runs, sessions, jobs, skills, and toolsets; searching gateway/platforms/api_server.py found no /api/kanban route.
  • Hermes Kanban is a durable SQLite-backed board. Tasks, comments, links, workspaces, logs, dispatcher behavior, boards, and board isolation are documented in /home/kisskin/.hermes/hermes-agent/website/docs/user-guide/features/kanban.md.
  • Hermes Kanban has two official surfaces today: agents use kanban_* tools, while humans/scripts use hermes kanban ... CLI, slash command, or the Hermes dashboard. Source: Kanban docs section "Two surfaces".

Telegram Mini Apps

From official Telegram Mini Apps docs and Telegram core docs:

  • Mini App init data is intended to be used as an authentication/authorization factor.
  • Server validation requires excluding hash, sorting key-value pairs, joining them with \n, deriving a secret with HMAC-SHA256 over the bot token using WebAppData, then HMAC-SHA256 over the data-check string and comparing hex digest to hash.
  • The bot token must remain server-side. A frontend may forward raw init data to its backend, but must not validate with the bot token in the browser.
  • The official React ecosystem template for Telegram Mini Apps uses React, TypeScript, Vite, @tma.js/sdk-react / Telegram Apps SDK packages, React Router HashRouter, and Telegram UI components.

Vercel / browser deployment

  • Vercel is a good fit for Vite static frontend hosting and for lightweight Node.js serverless API routes.
  • Vercel serverless functions can call HTTPS APIs, but they cannot be trusted to execute a user's private local hermes CLI or access a local SQLite board unless the user separately exposes a backend/bridge over HTTPS.
  • Browser-side direct calls to Hermes API Server are unsafe for a community default because they would require exposing API_SERVER_KEY to JavaScript. If direct browser mode is documented, it must be explicitly marked local/dev only.

Comparable open-source Telegram Mini Apps

GitHub search verified active public projects/templates:

  • Telegram-Mini-Apps/reactjs-template: React, TypeScript, Vite, Telegram Mini Apps SDK, Telegram UI; suitable baseline for project structure.
  • telegram-mini-apps-dev/TelegramUI: React component library inspired by Telegram interface; suitable UI dependency.
  • Other public Telegram Mini App examples commonly combine React/TypeScript with Next.js or Vite and place secrets in a backend/server component, not in static browser code.

3. Decision

Use this stack decisively:

  1. Frontend: React + TypeScript + Vite, hosted as static assets on Vercel or any static host.
  2. Telegram SDK/UI: @telegram-apps/sdk-react (or current successor package from Telegram Mini Apps docs) + @telegram-apps/telegram-ui for native-feeling Telegram UX.
  3. API layer for Vercel/community default: TypeScript serverless BFF (Backend-for-Frontend) API routes deployed on Vercel.
  4. Local integration: optional self-hosted Hermes Kanban Bridge, implemented in TypeScript/Node.js, running on the same trusted machine as Hermes.
  5. Data source: the bridge uses stable hermes kanban ... --json CLI commands first, with fixed argv and shell: false. It may later switch to official Hermes Kanban HTTP endpoints if Hermes adds them.
  6. Authentication: Vercel BFF validates Telegram initData server-side and issues a short-lived app session. The BFF then calls the self-hosted bridge using a separate bridge token. The bridge never trusts the browser directly.
  7. Documentation: docs must include English and Russian install paths for Vercel + bridge, Docker Compose, and local dev.

Short version: Vercel static React Mini App + Vercel TS BFF + optional self-hosted TS bridge near Hermes.

4. Why not keep the prototype stack as-is?

The Python/FastAPI prototype proved the shape, but it is not the best public target because:

  • The user prefers TypeScript/JS and Vercel.
  • A Vercel-centered release is simpler if frontend and BFF share TypeScript types and validation schemas.
  • A Python backend on Vercel is possible but less ergonomic for this use case than a small Node BFF plus a separate self-hosted bridge.
  • The current prototype mixes concerns: Telegram auth, browser CORS, Hermes CLI subprocess, and static serving in one backend. For community release, the trust boundary must be explicit.

Python/FastAPI is not rejected forever; it becomes a migration source and optional legacy adapter, not the recommended architecture.

5. Target architecture

Telegram client / mobile browser / desktop browser
        |
        | HTTPS, static assets, no secrets
        v
Vercel-hosted React + TypeScript + Vite app
        |
        | Authorization: tma <Telegram initData>
        | or app session cookie/JWT after validation
        v
Vercel TypeScript BFF API routes
        |  - validates Telegram initData with TELEGRAM_BOT_TOKEN
        |  - enforces allowed Telegram user IDs / roles
        |  - rate limits and normalizes errors
        |  - holds no Hermes CLI access
        |
        | HTTPS + Bridge token, server-to-server only
        v
Optional self-hosted Hermes Kanban Bridge (Node.js)
        |  - runs on trusted host near Hermes
        |  - validates bridge token/mTLS or reverse-proxy auth
        |  - maps narrow REST endpoints to fixed Hermes Kanban operations
        |  - uses child_process.spawn/execFile with shell=false
        v
Hermes install / profile
        |
        v
Hermes Kanban board SQLite + dispatcher + logs/workspaces

Local-only shortcut

For local development only:

Vite dev server -> local bridge on 127.0.0.1 -> hermes kanban CLI

This mode may use AUTH_MODE=dev and a fake Telegram user fixture, but it must be impossible to enable in production without an explicit env var.

6. Trust boundaries

Public browser / Mini App

Trusted for display only. Not trusted for identity, permissions, board slug, task IDs, or action payloads. It must not receive:

  • Telegram bot token.
  • Hermes API Server key.
  • Bridge token.
  • Local filesystem paths beyond what the bridge explicitly exposes as display-safe metadata.
  • Raw command output containing secrets.

Vercel BFF

Trusted to authenticate Telegram users and authorize UI actions. It has:

  • TELEGRAM_BOT_TOKEN.
  • SESSION_SECRET or JWT signing secret.
  • BRIDGE_BASE_URL and BRIDGE_TOKEN if a bridge is configured.

It does not have direct local CLI access.

Self-hosted bridge

Trusted local integration process. It has:

  • Access to hermes binary and HERMES_HOME.
  • Optional API_SERVER_KEY only if a future bridge mode calls Hermes API Server for non-Kanban agent actions.
  • A bridge token accepted only from the BFF/reverse proxy.

Hermes API Server

Powerful agent API. Do not expose directly to the Mini App browser for community default. If users expose it, they must keep bearer auth server-side and set narrow CORS only when absolutely needed.

7. Authentication model

Telegram initData validation

Recommended request from frontend to BFF:

Authorization: tma <raw initData>

Server validation steps:

  1. Parse query-string-like init data.
  2. Remove and store hash.
  3. Sort remaining key-value pairs alphabetically.
  4. Join as key=value lines separated by \n.
  5. Derive secret key: HMAC-SHA256 with key WebAppData and message TELEGRAM_BOT_TOKEN.
  6. Compute HMAC-SHA256 of the data-check string using the derived key.
  7. Compare hex digest to received hash using constant-time comparison.
  8. Reject missing/invalid auth_date, expired data, missing user, or malformed JSON.
  9. Apply allowlist/RBAC: TELEGRAM_ALLOWED_USER_IDS, optional TELEGRAM_ADMIN_USER_IDS.
  10. Issue a short-lived app session cookie/JWT or validate initData on every API request.

Recommended TTL: 1 hour for write actions, configurable up to 24 hours for read-only dashboards.

App roles

Minimum roles:

  • viewer: boards/tasks read-only, logs redacted/truncated.
  • operator: create/comment/assign/promote/block/unblock/archive/dispatch.
  • admin: configure bridge target, manage board allowlist, view diagnostics.

Initial open-source release may implement allowlist-only authorization with operator for all allowed users and document RBAC as planned, but the API contract should reserve role fields now.

Bridge authentication

Recommended:

  • Authorization: Bearer <bridge-token> between BFF and bridge.
  • HTTPS at the public edge if bridge is reachable from Vercel.
  • Optional reverse-proxy IP allowlist, Cloudflare Access, Tailscale, or mTLS for advanced users.
  • Bridge should reject requests without X-Request-Id and should log audit metadata without storing Telegram raw initData.

8. Threat model

Threat Risk Mitigation
Browser steals Hermes API key Full agent/tool compromise Never put API_SERVER_KEY or bridge token in frontend env. Use BFF.
Forged Telegram user Unauthorized board control Validate initData server-side using official HMAC flow and max age.
Replay of old initData Unauthorized action reuse Enforce auth_date TTL; short-lived session; optional nonce/idempotency for writes.
CLI injection Arbitrary local commands Bridge maps endpoints to fixed argv; use execFile/spawn with shell:false; validate task IDs/board slugs.
Path traversal via board/workspace Filesystem escape Accept only Hermes-valid board slugs; never pass raw paths from browser except documented workspace kind with strict validation.
Leaking secrets in logs/errors Token disclosure Redact token, secret, password, api_key; truncate logs; avoid returning raw stderr.
CSRF against BFF Actions from malicious site Use SameSite cookies or Authorization header; verify Origin; require Telegram/session auth for writes.
CORS overexposure Browser exfiltration BFF CORS only for app origin; bridge CORS disabled by default.
SSRF from BFF to arbitrary bridge URL Internal network probing BRIDGE_BASE_URL env only; no user-provided target URLs.
Bridge public exposure Local Hermes takeover Strong token, HTTPS, rate limiting, reverse-proxy auth, optional local-only mode.
Denial of service by refresh/log tail Resource exhaustion Pagination, limit caps, debounce refresh, server-side rate limits, timeouts.
Confused deputy across boards User controls wrong board Server-side board allowlist; all write endpoints include normalized board slug and audit actor.
Supply-chain compromise Public package risk Pin lockfile, Dependabot/Renovate, CI tests, minimal dependencies.

9. Deploy matrix

Mode Frontend BFF Bridge Hermes access Recommended for Notes
Vercel + self-hosted bridge Vercel static Vite Vercel Node serverless routes Node bridge on user's server/Pi/VPS Bridge runs local hermes kanban Community default Best UX for Telegram HTTPS and simple updates. Requires exposing bridge securely.
Docker Compose single host Containerized static frontend served by BFF or Nginx Node BFF container Node bridge container or same process Bind-mount Hermes home / host hermes Self-hosters Strong docs needed for volumes and user permissions. Avoid mounting Docker socket.
Local dev Vite dev server Local Node BFF Local bridge Local hermes kanban Contributors Supports Telegram mock/dev auth. No public exposure.
Fully static Vercel only Vercel static None None None Demo/readme only Cannot control real Hermes. Can show mock data only.
Browser -> Hermes API direct Static or local None None Hermes API Server over HTTPS Not recommended Exposes bearer token to browser unless using a trusted private network. Document as unsafe for public/community default.
Future native Hermes Kanban API Vercel static Vercel BFF Optional/none BFF calls official /api/kanban Future Adopt only when official Hermes exposes least-privilege Kanban CRUD separate from full agent API.

10. API contract for v1

All public frontend calls go to the BFF under /api/*. The BFF calls the bridge under /bridge/v1/* or equivalent. The frontend should not know whether data came from a bridge or future native Hermes API.

Common headers

Frontend -> BFF:

Authorization: tma <raw initData>
Content-Type: application/json
X-Request-Id: <uuid>

BFF -> Bridge:

Authorization: Bearer <bridge-token>
Content-Type: application/json
X-Request-Id: <uuid>
X-Actor-Telegram-Id: <id>
X-Actor-Role: viewer|operator|admin

Common response envelope

type ApiOk<T> = {
  ok: true;
  data: T;
  requestId: string;
};

type ApiError = {
  ok: false;
  error: {
    code: string;
    message: string;
    details?: unknown;
  };
  requestId: string;
};

Core models

type Board = {
  slug: string;
  name?: string;
  description?: string;
  icon?: string;
  current?: boolean;
};

type TaskStatus = 'triage' | 'todo' | 'ready' | 'running' | 'blocked' | 'done' | 'archived';

type TaskSummary = {
  id: string;
  title: string;
  status: TaskStatus;
  assignee?: string | null;
  priority?: number | null;
  createdAt?: string;
  updatedAt?: string;
  blockedReason?: string | null;
  parentIds?: string[];
  childIds?: string[];
};

type TaskDetail = TaskSummary & {
  body?: string | null;
  comments?: TaskComment[];
  runs?: TaskRun[];
  events?: TaskEvent[];
  attachments?: TaskAttachment[];
};

type TaskComment = {
  id?: string;
  author?: string;
  body: string;
  createdAt?: string;
};

type TaskRun = {
  id?: string;
  status?: string;
  startedAt?: string;
  finishedAt?: string;
  logAvailable?: boolean;
};

type TaskEvent = {
  id?: string;
  type: string;
  message?: string;
  createdAt?: string;
};

type TaskAttachment = {
  id: string;
  filename: string;
  sizeBytes?: number;
  mimeType?: string;
};

BFF endpoints

Read:

  • GET /api/health -> { status, version, bridge: { configured, reachable } }
  • GET /api/me -> current Telegram user + role + auth expiry
  • GET /api/boards -> Board[]
  • GET /api/boards/:board/tasks?status=&assignee=&q=&limit=&cursor= -> paginated TaskSummary[] grouped client-side or server-side
  • GET /api/boards/:board/tasks/:taskId -> TaskDetail
  • GET /api/boards/:board/tasks/:taskId/log?lines=200 -> redacted log tail
  • GET /api/boards/:board/events/stream -> SSE for future live updates; polling fallback required

Write:

  • POST /api/boards/:board/tasks
  • POST /api/boards/:board/tasks/:taskId/comment
  • POST /api/boards/:board/tasks/:taskId/assign
  • POST /api/boards/:board/tasks/:taskId/promote
  • POST /api/boards/:board/tasks/:taskId/block
  • POST /api/boards/:board/tasks/:taskId/unblock
  • POST /api/boards/:board/tasks/:taskId/archive
  • POST /api/boards/:board/dispatch

Write payload examples:

{
  "title": "Implement mobile task drawer",
  "body": "Acceptance criteria...",
  "assignee": "developer",
  "priority": 3,
  "workspaceKind": "worktree",
  "workspacePath": null,
  "goalMode": false,
  "maxRuntimeSeconds": 3600,
  "idempotencyKey": "optional-client-generated-key"
}
{ "body": "Looks good, unblock after config is set." }
{ "assignee": "reviewer" }
{ "reason": "Needs maintainer decision on API shape." }

Bridge endpoint behavior

Bridge can mirror BFF paths under /bridge/v1/boards/..., but it must be internal-only. It should return the same envelope. It must:

  • Validate all slugs and IDs before spawning Hermes CLI.
  • Use a command allowlist, not arbitrary pass-through.
  • Set HERMES_HOME from env, not request payload.
  • Set HERMES_KANBAN_BOARD or --board from validated board slug.
  • Enforce timeouts per command.
  • Redact and normalize errors.

11. UX requirements

Telegram Mini App:

  • Respect Telegram theme variables and safe areas.
  • Use Telegram-style components for lists, section headers, buttons, badges, bottom sheets/drawers, destructive confirmations.
  • Use haptic feedback only for meaningful actions if SDK supports it.
  • MainButton/bottom action area for primary task action on mobile.
  • HashRouter or equivalent router compatible with Telegram webview and static hosting.

Browser/mobile:

  • Responsive board: columns on desktop, status tabs/list on mobile.
  • Fast read path: skeleton loading, optimistic comment append only after server accepts writes or clearly mark pending.
  • Polling fallback; SSE enhancement later.
  • Accessible color contrast and keyboard navigation in browser.

Community docs/quality:

  • .env.example only placeholders.
  • No real bot tokens, Hermes keys, domains, local IPs, or passwords.
  • README in English plus Russian quickstart or README.ru.md.
  • SECURITY.md, CONTRIBUTING.md, CI, tests, build checks.

12. Documentation outline

English docs

  1. README.md
    • What is Hermes Kanban Mini App?
    • Screenshots/GIFs
    • Architecture summary
    • Quickstart matrix: Vercel + bridge, Docker Compose, local dev
    • Security warning: static frontend cannot run Hermes CLI
    • Links to full docs
  2. docs/installation.en.md
    • Prerequisites: Node.js, Hermes Agent, Telegram bot, HTTPS URL
    • Create Telegram bot and configure Mini App domain
    • Vercel frontend/BFF deploy
    • Self-hosted bridge setup
    • Docker Compose setup
    • Local dev setup with mock Telegram auth
  3. docs/configuration.en.md
    • All env vars
    • Auth modes
    • Board allowlist
    • Rate limit settings
    • Reverse proxy examples
  4. docs/security.en.md
    • Threat model
    • Token handling
    • Telegram initData validation
    • Bridge exposure hardening
    • Responsible disclosure
  5. docs/api.en.md
    • BFF and bridge endpoint contracts
    • Error codes
    • SSE/polling behavior
  6. docs/migration-from-fastapi-prototype.en.md
    • Mapping current prototype endpoints to new TS BFF/bridge
    • Removed assumptions
    • Compatibility notes

Russian docs

  1. README.ru.md
    • Что это и кому нужно
    • Рекомендуемая схема установки
    • Главное предупреждение про Vercel/static и локальный Hermes CLI
  2. docs/installation.ru.md
    • Telegram BotFather
    • Vercel
    • self-hosted bridge
    • Docker Compose
    • локальный dev
  3. docs/configuration.ru.md
    • Переменные окружения
    • режимы авторизации
    • allowlist пользователей/досок
  4. docs/security.ru.md
    • модель угроз
    • где хранить токены
    • как безопасно открыть bridge
  5. docs/api.ru.md
    • контракт API
    • ошибки
  6. docs/migration-from-fastapi-prototype.ru.md
    • план миграции с текущего прототипа

13. Environment variables for the new stack

Frontend build-time public vars:

VITE_APP_NAME=Hermes Kanban
VITE_BFF_BASE_URL=              # empty for same-origin Vercel deployment
VITE_TELEGRAM_BOT_USERNAME=     # optional display/link only, not token

Vercel BFF server vars:

TELEGRAM_BOT_TOKEN=             # secret, server-only
TELEGRAM_ALLOWED_USER_IDS=      # comma-separated Telegram IDs
TELEGRAM_ADMIN_USER_IDS=        # optional
SESSION_SECRET=                 # random 32+ bytes
BRIDGE_BASE_URL=                # https://bridge.example.com
BRIDGE_TOKEN=                   # secret, server-to-server
ALLOWED_ORIGINS=                # exact frontend origins
AUTH_MAX_AGE_SECONDS=3600
READ_AUTH_MAX_AGE_SECONDS=86400
RATE_LIMIT_PER_MINUTE=60

Bridge server vars:

BRIDGE_TOKEN=                   # must match BFF
HERMES_BIN=hermes
HERMES_HOME=                    # target Hermes profile home
HERMES_KANBAN_BOARD=default
BRIDGE_HOST=127.0.0.1
BRIDGE_PORT=8787
BRIDGE_ALLOWED_BOARDS=default
COMMAND_TIMEOUT_SECONDS=30
LOG_TAIL_MAX_LINES=500

Docker Compose vars should mirror the same names. .env must stay gitignored; .env.example must contain placeholders only.

14. Migration plan from current FastAPI prototype

Do not modify the current Python implementation until this ADR is accepted.

Phase 0: Freeze and document

  • Keep current prototype uncommitted as reference only.
  • Add this ADR.
  • Record endpoint inventory and data shapes from backend/app/main.py, adapter.py, auth.py, and frontend calls.

Phase 1: Extract contracts

  • Create shared TypeScript schemas for Board, TaskSummary, TaskDetail, API envelope, and write payloads.
  • Write contract tests against fixtures copied from current prototype responses.
  • Define error code taxonomy.

Phase 2: Build TypeScript bridge

  • Implement Node bridge with fixed command allowlist matching current HermesCliKanbanAdapter behavior.
  • Port Telegram-independent validation helpers: board slug, task ID, safe log limits, error redaction.
  • Add tests that assert shell:false / execFile usage and reject invalid IDs/slugs.
  • Verify against local hermes kanban commands.

Phase 3: Build Vercel BFF

  • Implement Telegram initData validation using a maintained Node package such as @tma.js/init-data-node or a small audited HMAC helper with tests from Telegram examples.
  • Add allowlist auth and bridge proxy endpoints.
  • Add rate limiting and CORS/origin checks.

Phase 4: Rebuild frontend UX

  • Keep React + TypeScript + Vite.
  • Replace ad-hoc Telegram integration with Telegram Apps SDK React package.
  • Add Telegram UI components, mobile task drawer, board status tabs, desktop columns.
  • Use BFF API only; no direct bridge/Hermes calls from browser.

Phase 5: Docs and release hardening

  • Write EN/RU docs from outline.
  • Add .env.example, SECURITY.md, CONTRIBUTING.md, CI.
  • Add unit tests, contract tests, frontend tests, build check.
  • Add Docker Compose example.
  • Add Vercel deployment guide.

Phase 6: Optional legacy compatibility

  • Either remove Python prototype from the public release or move it to legacy/fastapi-prototype with clear unsupported status.
  • Do not publish both as equal recommended paths; it confuses users.

15. Consequences

Positive:

  • Clear Vercel-first story without lying about local CLI access.
  • Secrets stay server-side.
  • TypeScript types can be shared across frontend, BFF, and bridge.
  • Bridge is optional but honest: users who want real Hermes control must run a trusted server near Hermes.
  • Future Hermes native Kanban HTTP API can replace bridge internals without changing frontend UX.

Negative/tradeoffs:

  • Requires two deploy targets for real use: Vercel app and bridge.
  • Public bridge exposure needs careful docs.
  • Slightly more moving parts than a single FastAPI container.
  • Until Hermes exposes first-class Kanban HTTP endpoints, the bridge still depends on CLI JSON behavior.

16. Rejected alternatives

Static Vercel app directly runs Hermes CLI

Rejected. Browsers/static hosting cannot execute the user's local CLI. Any workaround would require unsafe local agents or browser extensions and is not community-ready.

Static app directly calls Hermes API Server with API key

Rejected for public/community default. Hermes API Server bearer key controls a full agent toolset including terminal; exposing it to JavaScript is a critical security issue.

Rejected as recommended stack. It works locally and may be a useful prototype, but it conflicts with TypeScript/Vercel preference and mixes trust boundaries.

Full Next.js monolith

Not selected. Next.js could host frontend+BFF on Vercel, but Vite static + small serverless BFF is simpler, lighter, and closer to official Telegram Mini Apps React template patterns. If future SSR/auth needs grow, migration to Next.js remains possible.

Bridge reads kanban.db directly

Rejected for v1. Direct DB reads couple the project to Hermes internals and risk schema drift. Use official CLI behavior first; switch to official HTTP API when available.

17. Precise next implementation milestones

M1. Contract foundation

  • Add packages/shared or src/shared TypeScript schemas.
  • Add API envelope and error code tests.
  • No UI rewrite yet.

M2. Bridge MVP

  • Create apps/bridge TypeScript service.
  • Implement GET /health, GET /bridge/v1/boards, GET /bridge/v1/boards/:board/tasks, GET /bridge/v1/boards/:board/tasks/:taskId.
  • Use fixed execFile/spawn calls to hermes kanban with tests.

M3. BFF auth MVP

  • Create Vercel API routes.
  • Validate Telegram initData in server code.
  • Proxy read endpoints to bridge.
  • Add auth fixtures/tests.

M4. Frontend read-only UX

  • Migrate current React UI to new BFF contract.
  • Add Telegram SDK initialization and Telegram UI theme handling.
  • Ship read-only boards/tasks/log tail.

M5. Write actions

  • Add create/comment/assign/promote/block/unblock/archive/dispatch.
  • Add confirmations and role checks.
  • Add audit log fields passed to bridge.

M6. Release docs and CI

  • EN/RU install docs.
  • .env.example for frontend, BFF, bridge, Docker Compose.
  • SECURITY.md, CONTRIBUTING.md.
  • CI: typecheck, lint, unit tests, frontend build, bridge tests.

M7. Optional live updates

  • SSE endpoint from BFF to frontend.
  • Bridge polling or future Hermes dashboard/events integration.
  • Graceful fallback to polling.

18. Acceptance criteria for starting app-code rewrite

Do not write implementation code until these are accepted:

  • This ADR stack decision is approved.
  • Maintainer confirms whether bridge is a separate apps/bridge service or same package as BFF with a runtime mode switch.
  • Maintainer confirms whether the public repo should delete the Python prototype or keep it under legacy/.
  • First milestone is limited to contracts + tests + scaffold, not feature-complete UI.