Skip to content

Conversation

@lucaslyl
Copy link
Contributor

@lucaslyl lucaslyl commented Jan 8, 2026

Migrate field specs from field-showcase to individual subclass specs

This is the first pass to migrate fields from field-showcase to each subclass spec.

This change:

  • Creates spec for subclass override with isolated and edit templates
  • Adds reusable FieldSpecIsolatedTemplate and FieldSpecEditTemplate components for subclass specs
  • Removes field-showcase dependency
  • Updates all field spec subclasses to use the new shared templates

FieldSpecIsolatedTemplate / FieldSpecEditTemplate include the Field Configuration Playground (dynamic fields) and hide the example section, which differs from the Base Spec template.te and FieldSpecEditTemplate include the Field Configuration Playground (dynamic fields) and hide the example section, which differs from the Base Spec template.

Demo:

Screen.Recording.2026-01-08.at.6.21.03.PM.mov

Follow-up task:

https://linear.app/cardstack/issue/CS-9974/base-spec-isolatededit-and-subclass-spec-isolatededit-templates-have
In the catalog, we override the Subclass Spec isolated/edit template to display the Field Configuration Playground (dynamic fields / hide the example section). However, most of the remaining code is the same as
the Base Spec template.

We discussed whether we should move the shared isolatedTemplate and editTemplate components into the Base Spec (so both base spec and subclass spec can reuse it), or introduce a shared implementation that both Base and Subclass specs can reference. ** Currently all subclass spec is located under catalog-realms **

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

Preview deployments

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

Host Test Results

    1 files  ±  0      1 suites  ±0   1h 35m 44s ⏱️ + 5m 39s
1 856 tests +216  1 839 ✅ +216  15 💤 ±0  0 ❌  - 1  2 🔥 +1 
1 871 runs  +217  1 852 ✅ +216  15 💤 ±0  2 ❌ ±0  2 🔥 +1 

For more details on these errors, see this check.

Results for commit 3ff3f13. ± Comparison against base commit b2f9b85.

This pull request removes 1 and adds 217 tests. Note that renamed tests count towards both.
Chrome ‑ Global error: Uncaught TypeError: Failed to fetch at http://localhost:7357/assets/chunk.87676936a9f50c566053.js, line 153748  While executing test: Integration | card-copy: copy button does not appear when right and left stacks are both index cards but there are selections on both sides 
Chrome ‑ Integration | Command | create-specs: subclassed spec with custom edit template hides Examples section
Chrome ‑ Integration | Command | create-specs: subclassed spec with custom edit template shows Field Configuration Playground section
Chrome ‑ Integration | Store: added instance that was previously not saved will begin to auto save after being added
Chrome ‑ Integration | Store: an instance can be restored after a loader reset
Chrome ‑ Integration | Store: an instance can debounce auto saves
Chrome ‑ Integration | Store: an instance can live update thru an error state
Chrome ‑ Integration | Store: an instance live updates from indexing events for a code update
Chrome ‑ Integration | Store: an instance live updates from indexing events for an instance update
Chrome ‑ Integration | Store: an instance that started out with a local ID can be restored after a loader reset
Chrome ‑ Integration | Store: an instance will NOT auto save when its data changes, if the user does not have write permissions
…

♻️ This comment has been updated with latest results.

@lucaslyl lucaslyl self-assigned this Jan 8, 2026
@lucaslyl lucaslyl requested a review from a team January 8, 2026 10:33

static isolated =
FieldSpecIsolatedTemplate as unknown as typeof Spec.isolated;
static edit = FieldSpecEditTemplate as unknown as typeof Spec.edit;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TypeScript can't verify the compatibility directly. I use this double type assertion (as unknown as typeof Spec.isolated) to tell TypeScript the template is compatible with the expected type

@lucaslyl lucaslyl requested a review from a team January 8, 2026 13:19
@lucaslyl lucaslyl marked this pull request as ready for review January 8, 2026 13:19
"attributes": {
"ref": {
"name": "AudioFieldSpec",
"module": "http://localhost:4201/catalog/field-spec/audio-field-spec"
Copy link
Contributor

@tintinthong tintinthong Jan 9, 2026

Choose a reason for hiding this comment

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

why are the refs absolulte? How is this gonna work in live environment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think this is a bug in the existing create-spec command. I searched through the spec instances created recently, and they all point to an absolute path. I’ll work on changing this behavior so it uses relative paths instead.

image

'readMe content sourced from proxy mock',
);
});

Copy link
Contributor

Choose a reason for hiding this comment

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

does this test work? I thought we had to copy over files for host test that depend on catalog now

Copy link
Contributor Author

@lucaslyl lucaslyl Jan 9, 2026

