Skip to content

sync integrations/makeswift with canary - #3198

Merged
jorgemoya merged 37 commits into
integrations/makeswiftfrom
sync-integrations-makeswift
Aug 28, 2026
Merged

sync integrations/makeswift with canary#3198
jorgemoya merged 37 commits into
integrations/makeswiftfrom
sync-integrations-makeswift

Conversation

@jorgemoya

Copy link
Copy Markdown
Contributor

Brings integrations/makeswift up to date with canary through the @bigcommerce/catalyst-core@1.11.0 release, and adds the @bigcommerce/catalyst-makeswift minor changeset for it.

Do not squash or rebase-and-merge this PR. Use a true merge commit or rebase locally to preserve the merge base between canary and integrations/makeswift.

What/Why?

The bulk of this is LTRAC-1298, which moved locale routing from a build-time snapshot (i18n/locales.ts, build-config.json) to a per-request lookup (i18n/locale-config.ts, i18n/locale-routing.ts). That deleted i18n/locales.ts and the routing export, both of which the Makeswift surface used, so most of the work here is porting Makeswift code onto the runtime API.

Conflict resolutions

  • core/package.json — kept the branch's identity: name stays @bigcommerce/catalyst-makeswift, version stays 1.10.0, and nested catalyst.version/catalyst.ref stay pinned to @bigcommerce/catalyst-makeswift@1.10.0 rather than taking canary's @bigcommerce/catalyst-core@1.11.0.
  • core/CHANGELOG.md — kept the branch's history; latest entry is still ## 1.10.0.
  • core/i18n/routing.ts — took canary's version wholesale (it's now just re-exports). The Makeswift localeCookie config (partitioned, secure, sameSite: 'none', needed for the Builder canvas) moved into createRouting in core/i18n/locale-routing.ts, which is where both the proxy middleware and client navigation now build their config.
  • core/proxies/with-intl.ts — took canary's per-request routing resolution, and re-applied the Makeswift x-bc-disable-locale-detection header handling by spreading localeDetection: false over createRouting(localeRouting).
  • core/app/[locale]/layout.tsx — canary's structure (getLocaleRouting, LocaleRoutingProvider) with the Makeswift surface preserved: MakeswiftProvider still wraps <html>, SiteTheme still renders in <head>.
  • pnpm-lock.yaml — took canary's and regenerated.

Makeswift code ported off the deleted i18n/locales.ts

  • core/lib/makeswift/client.tsnormalizeLocale is now async and reads defaultLocale from getLocaleRouting().
  • core/vibes/soul/primitives/navigation/_actions/localized-pathname.ts — same, resolved inside the existing async functions.
  • core/app/api/products/{[entityId],group/[group],ids}/route.ts — the three Makeswift product API routes resolved routing.locales / routing.defaultLocale at module scope; they now await getLocaleRouting() per request.

Needs a second opinion

I removed generateStaticParams from core/app/[locale]/(default)/page.tsx and core/app/[locale]/(default)/[...rest]/page.tsx. Both fanned Makeswift page paths out over the build-time locales array, which no longer exists. Resolving the list at build time isn't available either — getLocaleRouting() reads headers(), which isn't callable from generateStaticParams.

I took the reading that matches what canary did to app/[locale]/layout.tsx for the same reason: every route under [locale] already renders on demand because the tree reads cookies, so the fan-out was adding a build-time dependency on the locale list without actually prerendering anything. If that reasoning doesn't hold for the Makeswift catch-all specifically, this is the hunk to push back on.

Testing

pnpm run lint and tsc --noEmit both pass clean in core/. Prettier is clean on every touched file. I have not run a build or exercised the Builder canvas — the locale-cookie and locale-detection paths above are the ones worth a manual check in Makeswift.

Refs LTRAC-1700

