[feature] Establish the application foundation and contracts #2
- @github-anthodev opened this issue · Aug 29, 2026edited
🔢 Impact Version (SemVer)
MINOR
🌿 Suggested Branch
feature/application-contracts/foundation/2
📝 Description
Lapin Futé is a greenfield Pebble application. Establish one reproducible repository foundation for the single Alloy embedded-JavaScript watch app (
src/embeddedjson the RePebble Alloy XS runtime plus only the standard Pebble VM bootstrap C file), the PebbleKit JS companion, the TypeScript backend, and the mobile configuration page, then freeze the shared data, credential, performance, and radio-message contracts before work fans out.This ticket owns the Fedora-compatible toolchain (RePebble Alloy with Pebble SDK 4.33.1 and pebble-tool 5.0.40, firmware floor 4.32+), the emery+gabbro platform matrix with emery primary and the Pebble Time 2 physical release watch, bilingual resources, personal-PRIM-key boundaries, event-driven request contract, measurable performance/battery budgets, and first PebbleKit JS ↔ watch round trip.
✅ Expected Behavior
A clean clone builds with one documented repository command. The primary emulator renders a recorded departure fixture, PebbleKit JS sends one complete favorite/result through the frozen AppMessage contract without sending the personal PRIM key, and the watch rejects unsupported schema versions without replacing its last complete state. Baseline resource/radio/battery measurements and pass/fail budgets exist before later tickets add work.
📋 Acceptance Criteria
- Create the Alloy embedded-JavaScript watch application —
src/embeddedjsmodules (main,controller,model,protocol,message-queue,localization,ui,contracts, all listed insrc/embeddedjs/manifest.json) plus only the standard Alloy VM bootstrap C file — alongside the PebbleKit JS companion, TypeScript backend, and configuration-page packages in one repository; keep each component inside the ownership boundaries described below. - Pin Pebble SDK 4.33.1, pebble-tool 5.0.40, and the RePebble Alloy runtime with firmware floor 4.32+ building reproducibly on Fedora; record installation prerequisites and focused build/emulator commands in the repository.
- Record the frozen platform matrix — emery (Pebble Time 2) as the primary emulator plus gabbro (Pebble Round 2) as the second emulator, with the Pebble Time 2 (emery) as the named physical release watch — that
[feature] Deliver Pebble watch consultation(https://github.com/Anthodev/lapin-fute/issues/7),[feature] Harden failures and compatibility(https://github.com/Anthodev/lapin-fute/issues/8), and[feature] Ship Lapin Futé version 1(https://github.com/Anthodev/lapin-fute/issues/9) must use for proof. Base the choice on verified Alloy platform support (emery and gabbro) and available hardware, not an assumed device. - Freeze
Favorite,DepartureResult,ErrorResult, and phone-onlyApiKeyUpdateas versioned contracts with every field/enum listed in Technical Context; addAPI_KEY_REQUIREDandAPI_KEY_INVALIDto the stable error codes. - Freeze client→backend
Authorization: Bearer <personal PRIM key>and backend→PRIMapikeyforwarding, including exact scheme, bounded UTF-8 value length, mandatory redaction, fixed timeout/response-size limits, and exact-host rules. - Freeze the numeric AppMessage key map 0–24 with symbolic runtime aliases:
packages/watch/package.jsondeclares an explicitmessageKeysobject mapping each symbolic alias to its frozen number (the array-style default, whose keys start at 10000, is forbidden), runtime payloads send only the symbolic aliases, and the build proves alias→numeric resolution. Include only non-secretkeyStatus(MISSING | CONFIGURED | INVALID), payload budgets for every platform, maximum favorite/result sizes, callback-chained sequencing, and atomic commit. - Freeze the event-driven request contract: app open, active-favorite selection after a 500 ms settling debounce, and manual Select only; 60-second fresh-cache reuse; no periodic timer, Wakeup API, Worker, timeline refresh, background request, or immediate retry loop.
- Measure per-platform binary size (≤120 KiB), loaded image/RAM (≤56 KiB), XS arena headroom (≥25% free with settled drift ≤1 KiB), computed AppMessage buffers, messages/bytes per fixture refresh, and a physical-watch battery baseline; these shared Alloy/XS/Piu/Message gates replace the C-specific buffer/heap gates while the radio/battery gates stay unchanged. Freeze pass/fail budgets plus the exact repeatable 24-hour A/B scenario (firmware/app build, watch model, connectivity/backlight, idle time, and scripted event counts) used by hardening/release.
- Freeze French/English authored-copy handling: the companion derives the normalized active-watch language and reuses the
DISPLAY_NAMEkey contextually onCONFIG_BEGINto deliver the exactfr/enresource set to the watch; French selects French and every other locale falls back to English; user-authored favorite labels and backend transport labels are never translated. - A clean clone builds with the documented repository command.
- The primary (emery) emulator displays a recorded
DepartureResult. - PebbleKit JS sends one
Favoriteand oneDepartureResultthrough the frozen contract. - The watch rejects an unsupported schema version and preserves its last complete state.
- Record links to build/emulator output, contract/redaction tests, resource/radio/battery baselines and budgets, message-round-trip proof, and unsupported-version proof in a resolution comment before closing this issue.
🔗 Affected Endpoints/Routes
- Freeze the departure-route credential/error interface for later backend fixtures; do not make a live PRIM call or store a personal key in this ticket.
- No user-account, credential-vault, or secret-bearing AppMessage route is permitted.
🧪 Tests to Add
Type Coverage Contract tests Every shared field/enum, phone-only ApiKeyUpdate, credential/error semantics, numeric AppMessage key, schema rejection, request/favorite binding, maximum payloadSecret-boundary tests Exact lifecycle: newly entered page memory → close fragment → PKJS handoff/request memory + localStorage → HTTPS Authorization → relay memory → PRIM header; absent elsewhere PebbleKit JS/Alloy tests Favorite/result encoding, keyStatusonly, versioned round trip, last-state preservation, symbolic-alias → numeric 0–24messageKeyspackage/build proofPerformance baseline Clean Fedora build, per-platform Alloy/XS/Piu/Message gate measurements (binary, loaded image/RAM, XS arenas, computed AppMessage buffers), zero periodic/background event path, controlled physical-watch battery baseline
⚙️ Constraints
serviceIdis opaque outside the backend; a configuration update replaces the whole favorite list atomically.- The only key lifecycle is: newly entered configuration-page control/JS memory → one-time changed-value close fragment → PebbleKit JS handoff/request memory plus plaintext localStorage → HTTPS Authorization → transient backend request/upstream-call memory → exact-host PRIM
apikey. The fragment is not HTTP. Never path/query, favorites/results, AppMessage/watch, backend persistence/cache, logs/metrics/traces/errors, or public artifacts. requestIdandfavoriteIdbind a response to the request that produced it; the watch discards a late response for another request or favorite.- AppMessage buffers are computed from measured dictionaries and runtime platform maxima, not hard-coded folklore; messages chain on
outbox_sent. - No recurring second/minute network timer exists. A minute tick may update displayed countdown/age only while the view is active and must never trigger network or Bluetooth traffic.
- Keep source code, issue text, logs, and technical documentation in English; only authored product copy is localized.
🔀 Dependencies
None. This is the first implementation ticket.
⚠️ Impact on Existing Behavior
Greenfield repository; there is no existing application behavior to preserve.
🔍 Technical Context
Architecture ownership
- Backend: IDFM lookup, transient personal-key relay to the exact PRIM host, external validation/normalization, and non-secret shared fresh caching; no credential/user-preference persistence or Pebble rendering.
- Configuration page: search/editing plus masked key enter/replace/remove; receives only
hasKey, never the stored key on reopen, and owns no persistent secret/watch state. - PebbleKit JS: one atomic phone-local configuration containing favorites and the personal key, event-driven HTTPS requests, and watch synchronization; no SIRI parsing or watch layout.
- Watch: one Alloy embedded-JavaScript app (Piu UI, built-in fonts,
touchCount0) with button input, non-secretkeyStatus, compact state, and rendering; no key, stop search, external API, animations/media/networking/WakeUp/Worker/timeline polling, or secret storage.
Favorite
schemaVersion,id, opaqueserviceId, optionaldisplayName,stopLabel,lineLabel,destinationLabel,sortOrder.DepartureResult
schemaVersion,requestId,favoriteId,fetchedAt, optionalsourceUpdatedAt,freshness(REALTIME | SCHEDULED | MIXED | STALE),departures[]withexpectedAt, optionalaimedAt,minutes,status(ON_TIME | DELAYED | CANCELLED | UNKNOWN), and optionalnextIntervalMinutes.ApiKeyUpdate (phone-only; never AppMessage)
schemaVersion,action(KEEP | REPLACE | REMOVE), optionalvaluepresent only forREPLACE.ErrorResult
schemaVersion,requestId, optionalfavoriteId,code(API_KEY_REQUIRED | API_KEY_INVALID | INVALID_SERVICE | SOURCE_UNAVAILABLE | RATE_LIMITED | INVALID_RESPONSE),occurredAt, and optional sanitized numericretryAfterSeconds.Required references
- Create the Alloy embedded-JavaScript watch application —
Sign in to comment.