Skip to content

Guard default card/field templates against a transiently undefined model - #5681

Merged
lukemelia merged 2 commits into
mainfrom
cs-12330-host-crashes-default-isolatededit-template-throws-when-card
Aug 4, 2026
Merged

Guard default card/field templates against a transiently undefined model#5681
lukemelia merged 2 commits into
mainfrom
cs-12330-host-crashes-default-isolatededit-template-throws-when-card

Conversation

@lukemelia

Copy link
Copy Markdown
Contributor

Problem

The production host intermittently crashes while rendering a card:

TypeError: Cannot read properties of undefined (reading 'constructor')
    at get cardInfoFieldDisplayNames (.../default-templates/isolated-and-edit)
    at get displayFields (.../default-templates/isolated-and-edit)

The default isolated/edit and field-edit templates derive the card/field class from this.args.model.constructor inside tracked getters, and dereference this.args.fields unguarded. The host can invoke a format component for a tick while the model instance is still resolving — on initial load, and on a store re-resolve when an incremental index invalidation lands under an open card — so the first tracked getter throws and takes down the render. Glimmer templates are null-safe on undefined paths; JS getters are not.

Fix

Read the field definitions off the card/field class instead of the model instance. The render path already threads the class in as @cardOrField — a stable value independent of the model: getBoxComponent receives it, field-component.gts passes it to every CardOrFieldFormatComponent, and BaseDefComponent already declares it in its Args. getField() accepts a class directly, so getField(this.args.cardOrField, …) is a drop-in for getField(this.args.model.constructor, …) that never touches the transient model.

The @fields-dependent paths are guarded too:

  • displayFields returns early when @fields is absent
  • isThemeCard coalesces to {}
  • the CardInfo edit header renders only when @fields.cardInfo exists
  • the notes footer is wrapped in {{#if @fields.cardInfo.notes}} — invoking <@fields.cardInfo.notes /> with undefined @fields throws "attempted to invoke undefined component"

Behavior is unchanged when the model and fields are present.

This supersedes the earlier #5608, which was closed because it was authored alongside unrelated userland work; this reapplies the @cardOrField approach cleanly on current main.

Notes

  • atom.gts has the same model.constructor shape but already guards with an early if (!this.args.model) return;, so it is not vulnerable and is left unchanged.
  • The crash is a transient-resolution race that only reproduces under live invalidation in production, so there is no deterministic unit test for the timing window (the tracking issue's own render-suite investigation reached the same conclusion). Verified via ember-template-lint (clean) and host lint:types (no errors); behavior-preserving for the normal load path.

🤖 Generated with Claude Code

The default isolated/edit and field-edit templates derived the card/field
class from `this.args.model.constructor` inside tracked getters, and
dereferenced `this.args.fields` unguarded. The host can invoke a format
component for a tick while the model instance is still resolving — on
initial load, and on a store re-resolve when an incremental index
invalidation lands under an open card — so the first tracked getter throws
`Cannot read properties of undefined (reading 'constructor')` and takes
down the render. Templates are null-safe on undefined paths; JS getters
are not.

Read the field definitions off the card/field class instead, which the
render path already threads in as `@cardOrField` (a stable value
independent of the model): `getBoxComponent` receives it and
`field-component.gts` passes it to every `CardOrFieldFormatComponent`, and
`BaseDefComponent` already declares it. `getField` accepts a class
directly, so this is a drop-in for `model.constructor`.

Also guard the `@fields`-dependent paths: `displayFields` returns early
when `@fields` is absent, `isThemeCard` coalesces to `{}`, the CardInfo
edit header renders only when `@fields.cardInfo` exists, and the notes
footer is wrapped in `{{#if @fields.cardInfo.notes}}` (invoking
`<@fields.cardInfo.notes />` with undefined `@fields` throws "attempted to
invoke undefined component"). Behavior is unchanged when the model and
fields are present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Host Test Results

    1 files      1 suites   2h 53m 11s ⏱️
3 813 tests 3 799 ✅ 14 💤 0 ❌
3 832 runs  3 818 ✅ 14 💤 0 ❌

Results for commit 7f3c78f.

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

Updates the base realm’s default card and field edit templates to be resilient when the host briefly renders them before the backing model/fields are fully available, avoiding crashes from JS-side dereferences.

Changes:

  • Use the stable @cardOrField class arg for getField(...) lookups instead of @model.constructor.
  • Add guards/conditional rendering around @fields-dependent paths (displayFields, theme detection, cardInfo edit header, notes footer).
  • Thread cardOrField into the affected template signatures to support the above change.

Reviewed changes

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

File Description
packages/base/default-templates/isolated-and-edit.gts Switch field-definition reads to @cardOrField, and guard @fields accesses/conditional subrenders to prevent transient-resolution crashes.
packages/base/default-templates/field-edit.gts Switch field-icon lookup to use @cardOrField instead of @model.constructor to avoid dereferencing an undefined model.

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

Comment thread packages/base/default-templates/isolated-and-edit.gts Outdated
Comment thread packages/base/default-templates/isolated-and-edit.gts
The isolated-format card-info header passed `@icon={{@model.constructor.icon}}`.
When the model is transiently undefined, that resolves to an undefined icon,
and `card-info.gts` renders `<@ICON />` unguarded when there is no thumbnail —
crashing the render, the same failure mode this change set guards elsewhere.
Read the icon off `@cardOrField` (the stable class arg already threaded for the
field-definition lookups) so it stays defined while the model resolves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia
lukemelia marked this pull request as ready for review August 4, 2026 16:57
@lukemelia
lukemelia requested review from a team and backspace August 4, 2026 16:57
@lukemelia
lukemelia merged commit 91ba458 into main Aug 4, 2026
87 of 88 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