feat: add one-tap photo uploads to bridge view - #85
Merged
Conversation
Let the captain send photos from a phone on the existing glance page while keeping every other route read-only and quarantining files in data/bridge-inbox/. 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
Add a one-tap photo drop to the existing Starship bridge view so the captain can send photos from his phone (for example scoring photos of a board game through his glasses) with one tap.
The bridge view (merged via #84: bin/fm-bridge-view.py, tests in tests/fm-bridge-view.test.sh, docs in docs/bridge-view.md) stays the same file/module conventions and the same passcode login. Add to that existing server:
Out of scope: any processing of the photos (firstmate reads the inbox itself), push notifications, non-image files.
Delivery is the no-mistakes pipeline through a PR. Do not use --yes. Ask-user findings belong to firstmate, not the implementation worker. Redeploy the live bridge service (launchctl kickstart of the slice-1 service label) only after the PR is green and merged - not at checks-green before merge.
What Changed
data/bridge-inbox/directory.Risk Assessment
✅ Low: The follow-up correctly resolves the atomic image-plus-sidecar publication failure, adds targeted regression coverage, and introduces no new material risks in the reviewed branch diff.
Testing
The complete bridge behavior suite passed, including existing read-only protections and upload authentication, 413/415/429 handling, rate limiting, atomic uniqueness, sidecars, and counts; a manual authenticated HTTP upload then returned success and persisted the expected quarantined pair, while the phone-sized authenticated page was preserved as rendered HTML because the screenshot utility reported a path but did not materialize an image file.
Evidence: Authenticated mobile glance page with photo-drop UI
Evidence: End-to-end authenticated upload and persisted inbox evidence
Authenticated phone upload returned HTTP 200 with {"ok": true, "received_today": 1}; the quarantined inbox contained a uniquely named JPEG and JSON sidecar, directory mode 0700, correct metadata, and preserved JPEG bytes.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed ✅
bin/fm-bridge-view.py:355- Required intent says uploads must “write atomically into data/bridge-inbox/ … with … a sidecar .json.” The changed code publishes the image first (os.link(tmp, dest)) and sidecar second (os.link(tmp_json, side)); if the second link fails with an OSError other than FileExistsError (for example ENOSPC), the published image is left orphaned. Do not merge without deciding how the image/sidecar pair must be exposed atomically or rolled back on every failure.bin/fm-bridge-view.py:1277- Several early upload rejections return without consuming the declared request body or settingself.close_connection = True(including unauthorized and rate-limited requests). Under HTTP/1.1 the handler can then interpret remaining multipart bytes as another request on the same connection, corrupting keep-alive behavior. Explicitly close the connection for every pre-read rejection, or drain the bounded body before responding.bin/fm-bridge-view.py:1314- The declared content type is only checked against the allowlist and never compared with the sniffed format. A PNG declared asimage/jpegis accepted, stored with a.pngextension, but its sidecar recordsimage/jpeg; downstream inbox consumers therefore receive contradictory metadata. Reject mismatches or record the verified media type.🔧 Fix: Ensure atomic inbox pair publication, captain
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
git diff --stat/--name-only d9c9f7512c96bdacc7a650c169147c7629be72c8..e498ba992929794525e2e16717d00823aa0b2c85bash tests/fm-bridge-view.test.shStartedbin/fm-bridge-view.sh serve --host bridge.test.example --port 18766against an isolated evidence home, authenticated throughPOST /login, then uploaded a magic-valid JPEG through multipartPOST /uploadInspected the resultingdata/bridge-inbox/image, JSON sidecar, unique timestamped names, directory mode, metadata, and stored bytesRendered the authenticatedglance_htmlat a 390×844 mobile viewport and inspected the visible file chooser, Send photo button, result area, and daily countbin/fm-bridge-view.py:68- The endpoint acceptsimage/heif, but the authoritative intent permits only JPEG, PNG, HEIC, and WebP. Resolving this requires executable and test changes outside this documentation-only task.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.