Skip to content

docs(thermidor): ADR + annex for first-class SSR support#7918

Open
fbeaudoincoveo wants to merge 4 commits into
mainfrom
KIT-5803-adr-for-first-class-ssr
Open

docs(thermidor): ADR + annex for first-class SSR support#7918
fbeaudoincoveo wants to merge 4 commits into
mainfrom
KIT-5803-adr-for-first-class-ssr

Conversation

@fbeaudoincoveo

@fbeaudoincoveo fbeaudoincoveo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 42fa87e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@svcsnykcoveo

svcsnykcoveo commented Jul 3, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds internal Thermidor architecture documentation (ADR + annex) proposing a per-interface snapshot API to enable first-class SSR and hydration patterns across frameworks, plus a small spellchecker dictionary update to support the new docs.

Changes:

  • Introduces ADR-007 describing the proposed SSR snapshot primitives (initialState, getInterfaceSnapshot, restoreInterfaceSnapshot) and a fromSnapshot shorthand concept.
  • Adds a detailed annex documenting proposed public types/options and end-to-end SSR lifecycle examples (including composed interfaces and React adapter ergonomics).
  • Updates cspell configuration to allow the HTTP header spelling referer.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
packages/thermidor/docs/internal/adr/ADR-007-ssr-snapshot-api.md New ADR proposing the SSR per-interface snapshot API and migration considerations.
packages/thermidor/docs/internal/adr/ADR-007-annex-ssr-implementation-details.md Annex with proposed types, implementation sketches, and SSR usage examples (server/client, composed, React adapter).
.cspell.json Adds referer to the allowed words list for documentation/examples.

Comment thread packages/thermidor/docs/internal/adr/ADR-007-annex-ssr-implementation-details.md Outdated
Comment thread packages/thermidor/docs/internal/adr/ADR-007-ssr-snapshot-api.md
@pkg-pr-new

pkg-pr-new Bot commented Jul 3, 2026

Copy link
Copy Markdown
@coveo/atomic

npm i https://pkg.pr.new/@coveo/atomic@7918

@coveo/atomic-hosted-page

npm i https://pkg.pr.new/@coveo/atomic-hosted-page@7918

@coveo/atomic-legacy

npm i https://pkg.pr.new/@coveo/atomic-legacy@7918

@coveo/atomic-react

npm i https://pkg.pr.new/@coveo/atomic-react@7918

@coveo/auth

npm i https://pkg.pr.new/@coveo/auth@7918

@coveo/bueno

npm i https://pkg.pr.new/@coveo/bueno@7918

@coveo/create-atomic

npm i https://pkg.pr.new/@coveo/create-atomic@7918

@coveo/create-atomic-component

npm i https://pkg.pr.new/@coveo/create-atomic-component@7918

@coveo/create-atomic-component-project

npm i https://pkg.pr.new/@coveo/create-atomic-component-project@7918

@coveo/create-atomic-result-component

npm i https://pkg.pr.new/@coveo/create-atomic-result-component@7918

@coveo/create-atomic-rollup-plugin

npm i https://pkg.pr.new/@coveo/create-atomic-rollup-plugin@7918

@coveo/headless

npm i https://pkg.pr.new/@coveo/headless@7918

@coveo/headless-react

npm i https://pkg.pr.new/@coveo/headless-react@7918

@coveo/shopify

npm i https://pkg.pr.new/@coveo/shopify@7918

commit: 42fa87e

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🔗 Scratch Orgs ready to test this PR:

@chromatic-com

chromatic-com Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Testing in progress…

🟢 UI Tests: 458 tests unchanged
UI Review: Comparing 458 stories…
Storybook icon Storybook Publish: 458 stories published

@chromatic-com

chromatic-com Bot commented Jul 3, 2026

Copy link
Copy Markdown

Tip

All tests passed and all changes approved!

🟢 UI Tests: 458 tests unchanged
🟢 UI Review: 458 stories published -- no changes
Storybook icon Storybook Publish: 458 stories published

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

