Skip to content

Release January 20, 2026 - #2582

Open
github-actions[bot] wants to merge 271 commits into
mainfrom
changeset-release/from-canary
Open

Release January 20, 2026#2582
github-actions[bot] wants to merge 271 commits into
mainfrom
changeset-release/from-canary

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated release PR from canary branch

@vercel

vercel Bot commented Jan 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
graphcommerce-246 Ready Ready Preview Aug 6, 2026 12:41pm
graphcommerce-247 Ready Ready Preview Aug 6, 2026 12:41pm
graphcommerce-hygraph-dynamic-rows-ui Ready Ready Preview Aug 6, 2026 12:41pm
graphcommerce-open-source Ready Ready Preview Aug 6, 2026 12:41pm
graphcommerce-storyblok Ready Ready Preview Aug 6, 2026 12:41pm

Request Review

Contains a Storyblok renderer on pages/index & pages/[…url]
Includes multi language support and renderes for RowHeroBanner and RowLinks
…rror) halfway through

Cursor: The issue was a TTY handling bug in graphql-codegen's CLI. When running in Cursor's terminal without a proper TTY:
The spinner/progress output was suppressed
The exit code was incorrectly set to 1

The fix was to pipe gc-gql-codegen through cat, which forces proper output handling
In live preview user should not be redirected based on system lang (i18n: { localeDetection: false }).
…ildren in a Fragment

MUI's ButtonBase passes props.children as-is to the LinkComponent (NextLink).
When ButtonBase has multiple children (Asset + Box), props.children is an array.
React treats this array as an iterable inside LinkComponent and warns about missing keys. Wrapping in a Fragment (<>...</>) makes children a single element, avoiding the warning without adding an extra DOM node.
The Storyblok CLI generates types and component schemas under directories
named after the space ID. This makes imports brittle when cloning the
example as a starting point for new projects — every import path would
need updating.
- Add copy steps to storyblok:pull and storyblok:types scripts that
  copy artifacts to fixed paths without the space ID
- Create components/Storyblok/types.ts barrel file so components import
  from a single location
- Gitignore .storyblok/types/*/ and .storyblok/components/*/ (the
  space-ID directories are intermediate artifacts recreated by the CLI)
- The space ID now only exists in storyblok.config.ts
… and per-page row component overrides

When no override is provided, it just uses the default StoryblokComponent (components defined in /lib/storyblok.ts)
Pull components in space-id folder first, to prevent command from failing if (ignored) space-id folder is missing.
Then copy the generic component schema to the space-id folder and push
…m the Storyblok visual editor)

Sadly you can’t edit multiple stories in 1 visual editor session (edit footer from any page). To allow updating this content, a separate /pages/global-content.tsx path is created.
Slashes aren’t valid in Story slugs
bramvanderholst and others added 30 commits July 17, 2026 14:31
A `<video>` paints nothing until it has buffered enough for its first
frame, and nothing at all when autoplay is blocked (iOS Low Power Mode).
An autoplaying video banner therefore starts out black, and can stay
black. `Asset` now takes a `poster`, rendered as `<video poster>`.

Storyblok's `type: asset` has no room for a poster, so `assetWithPoster()`
reads the convention of an asset value carrying an extra `poster` key:

  { "fieldtype": "asset", "filename": "…", "poster": { "filename": "…" } }

Keeping the poster beside the asset rather than nesting both under a
wrapper is what makes a custom field type storing that shape a drop-in
for a plain asset field: existing content stays valid and
`value.filename` keeps working for consumers that ignore the poster. The
narrowing is unavoidable regardless — Storyblok has no JSONSchema for
custom field types, so its type generator emits `unknown` for them.

`@graphcommerce/image` gains `imageUrl()`, which builds an optimized URL
outside of a React tree for the spots that need a bare URL rather than an
`<Image>` — `<video poster>`, `background-image`, `og:image`. It routes
through the configured loader, so the bytes are served and cached by the
deployment instead of every visitor hitting the origin host; that matters
when the origin meters bandwidth. Width snaps up to the nearest
configured size, since the optimizer 400s on anything outside
`imageSizes`/`deviceSizes`.
Improve Braintree checkout flow and make 3D Secure optional
…ndpoint

feat(graphql-mesh): route Magento traffic over an internal network via GC_MAGENTO_ENDPOINT_SERVER
…Poster

Each asset field in the example now goes through `assetWithPoster()`
instead of reading `blok.asset` directly, and forwards the resulting
`poster` to `<Asset>`.

This makes the choice of field type a content decision rather than a code
one: a plain `type: asset` and a custom field storing the poster
convention both narrow through the same call, so a project can switch any
field between the two — in either direction — without touching a
component. Fields that never hold a video simply resolve `poster` to
undefined.
`HeroBanner` rendered a `<video>` itself, from a raw URL. That meant it
could only ever hold a video, and left nowhere to hang a poster — the one
component where a poster matters most, being a full-bleed autoplaying
video above the fold.

It now takes `asset?: React.ReactNode` and renders it, exactly as its
sibling SpecialBanner already does. Positioning stays with the banner:
whatever is passed gets stretched to fill via `& img, & video`.

  - <HeroBanner videoSrc={asset.filename} … />
  + <HeroBanner asset={<Asset asset={asset} poster={poster} />} … />

Taking a node rather than data also keeps next-ui free of any CMS: the
Storyblok and Hygraph examples each pass their own `<Asset>`, both of
which already render images and video. Importing a CMS `Asset` into
next-ui would have inverted the dependency — storyblok-ui and hygraph-ui
depend on next-ui, not the other way round.

Non-breaking: `videoSrc` stays as a deprecated fallback that renders a
bare autoplaying video (keeping the HeroBanner-video class), and the
`animated`/`video` class parts stay exported. The only behavioural change
on the videoSrc path is the loss of the scroll parallax, along with the
framer-motion, useScrollY and clientSize machinery it required.
…et-poster

Feature/storyblok asset poster
…routes

Magento renders links from base_link_url, which on a headless setup points
at the GraphCommerce storefront. Every stock email template contains a
customer/account/ link ("Sign in to your account") and gated Magento routes
302 to customer/account/login — GraphCommerce serves none of those, so
customers landed on a 404.

withGraphCommerce now adds permanent redirects for /customer/account,
/customer/account/index, /customer/account/login, /customer/account/create,
/customer/account/forgotpassword and /sales/order/history. They are exact
matches on purpose: redirects run before the filesystem routes, so a
/customer/account/:path* catch-all would shadow the
pages/customer/account/{confirm,createPassword} routes that
@graphcommerce/magento-customer copies into the project.

This restores the /customer/account redirect that was dropped as collateral
in "Remove redirects for /product/$type/[url] routes".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`multilinkHref` preferred the multilink's `cached_url`, which Storyblok freezes
at the moment the *referencing* story is published. Renaming the target story
does not rewrite it in already-published content, so every link to the renamed
story kept emitting the old URL — a 404 — until an editor re-published each
referencing story.

`story.full_slug` is injected by `@storyblok/react` from the `links` map the CDN
builds from the live story index on every request, and `sbParams` already sends
`resolve_links: 'story'` on all reads, so it is populated for story links and
always current. Prefer it, and fall through unchanged for `url` / `email` /
`asset` links, which have no resolved story.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback: the entry had no evidence behind it. Grepping every stock
Magento email template shows only three customer URLs — customer/account/
(48x), customer/account/createPassword (12x, carries id + rp_token) and
customer/account/confirm (2x, carries id + key). forgotpassword appears in
none of them; Magento's reset token rides on createPassword, and
forgotpassword is just the form where a customer types their email.

The remaining five are re-audited for shadowing. GraphCommerce serves exactly
two paths under /customer: confirm and createPassword, both real pages in all
three examples. None of the five sources collide with them (/customer/account/
create is an exact match and does not cover createPassword), and the comment
now states the mechanism that makes this matter: a redirect wins over a
filesystem route, so redirecting a path that has a page makes that page
unreachable.

Also corrects the comment's claim that @graphcommerce/magento-customer copies
those pages in — it has no copy/ directory; they are scaffolded from the
examples.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…refer-resolved-full-slug

fix(storyblok-ui): resolve story multilinks to the target's current slug
…omer-account-urls

fix(next-config): redirect Magento account URLs to the GraphCommerce routes
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.

6 participants