docs(thermidor): ADR + annex for first-class SSR support#7918
docs(thermidor): ADR + annex for first-class SSR support#7918fbeaudoincoveo wants to merge 4 commits into
Conversation
|
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
There was a problem hiding this comment.
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 afromSnapshotshorthand 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. |
@coveo/atomic
@coveo/atomic-hosted-page
@coveo/atomic-legacy
@coveo/atomic-react
@coveo/auth
@coveo/bueno
@coveo/create-atomic
@coveo/create-atomic-component
@coveo/create-atomic-component-project
@coveo/create-atomic-result-component
@coveo/create-atomic-rollup-plugin
@coveo/headless
@coveo/headless-react
@coveo/shopify
commit: |
|
Important Testing in progress…🟢 UI Tests: 458 tests unchanged |
|
Tip All tests passed and all changes approved!🟢 UI Tests: 458 tests unchanged |
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); |
There was a problem hiding this comment.
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,
})There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
fromSnapshotonly ever makes sense on the client-sideinitialParametersonly ever makes sense whenfromSnapshotis undefined.
So I suppose the right thing to do would be:
- if we ever receive
fromSnapshoton the server-side, we warn and ignore it. - if we receive both
fromSnapshotandinitialParameterson the client-side, we warn and ignoreinitialParameters.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Yes that would be good. I'll update the ADR.
| const searchBox = buildSearchBoxController({ interface: searchInterface }); | ||
| await searchBox.submit(); |
There was a problem hiding this comment.
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.
https://coveord.atlassian.net/browse/KIT-5803