Docs: textfield-centering pitfall + config-source / FCM channel conventions

- mitra_app/CLAUDE.md: pitfall entry for the InputDecorationTheme
  min-height collision that broke chat-input centering. Walks through
  the working recipe (constraints: BoxConstraints(), Material +
  StadiumBorder + Center wrapper). Points at chat_screen.dart::_InputBar
  in both apps as the source of truth.
- backend/CLAUDE.md: two new convention sections.
  - Config-source: when to use DB-stored (operator-tunable via CC) vs
    env-driven (deploy-fixed). Codifies the pattern shipped today for
    MITRA_HEARTBEAT_CADENCE_SECONDS so Xendit credentials / callback
    tokens follow the same shape tomorrow.
  - FCM channel: single shared `halobestie_chat_v1` channel for both
    apps, target via android.notification.channelId. Bump the channel
    ID when introducing a new sound (Android API 26+ binds sound at
    channel-create time).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 22:38:50 +08:00
parent 387f0f65de
commit bfb072ddfb
2 changed files with 45 additions and 0 deletions

View File

@@ -48,3 +48,22 @@ Internal listener must never be exposed to the public internet.
- Use Fastify plugins for shared middleware (auth, error handling, logging)
- Business logic lives in `services/` — never directly in route handlers
- Never reintroduce Firebase Auth. `firebase-admin` is FCM-only; do not import `.auth()` from it.
## Config-Source Convention
Two distinct knob-types exist; do not conflate them:
- **DB-stored** (`app_config` table, mutable via CC SettingsPage at runtime): used for operator-tunable values that may change between deploys without a code roll — `mitra_stale_after_seconds`, `extension_timeout_seconds`, `pricing_tiers`, `support_handles_json`, `max_customers_per_mitra`, etc. Read via getters in `services/config.service.js`. Cache invalidation goes through `valkey` pub/sub when needed.
- **Env-driven** (`process.env`, set per deployment via `.env` or Cloud Run env vars): used for deploy-fixed values that should never differ between operator actions — `MITRA_HEARTBEAT_CADENCE_SECONDS`, `FIREBASE_SERVICE_ACCOUNT_PATH`, `AUTH_JWT_SECRET`, `DATABASE_URL`. Always expose via a getter helper with a sane default + numeric parsing (see `getMitraHeartbeatCadenceSeconds` in config.service.js for the pattern).
When a new value needs to flow from CC → app, prefer DB. When it's a deploy-fixed contract (e.g. heartbeat cadence the apps must honor, Xendit credentials, callback tokens), prefer env. CC inputs that depend on env values (e.g. min/max validation) read the env-derived value via the same config endpoint that surfaces the DB value, and the PATCH route validates against it.
## FCM Channel Convention
Single channel `halobestie_chat_v1` is shared by both apps (registered in each app's `core/notifications/notification_service.dart`) and ships the branded `halobestie_notif.ogg` sound. Backend FCM payloads should always target this channel ID via `android.notification.channelId`:
```js
android: { priority: 'high', notification: { channelId: 'halobestie_chat_v1' } }
```
Do not introduce per-recipient or per-feature channels lightly. If a new sound is required (e.g. payment alert), bump the channel ID (`halobestie_chat_v2`) and update both apps simultaneously — Android binds channel sound at create-time on API 26+, so mutating the existing channel doesn't pick up the new sound for installed users.