尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗A digital mental-wellness platform built for Egyptian Arabic speakers first.
The planned MVP includes a warm, culturally relevant Egyptian Arabic AI wellness companion, daily mood check-ins, journaling, guided evidence-based exercises, basic progress tracking, and approved psychoeducation content. A path to qualified human therapists is planned for later.
This is a wellness-support product, not a therapist. It must never diagnose, prescribe or recommend medication, claim clinical certainty, present itself as a licensed professional, replace emergency or professional care, or encourage dependence on the AI. For high-risk users, normal AI generation stops and a controlled escalation flow takes over. That flow's content is pending clinical and legal review (docs/OPEN_DECISIONS.md). The service is for adults (18+) only.
Current status: Milestone 4 (mood check-in), built on M3 (consent and profile) and M2 (Supabase auth). There is no AI companion yet. See docs/IMPLEMENTATION_STATUS.md.
The system is a modular monolith: no microservices and no multi-agent AI.
| Layer | Stack |
|---|---|
| Frontend | Next.js 16 (App Router), TypeScript (strict), Tailwind CSS 4, @supabase/ssr |
| Backend | Python 3.12, FastAPI, Pydantic, PyJWT, SQLAlchemy 2 + Alembic, psycopg 3 |
| Auth | Supabase Auth (email/password, required email confirmation) |
| Database | PostgreSQL 17: Supabase in the cloud, a local container for dev, test and CI. App tables live in schema app. |
| Planned | pgvector (approved knowledge only), Redis only when there is a real need |
| Ops | Docker, Docker Compose, GitHub Actions |
Backend modules live in backend/app/. core, common, auth, users, consent,
audit and mood contain code; the rest are boundaries only:
core auth users consent conversations
ai/{provider,language,prompts,evaluation}
safety knowledge memory mood wellness journal analytics audit common
Every future chat message must pass through this pipeline, in this order. There is never a
direct request → LLM path. M2 implemented authentication and authorisation (identity
from the verified token). M3 adds the consent check (ConsentedUser, which answers 403
until the current consent version is accepted).
request → authentication → authorization → consent/policy checks
→ safety assessment → safety decision → context retrieval → AI generation
→ output safety validation → response → audit/telemetry
Design documents: ARCHITECTURE · API_CONTRACT · DATA_MODEL · OPEN_DECISIONS
/sign-up ──(Server Action: supabase.auth.signUp)──▶ /auth/check-email
email link ──▶ Supabase verify ──▶ /auth/confirm?code=… ──(exchangeCodeForSession)──▶ /dashboard
/sign-in ──(Server Action: signInWithPassword)──▶ session cookies ──▶ /dashboard
/dashboard ──(server-side fetch, Bearer access token)──▶ FastAPI GET /me ──▶ verified identity
Sign out ──(Server Action: signOut)──▶ cookies cleared ──▶ /sign-in
proxy.ts (Next 16's replacement for middleware) refreshes the session on
every request with supabase.auth.getClaims(). It sends signed-out visitors from
/dashboard to /sign-in, and signed-in users away from the auth pages. The dashboard
checks again on the server. Passwords only ever go to Supabase; this project stores none.app/auth verifies Supabase access tokens: signature via the project's JWKS
(or the legacy HS256 secret if configured), expiry, issuer, audience, subject and role.
CurrentPrincipal is the reusable FastAPI dependency. GET /me returns only
token-derived identity. Every failure is a uniform 401; if Supabase isn't configured,
it fails closed with 503./sign-in ─▶ /forgot-password ──(resetPasswordForEmail)──▶ "If an account exists…" (same for every email)
email link ──▶ Supabase verify ──▶ /auth/confirm?code=… ──(recovery verified)──▶ /reset-password
/reset-password ──(updateUser({ password }))──▶ signOut({ scope: "global" }) ──▶ /sign-in?status=password-updated
getClaims()), giving the user id and Supabase session id.getUser()). getClaims() alone can't
see a revoked session, because an access token stays valid until it expires.aw_password_reset: httpOnly, SameSite=Lax, path
/reset-password, 10 minutes). It is HMAC-SHA256-signed with the server-only
RECOVERY_MARKER_SECRET and binds the user id, the session id, an expiry and a random
token id. Signatures are compared in constant time./auth/confirm issues the marker, and only after Supabase has verified a
recovery. That means either the PKCE verifier created by resetPasswordForEmail, or a
type=recovery token hash. The marker is bound to the session in the token Supabase just
returned, verified with getClaims(token). next=/reset-password alone grants nothing.signOut({ scope: "global" }). Supabase then
revokes the refresh tokens and sessions, so a replay with that session fails the
getUser() check. This relies on the Auth server rejecting revoked sessions
(session_not_found). It was tested against a mock and still needs a manual check
against the real project. If the sign-out call fails, the session stays valid until the
marker expires.updateUser directly with them, without using this
page. Supabase's own controls are what limit that: session lifetime, and the project
setting "Secure password change", which requires a recent sign-in. Enabling that
setting is a project decision, and it is not changed here.Development setup for recovery (and sign-up) links:
SITE_URL (default http://localhost:3000). The
PKCE verifier cookie belongs to the host where the email was requested; a link opened on
127.0.0.1 when SITE_URL is localhost (or the reverse) can't complete. In
development, /forgot-password shows a note when the hosts differ.http://localhost to HTTPS ("localhost sent an invalid
response"), it has an HSTS entry for localhost. Either remove that entry yourself
(Edge/Chrome: edge://net-internals/#hsts → "Delete domain security policies" →
localhost), or use http://127.0.0.1:3000 with SITE_URL=http://127.0.0.1:3000, which
also needs that URL in the Supabase redirect allow-list.<SITE_URL>/auth/confirm?next=/reset-password (for example <SITE_URL>/**). If they
don't, Supabase sends the user to the Site URL instead and the reset can't finish.RECOVERY_MARKER_SECRET in frontend/.env.local (see frontend/.env.example). It is
read by the Next.js server only; never give it a NEXT_PUBLIC_ prefix or commit it.sign-in / email confirmation ─▶ /dashboard ─(GET /consents/me)─▶ status?
missing ─▶ /onboarding (optional display name, can be skipped)
─▶ /onboarding/consent (read, tick the box, "Agree and continue")
declined / outdated ─▶ /onboarding/consent
granted ─▶ dashboard
GET/PATCH /profile) is created on the first authenticated request and is
keyed only on the verified Supabase sub. There is no way to address another user's
profile.outdated until they
accept again.audit_events table, in the same transaction.consent accepted ─▶ /check-in (1–5 scale, "Skip for now") ─(POST /mood/checkins)─▶ /dashboard
/dashboard ─(GET /mood/history?limit=7)─▶ recent check-ins (date + score)
403 consent_required).[] until a vocabulary
is approved (OPEN_DECISIONS D-30). Notes are temporarily rejected server-side (any
note string → 422; omitted or null is fine) and the UI offers no note field, until
D-32 is decided.Deployment and configuration for every service: docs/DEPLOYMENT_RUNBOOK.md.
consent accepted ─▶ /chat (Adult · Prototype) ─(POST /chat/messages)─▶ signed reply + citations
CHAT_ENABLED=true and
CHAT_SAFETY_POLICY=synthetic-test-v0, then load the synthetic fixtures:
python -m app.knowledge.ingest app/knowledge/fixtures (from backend/).backend/ FastAPI app (app/), Alembic (alembic.ini, migrations/), tests/, Dockerfile
frontend/ Next.js app (app/, components/, lib/, proxy.ts), Vitest tests (*.test.ts[x])
docs/ Architecture, contracts, open decisions, implementation status
.github/ CI workflow
docker-compose.yml Local dev: Postgres 17, one-shot migrations, API
.env.example Server-only settings (backend / Compose)
Server-side and browser-side settings are kept in separate files:
| File | Read by | Contents |
|---|---|---|
.env (from .env.example) | Docker Compose | Server-only settings and secrets |
backend/.env (from .env.example) | Backend run directly | Same as above |
frontend/.env.local (from frontend/.env.example) | Next.js | NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY (public), plus SITE_URL and API_BASE_URL (server-side, not secret) |
To run authentication locally you need, from the Supabase dashboard (Project Settings → API):
sb_publishable_…).SUPABASE_URL (the same project URL). Add SUPABASE_JWT_SECRET only if the
project still uses the legacy shared JWT secret.Rules:
.env file. They are git-ignored, and CI fails if one is tracked.frontend/ or carry a NEXT_PUBLIC_
prefix. CI checks for both.SecretStr, so they are masked in repr, logs and
serialisation. Blank values count as unset.Authorization headers, passwords or emails. Auth rejections
log only a reason code, such as expired.pyproject.toml accepts 3.12–3.13)cd backend
python -m venv .venv
source .venv/bin/activate # Windows (Git Bash): source .venv/Scripts/activate
pip install -e ".[dev]"
cp ../.env.example .env # then set SUPABASE_URL
# Database: start the local Postgres, then apply migrations
docker compose up -d db # from the repository root
# in backend/.env: DATABASE_URL=postgresql://wellness:[email protected]:5432/wellness
alembic upgrade head
uvicorn app.main:app --reload # add --port 8001 if 8000 is taken
python -m pytest -q # starts a throwaway Postgres 17 via Docker (testcontainers),
# or uses TEST_DATABASE_URL if set; no real credentials needed
ruff check .
ruff format --check .
mypy app --show-error-codes # strict (CI also checks tests/)
GET /health returns exactly {"status": "ok"}.GET /me, /profile and /consents/me need Authorization: Bearer <Supabase access token>.
GET /consents/current is public.DATABASE_URL, profile and consent endpoints return 503 (fail closed).ENVIRONMENT=production.cd frontend
npm install
cp .env.example .env.local # then set the two NEXT_PUBLIC_SUPABASE_* values
npm run dev # http://localhost:3000
npm run lint
npm run typecheck
npm test # Vitest; Supabase is mocked
npm run build
If the API runs on a port other than 8000, set API_BASE_URL in frontend/.env.local.
app/globals.css defines semantic colours (lavender primary, sage
secondary, warm neutrals) for light and dark themes; every text/background pair was
checked for WCAG AA (≥ 4.5:1; control borders, focus and chart marks ≥ 3:1). Use the
token utilities (bg-surface, text-ink, border-line-strong, …), not raw colours.next/font/google — downloaded at build time and
served from this site (no runtime requests to Google).components/ui/primitives.tsx (buttons, cards, notices, badges,
24px checkbox), components/shell/ (app shell with skip link, desktop navigation
from 1024px and a bottom tab bar below it; public shell).aw_locale cookie (the
language switch), else the browser's Accept-Language (Arabic or English), else
Arabic. <html lang dir> is set per request. Copy lives in lib/i18n/messages.ts;
fixed server-action messages are translated at display time (lib/i18n/translate.ts,
with a test that every one has Arabic). The Arabic is provisional (D-19). Legal
consent text is shown in English until a reviewed translation exists.aw_theme cookie (device only).Africa/Cairo), in the visitor's language.npm run e2e runs the end-to-end, recovery-security and accessibility suite (axe,
WCAG 2.2 A/AA rules, 320–1440px, Arabic/English, light/dark) against a local stack with
a test-only mock auth server. See frontend/e2e/README.md. Not part of CI yet.
docker compose config # validate
docker compose up --build # API on http://127.0.0.1:8000
API_PORT=8001 docker compose up # if port 8000 is already taken
Compose runs Postgres, applies migrations once (migrate service), then starts the API.
It reads SUPABASE_URL from the root .env. The local database password is a
development-only value (LOCAL_DB_PASSWORD), and Postgres binds to 127.0.0.1.
.github/workflows/ci.yml runs on every pull request and on every push to main:
npm ci, lint, typecheck, Vitest, production build, on Node 22.env files; no server-only secret names in frontend/; no
secret-looking NEXT_PUBLIC_ variablesdocker compose config, image build, Postgres, migrations (must exit 0),
API start, /health returns exactly {"status":"ok"}, /consents/current is served,
and /me returns 401 anonymously and 503 without Supabase configuration/forgot-password?status=link-invalid.
Redirects stay on this site (safeNextPath). The "sent" message is identical whether or
not an account exists. Passwords, markers and tokens are never echoed or logged.