Skip to content

Android: fix localhost launch, login→Menu Studio, edge-to-edge, native print bridge (BT Classic/USB) - #350

Merged
baba-dev merged 11 commits into
mainfrom
claude/menuminute-android-analysis-6t80ut
Nov 10, 2026
Merged

baba-dev merged 11 commits into
mainfrom
claude/menuminute-android-analysis-6t80ut

Conversation

@baba-dev

@baba-dev baba-dev commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Outcome

Fixes the Android app opening localhost and moves the app toward a launch-ready build, following the owner's decisions of 2026-11-10: launch login then Menu Studio, edge-to-edge layout, and Bluetooth Classic + USB printers as a launch requirement. Related: #340.

  1. Hotfix. GET /api/app/launch returned Location: https://localhost:3100/app (Next derives request.url from the internal HOSTNAME=127.0.0.1:3100 listener behind Nginx). It now returns a relative /app. Reproduced on a standalone Next 16.4.5 server and observed live.
  2. Launch journey. /app sends signed-in merchants with an outlet to /dashboard/menu; signed-out users go to /login?next=%2Fapp and return.
  3. Edge-to-edge. viewport-fit=cover, side and top safe-area padding, status-bar scrim in installed display modes, sticky headers below the top inset (13 CSS blocks + 3 Tailwind classes). Insets are 0 in a normal browser tab.
  4. Native shell. Adaptive + themed (monochrome) icon from the approved mark, splash, status/navigation bar colours, notification small icon, launcher label MenuMinute, notification permission, merchant routes added to App Links (diner /m/... stays in the browser).
  5. Native print bridge. PrintBridgeActivity with Bluetooth Classic (SPP) and USB transports. The web session mints a 120 s HMAC grant bound to user/session/outlet/sale/paper; the app redeems it at a fixed HTTPS endpoint for ESC/POS bytes produced from the same receipt mapping as the page (extracted to thermal-view.ts). Redemption re-checks the live session and current membership. Printing needs an explicit tap. Design and threat model: docs/android-release/print-bridge.md.
  6. Launch tests. scripts/android/verify-launch-contract.sh (redirect, cookie, login page, assetlinks) plus an emulator UI classifier so the smoke test can no longer pass on an error page. Strict on manual and daily runs; a warning on PRs because PRs run against the currently deployed server.
  7. Docs. mobile-production-plan.md (analysis, phased plan, status table incl. the external audit), print-bridge.md, README and device matrix updates.

Contract and ownership

Paths: src/app/api/{app/launch,pos,native-print}, src/modules/pos/{native-print,thermal-view}.ts, src/lib/pos, receipt page, src/styles/globals.css and sticky-header CSS, android/**, scripts/android/**, one workflow, docs. No schema, migration, lockfile or env-var change. AUTH_SECRET is reused with a domain-separated derivation. Triggering a native change means the signed-AAB workflow will build a new candidate after merge.

Correctness and recovery

  • Grant is not authorization: every redemption re-checks session activity and outlet membership; fiscal documents are refused. Cookie semantics of the Play marker are unchanged.
  • Rollback: revert; no data or schema is touched. The new native code only runs when the web app launches it.
  • New endpoint POST /api/native-print/receipt is cookie-less by design (bearer grant, 4 KiB body cap). No dedicated rate limit yet; the grant expiry and a single-sale scope bound the exposure.

Evidence

  • Local: tsc --noEmit, ESLint, Prettier, git diff --check clean. 10 suites / 50 tests pass DB-free (grant sign/verify/expiry/tamper/revocation, intent URL, Android manifest contract, UI classifier, launch route, PWA, thermal receipt). The DB-backed suite was not run locally (no Postgres); CI covers it.
  • Local Gradle assembleDebug + bundleRelease succeed (compileSdk 36); aapt2 dump badging confirms label, adaptive icon, permissions and activities. Icons rendered and visually checked, including a fixed monochrome cut-out bug.
  • Live production still returns the localhost redirect (this fix is not deployed); verify-launch-contract.sh fails on it today and passes the other checks.

Not verified (honest limits)

  • No physical printer, phone or emulator run. Bluetooth Classic and USB printing, the splash, bar colours, edge-to-edge behaviour in a TWA, notification permission and App Links are unverified on a device.

  • Whether Chrome draws TWA content behind the system bars depends on its version; the web layer is ready, the behaviour needs checking on Android 10/13/15/16.

  • Branch protection on main (audit finding) is an owner action. Play Billing remains disabled.

  • Scope and invariants match the issue; current versus target behavior is explicit.

  • Changed docs formatting, local links and git diff --check pass.

  • Applicable runtime/browser/Android checks pass on final SHA: rely on CI.

  • Independent review evidence recorded.

Delivery

Merge is not a deployment. After the gated web deploy: run scripts/android/verify-launch-contract.sh (must pass), then install the new signed Play candidate on a real device and walk device-test-matrix.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe

…/localhost:3100

Behind Nginx the standalone server listens on HOSTNAME=127.0.0.1 PORT=3100 and Next
derives request.url from that listener, so new URL('/app', request.url) emitted
Location: https://localhost:3100/app and the Play TWA left the verified origin.
Reproduced on a Next 16.4.5 standalone server and observed live on production.
A relative Location is resolved by the client against the origin it requested.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
Signed-in merchants with an outlet land on /dashboard/menu; signed-out users sign in and return to /app.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
viewport-fit=cover, side and top safe-area padding, a status-bar scrim in installed display modes
and sticky headers that stick below the top inset. No effect in a normal browser tab (insets are 0).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
…printing

Short-lived HMAC grant bound to user, session, outlet, sale and paper width. Redemption re-checks the
live session and current membership and returns ESC/POS bytes from the same receipt mapping the page
uses (extracted to thermal-view.ts). Adds the receipt-page entry point for the Android print bridge.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
…bridge

Adaptive + themed icon from the approved brand mark, splash, system bar colours, notification small icon,
short launcher label, wider merchant App Links, notification permission, and PrintBridgeActivity with
Bluetooth Classic (SPP) and USB transports. Not yet verified on physical printers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
… a crash

Adds a deterministic launch-contract script (redirect, cookie, login page, assetlinks) and a UI
classifier for the emulator smoke test. Strict on manual and daily runs, warning on pull requests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
@baba-dev baba-dev changed the title fix(android): TWA launch redirected to https://localhost:3100 + mobile production plan Android: fix localhost launch, login→Menu Studio, edge-to-edge, native print bridge (BT Classic/USB) Nov 10, 2026
…fixture finding

Gitleaks flagged a fake AUTH_SECRET literal in the native-print grant test (not a credential).
Follows the existing .gitleaksignore convention: ignore that fingerprint, generate it at runtime after.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
The repo's API boundary guard forbids unbounded req.json(). Read at most 256 bytes via boundedBytes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe
…the login page

The first CI emulator run reported 'good:sign in' on Chrome's own 'Welcome to Chrome / Sign in to
browse easier' screen. Handle the first-run screen first (dismiss it), and accept only MenuMinute-
specific login text. Regression test uses the exact screen captured in CI.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018caQbzeYyb3uEGoW7Sbthe

Copy link
Copy Markdown
Owner Author

CI status on 6bca6cf: all 21 checks green.

One flake, not from this PR: Production Browser Shard 3/6 failed on its first attempt in tests/e2e/menu-product.spec.ts:641 ("Tabletop Press is responsive…"). Playwright found two QR inventory at a glance sections where the test expects one (strict-mode violation). It failed twice in that run, passed in both earlier runs of this PR, and passed on a single re-run of the failed job. The commit under test only changed scripts/android/* and one unit test, and qr-manager.tsx renders that section once, so this looks like a timing race in the QR studio navigation rather than something this change caused. I did not change or skip the test. It is worth a look by whoever owns the QR studio.

Emulator evidence (useful for review): the Android smoke job now reports its destination verdict. On this run it dismissed Chrome's first-run screen, followed the app's real launch URL and reported bad: page is on an internal address (localhost): the screen showed localhost:3100 and ERR_CONNECTION_REFUSED. That is the live production bug this PR fixes, seen on a real emulator. On pull requests the destination check only warns, because production does not yet have the fix; the manual and daily runs are strict and will fail until the fix is deployed.


Generated by Claude Code

@baba-dev
baba-dev marked this pull request as ready for review November 10, 2026 12:45
@baba-dev
baba-dev merged commit 31ffcdd into main Nov 10, 2026
38 of 40 checks passed
@baba-dev
baba-dev deleted the claude/menuminute-android-analysis-6t80ut branch November 10, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants