Separate venue island from locality in admin and public grouping (#134) - #137
Merged
Merged
Conversation
The free-text locationName field doubled as island identity and locality, which let localities like "Philipsburg" or "Windwardside" surface as public island groups. Venues now carry a canonical `island` field (saba | sxm | statia) that drives /where-to-buy grouping, the island filter, and the card's composed "locality, island" line; `locationName` is now a pure free-text locality. Reads keep the legacy locationName inference as a transitional fallback until the migrate:venue-islands backfill runs. - lib/venue-islands.ts: canonical key/label/options vocabulary shared by the admin select, rules allowlist, and public display - Admin venue form: required Island select + separate locality input; legacy records preselect the inferred island; save rejects non-canonical values - firestore.rules: venue create/update requires a canonical island - scripts/migrate-venue-islands.ts: dry-run/--write backfill; ambiguous or conflicting records are reported, never guessed - Venue card: "Philipsburg, Sint Maarten"-style jurisdiction-aware display; analytics island param is now the stable canonical key Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideThe PR replaces free-text venue location grouping with a canonical, validated island field while retaining a legacy-read fallback, updates admin/public presentation and analytics accordingly, enforces the allowlist in Firestore rules, and supplies a dry-run-safe transactional migration plus comprehensive regression coverage. Sequence diagram for legacy venue migration and admin savesequenceDiagram
actor Admin
participant Dashboard
participant Filters
participant Firestore
participant Rules
Dashboard->>Firestore: load venue
Dashboard->>Filters: resolveVenueIsland(venue)
Filters-->>Dashboard: canonical island or undefined
Admin->>Dashboard: select island and edit locality
Dashboard->>Dashboard: isVenueIsland(venueForm.island)
Dashboard->>Firestore: setDoc venue with island and locationName
Firestore->>Rules: validate island
Rules-->>Firestore: allow canonical key or reject write
Flow diagram for safe venue island migrationflowchart TD
Start[Scan venues] --> Resolve["resolveVenueGeography(locationName)"]
Resolve --> Classified{Classified without conflict?}
Classified -->|No| Report[Report ambiguous or conflicting record]
Classified -->|Yes| Plan[Add island and locality to update plan]
Plan --> WriteFlag{--write supplied?}
WriteFlag -->|No| DryRun[Print dry-run plan]
WriteFlag -->|Yes| Verify[Re-read venue in transaction]
Verify --> Changed{Still matches plan?}
Changed -->|No| Skip[Skip changed record]
Changed -->|Yes| Apply[Write island and locality]
Report --> Done[Finish without guessing]
DryRun --> Done
Skip --> Done
Apply --> Done
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
This branch was successfully deployed
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.
Summary
The venue model overloaded free-text
locationNamefor two concepts: the island identity used for/where-to-buygrouping/filtering, and the locality (Windwardside,Fort Bay,Philipsburg). Valid locality text could accidentally become a public island group. This PR separates them: a required canonicalislandfield (saba|sxm|statia) drives all grouping, whilelocationNamebecomes a pure free-text locality.Closes #134
Changes
lib/venue-islands.ts(new): single canonical vocabulary —VENUE_ISLAND_KEYS, public labels (saba→Saba,sxm→Sint Maarten / Saint Martin,statia→Sint Eustatius / Statia), short card labels (Philipsburg, Sint Maarten, never…, Sint Maarten / Saint Martin), admin select options,isVenueIslandguard.lib/types.ts:Venue.island?: VenueIsland(optional because legacy docs predate it).lib/venue-filters.ts:venueIslandKey()prefersvenue.island, falls back to legacylocationNameinference (transitional — retained until every record is migrated);distinctIslands/groupVenuesByIsland/filterVenuesall use it.resolveVenueIsland()preselects the island in the admin editor for legacy records;resolveVenueGeography()is the migration mapping;venueCardLocation()composes card text (Windwardside, Saba/Philipsburg, Sint Maarten, neverSaba, Saba).components/admin-workspace.tsx: Island is a required<select>driven byVENUE_ISLAND_OPTIONS;Location / localitystays free text.components/admin-dashboard.tsx/admin-fixture.tsx: legacy records load with the inferred island preselected;saveVenuerejects non-canonical island values before writing.components/venue-card.tsx: card shows the composed locality+island; theislandanalytics param now emits the canonical key instead of raw free text (matching the documented "stable identifiers" contract indocs/operations/analytics.md).firestore.rules: venue create/update requiresisland in ['saba','sxm','statia']— server-side rejection of arbitrary islands, not just the<select>. Delete is unaffected (request.resourceis null there).scripts/migrate-venue-islands.ts+npm run migrate:venue-islands: dry-run by default,--writeapplies; idempotent (skips already-migrated records), transactionally re-verifies each doc before writing, and reports conflicts/ambiguous records instead of guessing.island+ locality-onlylocationName(Harbor Bar =sxm+Philipsburg; Quiet Cafe deliberately left legacy to cover the fallback); new unit tests for the vocabulary, canonical-first grouping, card composition, admin preselection, and migration mappings; rules tests cover allow/deny; smoke spec gains a "locality never becomes an island option" regression test.managing-locations.mdrewritten for Island/locality;TECHNICAL.mdvenue model, admin-flow, and scripts tables updated; README script table updated.scripts/venue-seed-data.json) migrated toisland: "saba"+ blank locality.Verification
npm cinpm run check:react-versionsnpx tsc --noEmitnpm run lintnpm test— 379 pass (incl. new island vocabulary, canonical grouping, card display, migration mapping tests)npm run test:rules— 30 pass (incl. new island allowlist allow/deny)npm run buildnpx playwright test— 124 pass (incl. new locality-never-an-island-option test; Align venue card actions and hide venues with no current beer inventory #130 unstocked-venue exclusion intact)npm run check:md-linksnpm audit --omit=dev— 0 vulnerabilitiesRisk / deployment notes
Saba,Windwardside, Saba,Fort Bay, Saba,Windwardside / The Bottom, Saba), 1 ×Sint Maarten. No ambiguous or conflicting records. Dry-run output:13 to update, 0 unchanged, 0 conflicts, 0 ambiguous.npm run migrate:venue-islands -- --write(dry-run default). Reads are already compatible either way.firebase deploy, separately from the site. Deploy the site first — once this ships, every admin venue save writesisland, so rules can tighten without breaking saves. (Rules-first would briefly reject saves from a stale admin bundle.)locationNameinference fallback, and the admin editor preselects the inferred island so saving a legacy record writes the canonical field. Do not remove the fallback until the backfill is confirmed complete.Generated with Devin
Summary by Sourcery
Separate canonical venue island identity from free-text locality data throughout administration and public venue listings.
New Features:
Bug Fixes:
Enhancements:
Deployment:
Documentation:
Tests:
Chores: