[feature] Ship Lapin Futé version 1 #9
- @github-anthodev opened this issue · Aug 29, 2026edited
🔢 Impact Version (SemVer)
MAJOR
🌿 Suggested Branch
feature/ship-version-1/release/9
📝 Description
Ship public
1.0.0of the phone-only D2 architecture anchored by merged PR #17 — merged intodevelopon 2026-09-12 (mergea46bf24a, head commit d894f407 onfeature/redesign-watch-experience/watch/16), not yet a release: deploy the statically hosted bilingual configuration page and its static catalog to stable HTTPS with asset-availability/schema-consistency checks and a rehearsed rollback, while PebbleKit JS keeps direct exact-host PRIM access, the 60-second phone-local cache/single-flight, and the whole credential/security boundary. Execute key lifecycle plus all-mode and traffic production journeys, verify the IDFM license/attribution, and publish package, privacy, coverage, performance, and battery evidence. There is no hosted backend: the merged D2 build removedpackages/backend.
✅ Expected Behavior
A new user installs Lapin Futé, enters their own PRIM key in the statically hosted options page, configures six favorites, and consults bus, metro, tram, RER, and Transilien departures plus the already-implemented per-line traffic on the named watch. The key stays phone-local at rest and leaves the phone only as the PRIM
apikeyheader on the companion's direct HTTPS request to the exact PRIM host; no Lapin Futé server holds or relays it; PRIM receives it for authentication. Event-only retrieval, stale recovery, static-host rollback, attribution, and battery budgets are proven.
📋 Acceptance Criteria
- Select/record static HTTPS hosting for the configuration page and catalog, verify served-asset availability (page plus every catalog manifest/search/service/place JSON) and schema consistency (manifest
schemaVersionmatching the static catalog contract, onerevision/sourceRevisionacross the published set), and record a rehearsed rollback to the previous complete revision set; the host serves no credentials and no dynamic API. - Select the current Pebble/Rebble publication channel and packaging/metadata requirements from official documentation.
- Publish the bilingual configuration page with the static catalog beside it, built from a real published catalog via
npm run build:config-site(explicit failure without one, no fixture fallback); the PBW embeds the credential-free configuration-page URL (LAPIN_FUTE_CONFIG_URL). - Confirm the companion owns, phone-locally: direct exact-host PRIM GETs with the
apikeyheader (stop-monitoring departures, bulk disruptions v2 traffic), the shared 60-second cache/single-flight, response-size caps including the 2 MiB traffic ceiling, zero automatic retry, and credential-free errors/metrics/logs; no relay and no shared or server-owned key exist. - Run clean install → enter personal key → configure six favorites → open/select/manual refresh → edit → disconnect/recover → replace key → force a miss with revoked/invalid key → remove key against production, on both emulators (emery and gabbro) and the Pebble Time 2 physical watch.
- Prove the key path: masked configuration-page control → one-time changed-value close fragment → plaintext phone
localStorage→apikeyheader on the direct exact-host PRIM request; request-memory-only in flight; outside the explicit phone-local credential record, excluded from result caches, other persistence, responses, static-host content, logs, metrics, traces, and errors. Neither the watch nor the static host ever receives it. - Prepare icon, French/English screenshots, metadata,
1.0.0, the single publishable Alloy PBW carrying both emery and gabbro platforms, and store submission material. - Publish privacy text covering phone-local favorites and the client result cache; plaintext key in
localStoragesurviving uninstall/reinstall; explicit Remove Key erasure; one-time replacement close fragment; direct phone-to-PRIM access with no backend, no relay, and no watch/server key storage (static hosting only); no accounts and no telemetry. - Verify the current authoritative IDFM/PRIM license before publishing and publish its exact required attribution plus license/source links.
- Publish all-mode coverage and scheduled/real-time/mixed/stale semantics, including the implemented traffic states (NORMAL/DELAYED/STOPPED/UNKNOWN) fetched from the bulk disruptions feed under the same freshness gate.
- Exercise bus, metro, tram, RER, and Transilien plus per-favorite traffic on production; if the current PRIM perimeter temporarily lacks a live example, prove scheduled/recorded fallback and link current coverage evidence rather than dropping the mode.
- Prove app-open/500 ms settled-selection/Select are the only production request triggers; fresh-cache hits and cache-only entry/recovery (trigger 5) spend zero PRIM calls; 429 causes no retry burst.
- Apply issue #8's conditional watch-app-closed lifecycle gate here without a duplicate campaign: reuse its recorded evidence when adequate and still valid; only if a true watch-app-closed observation is missing or invalidated by later changes, run exactly one targeted two-minute idle observation per emulator with the watch app closed, verifying zero PKJS XHR, AppMessage, Wakeup/Worker/timeline, or log activity; the 600-second APP_OPEN idle proof does not count as app-closed evidence.
- Run the frozen 24-hour physical A/B battery scenario on the release build (firmware ≥4.32) on the Pebble Time 2 and meet its budget.
- Scan repository, package, served static assets, captured companion/PRIM HTTP exchanges, screenshots, logs/metrics/traces, and release material: no real/test key or
apikeyvalue. - Record static-host origins, availability/schema checks, and rollback; journey/key-lifecycle/all-mode/traffic proof; quota/event counts; battery/idle evidence; physical captures; redacted secret scans; license/privacy/coverage publications; and release assets before close — attaching the local
var/run artifacts, which are not on GitHub, alongside the PR #17 merged-build evidence.
🔗 Affected Endpoints/Routes
- Exact PRIM hosts called directly by PebbleKit JS with the
apikeyheader and noAuthorization:https://prim.iledefrance-mobilites.fr/marketplace/stop-monitoring(departures) andhttps://prim.iledefrance-mobilites.fr/marketplace/disruptions_bulk/disruptions/v2(traffic). - One stable HTTPS origin serving the bilingual configuration page and the static catalog JSON (manifest, search buckets, services, place pages) built by
npm run build:config-site. - No backend origin, no health-check endpoint, no new user-data endpoint or account surface;
packages/backendno longer exists in the repository.
🧪 Tests to Add
Type Coverage Production smoke Clean install, personal-key enter/replace/revoke/remove, six favorites, three request triggers, edit/disconnect/recover Physical/mode smoke Bus, metro, tram, RER, Transilien plus traffic on the Pebble Time 2 (emery) physical watch, with gabbro emulator evidence and current coverage evidence Security/privacy verification Direct-to-PRIM key path, redaction, no persistence outside the phone-local credential record; repository/package/static-asset/HTTP-exchange/screenshot key scans; close-fragment lifecycle isolated Operations drill Static-site availability and schema-consistency checks with rollback rehearsal; cache/single-flight/quota/no-retry counts; conditional watch-app-closed gate inherited from issue #8 Release verification Reproducible 1.0.0single Alloy PBW (emery+gabbro), French/English assets, current license attribution, privacy and coverage publicationsThe 50-refresh soak and full recovery matrix remain owned by issue #8 and are inherited here, not duplicated.
⚙️ Constraints
- Release version is
1.0.0; every production user supplies a personal PRIM key. - The release artifact is the single Alloy PBW built from the shared embedded-JavaScript codebase for emery and gabbro with firmware ≥4.32; there is no native-C watch-app fallback.
- The six-favorite cap applies uniformly to configuration reads, saves, and synchronization; no legacy 7/8-favorite configurations exist to migrate.
- A real/test key may appear only in newly entered configuration-page control/JS memory, the one-time replacement close fragment, PebbleKit JS handoff/request memory and plaintext
localStorage, and the outbound exact-host PRIMapikeyheader; never in a public artifact, on the watch, in static-host content, server persistence, an HTTP response, screenshot, log, metric, trace, or error. - Favorites remain phone-local; no account, cloud sync, telemetry, backend, relay, polling, Wakeup, Worker, timeline, reminder, or background alert.
- Hosting/publication and the authoritative IDFM data license/attribution are verified from current sources in this ticket; do not assume a provider, obsolete store, or the note's license label.
- Journey planning, nearby stops, national SNCF, native apps, favorite export, and cloud synchronization remain outside this release. The already-implemented traffic surface is in scope exactly as built (bulk feed, per-line summaries, prepared traffic records); do not design richer disruption features here.
🔀 Dependencies
- Blocked by
[feature] Harden failures and compatibility(https://github.com/Anthodev/lapin-fute/issues/8): inherit its conditional watch-app-closed gate, 50-refresh soak, recovery matrix, and battery gates instead of duplicating them. - Consume the frozen contracts and evidence from the closed children of
[Lapin Futé v1 delivery map](https://github.com/Anthodev/lapin-fute/issues/1)(#2–#7 and #16), now realized in the D2 build merged through PR #17.
⚠️ Impact on Existing Behavior
Cuts the emulator fixture campaign over to production PRIM journeys and static hosting, retires the removed hosted backend permanently, and establishes
1.0.0as the public compatibility baseline.
🔍 Technical Context
Architecture anchor: merged PR #17 (head commit d894f407, merge
a46bf24a) onfeature/redesign-watch-experience/watch/16delivered the phone-only D2 build this ticket ships. PebbleKit JS owns direct PRIM HTTP, normalization, the 60-second phone-local cache/single-flight, and display preparation; the configuration page and catalog are static; domain data stays at schema version 1 and display dictionaries at wire version 2 with 19 frozen aliases, the SDK transport key 15025 remaining separate; the fixed 57,344-byte VM split (stack 3,584, slots 41,472, chunks 12,288) carries 768-byte inbox / 192-byte outbox dictionaries and a four-message unsent queue. A changed-favorite DIFF releases results only after a complete valid inventory with a nonzero need mask, retains old metadata until its commit, and its automatic post-commit recovery re-acquires the overview and then the visible detail from the phone cache only (cache-only trigger 5), with no traffic request; no-op/reorder stay warm, FULL stays destructive with manual recovery when the app is already open.Public reference: the merged source and README record the reported 214-test and two-target build results. This is not a claim that the command logs or binaries are attached to this issue. Local only (not on GitHub): native emulator run artifacts under
var/— both targets' fixture flows (startup, six-favorite synchronization, detail/traffic views, unchanged/reordered/changed DIFF with loading and cache-only recovery, FULL manual recovery), retained reopen, 600-second APP_OPEN idle with zero AppMessages/fixture calls, and the bounded compacted-live chunk-arena reserve (11.9466% Emery, 10.9701% Gabbro). Attach them to this issue before close. They are bounded samples: not exact transient/lifetime memory peaks, not the 50-refresh soak, and no physical watch/phone or 24-hour battery proof exists yet.Required references
- Select/record static HTTPS hosting for the configuration page and catalog, verify served-asset availability (page plus every catalog manifest/search/service/place JSON) and schema consistency (manifest
Sign in to comment.