diff --git a/.dockerignore b/.dockerignore index 1901f009e..b95584b3f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -82,7 +82,6 @@ README.md CHANGELOG.md CLAUDE.md CONTRIBUTING.md -CONTRIBUTING-AI.md CODE_OF_CONDUCT.md SECURITY.md # LICENSE / LICENSE.md are deliberately NOT ignored: the PolyForm diff --git a/.gitignore b/.gitignore index 655bb269e..55ff4b2ad 100644 --- a/.gitignore +++ b/.gitignore @@ -72,11 +72,10 @@ next-env.d.ts .cursor/ .codex/ .aider* +.mcp.json -# Planning scratch — all internal per-release reports, QA findings, ad-hoc -# backlog, and the iOS coordination channel. These are internal working -# notes; everything user-facing lives in the public surfaces (README, -# CHANGELOG, docs/). Kept on disk for the working session, never tracked. +# Internal working notes (per-release reports, backlog, iOS coordination). +# Everything user-facing lives in README, CHANGELOG and docs/. /.planning/ # Docs scratch — temporary spec drafts that live in the repo only diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 29585572e..000000000 --- a/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "shadcn": { - "command": "npx", - "args": ["-y", "shadcn@latest", "mcp"] - } - } -} diff --git a/CONTRIBUTING-AI.md b/CONTRIBUTING-AI.md deleted file mode 100644 index 5cd662a4e..000000000 --- a/CONTRIBUTING-AI.md +++ /dev/null @@ -1,16 +0,0 @@ -# Contributing with AI tooling - -HealthLog is built with AI assistance, and contributions made the same way are welcome. This file used to carry a copy of the internal conventions document; the copy aged badly and started contradicting the codebase, so it now points at the living sources instead. - -## Where the real conventions live - -- `CLAUDE.md` at the repo root is the maintainer's operating manual for this codebase: architecture walk, code conventions, security patterns, test commands, the do-not list. It is checked in and kept current; whatever an AI agent (or a human) needs to work on the server is in there. -- `docs/CONTRIBUTING-CLIENTS.md` covers building a client app (Android, iOS, CLI) against the server: the API contract, auth from a client's point of view, sync semantics, and how coordination between repos works. -- `docs/api/openapi.yaml` is the wire contract, generated from the server's Zod schemas. CI fails on drift, so it is always accurate for the commit it sits in. - -## Ground rules, whatever tooling you use - -- The human who commits a change has read it and stands behind it. The git history knows authors, not tools: no AI attribution trailers, no assistant vocabulary in commit messages, PR text, or release notes. -- No real names of users or reporters in committed text, and no live health figures anywhere, including fixtures and examples. Invent data. -- No secrets in code, fixtures, or docs, and nothing secret-shaped either. -- Run the gates before pushing (`pnpm typecheck`, `pnpm lint`, `pnpm test`) and fix what they find rather than suppressing it. A guard that fails is telling you something; the fix goes in the code, not in the guard. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78d3286d8..157eb8949 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,7 +76,7 @@ pnpm typecheck && pnpm lint && pnpm test && pnpm format:check && pnpm build - **shadcn/ui** components (new-york style) for UI primitives. - **Dracula theme** -- dark mode is the default; a `light`/`system` toggle is exposed in the topbar. Chart and accent colors use the `--dracula-*` CSS tokens so both themes stay coherent. -See `CONTRIBUTING-AI.md` for detailed architecture and patterns. +Whoever commits a change has read it and stands behind it. Committed text names no real users or reporters and carries no live health figures; fixtures and examples use invented data. Nothing secret, and nothing secret-shaped, goes into code, fixtures or docs. ## Database Changes @@ -121,7 +121,7 @@ Contributors: branch off `main`; PRs target `main`. ## Translations -HealthLog ships with English (`en`) and German (`de`) maintained by the project, plus French (`fr`), Spanish (`es`), Italian (`it`), Polish (`pl`), Korean (`ko`), Dutch (`nl`), and Brazilian Portuguese (`pt-BR`) that are **AI-initial, community-maintained**. The maintained locales are reviewed line-by-line; the community-maintained ones surface a small banner inviting users to improve them. +HealthLog ships with English (`en`) and German (`de`) maintained by the project, plus French (`fr`), Spanish (`es`), Italian (`it`), Polish (`pl`), Korean (`ko`), Dutch (`nl`), Brazilian Portuguese (`pt-BR`) and Russian (`ru`), which started as machine-translated first drafts and are reviewed by the community. The maintained locales are reviewed line by line; the community-reviewed ones show a small banner inviting users to improve them. ### Improving an existing locale @@ -168,9 +168,9 @@ To propose a validated version for a language that has none, or a better source If you want to introduce a locale that isn't shipped (e.g. `sv`, `cs`): -1. Open an issue first using the **Translation request** template. We'll confirm the locale is in scope and decide whether it joins the AI-initial-community-maintained tier or the maintained tier. +1. Open an issue first using the **Translation request** template. We'll confirm the locale is in scope and decide whether it joins the community-reviewed tier or the maintained tier. 2. Copy `messages/en.json` to `messages/.json`. Questionnaire text is not in it; the new language needs a wording file per instrument instead (see Questionnaire wording above), which step 4 makes impossible to forget. -3. Translate. AI assistance is welcome as a starting point but the PR description should note whether it's an AI-initial pass or a fully human-reviewed one — that information drives the banner state. +3. Translate. A machine translation is fine as a starting point, but the PR description should say whether the pass is machine-translated or fully reviewed by someone who reads the language; that decides the banner state. 4. Add the locale to `locales` in `src/lib/i18n/config.ts` and to `locales` in `project.inlang/settings.json`, then run `pnpm typecheck`. Every per-locale table that still lacks the new locale fails to compile, and that list is the work: date and number formatting, the reply-language footers of the Coach and insights prompts, the outbound safety banks, the prose-grounding patterns, the no-key fallback copy and the questionnaire wording registry. Include those in the same PR. 5. Run `pnpm test` before submitting. The i18n guards hold the new bundle to the English keys and placeholders. @@ -185,7 +185,7 @@ If you want to introduce a locale that isn't shipped (e.g. `sv`, `cs`): ### Long-term -Fink covers browser editing today. It edits the files in this repository directly and keeps no copy of the translations anywhere else. If the community outgrows it, a self-hosted [Weblate](https://weblate.org/) instance remains the fallback. Tracking issue: `v1.6 — Weblate self-hosted on edge-01`. +Fink covers browser editing today. It edits the files in this repository directly and keeps no copy of the translations anywhere else. If the community outgrows it, a self-hosted [Weblate](https://weblate.org/) instance remains the fallback. ## Code of Conduct diff --git a/README.md b/README.md index 4a1a137ee..6fa8591aa 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

