Scout — Full Product Context → feature documentation

Submit a patch

A user-facing form that proposes a real-world place for the catalog: a photo, a name, the device's coordinates, an optional collection and optional notes.

Summary

A user-facing form that proposes a real-world place for the catalog: a photo, a name, the device's coordinates, an optional collection and optional notes. It posts one multipart request and lands a PatchSubmission row in the admin review queue. Nothing about it reaches the catalog until an admin promotes it.

It is a proposal surface, not an authoring one — the submitter picks no type, sets no geofence, and never learns what happened to their submission.

Status

Built, reachable, and FLAGGED OFF. submit_patch has defaultEnabled: false in both registries — mobile mobile/src/config/feature-flags.ts:33 and backend backend/src/admin/feature-flag-definitions.ts:40 — and the backend's own description says why: "Off until the submission review pipeline is ready — the admin Submissions queue is unaffected."

With the flag off, the drawer row is hidden and the screen itself redirects home (mobile/app/submit-patch.tsx:354). The admin queue and the POST /patch-submissions endpoint are not gated by the flag; only the mobile entry points are.

User-facing surfaces

Surface Where
/submit-patch mobile/app/submit-patch.tsx, deep link scout://submit-patch
Drawer row mobile/src/components/navigation/drawerSections.ts:362, gated on the flag
Home FAB action Gone. No FAB renders on Home — mobile/app/(drawer)/index.tsx has no FAB and nothing outside the drawer reads submitPatchEnabled. The drawer row is the only tap target
Signed-out bounce /auth?returnTo=/submit-patch — the endpoint requires a session
Admin queue /admin → Submissions

It is NOT reached from Settings. Settings → Help & feedback → "Send feedback or suggest patches" goes to mobile/app/(drawer)/feedback.tsx?type=suggestion, a different screen that files a Feedback row, not a PatchSubmission.

How it works

  1. The screen gates on the flag before rendering anything (app/submit-patch.tsx:229): flags-loading while the server answer is in flight, disabled<Redirect href="/" />, ready otherwise.
  2. The user attaches a photo, types a name, and the screen reads coordinates off the Zustand store (the current device fix — the screen has no location UI of its own). An optional collection comes from the synced catalog; an optional free-text collection name is offered when none fits.
  3. canSubmit requires a photo, a trimmed name of at least NAME_MIN_CHARS (3), a fix, and no submission already in flight (submitPatchFormState, app/submit-patch.tsx:78).
  4. On submit the photo is compressed, packed into a FormData with name, latitude, longitude, and any of collectionId / suggestedCollectionName / userNotes, and posted to POST /patch-submissions (mobile/src/api/submissions.ts).
  5. Both outcomes are a native Alert — "Submission received" or "Submission failed". Nothing on the screen changes.
  6. An admin reviews the queue and promotes a submission into a real patch, or dismisses it.

Data model

PatchSubmission (backend/prisma/schema.prisma:290):

Field Note
userId who proposed it
name VarChar(100) — the mobile field's maxLength matches
latitude / longitude Decimal(10,7), the device fix at submit time
photoUrl, photoBlurhash the compressed upload on S3
collectionId optional, SetNull — deleting a collection orphans the submission, never deletes it
suggestedCollectionName VarChar(60), free text when no collection fits
userNotes VarChar(500) — matches NOTES_MAX_CHARS
promotedAt / dismissedAt both null = still queued

API surface

Endpoint Auth Note
POST /patch-submissions JwtAuthGuard — a session is required multipart; the only write the app makes
GET /api/admin/patch-submissions AdminGuard the queue
GET /api/admin/patch-submissions/:id AdminGuard one submission
POST /api/admin/patch-submissions/promote AdminGuard + ContentWriteGuard creates the patch

ContentWriteGuard is the content-editing gate (backend/src/admin/content-write.guard.ts:18), so promotion only works where CONTENT_EDITING_ENABLED=true — i.e. locally. On prod it throws CONTENT_READ_ONLY. A promoted patch is authored locally and then published, like every other piece of content. See backend/docs/CONTENT_PUBLISH.md.

Key files

Configuration and flags

Edge cases and known limits

What this feature does NOT do

Tests that cover it

Open questions