Skip to content

Re-render a card in place when its adoptsFrom (type) changes after indexing#5567

Merged
habdelra merged 3 commits into
mainfrom
cs-12247-updating-card-json-with-an-adoptsfrom-change-should-cause
Jul 22, 2026
Merged

Re-render a card in place when its adoptsFrom (type) changes after indexing#5567
habdelra merged 3 commits into
mainfrom
cs-12247-updating-card-json-with-an-adoptsfrom-change-should-cause

Conversation

@habdelra

Copy link
Copy Markdown
Contributor

When you change a card's type by editing its JSON meta.adoptsFrom — for example re-pointing a realm's index card from the default CardsGrid to a custom index card — the realm re-indexes correctly, but the host kept rendering the card as its old type until a full browser reload.

How host refresh works

The host doesn't poll. When indexing completes, the realm broadcasts an index / incremental event listing the invalidated cards; the store (StoreService.handleInvalidations) re-fetches each loaded card's JSON and applies it to the instance it's holding. Instances live in a tracked identity map, so anything rendering a card by id re-renders when the map entry changes.

The bug

The reload applied the new JSON in place via card-api.updateFromSerialized. A card instance's JavaScript class is fixed at construction — updateFromSerialized reads the class off the existing object and never reconsults meta.adoptsFrom. So field edits worked, but a type change copied the new fields onto an instance that was still the old class, which kept rendering with the old template.

The fix

reloadInstance now resolves the incoming adoptsFrom; when the current instance is no longer an instance of that type, it re-instantiates via createFromSerialized (which builds the new-typed instance and swaps it into the tracked identity map) instead of updating in place. Same-type field edits are unchanged — still updated in place, preserving object identity.

Two details worth calling out:

  • The new-typed instance carries over the existing local id. A card keeps its identity across a type change, and the identity map's id-resolver rejects a second local id for an already-known remote id; reusing the local id also keeps references and the identity map resolving to the new instance.
  • A failure to resolve the incoming type on the client surfaces as a card error state, not a deletion — the reload's 404-means-deleted handling is reserved for a genuinely removed instance (a 404 from the card fetch).

Tests

  • Integration (Integration | Store): loads a card as one type, rewrites its JSON to an unrelated type, and asserts both the stored instance and the rendered isolated template become the new type.
  • Acceptance (code submode | editor tests): exercises the whole path — edit adoptsFrom in the code editor → realm reindex → index event → store reload → the preview re-renders as the new type.

Both tests were confirmed to fail without the fix and pass with it. The full Integration | Store (82) and editor acceptance (17) suites stay green.

🤖 Generated with Claude Code

habdelra and others added 2 commits July 21, 2026 11:28
The host store's reload path applied incoming JSON to the existing card
instance in place via updateFromSerialized. A card instance's JavaScript
class is fixed at construction, so a change to meta.adoptsFrom (the card's
type) — e.g. a realm index card re-pointed from CardsGrid to a custom index
card — left the old class in place and kept rendering the old type until a
full browser reload.

reloadInstance now resolves the incoming adoptsFrom and, when the instance
is no longer an instance of that type, re-instantiates via
createFromSerialized (carrying over the existing local id so the identity
map's id-resolver accepts the swap) and returns the new-typed instance for
reloadTask to swap into the tracked identity map. Same-type edits keep the
in-place update. A failure to resolve the incoming type surfaces as a card
error rather than being mistaken for a deletion.

Adds a host integration test and a full-path code-submode acceptance test
(edit adoptsFrom in the editor -> reindex -> index event -> re-render).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d-json-with-an-adoptsfrom-change-should-cause

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6905abc3f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/host/app/services/store.ts Outdated
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

152 tests   152 ✅  5m 1s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit 06b1947.

Realm Server Test Results

    1 files      1 suites   12m 17s ⏱️
1 913 tests 1 913 ✅ 0 💤 0 ❌
1 992 runs  1 992 ✅ 0 💤 0 ❌

Results for commit 06b1947.

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

This PR fixes a host refresh correctness bug where a card instance whose meta.adoptsFrom (type) changes after indexing would keep rendering as its previous type because the existing in-memory instance was only updated in place. The store now detects a type mismatch on reload and re-instantiates the card so the identity map points at the new-typed instance and renders update without a full page reload.

Changes:

  • Update StoreService.reloadInstance() to resolve the incoming adoptsFrom and, when the existing instance is not an instance of that type, re-instantiate via createFromSerialized() (carrying over the existing local id).
  • Adjust reload autosave subscription teardown logic so the old instance’s subscription is detached when it’s been superseded by an error state or a new-typed instance.
  • Add integration and acceptance tests that change meta.adoptsFrom and assert the store + rendered preview update to the new type.

Reviewed changes

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

File Description
packages/host/app/services/store.ts Rebuilds card instances on reload when the incoming adoptsFrom resolves to a different type; updates autosave subscription handling accordingly.
packages/host/tests/integration/store-test.gts Adds an integration test that rewrites a card’s meta.adoptsFrom and asserts the stored instance and isolated render become the new type.
packages/host/tests/acceptance/code-submode/editor-test.ts Adds an acceptance test that edits meta.adoptsFrom in the code editor and asserts the preview re-renders as the new type after indexing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/host/app/services/store.ts
Comment thread packages/host/app/services/store.ts Outdated
- Compare the resolved adoptsFrom against the instance's exact class instead
  of a subtype check, so re-pointing from a subclass to one of its ancestors
  rebuilds instead of keeping the subclass instance. This also removes the
  instanceof narrowing that tripped the type-check.
- Build the new-typed instance directly (rather than via createFromSerialized,
  whose cached-instance reuse would keep the old object for an ancestor type)
  and let updateFromSerialized swap it into the identity map.
- Preserve the CardError detail from a failed loadCardDef, only dropping the
  404 status so it surfaces as an error state rather than a phantom delete.
- Add an integration test for the subclass-to-ancestor case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@habdelra
habdelra requested a review from a team July 21, 2026 16:50
@habdelra
habdelra merged commit d9a99e0 into main Jul 22, 2026
99 of 101 checks passed
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.

3 participants