HealthLog

- Self-hosted, privacy-first health tracking. Your vitals, your devices, your server. + Self-hosted health tracking that syncs your devices and keeps the data on your own server.

@@ -17,10 +17,10 @@

- Website · - Live Demo · - Documentation · - iOS App Store · + Website | + Live Demo | + Documentation | + iOS App Store | iOS TestFlight

@@ -36,25 +36,25 @@ Coach: a conversation about the week's recovery, grounded in the recorded data

-## Why HealthLog +## What HealthLog is -HealthLog is a self-hosted health tracking app (PWA + native iOS client) that runs from a single `docker compose up` on a NAS, homelab, or small VPS. It brings weight, blood pressure, pulse, blood glucose, body composition, sleep, mood, menstrual cycle, and medications onto one timeline, synced from the devices you already own, and keeps it in a Postgres database on hardware you control. No vendor cloud, no subscription, no usage telemetry. The few requests the server makes on its own (an update check, a password breach check, an IP geolocation lookup for logins) can each be switched off. +HealthLog is a self-hosted health tracking app (PWA + native iOS client) that runs from a single `docker compose up` on a NAS, homelab, or small VPS. It brings weight, blood pressure, pulse, blood glucose, body composition, sleep, mood, menstrual cycle, and medications onto one timeline, synced from the devices you already own, and keeps it in a Postgres database on hardware you control. It needs no vendor cloud or subscription and sends no usage telemetry. The few requests the server makes on its own (an update check, a password breach check, an IP geolocation lookup for logins) can each be switched off. -HealthLog is a personal health record-keeping and wellness tool. It is not a medical device and does not diagnose, treat, cure, or prevent any disease — it helps you log, visualise, and export your own data, and leaves clinical judgement to a clinician. +HealthLog is a personal health record-keeping and wellness tool. It is not a medical device and does not diagnose, treat, cure, or prevent any disease. It helps you log, visualise, and export your own data, and leaves clinical judgement to a clinician. -Try the [live demo](https://demo.healthlog.dev) to see a working install — sign in with `demo` / `demo123demo123`. +Try the [live demo](https://demo.healthlog.dev) to see a working install. Sign in with `demo` / `demo123demo123`. -## Highlights +## Features -- **Every vital on one timeline.** Weight, blood pressure, pulse, glucose, body composition, sleep, SpO₂, respiratory rate, mood, and cycle tracking — charted with trends, moving averages, and clinical reference ranges (ESH 2023, ADA 2024), overridable with the targets your clinician set. -- **Clinical signals, opt-in and additive.** Validated self-checks for mental wellbeing and sleep (WHO-5, PHQ-9, GAD-7, Sleep Condition Indicator — official questionnaire wording, calm crisis-resource handling), grip strength, a 0–10 pain score, waist and body measurements, and a longevity lab panel — each added through one signal registry, so a new signal wires into charts, correlations, the coach, the FHIR map, and the API at once. -- **Records, structured.** Allergies and family history as structured records that flow into the doctor report (reactions and notes are encrypted, the substance and condition names are not), plus lab values you key in or scan from a printed report — OCR extracts the stated values, units, and dates for you to review and confirm before anything is saved. HealthLog transcribes what a report says; it never interprets or diagnoses from it. A vaccination record keeps a lifetime immunization log, doses grouped under the disease they protect against, and prints in the doctor report. -- **A document vault for the paper trail.** An opt-in module stores every letter, report, and scan encrypted at rest — PDFs and photos preview inline, Office and text files stay download-only, and nothing user-uploaded can ever execute in the app. One searchable timeline with type, condition, and year filters, drag-and-drop and paste-to-upload, bulk filing, undo-able delete with a 30-day grace, and per-file/per-account limits the operator tunes. -- **Context that explains the numbers.** An environmental module folds weather, daylight, temperature, air quality, and pollen in as influence signals (winter darkness against mood, heat against blood pressure) using a home location you set, so a pattern has somewhere to point. It is on by default, and nothing leaves the host until someone sets that location. -- **Your devices, one server.** Withings, WHOOP, Oura and Polar sync over OAuth2, Google Health connects Fitbit and Pixel Watch through your own Google Cloud client, Nightscout streams CGM glucose, an Apple Health `export.zip` folds your full history in, and the native iOS app streams HealthKit live. A per-metric source priority decides which reading is canonical when wearables overlap. -- **Medication tracking that tells the truth.** Flexible schedules (weekly injections, weekday-only, intervals, PRN, cyclic), a configurable intake window per dose, and a traceable dose history where every slot reads taken, late, skipped, or missed — the same ledger the compliance rate is computed from, so the percentage can never disagree with the timeline. -- **AI insights you own.** Daily briefing, health scores, correlations, and a conversational coach grounded in your own measurements and interpreted against cited guideline reference bands — where a value sits and what that means in plain words, never a diagnosis. The coach proposes concrete if-then plans you accept or decline, and suppresses proactive check-ins when there's nothing genuine to say. Bring your own OpenAI or Anthropic key, or point at a local endpoint (Ollama, LM Studio, vLLM) so AI requests stay on your network. -- **Clinician-ready output.** A doctor-report PDF rendered on your server, a read-only HL7 FHIR R4 API, and scoped, time-limited share links you revoke after the visit. +- **Vitals and trends.** Weight, blood pressure, pulse, glucose, body composition, sleep, SpO₂, respiratory rate, mood, and cycle tracking, charted with trends, moving averages, and clinical reference ranges (ESH 2023, ADA 2024) that you can override with the targets your clinician set. Health scores and correlations between your metrics are computed on your server without an AI model. +- **Questionnaires and self-checks.** Optional validated self-checks for mental wellbeing and sleep (WHO-5, PHQ-9, GAD-7 and the Sleep Condition Indicator, in their official wording, with crisis resources when an answer calls for them), grip strength, a pain score from 0 to 10, waist and body measurements, and a longevity lab panel. Each comes from one signal registry, so a new signal reaches charts, correlations, the coach, the FHIR map, and the API together. +- **Allergies, family history, labs and vaccinations.** Allergies and family history are structured records that flow into the doctor report (reactions and notes are encrypted, the substance and condition names are not). Lab values are keyed in or scanned from a printed report: OCR reads the stated values, units, and dates for you to review and confirm before anything is saved. HealthLog transcribes what a report says; it never interprets or diagnoses from it. A vaccination record keeps a lifetime immunization log, doses grouped under the disease they protect against, and prints in the doctor report. +- **Documents.** An opt-in module stores letters, reports, and scans encrypted at rest. PDFs and photos preview inline, Office and text files stay download-only, and nothing a user uploads can execute in the app. One searchable timeline with type, condition, and year filters, drag-and-drop and paste-to-upload, bulk filing, a delete you can undo within 30 days, and per-file and per-account limits the operator sets. +- **Weather and environment.** An environmental module adds weather, daylight, temperature, air quality, and pollen as possible influences (winter darkness and mood, heat and blood pressure), based on a home location you set. It is on by default, and nothing leaves the host until someone sets that location. +- **Devices and integrations.** Withings, WHOOP, Oura and Polar sync over OAuth2, Google Health connects Fitbit and Pixel Watch through your own Google Cloud client, Nightscout streams CGM glucose, an Apple Health `export.zip` folds your full history in, and the native iOS app streams HealthKit live. A per-metric source priority decides which reading is canonical when wearables overlap. +- **Medications.** Flexible schedules (weekly injections, weekday-only, intervals, PRN, cyclic), a configurable intake window per dose, and a dose history where every slot reads taken, late, skipped, or missed. The compliance rate is computed from the same ledger, so the percentage always matches the timeline. +- **AI features (optional).** A daily briefing and a conversational coach grounded in your own measurements and read against cited guideline reference bands: where a value sits and what that means, never a diagnosis. The coach proposes concrete if-then plans you accept or decline, and skips a proactive check-in when there is nothing new to say. Bring your own OpenAI or Anthropic key, or point at a local endpoint (Ollama, LM Studio, vLLM) so AI requests stay on your network. +- **Doctor report and FHIR.** A doctor-report PDF rendered on your server, a read-only HL7 FHIR R4 API, and scoped, time-limited share links you revoke after the visit. - **Private by construction.** AES-256-GCM encryption at rest for notes and other free text, documents, questionnaire answers, AI-written text and credentials, with zero-downtime key rotation. Measurement values, lab values, medication names and mood scores are stored unencrypted in the database, so the database volume and its backups need the same protection as the host. Passkey login, server-side sessions, strict CSP, and no third-party tracking anywhere. - **Built to be lived in.** Installable PWA with offline support, medication reminders over APNs, Telegram, ntfy, and Web Push, a sub-second dashboard on years of imported history — in English, German, Spanish, French, Italian, Polish, Korean, Dutch, and Brazilian Portuguese end to end. @@ -71,13 +71,13 @@ echo "POSTGRES_PASSWORD=$(openssl rand -hex 32)" >> .env echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env echo "API_TOKEN_HMAC_KEY=$(openssl rand -hex 32)" >> .env -# LAN / plain-HTTP host (NAS, homelab, Tailscale) — required, or login fails: +# Required on a LAN or plain-HTTP host (NAS, homelab, Tailscale), or login fails: echo "SESSION_COOKIE_SECURE=false" >> .env docker compose up -d ``` -Open **http://localhost:3000** (or `http://:3000`) — the first registered user becomes admin. The compose file pulls a pre-built multi-arch image (`amd64` + `arm64`) from [GHCR](https://github.com/MBombeck/HealthLog/pkgs/container/healthlog); no build step required. +Open **http://localhost:3000** (or `http://:3000`). The first registered user becomes admin. The compose file pulls a pre-built multi-arch image (`amd64` + `arm64`) from [GHCR](https://github.com/MBombeck/HealthLog/pkgs/container/healthlog); no build step required. Verify the running version: @@ -85,7 +85,7 @@ Verify the running version: curl -s http://localhost:3000/api/version # returns version + buildSha + builtAt ``` -`SESSION_COOKIE_SECURE=false` is the one setting plain-HTTP self-hosts must set: without it the session cookie carries the `Secure` flag, the browser drops it over HTTP, and login silently fails. Leave it unset (or `true`) only when a TLS reverse proxy serves HTTPS — and then set `NEXT_PUBLIC_APP_URL` and `APP_URL` to the public URL first (see the [self-hosting guide](https://docs.healthlog.dev/getting-started/self-host-in-10-minutes/)). +`SESSION_COOKIE_SECURE=false` is the one setting plain-HTTP self-hosts must set: without it the session cookie carries the `Secure` flag, the browser drops it over HTTP, and login silently fails. Leave it unset (or `true`) only when a TLS reverse proxy serves HTTPS, and in that case set `NEXT_PUBLIC_APP_URL` and `APP_URL` to the public URL first (see the [self-hosting guide](https://docs.healthlog.dev/getting-started/self-host-in-10-minutes/)). Persistent state lives in the database volume (`postgres-data`) and in `backups`, which holds the nightly database dumps; the app container is stateless. An install from v1.45 or earlier moves its Postgres 16 database from `pgdata` into the new volume on the first start ([details](docs/ops/postgres-major-upgrade.md)); recovering a lost server is in [disaster-recovery.md](docs/ops/disaster-recovery.md). @@ -95,7 +95,7 @@ NAS and homelab users have ready-made paths: [Unraid Community Applications](doc One container hosts both the web server and the job worker by default; split them via `HEALTHLOG_PROCESS_TYPE=web|worker` for horizontal scale. Encrypted nightly backups to any S3-compatible bucket are opt-in through five `BACKUP_*` environment variables (the admin panel shows their status), new users join by invite link or QR code, and the stack works out of the box behind Caddy, Traefik, Nginx, or [Coolify](https://coolify.io/). -HealthLog ships releases roughly weekly. Pin a tag, back up before upgrades, and skim the [CHANGELOG](CHANGELOG.md) before pulling `latest`. The operator manual — reverse proxy, migrations, encryption-key rotation, backup and restore — lives at [docs.healthlog.dev](https://docs.healthlog.dev). +HealthLog ships often. Pin a tag, back up before upgrades, and read the [release notes](https://github.com/MBombeck/HealthLog/releases) before pulling `latest`. The operator manual (reverse proxy, migrations, encryption-key rotation, backup and restore) lives at [docs.healthlog.dev](https://docs.healthlog.dev). ## Native iOS app @@ -109,18 +109,18 @@ Next.js 16 (App Router, React Server Components), TypeScript strict, PostgreSQL ## Documentation -| | | -| ---------------------- | ------------------------------------------------------------ | -| User and operator docs | [docs.healthlog.dev](https://docs.healthlog.dev) | -| Release history | [CHANGELOG.md](CHANGELOG.md) | -| API contract | [`docs/api/openapi.yaml`](docs/api/openapi.yaml) | -| Security policy | [SECURITY.md](SECURITY.md) | -| Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) | -| Translating | [CONTRIBUTING.md#translations](CONTRIBUTING.md#translations) | +| | | +| ---------------------- | --------------------------------------------------------------- | +| User and operator docs | [docs.healthlog.dev](https://docs.healthlog.dev) | +| Release history | [Release notes](https://github.com/MBombeck/HealthLog/releases) | +| API contract | [`docs/api/openapi.yaml`](docs/api/openapi.yaml) | +| Security policy | [SECURITY.md](SECURITY.md) | +| Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) | +| Translating | [CONTRIBUTING.md#translations](CONTRIBUTING.md#translations) | ## Status -Actively developed — new releases roughly weekly, issue reports and PRs welcome. Behaviour and schema can change between versions; migrations are forward-only. +Actively developed, with frequent releases. Issue reports and pull requests are welcome. Behaviour and schema can change between versions; migrations are forward-only. ## Thanks @@ -143,15 +143,15 @@ including anyone who found the defect without sending the fix. ## License -HealthLog is licensed under the [PolyForm Noncommercial License 1.0.0](LICENSE): free to use, self-host, and modify for noncommercial purposes. Commercial use requires a separate agreement — open an issue or reach out via [healthlog.dev](https://healthlog.dev). Releases up to and including v1.15.18 were published under AGPL-3.0 and remain available under that license. +HealthLog is licensed under the [PolyForm Noncommercial License 1.0.0](LICENSE): free to use, self-host, and modify for noncommercial purposes. Commercial use requires a separate agreement; open an issue or reach out via [healthlog.dev](https://healthlog.dev). Releases up to and including v1.15.18 were published under AGPL-3.0 and remain available under that license. ---

