Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
152 commits
Select commit Hold shift + click to select a range
4d92a0a
docs: add v3.0.0 unified TS core + Bun server design spec
hessius Jul 2, 2026
34e4343
docs: add v3.0.0 unified TS core + Bun server implementation plan
hessius Jul 2, 2026
61ada1c
feat(core): scaffold @metic/core handler + Platform interface
hessius Jul 5, 2026
1106d95
refactor(core): extract compass rules into @metic/core
hessius Jul 5, 2026
be91880
refactor(core): extract pure logic modules into @metic/core
hessius Jul 5, 2026
6603222
fix(docker): preserve monorepo layout so frontend build resolves @met…
hessius Jul 5, 2026
dca2de2
refactor(core): extract portable AI knowledge, errors and prompts int…
hessius Jul 5, 2026
eac1609
feat(server): Bun proxy-mode server + Node platform (Phase 4)
hessius Jul 6, 2026
778b5e4
test(server): fix type-only errors in bun-server tests
hessius Jul 6, 2026
b52bd4d
feat(core): port shot annotations route family
hessius Jul 6, 2026
a68fe57
feat(core): port dial-in session route family
hessius Jul 6, 2026
58ffadf
feat(core): port pour-over preferences route family
hessius Jul 6, 2026
6afdc7b
feat(core): add AI provider seam + dial-in AI recommendations
hessius Jul 6, 2026
77f0aac
feat(core): add machine.fetch seam to the Platform
hessius Jul 6, 2026
b5cce58
feat(core): port shot-analysis pure logic into the core package
hessius Jul 6, 2026
e0ed18e
feat(core): port shot-analysis route family into the core package
hessius Jul 6, 2026
0de1e41
feat(core): port profile recommendation routes into the core package
hessius Jul 6, 2026
d5b248e
feat(core): port apply-recommendations route into the core package
hessius Jul 6, 2026
47e6930
feat(core): port regenerate-description route into the core package
hessius Jul 6, 2026
d8e8c04
feat(core): port analyze_and_profile (full profile generation) into t…
hessius Jul 6, 2026
02e19f0
feat(web): add browser Platform impl for the unified core (Phase 3 ke…
hessius Jul 6, 2026
1c20dfa
feat(core): port system settings/version/network-ip routes into the c…
hessius Jul 6, 2026
39ffd68
chore: gitignore *.tsbuildinfo build artifacts
hessius Jul 6, 2026
baa10f4
feat(core): port machine shot-history routes with tombstone/notes ove…
hessius Jul 6, 2026
3e5749f
feat(core): port machine shot-history read routes into the core package
hessius Jul 6, 2026
ed58fc0
feat(core): port machine command + status routes into the core package
hessius Jul 6, 2026
c2938a1
feat(core): port profiles-crud route family to @metic/core
hessius Jul 6, 2026
7c5717e
feat(core): port system meta routes + model discovery to @metic/core
hessius Jul 6, 2026
26e9ed8
feat(core): port recipe library to @metic/core as single source
hessius Jul 6, 2026
7a10e4e
fix(server): serve config.json with an empty serverUrl
hessius Jul 6, 2026
0656aaf
feat(core): port profile image-proxy route into @metic/core
hessius Jul 6, 2026
5a001eb
feat(core): cover remaining proxy-mode routes (update status, schedul…
hessius Jul 6, 2026
275c8eb
test(e2e): visit Settings in the full-app sweep
hessius Jul 6, 2026
4f82261
fix(e2e): drop useless body initializer in full-app sweep
hessius Jul 6, 2026
b875421
fix(e2e): exclude local-only full-app sweep from CI
hessius Jul 6, 2026
697dcfc
feat(core): wire @metic/core into the browser fetch path behind a flag
hessius Jul 11, 2026
44a479e
feat(core): port pour-over active flows, progress seam, and read stubs
hessius Jul 11, 2026
a16719d
feat(web): cut the browser over to @metic/core and delete the legacy …
hessius Jul 11, 2026
9d42b04
feat(server): SQLite storage backend + interceptor module map (#531, …
hessius Jul 12, 2026
cf98e08
feat(server): Tailscale routes over the LocalAPI unix socket (Phase 5…
hessius Jul 12, 2026
c10dfa1
feat(docker): distroless single-binary image + CI size gate (Phase 6,…
hessius Jul 12, 2026
8732672
chore: ignore bun --compile temp artifacts
hessius Jul 12, 2026
4a4edef
feat(core): benign /api/profiles/sync/accept/{id} stub (Phase 7 parit…
hessius Jul 12, 2026
6a0cdca
feat!: delete Python backend, cut over to the unified Bun image (Phas…
hessius Jul 12, 2026
5d2eea1
fix: remove orphaned MQTT bridge and MCP server UI in proxy mode
hessius Jul 12, 2026
85af799
feat(android): support the hardware back button (#536)
hessius Jul 11, 2026
526c2be
fix(ai): compact on-device prompts to fit small context windows
hessius Jul 12, 2026
05887aa
fix(web): handle rejected profile-image load in HistoryView effect
hessius Jul 13, 2026
55587df
fix(core): credit stage exit trigger reached at the stage transition
hessius Jul 13, 2026
3ad3eb4
fix(ai): make the AI gate provider-aware and promote AI_MODE when a h…
hessius Jul 14, 2026
2667dcd
Merge pull request #558 from hessius/feat/wire-core-interceptor-flag
hessius Jul 15, 2026
5fcc7e6
Merge remote-tracking branch 'origin/main' into version/3.0.0
hessius Jul 15, 2026
cdd6ecb
chore(deps): drop unused react-day-picker and dead ui/calendar component
hessius Jul 16, 2026
d015a26
ci: use metic healthcheck instead of curl in build-publish smoke test
hessius Jul 16, 2026
2a23a96
feat(profiles): import from metprofiles links, direct URLs, and raw JSON
hessius Jul 16, 2026
734e039
feat(share): native share-target import for profiles (Android + iOS s…
hessius Jul 16, 2026
7a534da
feat(share): complete iOS Share Extension implementation
hessius Jul 17, 2026
76e5c08
feat(share-import): central result popover for automatic profile imports
hessius Jul 17, 2026
c2d71ae
fix(share): foreground app on iOS share import and stop UI text selec…
hessius Jul 19, 2026
c2aa584
docs(readme): document server features removed in 3.0.0
hessius Jul 20, 2026
6b1413c
docs(updating): warn 2.x upgraders about the distroless healthcheck b…
hessius Jul 21, 2026
37cbbd4
fix(web): never block app init on slow machine-backed /api/history
hessius Jul 22, 2026
6293482
fix(web): avoid lookbehind regex in markdown cleaning (Safari <16.4)
hessius Jul 22, 2026
cc9035b
chore: bump version to 3.0.0-beta.2
hessius Jul 22, 2026
df81ad2
fix(android): update Capacitor stack to 8.5.0 to address Android 17 A…
hessius Aug 6, 2026
95f9aec
chore: bump version to 3.0.0-beta.3
hessius Aug 6, 2026
ad45da3
feat(diagnostics): on-device freeze/error capture for field debugging
hessius Aug 6, 2026
531ac3f
chore(release): 3.0.0-beta.4
hessius Aug 6, 2026
613cbcc
feat(diagnostics): boot overlay to surface freeze report when app is …
hessius Aug 6, 2026
fb5797b
fix(machine): fall back to port 80 for older firmware without :8080
hessius Aug 6, 2026
bb8b7bd
chore(release): 3.0.0-beta.5
hessius Aug 7, 2026
0d4961b
feat(live): back-fill live-shot graph from continuously-recorded tele…
hessius Aug 8, 2026
0dfb09c
feat(control-center): show live scale weight in collapsed state
hessius Aug 8, 2026
88c090b
docs: add iOS home-screen widgets design spec
hessius Aug 8, 2026
6e19b40
docs: add iOS home-screen widgets implementation plan
hessius Aug 8, 2026
04c9e31
feat(favourites): add favourites store and constants (#584)
hessius Aug 8, 2026
75b4afb
feat(favourites): add useFavourites hook (#584)
hessius Aug 8, 2026
55254e8
feat(favourites): star toggle and favourites section in catalogue (#584)
hessius Aug 8, 2026
2a8a2a7
feat(widgets): add metic:// deep-link parser and dispatcher (#584)
hessius Aug 8, 2026
9254b75
feat(widgets): add WidgetBridge interface and iOS-gated useWidgetSync…
hessius Aug 8, 2026
541b04b
feat(widgets): handle metic:// deep links and mount widget sync in Ap…
hessius Aug 8, 2026
77d978d
feat(widgets): add iOS-only open-app-on-start setting (#584)
hessius Aug 8, 2026
5eb4d46
feat(widgets): add native WidgetBridge plugin and register it (#584)
hessius Aug 8, 2026
20adf98
feat(widgets): add iOS WidgetKit extension — Favourites + Control Cen…
hessius Aug 8, 2026
36de015
fix(widgets): make Control Center Stop reachable and honour open-on-s…
hessius Aug 8, 2026
88120cc
fix(catalogue): keep scroll position when toggling a favourite (#584)
hessius Aug 9, 2026
5fbadb0
feat(widgets): round-2 layout, branding, snapshot & confirmation fixe…
hessius Aug 9, 2026
658953b
fix(control-center): hide target weight while idle (#584)
hessius Aug 9, 2026
73445b5
fix(widgets): hero bounds, larger grids, seamless overlay & state map…
hessius Aug 9, 2026
8d2ae91
fix(widgets): equal hero cards, remove duplicate branding & connectio…
hessius Aug 9, 2026
1300a47
fix(telemetry): show effective (temp-edited) target weight from loade…
hessius Aug 9, 2026
f2c53e0
chore(release): 3.0.0-beta.6
hessius Aug 9, 2026
62c394a
fix(ios): generate CLiteRTLM dSYM for App Store upload
hessius Aug 9, 2026
e65a23c
feat(diagnostics): make on-device diagnostics opt-in (default off)
hessius Aug 19, 2026
2783fe9
feat(diagnostics): show Diagnostics settings section on native only
hessius Aug 19, 2026
7110ead
feat(shot): auto-start on stable temperature (#588)
hessius Aug 19, 2026
48977bb
docs: design spec for separated Live View graphs (#589)
hessius Aug 19, 2026
31b81bf
docs: implementation plan for separated Live View graphs (#589)
hessius Aug 19, 2026
86bc9f5
feat(charts): persisted chart-layout preference helper (#589)
hessius Aug 19, 2026
a062997
feat(charts): useChartLayout hook resolving combined/stack/grid (#589)
hessius Aug 19, 2026
bd2bf25
feat(charts): thread brew-head temperature into live chart points (#589)
hessius Aug 19, 2026
1fe1c95
feat(charts): MetricPanels small-multiples component (#589)
hessius Aug 19, 2026
0af17a3
feat(live): combined/separated graph toggle in Live View (#589)
hessius Aug 19, 2026
c5dd8fe
feat(history): separated per-metric graphs in single-shot and compari…
hessius Aug 19, 2026
fd95e50
i18n(charts): layout toggle + metric labels in all locales (#589)
hessius Aug 19, 2026
39b8e61
test(charts): avoid unused-var lint in MetricPanels omit test (#589)
hessius Aug 19, 2026
32f76e8
feat(charts): scrubbing + target overlays in separated history/compar…
hessius Aug 19, 2026
e8fae93
chore(release): 3.0.0-beta.7
hessius Aug 19, 2026
1f75cce
docs: spec for machine auto-detection restore + native mDNS hardening…
hessius Aug 20, 2026
c1eff80
fix(discovery): restore server-side POST /api/machine/detect with sub…
hessius Aug 20, 2026
54f9942
fix(discovery): harden native mDNS + add WebRTC subnet-scan fallback …
hessius Aug 20, 2026
0e51e62
chore(release): 3.0.0-beta.8
hessius Aug 20, 2026
6941676
fix(core): resolve CodeQL ReDoS and ai-guard floating-promise finding…
hessius Aug 20, 2026
a28a2bf
chore(deps): apply relevant Dependabot security updates for 3.0.0
hessius Aug 20, 2026
6c0a1fe
chore(deps): bump next 16.2.6 -> 16.2.11 in /screenshots (#572)
hessius Aug 20, 2026
5d36480
chore(release): bump version to 3.0.0-beta.9
hessius Aug 20, 2026
963cf3a
fix: eliminate residual ReDoS and floating-promise code-scanning aler…
hessius Aug 22, 2026
3317964
docs: add iOS Live Activity for espresso shots design spec
hessius Aug 22, 2026
cf23794
docs: add iOS Live Activity implementation plan + shared test scheme
hessius Aug 22, 2026
d0845be
chore(ios): add scriptable Xcode file-adder
hessius Aug 22, 2026
e084de2
refactor(live-view): remove red target marker from heating bars
hessius Aug 22, 2026
c1353a4
feat(ios): add ShotActivityAttributes + ContentState for Live Activity
hessius Aug 22, 2026
67c096f
feat(ios): parse machine frames into ShotFrame with phase derivation
hessius Aug 22, 2026
d733b2a
feat(ios): add downsampling graph buffer for Live Activity (<4KB state)
hessius Aug 22, 2026
876cc4c
feat(ios): port heating ETA model + glanceable formatting to Swift
hessius Aug 22, 2026
c27c83e
feat(ios): build ContentState + compute shot summary
hessius Aug 22, 2026
00c7304
feat(ios): store Live Activity glanceable config in App Group (schema…
hessius Aug 22, 2026
feceb07
feat(ios): add persistent Socket.IO shot streamer
hessius Aug 22, 2026
ed73ce8
feat(ios): enable NSSupportsLiveActivities for app and widget
hessius Aug 22, 2026
da8c1a1
feat(ios): add StartShotIntent for Live Activity Ready state
hessius Aug 22, 2026
422eb09
feat(ios): add Live Activity Lock Screen view + ActivityConfiguration
hessius Aug 22, 2026
8520494
feat(ios): add Dynamic Island presentations for shot Live Activity
hessius Aug 22, 2026
6594cb2
feat(ios): drive Live Activity updates from native shot streamer
hessius Aug 22, 2026
c84021a
feat(ios): add LiveActivity Capacitor plugin + register it
hessius Aug 22, 2026
9614a28
feat(live-activity): add TS plugin wrapper + pure lifecycle transition
hessius Aug 22, 2026
d8ac956
feat(live-activity): sync shot lifecycle into iOS Live Activity + set…
hessius Aug 22, 2026
7c9ed31
feat(live-activity): add glanceable stat settings UI + i18n (6 locales)
hessius Aug 22, 2026
f164dde
fix(ios): emit .done summary state when Live Activity shot ends
hessius Aug 22, 2026
d119a80
fix(machine): self-heal stale machine URL on the live connection path
hessius Aug 22, 2026
dffb1f5
chore(release): bump version to 3.0.0-beta.10
hessius Aug 22, 2026
500b27c
fix(widgets): re-host favourite images onto the current machine base
hessius Aug 22, 2026
9d27853
chore(release): bump version to 3.0.0-beta.11
hessius Aug 22, 2026
2845a82
fix(ios): Live Activity triggering, heating temps, and expanded layout
hessius Aug 22, 2026
144dbd9
chore(release): bump version to 3.0.0-beta.12
hessius Aug 22, 2026
6f2c348
fix(ios): inset Dynamic Island expanded content and clarify branding
hessius Aug 22, 2026
fe853ab
chore(release): bump version to 3.0.0-beta.13
hessius Aug 22, 2026
77dadc3
fix(ios): reconnect Live Activity socket and mark stale data
hessius Aug 23, 2026
020f83c
chore(release): bump version to 3.0.0-beta.14
hessius Aug 23, 2026
238d39d
feat(ios): stale 'open app to resume' hint + localise Live Activity s…
hessius Aug 23, 2026
5e6c1f0
chore(release): bump version to 3.0.0-beta.15
hessius Aug 23, 2026
05b0b89
docs: update documentation for 3.0.0 Bun architecture
hessius Aug 26, 2026
6c253c7
chore: remove internal planning artifacts from the repo for 3.0.0
hessius Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  

This file was deleted.

5 changes: 4 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ data/
logs/
*.log

# Node modules (web builder stage installs fresh)
# Node modules (builder stages install fresh)
apps/web/node_modules/
apps/web/dist/
apps/bun-server/node_modules/
apps/bun-server/dist/
packages/core/node_modules/

# Python cache
__pycache__/
Expand Down
71 changes: 38 additions & 33 deletions .github/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

| Purpose | Pattern | Example |
|---------|---------|---------|
| Milestone work | `version/X.Y.Z` | `version/2.2.0` |
| Milestone work | `version/X.Y.Z` | `version/3.0.0` |
| Feature branch | `feat/<name>` | `feat/temp-variables` |
| Website updates | `website/<name>` | `website/v2.2-redesign` |
| Website updates | `website/<name>` | `website/v3-redesign` |
| Bugfix | `fix/<name>` | `fix/cache-overflow` |
| Pages deployment | `pages` | `pages` (protected) |

Expand All @@ -30,40 +30,43 @@

These are **non-negotiable**. Every PR, every push, every completion claim:

1. **CI must be completely green.** The Test Suite has 5 jobs (Web Tests, Server Tests, Code Quality, Web E2E, Docker Build Test) plus a separate Build and Publish workflow. All must pass.
1. **CI must be completely green.** The Test Suite jobs plus the separate Build and Publish workflow must pass.
2. **Zero tech debt.** Address all issues immediately. Never defer tasks to "later".
3. **No deferred tasks.** If a task is in scope, it gets done now — not added to a backlog.
4. **All code review comments addressed.** Including suppressed/collapsed threads. Don't dismiss without clear justification.
5. **Tests pass locally before pushing.** Don't rely on CI as your first test run.
6. **Wide review on bug discovery.** When discovering a bug or potential issue, always do a wide review to look for the same or similar issues across the codebase. Bugs are often part of a pattern — fix the pattern, not just the instance.
7. **Dual-runtime feature parity (server + native).** Metic ships in two runtimes (see *Architecture Patterns → Dual runtime*). Any change to analysis, profile generation, target-curve math, recommendations, dial-in, or machine-API behavior **must be applied to both** the Python server path *and* the native/Capacitor DirectMode path **in the same PR** — even when the user only reports it against one. Before claiming a fix complete, search the *other* runtime for a parallel implementation and update it too. Mismatched behavior between runtimes is a release-blocking defect.
7. **Dual-runtime feature parity (server + native).** Metic ships in two runtimes (see *Architecture Patterns → Dual runtime*) that consume shared `@metic/core` business logic. Any change to analysis, profile generation, target-curve math, recommendations, dial-in, or machine-API behavior belongs in `@metic/core` whenever possible and must be tested in both server mode and native/direct mode. Runtime-specific code should stay limited to thin platform adapters and native-only UI.
8. **Browser testing before release.** Run the full browser testing protocol (`.github/skills/browser-testing.md`) against a live Docker container before any non-beta version bump.

## Testing

### Backend (Python)
### Shared Core (TypeScript)
```bash
cd apps/server && TEST_MODE=true .venv/bin/python -m pytest test_main.py -x -q
cd packages/core && bun test && bun run typecheck
```
- Currently 750+ tests. New code must include tests in `test_main.py`.
- Test both success and failure/edge-case paths.
- Add tests under `packages/core/test/` for shared business logic, route behavior, and failure/edge-case paths.

### Bun Server (TypeScript)
```bash
cd apps/bun-server && bun test && bun run typecheck
```
- Add tests under `apps/bun-server/test/` for server-only behavior: static serving, machine proxying, storage/config wiring, and telemetry.

### Frontend (TypeScript/React)
```bash
cd apps/web && bun run test:run
```
- Currently 277+ tests.
- Lint must be clean: `bun run lint` (0 errors; warnings OK per eslint v7 migration issue #256).
- Lint must be clean: `bun run lint` (0 errors; warnings OK per eslint migration issues).
- Build must succeed: `bun run build`.

### Full Local Gate (run before pushing)
```bash
cd apps/server && TEST_MODE=true .venv/bin/python -m pytest test_main.py -x -q && \
cd ../web && bun run lint && bun run test:run && bun run build
cd packages/core && bun test && bun run typecheck && cd ../../apps/bun-server && bun test && bun run typecheck && cd ../web && bun run lint && bun run test:run && bun run build
```

- After all tests pass, also rebuild the Docker dev container (`docker compose -f docker-compose.yml -f docker-compose.dev.yml build`) and the Capacitor iOS app (`npx cap sync ios` + Xcode build) before marking a task complete when the change can affect container or native behavior.

- After all tests pass, also rebuild the Docker dev container (`docker compose -f docker-compose.yml -f docker-compose.dev.yml build`) and the Capacitor iOS app (`npx cap sync ios` + Xcode build) before marking a task complete.
## Commits

- **Format:** [Conventional Commits](https://www.conventionalcommits.org/) — `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `ci:`.
Expand Down Expand Up @@ -93,37 +96,39 @@ cd ../web && bun run lint && bun run test:run && bun run build
- Bun version is pinned at **1.3.14** in both `docker/Dockerfile.unified` and CI workflows.
- Import from `lucide-react` public paths only (never private `dist` paths).

### Backend
- Pin versions in `apps/server/requirements.txt`.
### Core / Bun Server
- Use `bun add <package>` in the affected package (`packages/core` or `apps/bun-server`).
- Always commit that package's `bun.lock` alongside `package.json` changes.
- Rebuild the container after dependency changes.

## Code Style

### Python (apps/server/)
- PEP 8 strict. Extensive type hints. Google-style docstrings.
- Functions: focused and single-purpose.
- Concurrency: `threading.Lock` for synchronous file I/O, `asyncio.Lock` for async operations.
- Lazy lock creation (`_get_lock()` pattern) to avoid "attached to different loop" errors in tests.
### TypeScript Core / Server (`packages/core/`, `apps/bun-server/`)
- Use strict TypeScript with clear public types.
- Keep shared product behavior in `@metic/core`; do not duplicate it in runtime-specific adapters.
- Keep runtime-specific I/O behind `packages/core/src/platform.ts` abstractions.
- Routes live in `packages/core/src/routes/`; request handling is coordinated by `packages/core/src/handler.ts`.

### TypeScript/React (apps/web/)
### TypeScript/React (`apps/web/`)
- Functional components with hooks only. No class components.
- ESLint compliance (react-hooks rules). 5 strict v7 rules downgraded to warn (issue #256).
- ESLint compliance (react-hooks rules). Strict rules downgraded to warnings remain warnings only; do not introduce errors.
- Extend shadcn/ui components in `apps/web/src/components/ui/`; don't replace them.
- Mobile-first responsive design.

## Architecture Patterns

- **Dual runtime (server + native) — feature parity is mandatory.** Metic runs in two modes that re-implement the same product behavior in two languages:
- **Server mode:** the React web app talks to the Python FastAPI backend (`apps/server/`), which performs AI analysis, profile generation, target-curve math, recommendations, and machine I/O.
- **Native/Capacitor mode:** the iOS app has **no Python server**. The same behavior is reimplemented client-side and served by a fetch interceptor. Key files: `apps/web/src/services/interceptor/DirectModeInterceptor.ts` (route/API parity + curve generation), `apps/web/src/services/ai/` (`BrowserAIService`, prompts), `apps/web/src/lib/directModeAI.ts`, and `apps/web/src/lib/profileAnalysis.ts`.
- **Dual runtime (server + native) — shared core is mandatory.** Metic runs in two modes that share product behavior through `@metic/core`:
- **Server mode:** the React web app is served by the Bun server (`apps/bun-server`, `@metic/server`), which delegates `/api/*` REST requests to the shared `@metic/core` `handle()` function.
- **Native/Capacitor mode:** iOS/Android (Capacitor) or PWA on-machine has no server. The web app installs the core interceptor (`installCoreInterceptor`) so `/api/*` calls route through the same `@metic/core` `handle()` in-process.

**Rule:** logic that exists in both runtimes must stay in sync. When you touch a backend service in `apps/server/services/` (or vice-versa), grep the DirectMode layer for the parallel implementation and apply the equivalent change **in the same PR**, with tests on both sides. Treat the Python function and its TypeScript port as one feature with two implementations. Examples of paired logic: target-curve generation (`analysis_service.py` ↔ `DirectModeInterceptor.ts`), shot analysis, profile/variable handling, recommendation patching, dial-in. This parity requirement is non-negotiable (Quality Gate #7) and applies even when the user only mentions one runtime.
**Rule:** business logic for analysis, profile generation, target-curve math, recommendations, dial-in, and machine-API behavior belongs once in `packages/core`. Changes there apply to both runtimes. What still differs per runtime is the thin platform adapter (server platform vs browser/native platform) and native-only UI such as iOS Live Activity and widgets. Test both runtimes when user-visible behavior can differ.

- **Dual route registration:** Both `/endpoint` and `/api/endpoint` are registered for every route. This is intentional to support clients that include or omit the `/api` prefix. Not a defect.
- **Unified container:** Single Docker container managed by s6-overlay. Port 3550 is the only exposed port (nginx proxy).
- **Settings hot-reload:** Changing `METICULOUS_IP` or `GEMINI_API_KEY` triggers `s6-svc -r` (service restart, not container restart).
- **Unified container:** Single Docker container (`meticai`) running one self-contained Bun binary at `/app/metic` on port 3550. The image is distroless and has no process supervisor.
- **Bun server:** `apps/bun-server` serves the built React SPA, delegates REST API handling under `/api/*` to `@metic/core`, transparently proxies the machine API (`/api/v1/*` → the Meticulous machine), and streams live telemetry over `/api/ws/live` while connecting upstream to the machine over Socket.IO.
- **Storage:** Persistent data lives in `/data`; `STORAGE_BACKEND` is `json` by default or `sqlite`.
- **Settings:** `METICULOUS_IP` and `GEMINI_API_KEY` changes are applied by the running Bun process or by restarting the container (`docker compose restart meticai` / `docker restart meticai`).
- **Cache bounding:** In-memory caches must be bounded (e.g., 50 entries max). On insert: purge expired, then clear all if still over limit.
- **Safe parsing:** Use helper functions (e.g., `_safe_float()`) for user-provided numeric values. Never trust raw input.
- **Safe parsing:** Use helper functions for user-provided numeric values. Never trust raw input.

## Release Process

Expand All @@ -140,16 +145,16 @@ cd ../web && bun run lint && bun run test:run && bun run build
- **Profile Generation Output:** Always include "Profile Created:", "Description:", "Preparation:", "Why This Works:", and "Special Notes:".
- **Reference:** See `PROFILING_AXIOMS.md` for extraction principles, stage-based profiling, and exit trigger rules.
- **Profile Variables:** Two types — INFO (`info_` prefix, display-only, emoji names) and ADJUSTABLE (no prefix, user-modifiable types: pressure/flow/weight/power/time).
- **Temporary Profiles:** Managed via `temp_profile_service.py` — create_and_load → cleanup/force_cleanup lifecycle.
- **Temporary Profiles:** Managed through shared `@metic/core` route/logic behavior and machine I/O; keep the lifecycle consistent across server and native/direct mode.

## CI Structure

- **Test Suite workflow:** 5 jobs — Web Tests, Server Tests, Code Quality, Web E2E, Docker Build Test.
- **Test Suite workflow:** includes web tests, core/server tests, code quality, web E2E, Docker build testing, and platform-specific checks.
- **Build and Publish workflow:** Separate, runs on merge to main.
- **Bun setup in CI:** Uses 3-attempt retry pattern to handle transient download failures.
- **Triggers:** Test Suite runs on PRs to `main`. Build and Publish on push to `main`.

---

*Last updated: 2026-06-16 | Maintained by the Metic team and AI agents*
*Last updated: 2026-08-26 | Maintained by the Metic team and AI agents*
*To add a new convention, use the `learn_convention` extension tool or edit this file directly.*
20 changes: 11 additions & 9 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Metic — Agent Instructions

Metic is an AI-powered controller for the Meticulous Espresso Machine. Stack: Python 3.13 (FastAPI), React + TypeScript (Vite/Bun), Google Gemini Python SDK, Docker + s6-overlay. Repository: https://github.com/hessius/MeticAI. The `VERSION` file triggers the auto-release workflow.
Metic is an AI-powered controller for the Meticulous Espresso Machine. Stack: Bun single-binary server (`apps/bun-server`, `@metic/server`) + shared TypeScript core (`packages/core`, `@metic/core`), React + TypeScript (Vite/Bun), Google Gemini, Docker distroless single container. Repository: https://github.com/hessius/MeticAI. The `VERSION` file triggers the auto-release workflow.

## Core Architecture

- **Unified Container:** Single container (`meticai`) via s6-overlay. Port 3550 exposed (nginx proxy).
- **Settings Hot-Reload:** Changing `METICULOUS_IP` or `GEMINI_API_KEY` restarts services (`s6-svc -r`) without full container restart.
- **Environment:** Requires `.env` with `GEMINI_API_KEY` and `METICULOUS_IP`.
- **Unified Container:** Single container (`meticai`) with one self-contained Bun binary at `/app/metic`. Port 3550 is exposed directly by the Bun server.
- **Bun Server:** `apps/bun-server` serves the built React SPA, delegates REST API handling under `/api/*` to `@metic/core`, transparently proxies machine API calls (`/api/v1/*` → the Meticulous machine), and streams live telemetry on `/api/ws/live` via native WebSocket while connecting upstream to the machine over Socket.IO.
- **Shared Core:** Business logic for AI analysis, profile generation, target-curve math, recommendations, dial-in, and machine I/O lives once in `packages/core` (`@metic/core`).
- **Storage & Environment:** Persistent data lives in `/data`; `STORAGE_BACKEND` is `json` (default) or `sqlite`. Common env: `GEMINI_API_KEY`, `GEMINI_MODEL`, `METICULOUS_IP`, `DATA_DIR=/data`.
- **Settings Changes:** `METICULOUS_IP` and `GEMINI_API_KEY` are applied by the running Bun process or by restarting the container (`docker compose restart meticai` / `docker restart meticai`).

## Dual Runtime — Feature Parity (read before any logic change)
## Dual Runtime — Shared Core (read before any logic change)

Metic ships in **two runtimes** that implement the same behavior twice:
Metic ships in **two runtimes** that consume the same `@metic/core` behavior:

- **Server mode:** React web app + Python FastAPI backend (`apps/server/`) for AI analysis, profile generation, target-curve math, recommendations, machine I/O.
- **Native/Capacitor mode:** iOS app with **no Python server** — that logic is reimplemented client-side in `apps/web/src/services/interceptor/DirectModeInterceptor.ts`, `apps/web/src/services/ai/`, `apps/web/src/lib/directModeAI.ts`, and `apps/web/src/lib/profileAnalysis.ts`.
- **Server mode:** React web app served by the Bun server, which calls the shared `@metic/core` `handle()` function.
- **Native/Capacitor mode:** iOS/Android (Capacitor) or PWA on-machine with no server; the web app installs the core interceptor (`installCoreInterceptor`) and routes `/api/*` calls through the same `@metic/core` `handle()` in-process.

**Guardrail (do this automatically, without being asked):** any change to analysis / profile / curve / recommendation / dial-in / machine-API logic in one runtime **must** be mirrored in the other within the same change, with tests on both sides. After fixing one side, always grep the other runtime for the parallel implementation. Mismatched behavior between runtimes is a release-blocking bug. See `.github/CONVENTIONS.md` → Quality Gate #7 and *Architecture Patterns → Dual runtime*.
**Guardrail (do this automatically, without being asked):** business logic belongs in `@metic/core`; changes there apply to both runtimes. Do not duplicate logic in runtime-specific adapters. What still differs per runtime is the thin platform adapter (server platform vs browser/native platform) and native-only UI (Live Activity, widgets). Test both server mode and native/direct mode for analysis / profile / curve / recommendation / dial-in / machine-API behavior. See `.github/CONVENTIONS.md` → Quality Gate #7 and *Architecture Patterns → Dual runtime*.

## Conventions

Expand Down
38 changes: 18 additions & 20 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,49 @@

version: 2
updates:
# PythonFastAPI server
- package-ecosystem: "pip"
directory: "/apps/server"
# npmWeb frontend (Bun uses package.json)
- package-ecosystem: "npm"
directory: "/apps/web"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
open-pull-requests-limit: 10
labels:
- "dependencies"
- "python"
- "javascript"
commit-message:
prefix: "chore(deps)"
groups:
dev-dependencies:
dependency-type: "development"
update-types: ["minor", "patch"]
production-dependencies:
dependency-type: "production"
update-types: ["patch"]

# PythonMCP server
- package-ecosystem: "pip"
directory: "/apps/mcp-server"
# npmBun server host (@metic/server)
- package-ecosystem: "npm"
directory: "/apps/bun-server"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 3
labels:
- "dependencies"
- "python"
- "javascript"
commit-message:
prefix: "chore(deps)"

# npm — Web frontend (Bun uses package.json)
# npm — shared @metic/core handler
- package-ecosystem: "npm"
directory: "/apps/web"
directory: "/packages/core"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "javascript"
commit-message:
prefix: "chore(deps)"
groups:
dev-dependencies:
dependency-type: "development"
update-types: ["minor", "patch"]
production-dependencies:
dependency-type: "production"
update-types: ["patch"]

# Docker — unified Dockerfile
- package-ecosystem: "docker"
Expand Down
Loading
Loading