feat(website/matchers): discriminated Map | Location union - #85
Conversation
…ocation union
The `(Location | Map)[]` union of two open, all-optional object interfaces
gets structurally merged by the schema generator into a single flat object
({city, regionCode, country, coordinates}) with no way to tell the branches
apart. In the CMS this renders every field at once instead of letting the
editor pick "Location" (country/region/city) vs "Map" (area radius), and the
@Format annotations are lost.
Add a required `type` literal discriminator ("location" | "map") to each
branch so the generated schema is a real discriminated anyOf. The Studio
renders a type-discriminated union as a branch selector, restoring the
Map-vs-Location choice and letting the @Format widgets attach to the right
branch. Also flesh out the field descriptions (cf-* header semantics).
Runtime matching is unchanged — matchLocation keys off the fields present and
ignores the extra `type` discriminator.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="website/matchers/location.ts">
<violation number="1" location="website/matchers/location.ts:47">
P3: The new city field description claims matching is case-insensitive, but the matcher uses strict case-sensitive equality. Either document exact casing or normalize both sides before comparing.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| /** | ||
| * @title City | ||
| * @example São Paulo | ||
| * @description Exact city name (case-insensitive) as returned by Cloudflare's cf-ipcity header. |
There was a problem hiding this comment.
P3: The new city field description claims matching is case-insensitive, but the matcher uses strict case-sensitive equality. Either document exact casing or normalize both sides before comparing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At website/matchers/location.ts, line 47:
<comment>The new city field description claims matching is case-insensitive, but the matcher uses strict case-sensitive equality. Either document exact casing or normalize both sides before comparing.</comment>
<file context>
@@ -7,32 +7,56 @@ export interface Coordinate {
/**
* @title City
* @example São Paulo
+ * @description Exact city name (case-insensitive) as returned by Cloudflare's cf-ipcity header.
*/
city?: string;
</file context>
| * @description Exact city name (case-insensitive) as returned by Cloudflare's cf-ipcity header. | |
| * @description Exact city name as returned by Cloudflare's cf-ipcity header. |
|
Closing — the |
Problem
Editing a Location matcher rule in the Studio CMS shows all fields at once — City, Region Code, Country and Area selection (Map) merged into one form — with no way to choose between a Location rule and a Map rule. The
@formatwidgets also never attach.Root cause is in the generated JSON schema (confirmed against a live
/live/_meta):includeLocations/excludeLocationswere typed(Location | Map)[], but both branches are open, all-optional object interfaces with no discriminator. The schema generator merges them into a single flat object:No
anyOf, nooneOf, and the@format location/@format mapannotations are dropped. So the CMS has nothing to render as a choice, and the location/map field widgets have nothing to attach to.Fix
Add a required
typeliteral discriminator ("location" | "map") to each branch so the generator emits a real discriminatedanyOf. The Studio already renders a type-discriminated union as a branch selector (resolve-schema→block-refwithdiscriminatorKey: "type"→AnyOfField), which restores the Map vs Location choice and lets the@formatwidgets bind to the correct branch.Also expanded the field descriptions to document the
cf-*header semantics.Behavior
matchLocationkeys off whichever fields are present and ignores the extratypefield. Existing saved rules keep matching.Location/Mapare preserved; each just gainstype.Testing
bun run typecheck✅bun run test✅ 60 passing (matcher tests updated to include thetypediscriminator)biome checkon changed files ✅ (only a pre-existinganywarning in an unrelated test)Follow-up / needs verification
I couldn't confirm from the current live meta that the generator emits object-level
@format(allformats there sit on string leaves). After this app is rebuilt, we should check/live/_metato confirmformat: "location"lands on the Location branch — if it doesn't, the Studio widget trigger will be adjusted to key off the discriminator instead. The Map/Location choice works regardless via thetypediscriminator.🤖 Generated with Claude Code
Summary by cubic
Adds a
typediscriminator to the location matcher, turning(Location | Map)[]into a true discriminated union so Studio shows a Map vs Location selector and the correct@formatwidgets attach. Updates tests; no runtime behavior change.type: "location" | "map"toLocationandMap; requireMap.coordinates.cf-ipcountry,cf-region-code,cf-ipcitysemantics.Written for commit 17a4a18. Summary will update on new commits.