- healthlog.dev · - Live Demo · - Docs · - iOS App Store · - iOS TestFlight · + healthlog.dev | + Live Demo | + Docs | + iOS App Store | + iOS TestFlight | Buy Me a Coffee

diff --git a/docs/CONTRIBUTING-CLIENTS.md b/docs/CONTRIBUTING-CLIENTS.md index e2f627e8c..3f84d6276 100644 --- a/docs/CONTRIBUTING-CLIENTS.md +++ b/docs/CONTRIBUTING-CLIENTS.md @@ -1,6 +1,6 @@ # Building a native client against HealthLog -Working notes for anyone building a client app (Android, iOS, CLI, anything else) against the HealthLog server, with or without AI tooling. Everything in here is a rule we actually follow between the server and the iOS app, written down so a second client does not have to rediscover it. Pair it with `CLAUDE.md` (server-side conventions) and `docs/api/openapi.yaml` (the wire contract). +Working notes for anyone building a client app (Android, iOS, CLI, anything else) against the HealthLog server. Everything in here is a rule we actually follow between the server and the iOS app, written down so a second client does not have to rediscover it. Pair it with [`CONTRIBUTING.md`](../CONTRIBUTING.md) (server-side conventions) and `docs/api/openapi.yaml` (the wire contract). ## The contract, and where truth lives @@ -43,7 +43,7 @@ The corollary: if a resolved value you need is missing from a payload, the fix i ## Ground rules for the repo itself -The voice-and-privacy rules from `CLAUDE.md` apply to any repo under the project, including a client repo: commit messages and public text carry no tooling attribution and no assistant vocabulary, no real names of users or reporters, and no live health figures, ever. Fixtures use invented data. If an AI tool wrote part of a change, the human who commits it has read it and stands behind it; the git history knows only authors. +The privacy rules apply to any repo under the project, including a client repo: commit messages and public text carry no real names of users or reporters and no live health figures, ever. Fixtures use invented data. Whoever commits a change has read it and stands behind it. Secrets never appear in code, fixtures, or examples, not even placeholder-shaped real ones. Anything user-identifying stays out of logs on the client just as it does on the server. diff --git a/docs/ui-guidelines.md b/docs/ui-guidelines.md index 5b00604e2..c4176a17c 100644 --- a/docs/ui-guidelines.md +++ b/docs/ui-guidelines.md @@ -1,104 +1,30 @@ # HealthLog UI Guidelines -> Visual & interaction patterns in the HealthLog PWA. The binding rules -> for card anatomy, spacing, text colour, tokens and primitives live in -> [`contributing/ui-standards.md`](./contributing/ui-standards.md); where -> this document disagrees with them, the standards win. If a pattern -> below doesn't fit your case, propose an addition in the same PR. - -Last updated: 2026-05-08 (v1.4 cycle) +> Interaction patterns for the HealthLog web app. Card anatomy, spacing, +> text colour, colour tokens, typography and the choice of primitive are +> owned by [`contributing/ui-standards.md`](./contributing/ui-standards.md); +> this page covers what those rules leave open. Where the two disagree, the +> standards win. If a pattern below doesn't fit your case, propose an +> addition in the same PR. --- ## 1. Foundations -### 1.1 Theme +Theme, spacing, typography, radius and colour tokens are defined in the +standards (§1 to §5 there). Two things to add: -HealthLog is a Dracula-themed app with a light counterpart (Alucard). Both -themes share the same six-color signal palette (purple, pink, green, cyan, -orange, red, yellow, lavender), exposed as CSS variables and as Tailwind -`dracula-*` color tokens. Use the semantic tokens (`--primary`, -`--destructive`, `--card`, …) for chrome and the `--dracula-*` tokens for -data viz only. - -| Surface | Token | -| ---------------------------- | ------------------ | -| App background | `--background` | -| Card / popover surface | `--card` | -| Primary action | `--primary` | -| Destructive action | `--destructive` | -| Secondary / muted background | `--secondary` | -| Border | `--border` | -| Focus ring | `--ring` | -| Chart series 1–5 | `--chart-1`…`-5` | -| Brand signal: purple | `--dracula-purple` | -| Brand signal: green (good) | `--dracula-green` | -| Brand signal: orange (warn) | `--dracula-orange` | -| Brand signal: red (alert) | `--dracula-red` | -| Brand signal: cyan (info) | `--dracula-cyan` | - -Dark mode is the default. The theme switcher in the sidebar three-dot menu -toggles `.dark` / `.light` on `:root`. Always test both modes before -shipping a component. - -### 1.2 Spacing - -Tailwind's default 4-pixel scale. Reach for these stops first: - -| Use | Token | Pixels | -| ------------------------------------ | ------------- | ------ | -| Inline gap between icon and label | `gap-1` | 4 | -| Tight stack inside a card | `gap-2` | 8 | -| Label-to-field stack (form fields) | `space-y-2` | 8 | -| Section block (heading + body) | `space-y-4` | 16 | -| Page section separator | `space-y-6` | 24 | -| Dashboard tile-row gap, hero spacing | `gap-4` | 16 | -| Page-edge padding (mobile / desktop) | `px-4 / px-6` | | - -Avoid arbitrary `space-y-5`, `gap-3.5`, etc. unless aligning to a fixed -external constraint. - -### 1.3 Typography - -The app uses the Tailwind defaults inherited via shadcn/ui. Reach for -these classes first: - -| Use | Class | -| ---------------------------------- | --------------------------------------- | -| Page title (h1) | `text-2xl font-semibold tracking-tight` | -| Section heading / section card | `text-lg font-semibold` | -| Tile / inner card title | `text-base font-semibold` | -| Body text | `text-sm` | -| Muted helper / metadata | `text-sm text-muted-foreground` | -| Microcopy / footer | `text-xs text-muted-foreground` | -| Tabular data (latency, large nums) | `font-mono tabular-nums` | - -Card titles come in exactly two sizes: `text-lg font-semibold` for a -section-level card (one per page region) and `text-base font-semibold` -for tiles and inner cards. Don't introduce a third level — a smaller -heading is a sign the content wants a list row, not a card. - -Never use `font-bold` for emphasis — bump to `font-semibold` instead and -let the design system breathe. - -### 1.4 Radius and elevation - -`--radius: 0.625rem` (10px) is the default. The Tailwind tokens -`rounded-md` (`--radius-md` ≈ 8px) and `rounded-lg` (`--radius` ≈ 10px) -cover 95% of cases. Use `rounded-2xl` only for hero cards or large -modals. - -Elevation is achieved via `shadow-xs`, `shadow-sm`, or -`shadow-[0_1px_0_rgba(255,255,255,0.04)]` for very subtle separators on -dark backgrounds. Avoid Tailwind's heavy shadows (`shadow-lg`+) in chrome. +- Dark is the default theme, and the light theme remaps the same semantic + tokens. Check every component in both before shipping it. +- Elevation stays light: `shadow-xs` or `shadow-sm` in chrome, never the + heavy `shadow-lg` and up. --- ## 2. Components -This section is **prescriptive, not exhaustive**. Each entry pairs a -Radix/shadcn primitive with the cases it's the right answer for and the -cases where another primitive should win. +This section is **prescriptive, not exhaustive**. Each entry names the +primitive for a case and the case where another one wins. ### 2.1 Buttons @@ -114,43 +40,35 @@ Every button comes from `@/components/ui/button`. Pick by purpose: | Inline link to external help docs | `link` | `sm` | | Toolbar / table row action when space is tight | `outline` | `xs` | -**Position rules.** +Where buttons sit in a card (one action row, last in the card, primary on +the right) is standards §12. Two additions: -- Forms (full-width or modal): primary on the **right**, cancel/back on - the **left**. Match shadcn dialog defaults. -- Settings sections: a single sticky `Save` button at the **right end** - of the section header _or_ per-row inline `Save`. Never both in the - same section. -- Test buttons (Telegram test, AI ping, etc.): immediately to the right - of the related Save / Apply control. Never below — keeps the eye - motion horizontal. -- Destructive actions live in their own confirm dialog with a - type-to-confirm input when the action is irreversible (delete account, - revoke API token, drop user data). +- A Test button (Telegram test, AI ping and the like) sits in the same row + as the Save or Apply control it belongs to. +- An irreversible action (delete account, revoke an API token, wipe data) + asks in its own confirm dialog, with a type-to-confirm field where the loss + is large. -**Disabled state.** A disabled button must always come with a tooltip or -helper text explaining why. Silent disabled buttons frustrate users. +**Disabled state.** A disabled button always comes with helper text that +says why. A tooltip is not enough: it never opens on a phone. ### 2.2 Inputs and forms Forms keep a controlled draft in component state and validate it with the Zod schemas from `src/lib/validations/`; the server's schema is the authority. -Wrap inputs in `