```ts
// Server
const params = deserializeSearchParameters(url.searchParams);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it wouldn't make more sense to put the snapshot methods on the interface itself 🤔

Since slices are organized per interface, something like this feels more natural, don't you think?

It would also make it more explicit that the interface is being initialized from a snapshot when using the interface builder.

// ON THE ENGINE ---------------->

// Server
const snapshot = engine.getInterfaceSnapshot(searchInterface)

// Client
engine.restoreInterfaceSnapshot(snapshot)
const searchInterface = buildSearchInterface({engine})

// ON THE INTERFACE ------------->

// Server
const snapshot = searchInterface.getSnapshot()

// Client
const searchInterface = buildSearchInterface({
  engine,
  fromSnapshot: snapshot,
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely!

I thought of adding the fromSnapshot property a bit late, so we needed to go through the engine without it, but with that property it would make much more sense to have these methods on the interfaces indeed.

- **Public API changes**: `initialState` option on `buildSearchInterface`, `buildCommerceInterface`, `composeInterfaces`. Two new engine methods. `InterfaceSnapshot` type. `deserializeSearchParameters` utility. `buildGenerativeInterface` explicitly excluded.
- **Backward compatibility impact**: Additive only. No breaking changes.
- **Deprecations required**: None.
- **Type/contract stability notes**: `InterfaceSnapshot.state` is `Record<string, unknown>` (opaque). Internal structure may change; `version` field enables graceful handling.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line mentions it enables "graceful handling", but the strategy is not specified.

What happens when the client receives a v1 snapshot but runs on SDK v2?
Should we do an automatic migration? Or just silently fallback to default state?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, this should not happen: you would normally get your snapshot from Thermidor on the server-side, and use the same version of Thermidor on the client-side.

Now that I think of it, I can't really come up with a realistic case where we would get a vX snapshot, but be expecting a vY snapshot... This could only happen if the user tampered with snapshot on the server-side. Perhaps we should just remove this "version fiueld enables graceful handling thing".

export interface BuildCommerceInterfaceOptions {
engine: Engine;
id?: string;
initialState?: CommerceInterfaceInitialState;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused about having both initialState and fromSnapshot, probably because they both represent "injecting an initial state".

My understanding is that initialState is to provide the request parameters, right?

So maybe having this naming would make it more explicit, what do you think?

  • initialParameters = request input (before fetch)
  • fromSnapshot = complete output (after fetch)

Also, what's the expected behavior if a consumer passes both? Should it throw? Merge? Prioritize? Warning?

@fbeaudoincoveo fbeaudoincoveo Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

This also makes me realize we should consider removing the id property too (while we still can! 😁). Otherwise, we could end up with conflicting ids (the one extracted from the snapshot and the one explicitly passed). And now that you can pass a snapshot (that includes the id), there really isn't much point in passing an explicit id for an interface. You should just let the system deal with that on its own.

Back to the matter at hand: indeed you are right: the naming here is extremely confusing, and initialParameters would be much clearer.

Now, the thing with these parameters is that:

  • fromSnapshot only ever makes sense on the client-side
  • initialParameters only ever makes sense when fromSnapshot is undefined.

So I suppose the right thing to do would be:

  • if we ever receive fromSnapshot on the server-side, we warn and ignore it.
  • if we receive both fromSnapshot and initialParameters on the client-side, we warn and ignore initialParameters.

The getSnapshot method on interfaces would also only be useful when on the server-side... so maybe we should warn when it's getting called on the client-side (because that would smell of a mistake in the implementation).

- **Public API changes**: `initialState` option on `buildSearchInterface`, `buildCommerceInterface`, `composeInterfaces`. Two new engine methods. `InterfaceSnapshot` type. `deserializeSearchParameters` utility. `buildGenerativeInterface` explicitly excluded.
- **Backward compatibility impact**: Additive only. No breaking changes.
- **Deprecations required**: None.
- **Type/contract stability notes**: `InterfaceSnapshot.state` is `Record<string, unknown>` (opaque). Internal structure may change; `version` field enables graceful handling.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add snapshot validation, don't you think?
Throw an error in the console if the snapshot is invalid and fallback to default state?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that would be good. I'll update the ADR.

Comment on lines +121 to +122
const searchBox = buildSearchBoxController({ interface: searchInterface });
await searchBox.submit();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe we do something like:

const searchBoxActions = loadSearchBoxActions({ interface: searchInterface });
searchBoxActions.submit();

On the server-side, since we don't need the full controller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants