feat: upgrade the captain bridge with photos and hold-to-speak - #90
Merged
Conversation
Let the phone picker send several photos, talk to firstmate through the existing mailbox without exposing the relay token, and show human titles with a full expandable waiting list. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Upgrade the captain's iPhone-facing Starship bridge page (bin/fm-bridge-view.py) to v1.5 with three requested improvements: multi-photo upload, hold-to-speak, and a more readable captain-first glance.
Multi-photo: the photo picker must accept multiple images in one picker session (multiple on the file input). Sequential posts per image are fine. Every image must still land as an atomic metadata+image pair in the bridge inbox exactly as single uploads do (same naming and write-then-rename discipline). Show simple progress such as "3 of 5 sent". One photo failing must be reported, not silently dropped, and must not abort the rest.
Hold-to-speak: add a press-and-hold microphone button so the captain can talk to firstmate from the bridge page as a second voice channel besides the VoiceLoop app. MediaRecorder while held; on release POST the audio to the bridge server, which forwards it into the local glasses mailbox as a new pending audio request (the same request pipeline VoiceLoop uses). Mark source "bridge" so it is distinguishable; the mailbox HTTP API and its relay token are the integration seam. The relay token stays SERVER-SIDE in the bridge process and must never ship to the browser. All new endpoints sit behind the existing passcode session. iOS Safari records audio/mp4 (m4a), not webm - whatever Safari actually produces must survive the pipeline (the Mac-side transcriber already handles m4a from VoiceLoop). After sending, show "sent" and poll for the answer; when answer text arrives, render it on the page. Text only in v1.5; no audio playback.
Page readability: restructure for a phone screen in plain outcome language. Top: anything waiting on the captain (decisions, reviews), then active work, then a COLLAPSED waiting section that expands to the full list on tap (no arbitrary truncation). Item text short and human; never raw ids/paths/repro strings as the primary line. Fast and dependency-free (no external CDNs; the page is tailnet-only).
Constraints that must not be regressed: iPhone Safari is the only client that matters. It omits/nulls Origin on same-origin POSTs (fixed in PRs #86/#87). Every new endpoint must accept client-shaped requests (Origin: null, Safari-shaped multipart/audio). Do not restart, reinstall, or point any launchd service at this worktree; the live bridge stays anchored to the permanent home. Do not change the mailbox service or its database schema beyond additive request fields. Login, single photo, and fleet view must still work. Colocate tests covering the new upload and audio endpoints (auth required, atomic writes, multi-file, Origin: null). Distinguishable bridge-origin audio may use the existing mailbox filename field (bridge.m4a) rather than a new schema field, because VoiceQuestion rejects unknown keys.
What Changed
Risk Assessment
✅ Low: The latest commit correctly restores the required captain-first ordering, and the full base-to-HEAD review found no remaining material defects or intent conflicts.
Testing
The complete bridge integration suite passed, covering login and existing behavior, authenticated Origin-null multi-photo uploads with atomic pairs and partial-failure continuation, Safari-shaped m4a relay and text-answer polling, token secrecy, and browser interaction state; manual 390×844 rendering confirmed the captain-first layout and expandable full waiting list. The screenshot command did not persist its output, so reviewer-visible evidence is supplied as interactive rendered HTML instead.
Evidence: Interactive 390px Starship v1.5 phone UI evidence
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed (2) ✅
bin/fm-bridge-view.py:1098- Required criterion: “a COLLAPSED waiting section that expands to the full list on tap (no arbitrary truncation).” The diff setsFM_BEARINGS_GATESto 1000 instead of invoking the snapshot’s existing--all-queuedoption, so more than 1000 queued items are silently omitted while the response reportsmore: 0. Use--all-queuedso the expanded section is genuinely complete.bin/fm-bridge-view.py:1360- Required criterion: “MediaRecorder while held; on release POST the audio.”startSpeakawaitsgetUserMediabefore assigningspeakRecorder, whilefinishSpeakimmediately returns when no recorder exists. If the captain releases during the permission prompt or before the async request resolves, recording begins after release and continues indefinitely because that release was lost. Track the held/pointer state across the await and cancel or immediately stop when release occurred.bin/fm-bridge-view.py:1478- Required criterion: “Show simple progress such as ‘3 of 5 sent’.” The diff displays(i + 1) of N sentbefore that photo’s request begins, so it can claim a photo was sent while still uploading or before it fails. Update progress after each successful response, using a neutral “Sending X of N” message while the request is pending.🔧 Fix: Fix complete waiting, speech release, and upload progress
1 error still open:
bin/fm-bridge-view.py:1633- Required criterion: “Top: anything waiting on the captain (decisions, reviews), then active work, then a COLLAPSED waiting section.” The changed markup inserts the Talk and Send photos controls betweenNeeds youandUnder way, so active work is pushed below both input panels rather than following the captain-needed section. Move the controls after the three glance sections, or confirm that this ordering is intentionally being relaxed.🔧 Fix: Order captain glance before communication controls
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-bridge-view.test.shRenderedbridge-v1.5-phone.htmlat a 390×844 iPhone-sized viewport using the actual bridge HTML, CSS, and JavaScript.Expanded the collapsed “Waiting · 4” section and verified all four human-readable items appeared without truncation.Visually verified captain-first ordering, hold-to-speak controls, multi-photo picker, photo count, and dependency-free phone layout.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.