docs: restructure AGENTS.md into hierarchical knowledge base
This commit is contained in:
parent
157cbc425e
commit
470d49b061
3 changed files with 339 additions and 47 deletions
133
AGENTS.md
133
AGENTS.md
|
|
@ -1,83 +1,122 @@
|
|||
# AGENTS.md
|
||||
|
||||
This file provides guidance to AI coding agents when working with code in this repository.
|
||||
Personal health & skincare data hub with LLM agent integration. Monorepo: Python FastAPI backend + SvelteKit frontend.
|
||||
|
||||
## Repository Structure
|
||||
## Structure
|
||||
|
||||
This is a monorepo with **backend** and **frontend** directories.
|
||||
```
|
||||
innercontext/
|
||||
├── backend/ # Python 3.12, FastAPI, SQLModel, PostgreSQL, Gemini
|
||||
│ ├── innercontext/ # Main package
|
||||
│ │ ├── api/ # 7 FastAPI routers + LLM endpoints
|
||||
│ │ ├── models/ # SQLModel tables + Pydantic types (12 files)
|
||||
│ │ ├── validators/# LLM response validators (6 validators)
|
||||
│ │ ├── services/ # FX rates (NBP API), pricing jobs
|
||||
│ │ └── workers/ # Background pricing worker
|
||||
│ ├── tests/ # pytest (171 tests, SQLite in-memory)
|
||||
│ ├── alembic/ # DB migrations (17 versions)
|
||||
│ ├── main.py # App entry, lifespan, CORS, router registration
|
||||
│ └── db.py # Engine, get_session(), create_db_and_tables()
|
||||
├── frontend/ # SvelteKit 2, Svelte 5, Tailwind v4, bits-ui
|
||||
│ └── src/
|
||||
│ ├── routes/ # File-based routing (15+ pages)
|
||||
│ ├── lib/ # API client, types, components, i18n
|
||||
│ └── app.css # Theme + editorial design system
|
||||
├── docs/ # Deployment guides + frontend-design-cookbook.md
|
||||
├── nginx/ # Reverse proxy (strips /api prefix → backend:8000)
|
||||
├── systemd/ # 3 units: backend, frontend-node, pricing-worker
|
||||
├── scripts/ # Health checks, backups, env validation
|
||||
└── deploy.sh # Push-based deploy (Capistrano-style symlinked releases)
|
||||
```
|
||||
|
||||
## Agent Skills
|
||||
|
||||
Use repository skills when applicable:
|
||||
- `svelte-code-writer`: REQUIRED for `.svelte`, `.svelte.ts`, `.svelte.js` files.
|
||||
- `frontend-design`: Frontend UI, page, and component design work.
|
||||
- `conventional-commit`: Commit messages following Conventional Commits.
|
||||
- `gemini-api-dev`: Gemini API integrations, multimodal, function calling, structured output.
|
||||
|
||||
- `svelte-code-writer`: required for creating, editing, or analyzing `.svelte`, `.svelte.ts`, and `.svelte.js` files.
|
||||
- `frontend-design`: use for frontend UI, page, and component design work.
|
||||
- `conventional-commit`: use when drafting commit messages that follow Conventional Commits.
|
||||
- `gemini-api-dev`: use when implementing Gemini API integrations, multimodal flows, function calling, or model selection details.
|
||||
When editing frontend code, follow `docs/frontend-design-cookbook.md` and update it when introducing or modifying reusable UI patterns, visual rules, or shared styling conventions.
|
||||
|
||||
When editing frontend code, always follow `docs/frontend-design-cookbook.md` and update it in the same change whenever you introduce or modify reusable UI patterns, visual rules, or shared styling conventions.
|
||||
## Where to Look
|
||||
|
||||
## Commit Guidelines
|
||||
|
||||
This repository uses Conventional Commits (e.g., `feat(api): ...`, `fix(frontend): ...`, `test(models): ...`). Always format commit messages accordingly and ensure you include the correct scope to indicate which part of the monorepo is affected.
|
||||
| Task | Location | Notes |
|
||||
|------|----------|-------|
|
||||
| Add API endpoint | `backend/innercontext/api/` | Follow router pattern, use `get_or_404()` |
|
||||
| Add/modify model | `backend/innercontext/models/` | See `backend/AGENTS.md` for JSON col + timestamp conventions |
|
||||
| Add DB migration | `backend/alembic/` | `cd backend && uv run alembic revision --autogenerate -m "desc"` |
|
||||
| Add frontend page | `frontend/src/routes/` | `+page.svelte` + `+page.server.ts` (load + actions) |
|
||||
| Add component | `frontend/src/lib/components/` | Use bits-ui primitives, check design cookbook |
|
||||
| Add LLM feature | `backend/innercontext/api/` + `llm.py` | `call_gemini()` or `call_gemini_with_function_tools()` |
|
||||
| Add LLM validator | `backend/innercontext/validators/` | Extend `BaseValidator`, return `ValidationResult` |
|
||||
| Add i18n strings | `frontend/messages/{en,pl}.json` | Auto-generates to `src/lib/paraglide/` |
|
||||
| Modify design system | `frontend/src/app.css` + `docs/frontend-design-cookbook.md` | Update both in same change |
|
||||
| Modify types | `frontend/src/lib/types.ts` + `backend/innercontext/models/` | Manual sync, no codegen |
|
||||
|
||||
## Commands
|
||||
|
||||
Run the backend from the `backend/` directory:
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
cd backend && uv run python main.py
|
||||
cd backend && uv run python main.py # Start API server
|
||||
cd backend && uv run ruff check . # Lint
|
||||
cd backend && uv run black . # Format
|
||||
cd backend && uv run isort . # Sort imports
|
||||
cd backend && uv run pytest # Run tests
|
||||
|
||||
# Linting / formatting
|
||||
cd backend && uv run ruff check .
|
||||
cd backend && uv run black .
|
||||
cd backend && uv run isort .
|
||||
```
|
||||
|
||||
Run the frontend from the `frontend/` directory:
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
cd frontend && pnpm dev
|
||||
|
||||
# Type checking / linting / formatting
|
||||
cd frontend && pnpm check
|
||||
cd frontend && pnpm lint
|
||||
cd frontend && pnpm format
|
||||
cd frontend && pnpm dev # Dev server (API proxied to :8000)
|
||||
cd frontend && pnpm check # Type check + Svelte validation
|
||||
cd frontend && pnpm lint # ESLint
|
||||
cd frontend && pnpm format # Prettier
|
||||
cd frontend && pnpm build # Production build → build/
|
||||
```
|
||||
|
||||
No test suite exists yet (backend has some test files but they're not integrated into CI).
|
||||
## Commit Guidelines
|
||||
|
||||
Conventional Commits: `feat(api): ...`, `fix(frontend): ...`, `test(models): ...`. Include scope indicating which part of the monorepo is affected.
|
||||
|
||||
## Architecture
|
||||
|
||||
**innercontext** collects personal health and skincare data and exposes it to an LLM agent.
|
||||
**Backend:** Python 3.12, FastAPI, SQLModel 0.0.37 + SQLAlchemy, Pydantic v2, PostgreSQL (psycopg3), Gemini API (google-genai).
|
||||
|
||||
**Backend Stack:** Python 3.12, SQLModel (0.0.37) + SQLAlchemy, Pydantic v2, FastAPI, PostgreSQL (psycopg3).
|
||||
**Frontend:** SvelteKit 2, Svelte 5 (Runes), TypeScript, Tailwind CSS v4, bits-ui (shadcn-svelte), Paraglide (i18n), svelte-dnd-action, adapter-node.
|
||||
|
||||
**Frontend Stack:** SvelteKit 5, Tailwind CSS v4, bits-ui, inlang/paraglide (i18n), svelte-dnd-action.
|
||||
### Cross-Cutting Patterns
|
||||
|
||||
### Models (`backend/innercontext/models/`)
|
||||
- **Type sharing**: Manual sync between `frontend/src/lib/types.ts` and `backend/innercontext/models/`. No code generation.
|
||||
- **API proxy**: Frontend server-side uses `PUBLIC_API_BASE` (http://localhost:8000). Browser uses `/api` (nginx strips prefix → backend).
|
||||
- **Auth**: None. Single-user personal system.
|
||||
- **Error flow**: Backend `HTTPException(detail=...)` → Frontend catches `.detail` field → `FlashMessages` or `StructuredErrorDisplay`.
|
||||
- **LLM validation errors**: Non-blocking (HTTP 200). Returned in `validation_warnings` field. Frontend parses semicolon-separated strings into list.
|
||||
|
||||
### Models
|
||||
|
||||
| File | Tables |
|
||||
|------|--------|
|
||||
| `product.py` | `products`, `product_inventory` |
|
||||
| `health.py` | `medication_entries`, `medication_usages`, `lab_results` |
|
||||
| `routine.py` | `routines`, `routine_steps` |
|
||||
| `routine.py` | `routines`, `routine_steps`, `grooming_schedules` |
|
||||
| `skincare.py` | `skin_condition_snapshots` |
|
||||
| `profile.py` | `user_profiles` |
|
||||
| `pricing.py` | `pricing_recalc_jobs` |
|
||||
| `ai_log.py` | `ai_call_logs` |
|
||||
|
||||
**`Product`** is the core model. JSON columns store `inci` (list), `actives` (list of `ActiveIngredient`), `recommended_for`, `targets`, `incompatible_with`, `synergizes_with`, `context_rules`, and `product_effect_profile`. The `to_llm_context()` method returns a token-optimised dict for LLM usage.
|
||||
**Product** is the core model with JSON columns for `inci`, `actives`, `recommended_for`, `targets`, `product_effect_profile`, and `context_rules`. `to_llm_context()` returns a token-optimised dict for LLM usage.
|
||||
|
||||
**`ProductInventory`** tracks physical packages (opened status, expiry, remaining weight). One product → many inventory entries.
|
||||
### Deployment
|
||||
|
||||
**`Routine` / `RoutineStep`** record daily AM/PM skincare sessions. A step references either a `Product` or a free-text `action` (e.g. shaving).
|
||||
- **CI**: Forgejo (`.forgejo/workflows/`), manual trigger only.
|
||||
- **Deploy**: `deploy.sh` pushes via SSH to LXC host. Capistrano-style timestamped releases with `current` symlink. Auto-rollback on health check failure.
|
||||
- **Services**: 3 systemd units — backend (uvicorn :8000), frontend-node (:3000), pricing-worker.
|
||||
- **Env**: Backend `.env` has `DATABASE_URL` + `GEMINI_API_KEY`. Frontend `PUBLIC_API_BASE` set at build time.
|
||||
|
||||
**`SkinConditionSnapshot`** is a weekly LLM-filled record (skin state, metrics 1–5, active concerns).
|
||||
## Anti-Patterns (this project)
|
||||
|
||||
### Key Conventions
|
||||
|
||||
- All `table=True` models use `Column(DateTime(timezone=True), onupdate=utc_now)` for `updated_at` via raw SQLAlchemy column — do not use plain `Field(default_factory=...)` for auto-update.
|
||||
- List/complex fields stored as JSON use `sa_column=Column(JSON, nullable=...)` pattern (DB-agnostic; not JSONB).
|
||||
- `model_validator(mode="after")` **does not fire** on `table=True` SQLModel instances (SQLModel 0.0.37 + Pydantic v2 bug). Validators in `Product` are present for documentation but are unreliable at construction time.
|
||||
- `backend/skincare.yaml` is a legacy notes file — ignore it, it is not part of the data model and will not be imported.
|
||||
- `_ev()` helper in `product.py` normalises enum values when fields may be raw dicts (as returned from DB) or Python enum instances.
|
||||
- `model_validator(mode="after")` does NOT fire on `table=True` SQLModel instances (SQLModel 0.0.37 + Pydantic v2 bug). Validators in Product are documentation only.
|
||||
- Never use plain `Field(default_factory=...)` for `updated_at` — must use `sa_column=Column(DateTime(timezone=True), onupdate=utc_now)`.
|
||||
- JSON columns use `sa_column=Column(JSON, nullable=...)` — NOT JSONB. DB-agnostic.
|
||||
- Gemini API rejects int-enum in `response_schema` — `AIActiveIngredient` overrides with `int` + `# type: ignore[assignment]`.
|
||||
- `backend/skincare.yaml` is legacy notes — ignore, not part of data model.
|
||||
- ESLint rule `svelte/no-navigation-without-resolve` has `ignoreGoto: true` workaround (upstream bug sveltejs/eslint-plugin-svelte#1327).
|
||||
- `_ev()` helper in `product.py` normalises enum values when fields may be raw dicts (from DB) or Python enum instances.
|
||||
- No frontend tests exist. Backend tests use SQLite in-memory (not PostgreSQL).
|
||||
|
|
|
|||
121
backend/AGENTS.md
Normal file
121
backend/AGENTS.md
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Backend
|
||||
|
||||
Python 3.12 FastAPI backend. Entry: `main.py` → `db.py` → routers in `innercontext/api/`.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
backend/
|
||||
├── main.py # FastAPI app, lifespan, CORS, router registration
|
||||
├── db.py # Engine, get_session() dependency, create_db_and_tables()
|
||||
├── innercontext/
|
||||
│ ├── api/ # 7 FastAPI routers
|
||||
│ │ ├── products.py # CRUD + LLM parse/suggest + pricing
|
||||
│ │ ├── routines.py # CRUD + LLM suggest/batch + grooming schedule
|
||||
│ │ ├── health.py # Medications + lab results CRUD
|
||||
│ │ ├── skincare.py # Snapshots + photo analysis (Gemini vision)
|
||||
│ │ ├── inventory.py # Product inventory CRUD
|
||||
│ │ ├── profile.py # User profile upsert
|
||||
│ │ ├── ai_logs.py # LLM call log viewer
|
||||
│ │ ├── llm_context.py # Context builders (Tier 1 summary / Tier 2 detailed)
|
||||
│ │ ├── product_llm_tools.py # Gemini function tool declarations + handlers
|
||||
│ │ └── utils.py # get_or_404()
|
||||
│ ├── models/ # SQLModel tables + Pydantic types
|
||||
│ │ ├── product.py # Product, ProductInventory, _ev(), to_llm_context()
|
||||
│ │ ├── health.py # MedicationEntry, MedicationUsage, LabResult
|
||||
│ │ ├── routine.py # Routine, RoutineStep, GroomingSchedule
|
||||
│ │ ├── skincare.py # SkinConditionSnapshot (JSON: concerns, risks, priorities)
|
||||
│ │ ├── profile.py # UserProfile
|
||||
│ │ ├── pricing.py # PricingRecalcJob (async tier calculation)
|
||||
│ │ ├── ai_log.py # AICallLog (token metrics, reasoning chain, tool trace)
|
||||
│ │ ├── enums.py # 20+ enums (ProductCategory, SkinType, SkinConcern, etc.)
|
||||
│ │ ├── base.py # utc_now() helper
|
||||
│ │ ├── domain.py # Domain enum (HEALTH, SKINCARE)
|
||||
│ │ └── api_metadata.py # ResponseMetadata, TokenMetrics (Phase 3 observability)
|
||||
│ ├── validators/ # LLM response validators (non-blocking)
|
||||
│ │ ├── base.py # ValidationResult, BaseValidator abstract
|
||||
│ │ ├── routine_validator.py # Retinoid+acid, intervals, SPF, barrier safety
|
||||
│ │ ├── batch_validator.py # Multi-day frequency + same-day conflicts
|
||||
│ │ ├── product_parse_validator.py # Enum checks, effect_profile, pH, actives
|
||||
│ │ ├── shopping_validator.py # Category, priority, text quality
|
||||
│ │ └── photo_validator.py # Skin metrics 1-5, enum checks
|
||||
│ ├── services/
|
||||
│ │ ├── fx.py # NBP API currency conversion (24h cache, thread-safe)
|
||||
│ │ └── pricing_jobs.py # Job queue (enqueue, claim with FOR UPDATE SKIP LOCKED)
|
||||
│ ├── workers/
|
||||
│ │ └── pricing.py # Background pricing worker
|
||||
│ ├── llm.py # Gemini client, call_gemini(), call_gemini_with_function_tools()
|
||||
│ └── llm_safety.py # Prompt injection prevention (sanitize + isolate)
|
||||
├── tests/ # 171 pytest tests (SQLite in-memory, isolated per test)
|
||||
├── alembic/ # 17 migration versions
|
||||
└── pyproject.toml # uv, pytest (--cov), ruff, black, isort (black profile)
|
||||
```
|
||||
|
||||
## Model Conventions
|
||||
|
||||
- **JSON columns**: `sa_column=Column(JSON, nullable=...)` on `table=True` models only. DB-agnostic (not JSONB).
|
||||
- **`updated_at`**: MUST use `sa_column=Column(DateTime(timezone=True), onupdate=utc_now)`. Never plain `Field(default_factory=...)`.
|
||||
- **`_ev()` helper** (`product.py`): Normalises enum values — returns `.value` if enum, `str()` otherwise. Required when fields may be raw dicts (from DB) or Python enum instances.
|
||||
- **`model_validator(mode="after")`**: Does NOT fire on `table=True` instances (SQLModel 0.0.37 + Pydantic v2 bug). Product validators are documentation only.
|
||||
- **`to_llm_context()`**: Returns token-optimised dict. Filters `effect_profile` to nonzero values (≥2). Handles both dict and object forms.
|
||||
- **`short_id`**: 8-char UUID prefix on Product. Used in LLM context for token efficiency → expanded to full UUID before DB queries.
|
||||
|
||||
## LLM Integration
|
||||
|
||||
Two config patterns in `llm.py`:
|
||||
- `get_extraction_config()`: temp=0.0, MINIMAL thinking. Deterministic data parsing.
|
||||
- `get_creative_config()`: temp=0.4, MEDIUM thinking. Suggestions with reasoning chain capture.
|
||||
|
||||
Three context tiers in `llm_context.py`:
|
||||
- **Tier 1** (~15-20 tokens/product): One-line summary with status, key effects, safety flags.
|
||||
- **Tier 2** (~40-50 tokens/product): Top 5 actives + effect_profile + context_rules. Used in function tool responses.
|
||||
- **Tier 3**: Full `to_llm_context()`. Token-heavy, rarely used.
|
||||
|
||||
Function calling (`product_llm_tools.py`):
|
||||
- `call_gemini_with_function_tools()`: Iterative tool loop, max 2 roundtrips.
|
||||
- `PRODUCT_DETAILS_FUNCTION_DECLARATION`: Gemini function schema for product lookups.
|
||||
- INCI lists excluded from LLM context by default (~12-15KB per product saved).
|
||||
|
||||
All calls logged to `AICallLog` with: token metrics, reasoning_chain, tool_trace, validation results.
|
||||
|
||||
Safety (`llm_safety.py`):
|
||||
- `sanitize_user_input()`: Removes prompt injection patterns, limits length.
|
||||
- `isolate_user_input()`: Wraps with boundary markers, treats as data not instructions.
|
||||
|
||||
## Validators
|
||||
|
||||
All extend `BaseValidator`, return `ValidationResult` (errors, warnings, auto_fixes). Validation is **non-blocking** — errors returned in response body as `validation_warnings`, not as HTTP 4xx.
|
||||
|
||||
Key safety checks in `routine_validator.py`:
|
||||
- No retinoid + acid in same routine (detects via `effect_profile.retinoid_strength > 0` and exfoliant functions in actives)
|
||||
- Respect `min_interval_hours` and `max_frequency_per_week`
|
||||
- Check `context_rules`: `safe_after_shaving`, `safe_with_compromised_barrier`
|
||||
- AM routines need SPF when `leaving_home=True`
|
||||
- No high `irritation_risk` or `barrier_disruption_risk` with compromised barrier
|
||||
|
||||
## API Patterns
|
||||
|
||||
- All routers use `Depends(get_session)` for DB access.
|
||||
- `get_or_404(session, Model, id)` for 404 responses.
|
||||
- LLM endpoints: build context → call Gemini → validate → log to AICallLog → return data + `ResponseMetadata`.
|
||||
- Product pricing: enqueues `PricingRecalcJob` on create/update. Worker claims with `FOR UPDATE SKIP LOCKED`.
|
||||
- Gemini API rejects int-enum in `response_schema` — `AIActiveIngredient` overrides fields with plain `int` + `# type: ignore[assignment]`.
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Default | Required |
|
||||
|----------|---------|----------|
|
||||
| `DATABASE_URL` | `postgresql+psycopg://localhost/innercontext` | Yes |
|
||||
| `GEMINI_API_KEY` | — | For LLM features |
|
||||
| `GEMINI_MODEL` | `gemini-3-flash-preview` | No |
|
||||
|
||||
`main.py` calls `load_dotenv()` before importing `db.py` to ensure `DATABASE_URL` is read from `.env`.
|
||||
|
||||
## Testing
|
||||
|
||||
- `cd backend && uv run pytest`
|
||||
- SQLite in-memory per test — fully isolated, no cleanup needed.
|
||||
- `conftest.py` fixtures: `session`, `client` (TestClient with patched engine), `product_data`, `created_product`, `medication_data`, `created_medication`, `created_routine`.
|
||||
- LLM calls mocked with `unittest.mock.patch` and `monkeypatch`.
|
||||
- Coverage: `--cov=innercontext --cov-report=term-missing`.
|
||||
- No test markers or parametrize — explicit test functions only.
|
||||
132
frontend/AGENTS.md
Normal file
132
frontend/AGENTS.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# Frontend
|
||||
|
||||
SvelteKit 2 + Svelte 5 (Runes) web UI. Adapter: `@sveltejs/adapter-node` (required for form actions).
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── app.css # Tailwind v4 theme + editorial design system (1420 lines)
|
||||
├── app.html # HTML shell (Cormorant Infant + Manrope fonts)
|
||||
├── hooks.server.ts # Paraglide i18n middleware
|
||||
├── routes/ # SvelteKit file-based routing
|
||||
│ ├── +layout.svelte # App shell, sidebar, mobile drawer, domain-based theming
|
||||
│ ├── +page.svelte # Dashboard (routines, snapshots, lab results)
|
||||
│ ├── products/ # List, [id] detail/edit, new, suggest (AI)
|
||||
│ ├── routines/ # List, [id] detail/edit, new, suggest (AI), grooming-schedule/
|
||||
│ ├── health/ # medications/ (list, new), lab-results/ (list, new)
|
||||
│ ├── skin/ # Snapshots list, new (with photo analysis)
|
||||
│ └── profile/ # User profile
|
||||
└── lib/
|
||||
├── api.ts # Typed fetch wrappers (server: PUBLIC_API_BASE, browser: /api)
|
||||
├── types.ts # TypeScript types mirroring backend models (manual sync)
|
||||
├── utils.ts # cn() class merger, bits-ui types
|
||||
├── utils/ # forms.ts (preventIfNotConfirmed), skin-display.ts (label helpers)
|
||||
├── paraglide/ # Generated i18n runtime — DO NOT EDIT
|
||||
└── components/
|
||||
├── ui/ # bits-ui primitives: button, card, badge, input, label, select, tabs, table, separator
|
||||
├── forms/ # DRY helpers: SimpleSelect, GroupedSelect, HintCheckbox, LabeledInputField, FormSectionCard, form-classes.ts
|
||||
├── product-form/ # Sectioned form: Basic, Details, Classification, Assessment, Notes
|
||||
├── PageHeader.svelte # Reusable page header (kicker, title, subtitle, backlink, actions via snippets)
|
||||
├── ProductForm.svelte # Main product form (tabbed, 737 lines)
|
||||
├── ProductFormAiModal.svelte # AI text-to-product parsing modal
|
||||
├── FlashMessages.svelte # Error/success/warning/info alerts
|
||||
├── StructuredErrorDisplay.svelte # Parses semicolon-separated backend errors into list
|
||||
├── ValidationWarningsAlert.svelte # LLM validation warnings display
|
||||
├── ReasoningChainViewer.svelte # AI reasoning chain viewer (collapsible)
|
||||
├── MetadataDebugPanel.svelte # Token metrics, model info (collapsible)
|
||||
├── AutoFixBadge.svelte # Auto-fix indicator
|
||||
└── LanguageSwitcher.svelte # i18n locale toggle
|
||||
```
|
||||
|
||||
## Design System
|
||||
|
||||
**MUST READ**: `docs/frontend-design-cookbook.md` — update when introducing new UI patterns.
|
||||
|
||||
- **Typography**: `Cormorant Infant` (display/headings), `Manrope` (body/UI).
|
||||
- **Colors**: CSS variables in `app.css`. Domain accents per route: products (green), routines (cyan), skin (orange), profile (blue), health-labs (purple), health-meds (teal).
|
||||
- **Layout wrappers**: `.editorial-page`, `.editorial-hero`, `.editorial-panel`, `.editorial-toolbar`, `.editorial-backlink`, `.editorial-alert`.
|
||||
- **Page header**: Use `PageHeader.svelte` for consistent title hierarchy, backlinks, and actions.
|
||||
- **Accent rule**: ~10-15% of visual area. Never full backgrounds or body text.
|
||||
- **Motion**: Short purposeful reveals. Always respect `prefers-reduced-motion`.
|
||||
|
||||
## Route Patterns
|
||||
|
||||
Every page: `+page.svelte` (UI) + `+page.server.ts` (load + actions).
|
||||
|
||||
Load functions fetch from API, return data:
|
||||
```typescript
|
||||
export const load: PageServerLoad = async () => {
|
||||
const data = await getProducts();
|
||||
return { products: data };
|
||||
};
|
||||
```
|
||||
|
||||
Form actions parse FormData, call API, return result or `fail()`:
|
||||
```typescript
|
||||
export const actions = {
|
||||
default: async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
try {
|
||||
const result = await createProduct(payload);
|
||||
return { success: true, product: result };
|
||||
} catch (e) {
|
||||
return fail(500, { error: (e as Error).message });
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Component Conventions
|
||||
|
||||
- Prefer `SimpleSelect` / `GroupedSelect` over bits-ui `ui/select` unless search/rich popup UX needed.
|
||||
- Use `form-classes.ts` tokens (`baseSelectClass`, `baseTextareaClass`) for consistent form styling.
|
||||
- Svelte 5 runes: `$props()`, `$state()`, `$derived()`, `$effect()`, `$bindable()`.
|
||||
- Snippet-based composition in `PageHeader` (actions, meta, children snippets).
|
||||
- Compound components: Card → CardHeader, CardContent, CardFooter, etc.
|
||||
|
||||
## i18n
|
||||
|
||||
- Source messages: `frontend/messages/{en,pl}.json`.
|
||||
- Generated runtime: `src/lib/paraglide/` (via Vite plugin).
|
||||
- Import: `import * as m from '$lib/paraglide/messages.js'`.
|
||||
- **No hardcoded English labels.** Use `m.*` keys. Add new keys to message files if needed.
|
||||
- Fallback display: use `m.common_unknown()` not hardcoded `n/a`.
|
||||
|
||||
## API Client
|
||||
|
||||
`src/lib/api.ts` — typed fetch wrappers.
|
||||
|
||||
```typescript
|
||||
const base = browser ? "/api" : PUBLIC_API_BASE;
|
||||
// Browser: /api (nginx proxies, strips prefix to backend)
|
||||
// Server-side (SSR): PUBLIC_API_BASE (http://localhost:8000)
|
||||
```
|
||||
|
||||
Methods: `api.get<T>()`, `api.post<T>()`, `api.patch<T>()`, `api.del()`.
|
||||
File upload: `analyzeSkinPhotos()` uses FormData (not JSON).
|
||||
Error handling: throws `Error` with `.detail` from backend response.
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Default | Set at |
|
||||
|----------|---------|--------|
|
||||
| `PUBLIC_API_BASE` | `http://localhost:8000` | Build time |
|
||||
|
||||
Production: `PUBLIC_API_BASE=http://innercontext.lan/api pnpm build`.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
pnpm dev # Dev server (API proxied to :8000)
|
||||
pnpm check # Type check + Svelte validation
|
||||
pnpm lint # ESLint
|
||||
pnpm format # Prettier
|
||||
pnpm build # Production build → build/
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- No frontend tests exist. Only linting + type checking.
|
||||
- ESLint `svelte/no-navigation-without-resolve` has `ignoreGoto: true` workaround (upstream bug sveltejs/eslint-plugin-svelte#1327).
|
||||
- `src/paraglide/` is a legacy output path — active i18n output is in `src/lib/paraglide/`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue