Separate the local page from Shopify - #5
Open
gaelsimon wants to merge 4 commits into
Open
Conversation
…onto it Add @woosmap/local-page-engine: a platform-neutral LocalPage document — store + enrichment + per-client config in, a structured page out. One document, several adapters: a new platform is an adapter, a new client is a config. Three things blocked any non-Shopify consumer: - The SEO title, the JSON-LD and the static-map URL lived in Liquid, so they could not be reused or unit-tested. Now TypeScript, behaviour-preserving: same endpoint, zoom and geometry for the map, LocalBusiness still omitting empty optionals, BreadcrumbList still gated on a region or county. - STORE_FIELD_DEFINITIONS — a Shopify metaobject schema — sat in the shared store-search-client, making it the workspace's de facto contract. It moves to apps/store-pages/app/metaobject-mapping.server.ts, downstream of the engine. The shared client no longer contains any Shopify. - The enrichment resolvers (Nearby + per-mode Distance Matrix, reverse-geocode, haversine neighbours, the TTL) move into the engine, still over an injected fetch. storeSlug is byte-for-byte the storeToMetaobjectHandle it replaces, so no published page changes URL. The sync now builds a LocalPage and maps it; SyncDeps.enrich returns a LocalPageEnrichment and the clock is injected. The conditional behaviour falls out of the model: a fresh TTL means no `nearby` key, so metaobjectUpsert leaves the stored value untouched. New, and the one part that is not a lift: the SEO copy. Shopify got the title and description from the renderable capability, so nothing generated them; a feed consumer has no such capability. examples/local-page.example.json is committed and guarded by a drift test — it is what a client's developer reads to judge whether they can consume the feed. 320 tests (was 235). Engine 98.9% statements, store-pages 98.2%, both over the 80% gate; the mapper is added to the store-pages gate.
- checkout-autocomplete: `typecheck` now runs `prisma generate` first. Without a generated client, `@prisma/client` exports no `PrismaClient` and the app failed typecheck on any fresh clone. - .eslintignore: add `dist` and `coverage`. Build output was being linted, so every package reported errors on its own emitted `.d.ts`. - Drop two unused imports and swap three `@ts-ignore` for `@ts-expect-error` in the extension's declaration file — the five errors that were hidden behind the build-output noise.
`nearbyStores` defaulted to `[]`, so the page could not say whether the neighbour search had run. The mapper keyed off `.length > 0`, so a store that lost its last neighbour got no `nearby_stores` key and kept rendering yesterday's links. Now `NearbyStore[] | null`: `null` leaves the stored value, `[]` clears it. Also: - `directionsProvider` is recorded on the page instead of being silently dropped - `map.alt` comes from `seo.imageAlt`, so an override reaches both - `config.origin` gives an absolute `canonicalUrl` and breadcrumb `item` - `config.locale` labels the copy's language - `store.liquid` drops consecutive breadcrumb duplicates like the engine does, and builds the trail once for both the visible nav and the JSON-LD - `mergeEnrichment` replaces `Object.assign(...) as …`, under the coverage gate - the clock test checks the injected clock is used per store 345 tests, engine 98.97%, store-pages 98.25%.
install --frozen-lockfile → typecheck → lint → coverage, via the root scripts so CI and a developer run the same thing. `coverage`, not `test`: the 80% thresholds live in the vitest configs. Two things it would have failed on: - typecheck on a fresh clone. The apps resolve `packages/*` through their built `dist`, no package had a `prepare`, nothing ordered a build — and an app's tests could pass against a stale engine. Each library now builds on `prepare`. - `eslint .` reported 5 `'globalThis' is not defined`: `env: es6` predates it. Now `es2022`, plus a root `lint` script. `packageManager` is pinned because pnpm/action-setup reads it. Note `trustPolicy` and `blockExoticSubdeps` in pnpm-workspace.yaml need pnpm >= 10.21 / 10.26 and are ignored today; bumping the pin is its own change.
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.
What
@woosmap/local-page-engineowns the local page contract: store + enrichment + per-client config in, aLocalPageout. Shopify becomes one adapter over it (app/metaobject-mapping.server.ts); a feed or a server-rendered page would sit next to that file.storeSlugis byte-for-byte the handle it replaces, so no published page changes URL. The static map keeps the same endpoint, zoom and geometry.Why
Three things blocked any non-Shopify consumer:
STORE_FIELD_DEFINITIONS, a Shopify metaobject schema, sat in the sharedstore-search-clientand was the workspace's de facto contract. It moved into the app.fetch.Fixed in review
nearbyStoresdefaulted to[], so the page couldn't say whether the neighbour search had run. The mapper wrotenearby_storesonly when non-empty, so a store that lost its last neighbour kept the old list andstore.liquidkept rendering those links. NowNearbyStore[] | null:nullleaves the stored value alone,[]clears it.config.directionsProviderwas accepted and read by nothing. It's on the page now.map.altwas hardcoded English whileseo.imageAlthonoured the template override. The map takes its alt from the SEO block.BreadcrumbListwants. Addedconfig.origin, andconfig.localeto record the copy's language.store.liquid's breadcrumb deduped county/region and city/county but never region/country ("Luxembourg › Luxembourg"). Same rule as the engine now, built once for both the nav and the JSON-LD.CI
There wasn't any.
.github/workflows/ci.ymlruns install--frozen-lockfile, typecheck, lint and coverage on every push and PR —coveragerather thantest, since the 80% thresholds are in the vitest configs. It caught two things:packages/*through their builtdistand nothing built them. An app's tests could also pass against a stale engine. Each library now builds onprepare.eslint .had 5'globalThis' is not definederrors, fromenv: es6in the root config. Nowes2022, plus a rootlintscript.Verification
mainstore-pages98.25% / 93.75%dist-less tree with onlypnpm installtheme-checkreports only the two knownRemoteAssetwarningsexamples/local-page.example.jsonis committed, with a drift testKnown state and follow-ups
page.seo,page.jsonLdandpage.maparen't mapped onto metaobject fields: Shopify covers them via therenderablecapability and the template's own JSON-LD and<img>. So the engine and the template both derive them, and the breadcrumb bug above is what that duplication looks like in practice. Feeding the Liquid frompage.jsonLdwould remove it, but it changes what the storefront renders and wants a dev-store pass.page.mapis referrer-restricted against whoever loads the image, so a third-party consumer needs their own key or an allow-listed domain.pnpm-workspace.yamlsetstrustPolicyandblockExoticSubdeps, which need pnpm >= 10.21 / 10.26. The pinnedpackageManageris older, so they're ignored today.@types/woosmap.mapmarksaddress/contact/open/weekly_openingrequired and spells the timestamplastUpdated; the API omits them and returnslast_updated.