Choose a reason for hiding this comment

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

ya ,it work but sometimes it flaky in the host test, i wil investigate this

const title = this.getSpecTitle(declaration, codeRef.name);
const specType = new SpecTypeGuesser(declaration).type;

const specClassToUse = await getSpecClassFromDeclaration(
Copy link
Contributor

Choose a reason for hiding this comment

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

lets just call this specKlass as before

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will to rename this to ResolvedSpecKlass because this function also passes SpecKlass as an argument.

relativeTo,
codeRefOpts,
codeRefOpts: metaCodeRefOpts,
customFieldDefinitions,
Copy link
Contributor Author

@lucaslyl lucaslyl Jan 9, 2026

Choose a reason for hiding this comment

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

@tintinthong

Here is my research.

When creating a Spec, the ref.module field (a CodeRef in attributes) was stored as an absolute URL (e.g., "http://localhost:4200/catalog-realm/blog-app") instead of a relative URL (e.g., "../blog-app/blog-app").

Root Cause:
In file-serializer.ts, processAttributes used codeRefOpts, which lacks:

  • allowRelative: true
  • maybeRelativeURL function

Without these, codeRefAdjustments couldn't convert absolute URLs to relative ones during serialization.

Solution:

Changed processAttributes to use metaCodeRefOpts instead of codeRefOpts. metaCodeRefOpts includes:
allowRelative: true — enables relative URL conversion
maybeRelativeURL — converts absolute URLs to relative when realmURL is available

Here is the demo after i made this changes:

Screen.Recording.2026-01-09.at.5.59.09.PM.mov

@lucaslyl lucaslyl force-pushed the CS-9803-migrate-field-configuration-showcases branch from eb05214 to 292e6c3 Compare January 9, 2026 11:07
*/
async function getSpecClassFromDeclaration(
codeRef: ResolvedCodeRef,
fallbackSpecClass: typeof BaseDef,
Copy link
Contributor

Choose a reason for hiding this comment

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

wouldnt this be a typeof CardDef--Specs are Cards not fields

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed with it, the issue has been addressed

Copy link
Contributor

Copilot AI left a comment

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 migrates field configuration showcases from a single centralized field-showcase component to individual field spec subclasses with reusable templates. The changes introduce two new template components (FieldSpecIsolatedTemplate and FieldSpecEditTemplate) that provide the Field Configuration Playground functionality while removing the dependency on the large field-showcase file.

Key Changes

  • Created reusable FieldSpecIsolatedTemplate and FieldSpecEditTemplate components for field spec subclasses
  • Implemented 20+ individual field spec files (AudioFieldSpec, ColorFieldSpec, DateFieldSpec, etc.) with proper configurations
  • Enhanced create-specs command to automatically use spec subclasses when available
  • Removed the monolithic field-showcase.gts file (~4000 lines) and its instance data

Reviewed changes

Copilot reviewed 70 out of 71 changed files in this pull request and generated no comments.

Show a summary per file
File Description
field-spec/components/field-spec-isolated-template.gts New reusable isolated template component for field specs with Field Configuration Playground
field-spec/components/field-spec-edit-template.gts New reusable edit template component for field specs with Generate README button
utils/is-own-field.ts Utility to filter inherited fields from own fields in field specs
field-spec/*-field-spec.gts 20+ new field spec files with field configurations and custom templates
commands/create-specs.ts Enhanced to automatically detect and use spec subclasses
tests/integration/commands/create-spec-test.gts Added tests for custom templates in subclassed specs
runtime-common/file-serializer.ts Variable rename for clarity
fields-preview/field-showcase.gts Deleted 4000-line monolithic showcase file
Various JSON files New spec instances and supporting image data

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

Comment on lines 1 to 11
import type { BaseDef } from 'https://cardstack.com/base/card-api';

// When checking ColorFieldSpec:
// ✅ Returns: 'standard', 'wheel', 'sliderRgb', etc. (directly on ColorFieldSpec)
// ❌ Does NOT return: 'readMe', 'ref', 'title' (from Spec.prototype - inherited)
// ❌ Does NOT return: 'id' (from CardDef.prototype - inherited)
export function isOwnField(card: typeof BaseDef, fieldName: string): boolean {
return Object.keys(Object.getOwnPropertyDescriptors(card.prototype)).includes(
fieldName,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a dupe of the schema-editor.ts's isOwnFields() function. instead of duplicating, please create function in runtime-common that can be shared by these two

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great catch. Thanks for the review and suggestion. I’ve created a shared isOwnField utility in runtime-common

@lucaslyl lucaslyl requested a review from habdelra January 9, 2026 16:12
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.

4 participants