chanceaclark and others added 30 commits July 23, 2026 21:55
…ct rules (#3131)

Merging canary into integrations/makeswift silently overwrites
core/package.json's nested catalyst.version/catalyst.ref fields with
canary's raw value instead of the makeswift package's own identity,
since this hunk doesn't produce a textual conflict for git to flag.
Document the field as something to check by hand on every sync.

Refs TRAC-1280

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Replace the branch-specific /sync-makeswift skill (and the proposed
/sync-b2b-makeswift skill) with a single parameterized
/sync-integration-branch skill. The target integration branch is passed
as an argument; SKILL.md holds the generic sync flow, and each supported
branch has a reference file (references/<name>.md) with its upstream,
package identity, integration surface, and branch-specific caveats.
Phase 0 resolves the target and loads the matching reference. When no
branch is given, or the branch is unknown or absent on origin, the skill
lists the valid integrations/* targets and asks the user to pick one.

Point the release-catalyst skill at the new skill name.

Refs TRAC-1236

Co-authored-by: Claude <noreply@anthropic.com>
…yst (#3133)

The release-catalyst skill released catalyst-core and catalyst-makeswift
but had no stage for catalyst-b2b-makeswift, so nothing moved its @latest
tag (which the CLI upgrade.ts resolves). Add a b2b sync-and-release stage
mirroring the makeswift one (sourced from integrations/makeswift), and
extend the @latest tag-push and cleanup stages to cover the b2b package.
Note the large-catch-up cadence: one b2b release per makeswift minor tag.

Refs TRAC-1290

Co-authored-by: Claude <noreply@anthropic.com>
* feat(other): LOCAL-1444 delivery translation

* chore(core): create translations patch

---------

Co-authored-by: bc-svc-local <bc-svc-local@users.noreply.github.com>
* TRAC-1368: fix(client) - Surface clear error for non-JWT storefront token

A 401 from the storefront GraphQL API previously threw a bare
BigCommerceAPIError with no indication of the cause. When
BIGCOMMERCE_STOREFRONT_TOKEN is not a storefront JWT (e.g. an OAuth
access token was supplied instead), this left developers to
reverse-engineer that the token type mattered.

Detect when a 401 response coincides with a malformed storefront token
and throw InvalidStorefrontTokenError, which explains that a storefront
JWT is required and links to how to generate one.

Refs TRAC-1368

Co-Authored-By: Claude <noreply@anthropic.com>

* TRAC-1368: ref(client) - Stop overclaiming JWT-shape check validates token

isWellFormedStorefrontToken only verified JWT shape (three base64url
segments, JSON-decodable payload) but the name and comment implied it
confirmed the token was a valid storefront token. The storefront
service accepts several distinct JWT-based token kinds (Simple,
Private, Customer Impersonation, etc, per bigcommerce/storefront's
GraphqlToken.scala), so shape alone can't prove storefront-token
validity — only that the token is a JWT at all, which is what the
original reported bug (an opaque OAuth token) actually violates.

Rename to looksLikeJwt, rewrite the doc comment to state this
limitation explicitly, and soften the InvalidStorefrontTokenError
message accordingly so it doesn't overclaim either. No behavior
change.

Refs TRAC-1368

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…3155)

The baseline-branch detection step only special-cased
@bigcommerce/catalyst-makeswift; every other package name, including
@bigcommerce/catalyst-b2b-makeswift, fell through to canary. Since
integrations/b2b-makeswift carries substantial B2B-only code on top
of makeswift, comparing its bundle against canary produced a
misleading diff on every b2b-makeswift PR.

Add an elif branch mapping @bigcommerce/catalyst-b2b-makeswift to
integrations/b2b-makeswift, mirroring the existing pattern in
changesets-release.yml's package-resolution step.

Refs LTRAC-1406

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* TRAC-1264: fix(cli) - Show progress feedback while creating a channel

The channel-creation API call provisions the channel and, when
requested, seeds sample data — both can take several seconds. With no
spinner, the CLI appeared frozen after the last prompt. Add a
consola.start/success pair around the call, matching the existing
"Fetching channels..." pattern used elsewhere in the CLI.

Fixes TRAC-1264
Co-Authored-By: Claude <noreply@anthropic.com>

* TRAC-1264: style - Fix import order lint warning in create-channel-flow.spec.ts

Refs TRAC-1264
Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
… directives (#3171)

* fix(core): TRAC-1425 Don't strip Expires from session cookie deletion directives

stripSessionCookieExpiry was stripping Expires from all session token
Set-Cookie headers, including deletion directives (empty value + Expires=past).
This turned cookie deletions into permanent empty-value session cookies,
so the browser never removed them. Stale cookies then persisted through
logout, causing session confusion and cart state issues.

Narrow the regex with [^;] after = so headers where the value is empty
(deletion directive) are not matched, preserving their Expires=past or
max-age=0 so the browser actually removes the cookie.

Fixes TRAC-1425
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: TRAC-1425 Add changeset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… setup (#3178)

setupCommerceHosting built a fresh object and wrote it over
.bigcommerce/project.json, so every key it doesn't set itself was dropped:
the `env` block managed by `catalyst env`, the persisted `apiHost`, and
stored credentials whenever setup ran without them.

The costly one is `env`. Those entries are sent as secrets on every
`catalyst deploy`, so losing them doesn't just lose local config -- the deploy
that wiped them proceeds, and the next one ships a worker with no
BIGCOMMERCE_STOREFRONT_TOKEN or BIGCOMMERCE_CHANNEL_ID. Nothing in the output
mentions env vars, so the first sign is a broken storefront.

It fires during ordinary use. deploy.ts calls setup whenever
getProjectState().isTransformed is false, behind a prompt defaulting to yes,
and isTransformed needs all three of middleware.ts present, proxy.ts absent,
and @opennextjs/cloudflare installed. Any one of those flipping -- a partly
reverted project, a merge restoring proxy.ts, an interrupted setup -- is
enough. `projects link` reaches the same path.

Now merges into whatever is already there. Unknown keys are carried through
untouched so a later addition to the config doesn't have to remember this
call site, and absent storeHash/accessToken means "not supplied on this run"
rather than "clear it". A corrupt or non-object file falls back to starting
fresh, since setup writes everything a working project needs.

Not switched to getProjectConfig(), which would merge for free via Conf: its
schema enforces `format: 'uuid'` on projectUuid, which would turn a
malformed-uuid case into a hard failure and change behaviour well beyond this
fix. Worth considering separately.

create.ts already worked around this by ordering its Conf writes after setup;
that comment described the old overwrite semantics, so it is updated rather
than the ordering changed.

Verified the three preservation specs fail against the previous behaviour.

Refs LTRAC-1582

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(other): LOCAL-1444 delivery translation

* chore(core): create translations patch

---------

Co-authored-by: bc-svc-local <bc-svc-local@users.noreply.github.com>
…ntries (#3176)

* LTRAC-1583: build(core) - Add a unit test harness to core

core has never had one. `tests/` holds 63 Playwright specs run by
`playwright test`; there was no `test` script, no runner, and no way to unit
test anything under `lib/`.

Scoped to `lib/**/*.spec.ts` deliberately. Playwright specs share the
`*.spec.ts` suffix, so an unscoped Vitest would collect all 63 and fail on
their fixture imports. This glob is the part worth reviewing closely: widen it
and Vitest swallows the e2e suite, narrow it and specs silently stop running.

Colocated rather than under `tests/`, matching how packages/catalyst already
does unit tests -- build.spec.ts next to build.ts, and so on. `tests/` is
Playwright's testDir, so specs placed there would need either a separate
suffix or a testIgnore entry to keep the two runners apart; and `tests/lib/`
already means "helpers for Playwright tests", which would make `tests/lib/kv`
ambiguous.

The `~` alias mirrors the tsconfig path alias so lib code importing `~/...`
resolves the same way it does in the app.

Kept as its own commit so the harness is separable from the fix that follows,
but shipped in the same PR: a runner with no specs passes CI vacuously and
proves nothing, which is a poor thing to ask a reviewer to approve.

Refs LTRAC-1583

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* LTRAC-1583: fix(core) - Expire in-memory KV entries so processes share refreshes

MemoryKvAdapter doubles as an L1 in front of whichever shared adapter is
selected, and KV.mget skips the shared store whenever memory holds every
requested key. Nothing ever expired those entries, so that short-circuit was
permanent.

That left the `expiryTime` callers embed in the value as the only thing driving
refreshes -- and that path refetches from the origin, not from the shared store.
So each process ran its own refresh loop on a clock starting from whenever it
first cached the key, and never picked up a value another process had already
fetched and written.

Observed on a deployed store: storeStatus was written at 01:19:01 with a window
ending 01:24:01, and at 01:21:12 a request read the *previous* value and
refetched again. That read never reached the shared store despite a fresher
value having been there for two minutes. A later trace showed a route entry
served from memory 14 hours stale.

Nothing was ever served incorrectly -- staleness stayed bounded by the caller's
own expiryTime, and stale-while-revalidate still served fast and refreshed
behind the request. The cost was duplicated work: origin requests and cache
writes scaling with process count instead of being shared.

60s matches Workers KV's floor for `cacheTtl` on a read, so the two layers share
one staleness window rather than one each. It sits inside the shortest window
callers embed in their values (5 minutes for storefront status, 30 for routes).
The trade is one shared-store read per key per window against a value nothing
ever replaces; those reads become near-free once CloudflareKvAdapter passes
`cacheTtl`, which serves them from the colo cache.

Capacity goes from 500 to 4096. Cache keys include the query string, so distinct
keys accumulate much faster than the number of real paths suggests -- a crawler
walking `?utm_*` permutations alone can churn through the old limit.

Not using allowStale/noDeleteOnStaleGet, which would serve an expired entry
instantly and refresh it off the response path: that needs a `waitUntil` handle
to run the refresh on, and KV.mget is called from with-routes without the
request's event. Returning stale values with no refresh would reinstate the bug
this fixes.

Expiry now comes from lru-cache rather than being tracked by hand. The previous
expiresAt bookkeeping was only consulted by a private `get` that nothing called
-- KV.get delegates to mget -- so it expired nothing, and a caller passing `ex`
would have had it silently ignored. `ex` now maps to a per-entry TTL override.
mget also returns null rather than undefined for a miss, matching KvAdapter.

Two things worth knowing about the tests. lru-cache captures a reference to the
`performance` object at module load, and Vitest's fake timers swap the global
for a new object that reference never sees -- so the cache's clock has to be
moved by stubbing the method in place. And lru-cache treats a recorded start
time of exactly 0 as "no TTL", so the fake clock starts at a non-zero baseline;
starting at zero makes every expiry assertion pass whether or not the TTL works.
Verified both the TTL and the capacity change fail their specs when reverted.

Refs LTRAC-1583

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ntries (#3180)

The 60s window added in LTRAC-1583 was meant for the L1 copy in front of a
shared store, so a process periodically re-reads it and picks up values other
processes wrote. But createKVAdapter falls back to the same adapter class when
no shared store is configured, so both layers got the window and now empty
together.

That matters because with-routes treats a missing entry differently from a
stale one: a stale entry refreshes in the background via waitUntil, a missing
one blocks on `await updateRouteCache(...)`. With both layers empty the value is
gone rather than stale, so every request past the window takes the blocking
branch -- a 30 minute background refresh became a blocking Storefront API call
every 60 seconds, roughly 30x the origin requests and now on the request path.

Reproduced against canary: kv.set() then kv.get() 61s later returns null, where
before LTRAC-1583 it returned the value.

Hits any deployment with no CATALYST_ROUTES_KV binding, no Upstash, and not on
Vercel: self-hosted without Upstash, native hosting until the binding ships, and
local dev.

The window is now opt-in. It exists so the L1 defers to a shared store; where
the fallback is itself in-process memory there is nothing to defer to, so it
stays unbounded as it was. MemoryKvAdapter takes { ttlMs }, KV's L1 passes
SHARED_STORE_RECHECK_MS, the fallback passes nothing.

No changeset: LTRAC-1583 is merged but unreleased, so its changeset is still
pending on canary. This corrects that work before it ships rather than changing
anything a consumer has seen, and a second entry would describe a fix for a bug
that never reached a release.

The regression test exercises the real `kv` singleton rather than the adapter
alone, because the bug is in how the two layers compose -- every spec in
LTRAC-1583 constructed the adapter directly, which is exactly why nothing caught
this. Verified it fails when the fallback is given the window back.

Refs LTRAC-1617

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* TRAC-1298: Update locale paths to runtime resolution

* TRAC-1298: Clean up comments
* feat(other): LOCAL-1444 delivery translation

* chore(core): create translations patch

---------

Co-authored-by: bc-svc-local <bc-svc-local@users.noreply.github.com>
… immutably (#3184)

`templates/public_headers` has always specified the right policy for
/_next/static/*, but nothing copied it into the build output. build.ts
wrote only open-next.config.ts and wrangler.jsonc, and no _headers file
existed anywhere in the repo.

Without it Workers Assets applies its own default. Measured on a deployed
store, all 32 hashed assets on a product page returned:

    cache-control: public, max-age=0, must-revalidate

That is wrong by construction for a content-hashed filename — the hash is
the version, so a URL can never return different bytes. It forced a
conditional request for all 32 assets on every repeat page view (~75ms
each, all 304 Not Modified), including 4 render-blocking CSS files and 3
fonts. The intended public,max-age=31536000,immutable removes them.

The header is missing in the first place because /_next/static/* never
reaches the Next.js server on Workers; Cloudflare's asset layer serves it
directly, so Next's own immutable header never applies. _headers is the
supported override, and OpenNext's own migrate command generates a
byte-identical public/_headers, treating it as the app's job.

Copy the template to .open-next/assets/_headers after the OpenNext build,
since that build regenerates the directory, and before the Wrangler
dry-run so Wrangler validates it. The existing recursive copy into
.bigcommerce/dist/assets carries it into the uploaded bundle.

Also add a regression test asserting the copy happens. The bug was an
absent value rather than a wrong one, with nothing asserting it — the new
test was verified to fail when the copy is removed.

Refs LTRAC-1462

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ts (#3183)

`cachePurge` was declared in the generated open-next.config.ts but never
had credentials on native hosting, so every purge no-opped with "No cache
zone ID or API token provided. Skipping cache purge." — confirmed in the
production Worker logs of a deployed store.

Correctly invalidating a regional (Cache API) entry needs one of two
mechanisms: a CDN purge that evicts it, or a tag-cache check on every hit.
doShardedTagCache.writeTags() always writes to its DO shards and always
clears the regional tag cache; only the CDN purge is gated behind
isPurgeCacheEnabled(). So purge exists specifically to evict incremental
cache entries — exactly the check bypassTagCacheOnCacheHit was skipping.

Catalyst was configured for purge and had neither. Declaring cachePurge
without credentials was worse than omitting it: isPurgeCacheEnabled() only
checks whether it is declared, so OpenNext believed purge was handling
invalidation and disabled shouldLazilyUpdateOnCacheHit (documented as on
by default for 'long-lived'). A hit was then neither purged, nor refreshed
from R2, nor tag-checked, so revalidateTag had no effect on it until
max-age expired.

Remove both to take the tag-checking route: the tag cache is consulted on
a hit and the entry refreshes from R2 in the background. Costs an extra
tag-cache and R2 read per hit, which is what those options were trading
for correctness.

Purge is still the faster mechanism and worth restoring later, but not by
supplying credentials: it needs a Cloudflare API token bound into the
Worker, a binding is readable by the merchant's own application code, and
purge is zone-scoped across every tenant on the shared hosting zone. That
needs a design keeping the credential out of tenant Workers.

The NEXT_CACHE_DO_PURGE binding is intentionally left in place: OpenNext's
worker template exports all three DO classes unconditionally, so it still
resolves and needs no Durable Object migration. It is inert until purge
returns.

Refs LTRAC-1458

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
chanceaclark and others added 7 commits August 26, 2026 16:32
#3192)

Every Catalyst tag ships core/package.json with `workspace:` specifiers,
because @bigcommerce/catalyst-core is private and pnpm's publish-time
rewrite never runs on the tagged tree. Both sides of the 3-way merge
therefore agreed on those lines, so a project's @bigcommerce/catalyst*
dependency versions never moved through an upgrade: flat projects kept
whatever `catalyst create` resolved at scaffold time, and monorepo-
structure projects kept `workspace:^` indefinitely.

Read the versions each tag actually published from that tag's own
tarball and pin the downloaded base and target trees to them, so the
existing merge carries the bump like any other change, or conflicts when
the merchant pinned deliberately. A dependency the project still holds
as `workspace:` is skipped on both sides, since normalizing one side
alone would manufacture a conflict on something that works fine as-is;
those are surfaced instead as an opt-in migration prompt.

Also add two advisories the merge cannot cover: a reminder to reinstall
when the upgrade changed package.json, and a notice when the pinned
@bigcommerce/catalyst is behind the published version, which it always
will be eventually since `catalyst create` pins it exactly and the
dependency does not exist upstream for a merge to touch.

Refs TRAC-1536

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ue (#3193)

* LTRAC-1457: fix(catalyst) - Revalidate via self-fetch instead of a DO queue

OpenNext's doQueue routes ISR revalidation through a Durable Object whose
constructor reads env.WORKER_SELF_REFERENCE and throws without it.
queueCache.send() catches that error and only logs, so revalidation would
fail silently rather than surface.

That binding cannot point at this Worker on native hosting. A Cloudflare
service binding resolves against account-level Workers, and a Catalyst
deployment is a script inside a dispatch namespace, which is not
addressable that way. Adding it was attempted and rejected at upload:

    400 Bad Request  code 10143
    Service binding 'WORKER_SELF_REFERENCE' references Worker '…'
    which was not found.

Two alternatives were considered and rejected on security grounds, both
because they would expose other tenants:

- A dispatch_namespace binding. Besides the API mismatch — OpenNext calls
  .fetch() while that binding exposes .get(name) — .get() accepts any
  script in the namespace, letting any deployment invoke any other.
- A service binding to the dispatch router, which is account-level and
  would resolve, routing back here by hostname and keeping the Durable
  Object queue intact. It would also hand every tenant a handle able to
  reach any other tenant's Worker. Public fetches already reach those
  endpoints, but only through the Cloudflare edge; an internal handle
  bypasses WAF and rate limiting, so it is not equivalent.

Revalidation does not require a binding. It is a HEAD request to the page's
own public URL carrying the build-time preview secret, which is exactly
what the Durable Object issues once it holds the service handle. Issue it
with a plain fetch instead: the subrequest leaves and re-enters through the
dispatch router and arrives at the same Worker, and
global_fetch_strictly_public is already set so it is not short-circuited
internally.

Still wrapped in queueCache, so concurrent stale hits for one path collapse
into a single revalidation. Bounded by a 10s timeout matching the Durable
Object's default, and a non-ok response throws so a failed regeneration is
logged and retried on the next stale hit rather than silently cached as
done.

Lost relative to the Durable Object: retry, max-concurrency, cross-region
dedup, and failed-route backoff. Accepted because the alternative is not
"keep the Durable Object" — it is no revalidation at all.

NEXT_CACHE_DO_QUEUE is left bound. OpenNext's worker template exports all
three DO classes unconditionally, so it still resolves and needs no
migration; it is simply inert.

Latent until ISR is adopted — the build currently produces no routes with a
revalidate window, so the queue is never invoked.

Refs LTRAC-1457
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LTRAC-1457: fix(catalyst) - Silence no-underscore-dangle on Next's env var

`__NEXT_PREVIEW_MODE_ID` is inlined by Next at build time, so the leading
underscores are its naming, not ours, and renaming is not an option.

Scoped to the single line rather than the file so the rule keeps applying
to the rest of the template.

Refs LTRAC-1457
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LTRAC-1457: docs(catalyst) - Lead the changeset with the no-behavior-change note

The caveat was the last line of a long entry, so a skim read like a caching
improvement — easy to confuse with the regional-cache tag-checking change,
which cost latency rather than saving it.

State up front that no route has a revalidate window today, cite the
prerender manifest, and distinguish fetch-level `next: { revalidate }` from
route-level ISR. Frame the fix as removing a silent-failure trap, since the
`IgnorableError` is dropped under the default log threshold.

Refs LTRAC-1457
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LTRAC-1457: docs(catalyst) - Trim rejected alternatives from the config comment

The header carried the full case against the service binding and the dispatch
router, including the 10143 upload error and the cross-tenant reach argument.
That is review context, not something a reader of this file needs, and it is
already stated at length in the pull request.

Keep only what the code cannot convey on its own: that `doQueue` is unusable
here, the mechanism the queue replaces it with, and that
`global_fetch_strictly_public` is load-bearing — without it the subrequest
would be short-circuited and a future reader might reasonably simplify the
plain `fetch` away. Point at LTRAC-1457 for the rest.

Also separate the queue comment from the `REVALIDATION_TIMEOUT_MS` one, which
ran together and read as a single block attached to the constant.

Refs LTRAC-1457
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LTRAC-1458: docs(catalyst) - Trim the purge rationale from the config comment

Same treatment as the queue comment above it, applied to the block that came
in with LTRAC-1458. The case against enabling purge — the zone-scoped token
being readable by merchant application code on a shared tenant zone — is
review context, and PR #3183 states it more completely than this comment did,
naming the env vars and linking the ignition attempt that was closed for it.

Kept the footgun, which is the part a reader of this file needs: that the
omission is deliberate, and that declaring `cachePurge` flips OpenNext's
defaults on declaration alone regardless of whether it can authenticate.
Without that, restoring it looks like a free speed win and silently
reintroduces the bug LTRAC-1458 fixed.

Dropped the per-hit read cost, which is quantified in the benchmark report on
LTRAC-1468 rather than estimated in a comment.

Refs LTRAC-1457
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…artifacts (#3195)

`npm run generate` writes `bigcommerce.graphql` and `bigcommerce-graphql.d.ts`
to the project root, and `catalyst create` makes its initial commit before
anything runs `generate`. Both files therefore appear as untracked the first
time a merchant runs `pnpm run dev`, with nothing in the project saying what
they are, whether they are read at runtime, or whether the intent was to commit
or ignore them.

Document in the scaffolded project's README that both files should be committed,
name `generate` as the command that produces them, and describe local versus
build-time regeneration. Record the same decision in `core/.gitignore`, where
their absence otherwise looks like an oversight.

Explain in CONTRIBUTING.md why the monorepo does the opposite: contributors work
against unreleased Storefront API schemas on their own test stores, so a
committed schema here would generate diffs belonging to no change and conflict
between `canary` and the `integrations/*` branches. CI regenerates from store
credentials before it lints and typechecks.

Refs LTRAC-1343

Co-authored-by: Claude <noreply@anthropic.com>
…3196)

Tidy the queued changesets so the generated changelog reflects what
actually shipped.

Bump `ltrac-1298-runtime-locale-subfolders` from patch to minor: it drops
locales from `build-config.json` entirely and moves resolution to runtime,
which is a behavior change rather than a fix.

Drop `ltrac-1343-document-graphql-artifacts`, which covers a README and
`.gitignore` comment only and does not belong in a released changelog.

Collapse the three identical "Update translations." changesets into one,
since three repeated entries say nothing the first does not.

None of this moves a version number — catalyst-core is already going minor
via the wallet buttons changeset.

Refs LTRAC-1700

Co-authored-by: Claude <noreply@anthropic.com>
…3197)

The accumulating syncs run unversioned, so complete had no version to
target; per Linear support, a pipeline with no natural labeling moment
needs one final versioned sync right before complete. Access keys are
also per pipeline, so each branch's package now maps to its own secret
and unset keys skip the Linear steps instead of writing into the
catalyst-core pipeline.

Refs TRAC-1601

Co-authored-by: Claude <noreply@anthropic.com>
* Version Packages (`canary`)

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jorge Moya <jorge.moya@bigcommerce.com>
…akeswift

# Conflicts:
#	core/CHANGELOG.md
#	core/app/[locale]/layout.tsx
#	core/i18n/routing.ts
#	core/package.json
#	core/proxies/with-intl.ts
@jorgemoya
jorgemoya requested a review from a team as a code owner August 28, 2026 01:46
@changeset-bot

changeset-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2792fcd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-makeswift Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview Aug 28, 2026 1:48am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Report

Comparing against baseline from 96af6d9 (2026-08-28).

Metric Baseline Current Delta
Total JS 729.1 kB 731.3 kB +2.2 kB (+0.3%)

Per-Route First Load JS

Route Baseline Current Delta
/(default)/(auth)/register/page 623.6 kB 624 kB +0.4 kB (+0.1%)
/(default)/(faceted)/category/[slug]/page 636.7 kB 637.2 kB +0.5 kB (+0.1%)
/(default)/account/settings/page 627.3 kB 627.7 kB +0.4 kB (+0.1%)
/(default)/cart/page 631 kB 631.9 kB +0.9 kB (+0.1%)
/(default)/gift-certificates/purchase/page 625.3 kB 625.7 kB +0.4 kB (+0.1%)
/(default)/webpages/[id]/contact/page 626.7 kB 627.1 kB +0.4 kB (+0.1%)

Threshold: 5% increase. Routes with ⚠️ exceed the threshold.

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 87 89 90 92

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 64 75 66 75
Accessibility 91 91 91 92
Best Practices 100 100 100 100
SEO 89 89 100 100

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 8.1 s 7.3 s 6.6 s 7.1 s
CLS 0.001 0.011 0 0.039
FCP 1.5 s 1.5 s 1.4 s 1.4 s
TBT 40 ms 40 ms 10 ms 50 ms
Max Potential FID 90 ms 90 ms 60 ms 100 ms
Time to Interactive 8.1 s 7.3 s 7.3 s 7.1 s

Full Unlighthouse report →

@jorgemoya
jorgemoya merged commit 2792fcd into integrations/makeswift Aug 28, 2026
19 of 20 checks passed
@jorgemoya
jorgemoya deleted the sync-integrations-makeswift branch August 28, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants