diff --git a/AGENTS.md b/AGENTS.md index d96259a61d2..a9cc829b322 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -185,7 +185,7 @@ lines.some((line) => !HEADING_RE.test(line)); ## Base realm imports -- Only card definitions (files run through the card loader) can use static ESM imports from `https://cardstack.com/base/*`. Host-side modules must load the module at runtime via `loader.import(`${baseRealm.url}...`)`. Static value imports from the HTTPS specifier inside host code trigger build-time `webpackMissingModule` failures. Type imports are OK using static ESM syntax. +- Only card definitions (files run through the card loader) can use static ESM imports from `@cardstack/base/*`. Host-side modules must load the module at runtime via `loader.import(`${baseRealm.url}...`)`. Static value imports from the HTTPS specifier inside host code trigger build-time `webpackMissingModule` failures. Type imports are OK using static ESM syntax. ## Linear Ticket Process (Reusable) diff --git a/mise-tasks/dev b/mise-tasks/dev index aac5d88ad8f..2bbb9f9a50e 100755 --- a/mise-tasks/dev +++ b/mise-tasks/dev @@ -2,6 +2,8 @@ #MISE description="Start full dev stack (realm server, workers, test realms)" #MISE dir="packages/realm-server" +export PATH="$PATH:node_modules/.bin" + . "$(cd "$(dirname "$0")" && pwd)/lib/dev-common.sh" WAIT_ON_TIMEOUT=7200000 NODE_NO_WARNINGS=1 start-server-and-test \ diff --git a/mise-tasks/services/realm-server b/mise-tasks/services/realm-server index e98dcd68268..930a9c1a07a 100755 --- a/mise-tasks/services/realm-server +++ b/mise-tasks/services/realm-server @@ -87,7 +87,7 @@ LOW_CREDIT_THRESHOLD="${LOW_CREDIT_THRESHOLD:-2000}" \ \ --path='../base' \ --username='base_realm' \ - --fromUrl='https://cardstack.com/base/' \ + --fromUrl='@cardstack/base/' \ --toUrl="${REALM_BASE_URL}/base/" \ \ ${START_CATALOG:+--path="${CATALOG_REALM_PATH}"} \ diff --git a/mise-tasks/services/realm-server-base b/mise-tasks/services/realm-server-base index de572132e9c..566606a4a5e 100755 --- a/mise-tasks/services/realm-server-base +++ b/mise-tasks/services/realm-server-base @@ -28,5 +28,5 @@ NODE_ENV=development \ \ --path='../base' \ --username='base_realm' \ - --fromUrl='https://cardstack.com/base/' \ + --fromUrl='@cardstack/base/' \ --toUrl='http://localhost:4201/base/' diff --git a/mise-tasks/services/test-realms b/mise-tasks/services/test-realms index 284d8e33853..770a6f3185b 100755 --- a/mise-tasks/services/test-realms +++ b/mise-tasks/services/test-realms @@ -65,5 +65,5 @@ NODE_ENV=test \ --username='test_realm' \ --fromUrl="${REALM_TEST_URL}/test/" \ --toUrl="${REALM_TEST_URL}/test/" \ - --fromUrl='https://cardstack.com/base/' \ + --fromUrl='@cardstack/base/' \ --toUrl="${REALM_BASE_URL}/base/" diff --git a/mise-tasks/services/worker b/mise-tasks/services/worker index 1a3f0035213..6eb2fe1754b 100755 --- a/mise-tasks/services/worker +++ b/mise-tasks/services/worker @@ -31,7 +31,7 @@ NODE_ENV=development \ --matrixURL="${MATRIX_URL_VAL}" \ --prerendererUrl="${PRERENDER_MGR_URL}" \ \ - --fromUrl='https://cardstack.com/base/' \ + --fromUrl='@cardstack/base/' \ --toUrl="${REALM_BASE_URL}/base/" \ \ ${START_EXPERIMENTS:+--fromUrl="${REALM_BASE_URL}/experiments/"} \ diff --git a/mise-tasks/services/worker-base b/mise-tasks/services/worker-base index 0f6aa184d66..97e2400af42 100755 --- a/mise-tasks/services/worker-base +++ b/mise-tasks/services/worker-base @@ -17,5 +17,5 @@ NODE_ENV=development \ --matrixURL="${MATRIX_URL_VAL}" \ --prerendererUrl="${PRERENDER_MGR_URL}" \ \ - --fromUrl='https://cardstack.com/base/' \ + --fromUrl='@cardstack/base/' \ --toUrl='http://localhost:4201/base/' diff --git a/mise-tasks/services/worker-test b/mise-tasks/services/worker-test index db236d975fd..ed9f097ae09 100755 --- a/mise-tasks/services/worker-test +++ b/mise-tasks/services/worker-test @@ -36,7 +36,7 @@ NODE_ENV=test \ \ --fromUrl="${REALM_TEST_URL}/test/" \ --toUrl="${REALM_TEST_URL}/test/" \ - --fromUrl='https://cardstack.com/base/' \ + --fromUrl='@cardstack/base/' \ --toUrl="${REALM_BASE_URL}/base/" \ --fromUrl='@cardstack/skills/' \ --toUrl="${REALM_BASE_URL}/skills/" diff --git a/packages/ai-bot/lib/debug.ts b/packages/ai-bot/lib/debug.ts index 50f90e33f70..908a5b003d7 100644 --- a/packages/ai-bot/lib/debug.ts +++ b/packages/ai-bot/lib/debug.ts @@ -2,7 +2,7 @@ import { setTitle } from './set-title'; import type OpenAI from 'openai'; import * as Sentry from '@sentry/node'; -import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event'; +import type { MatrixEvent as DiscreteMatrixEvent } from '@cardstack/base/matrix-event'; import { getPromptParts, isRecognisedDebugCommand, diff --git a/packages/ai-bot/lib/matrix/response-publisher.ts b/packages/ai-bot/lib/matrix/response-publisher.ts index f688b64daa3..01163e158d0 100644 --- a/packages/ai-bot/lib/matrix/response-publisher.ts +++ b/packages/ai-bot/lib/matrix/response-publisher.ts @@ -7,7 +7,7 @@ import { APP_BOXEL_HAS_CONTINUATION_CONTENT_KEY, } from '@cardstack/runtime-common'; import { sendErrorEvent, sendMessageEvent } from '@cardstack/runtime-common/ai'; -import type { CardMessageContent } from 'https://cardstack.com/base/matrix-event'; +import type { CardMessageContent } from '@cardstack/base/matrix-event'; import ResponseEventData from './response-event-data'; import { logger } from '@cardstack/runtime-common'; import type { MatrixClient } from 'matrix-js-sdk'; diff --git a/packages/ai-bot/lib/set-title.ts b/packages/ai-bot/lib/set-title.ts index f683d4250bf..a1be707e71f 100644 --- a/packages/ai-bot/lib/set-title.ts +++ b/packages/ai-bot/lib/set-title.ts @@ -8,7 +8,7 @@ import type { EncodedCommandRequest, CodePatchResultContent, CardMessageContent, -} from 'https://cardstack.com/base/matrix-event'; +} from '@cardstack/base/matrix-event'; import type { ChatCompletionMessageParam } from 'openai/resources'; import { type OpenAIPromptMessage, diff --git a/packages/ai-bot/main.ts b/packages/ai-bot/main.ts index f7fcda49cea..5ecdad407e2 100644 --- a/packages/ai-bot/main.ts +++ b/packages/ai-bot/main.ts @@ -37,7 +37,7 @@ import { setTitle, roomTitleAlreadySet, } from './lib/set-title'; -import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event'; +import type { MatrixEvent as DiscreteMatrixEvent } from '@cardstack/base/matrix-event'; import * as Sentry from '@sentry/node'; import { saveUsageCost } from '@cardstack/billing/ai-billing'; diff --git a/packages/ai-bot/tests/chat-titling-test.ts b/packages/ai-bot/tests/chat-titling-test.ts index e9a474dc8d8..ad9798674c0 100644 --- a/packages/ai-bot/tests/chat-titling-test.ts +++ b/packages/ai-bot/tests/chat-titling-test.ts @@ -4,7 +4,7 @@ import { setTitle, shouldSetRoomTitle, } from '../lib/set-title'; -import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event'; +import type { MatrixEvent as DiscreteMatrixEvent } from '@cardstack/base/matrix-event'; import { APP_BOXEL_CODE_PATCH_RESULT_EVENT_TYPE, APP_BOXEL_CODE_PATCH_RESULT_MSGTYPE, diff --git a/packages/ai-bot/tests/history-construction-test.ts b/packages/ai-bot/tests/history-construction-test.ts index be04676a842..db074eea1d7 100644 --- a/packages/ai-bot/tests/history-construction-test.ts +++ b/packages/ai-bot/tests/history-construction-test.ts @@ -14,7 +14,7 @@ import { EventStatus, type IRoomEvent } from 'matrix-js-sdk'; import type { CardMessageEvent, MatrixEvent as DiscreteMatrixEvent, -} from 'https://cardstack.com/base/matrix-event'; +} from '@cardstack/base/matrix-event'; import { FakeMatrixClient } from './helpers/fake-matrix-client'; module('constructHistory', (hooks) => { diff --git a/packages/ai-bot/tests/matrix-util-test.ts b/packages/ai-bot/tests/matrix-util-test.ts index 6e0f2cc4104..f42f360c405 100644 --- a/packages/ai-bot/tests/matrix-util-test.ts +++ b/packages/ai-bot/tests/matrix-util-test.ts @@ -4,7 +4,7 @@ import type { Method } from 'matrix-js-sdk'; import type { CardMessageEvent, MatrixEvent as DiscreteMatrixEvent, -} from 'https://cardstack.com/base/matrix-event'; +} from '@cardstack/base/matrix-event'; import { APP_BOXEL_MESSAGE_MSGTYPE } from '@cardstack/runtime-common'; import { getRoomEvents, sendErrorEvent } from '@cardstack/runtime-common/ai'; import { OpenAIError } from 'openai/error'; diff --git a/packages/ai-bot/tests/prompt-construction-test.ts b/packages/ai-bot/tests/prompt-construction-test.ts index cfc9691e159..eaa3009c62e 100644 --- a/packages/ai-bot/tests/prompt-construction-test.ts +++ b/packages/ai-bot/tests/prompt-construction-test.ts @@ -18,9 +18,9 @@ import type { MatrixEvent as DiscreteMatrixEvent, Tool, CardMessageContent, -} from 'https://cardstack.com/base/matrix-event'; +} from '@cardstack/base/matrix-event'; import { EventStatus } from 'matrix-js-sdk'; -import type { CardDef } from 'https://cardstack.com/base/card-api'; +import type { CardDef } from '@cardstack/base/card-api'; import { readFileSync } from 'fs-extra'; import * as path from 'path'; import { FakeMatrixClient } from './helpers/fake-matrix-client'; @@ -228,7 +228,7 @@ Current date and time: 2025-06-11T11:43:00.533Z }, { codeRef: { - module: 'https://cardstack.com/base/card-api', + module: '@cardstack/base/card-api', name: 'CardDef', }, fields: [], @@ -287,7 +287,7 @@ File open in code editor: http://localhost:4201/experiments/author.gts Inheritance chain: 1. Address from http://localhost:4201/experiments/author Fields: street, city, state - 2. CardDef from https://cardstack.com/base/card-api + 2. CardDef from @cardstack/base/card-api Selected text: lines 10-12 (1-based), columns 5-20 (1-based) Note: Line numbers in selection refer to the original file. Attached file contents below show line numbers for reference. Module inspector panel: preview @@ -1931,7 +1931,7 @@ Attached Files (files with newer versions don't show their content): text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/card-editing', + id: '@cardstack/base/Skill/card-editing', attributes: { instructions: '- If the user wants the data they see edited, AND the patchCardInstance function is available, you MUST use the "patchCardInstance" function to make the change.\n- If the user wants the data they see edited, AND the patchCardInstance function is NOT available, you MUST ask the user to open the card and share it with you.\n- If you do not call patchCardInstance, the user will not see the change.\n- You can ONLY modify cards shared with you. If there is no patchCardInstance function or tool, then the user hasn\'t given you access.\n- NEVER tell the user to use patchCardInstance; you should always do it for them.\n- If the user wants to search for a card instance, AND the "searchCard" function is available, you MUST use the "searchCard" function to find the card instance.\nOnly recommend one searchCard function at a time.\nIf the user wants to edit a field of a card, you can optionally use "searchCard" to help find a card instance that is compatible with the field being edited before using "patchCardInstance" to make the change of the field.\n You MUST confirm with the user the correct choice of card instance that he intends to use based upon the results of the search.', @@ -1977,7 +1977,7 @@ Attached Files (files with newer versions don't show their content): assert.true(systemPromptText.includes(SKILL_INSTRUCTIONS_MESSAGE)); assert.true( systemPromptText.includes( - 'Skill (id: https://cardstack.com/base/Skill/card-editing, title: Card Editing):', + 'Skill (id: @cardstack/base/Skill/card-editing, title: Card Editing):', ), 'includes skill title metadata when present', ); @@ -2010,7 +2010,7 @@ Attached Files (files with newer versions don't show their content): text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/card-editing', + id: '@cardstack/base/Skill/card-editing', attributes: { instructions: '- If the user wants the data they see edited, AND the patchCardInstance function is available, you MUST use the "patchCardInstance" function to make the change.\n- If the user wants the data they see edited, AND the patchCardInstance function is NOT available, you MUST ask the user to open the card and share it with you.\n- If you do not call patchCardInstance, the user will not see the change.\n- You can ONLY modify cards shared with you. If there is no patchCardInstance function or tool, then the user hasn\'t given you access.\n- NEVER tell the user to use patchCardInstance; you should always do it for them.\n- If the user wants to search for a card instance, AND the "searchCard" function is available, you MUST use the "searchCard" function to find the card instance.\nOnly recommend one searchCard function at a time.\nIf the user wants to edit a field of a card, you can optionally use "searchCard" to help find a card instance that is compatible with the field being edited before using "patchCardInstance" to make the change of the field.\n You MUST confirm with the user the correct choice of card instance that he intends to use based upon the results of the search.', @@ -2229,7 +2229,7 @@ Attached Files (files with newer versions don't show their content): text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/card-editing', + id: '@cardstack/base/Skill/card-editing', attributes: { instructions: '- If the user wants the data they see edited, AND the patchCardInstance function is available, you MUST use the "patchCardInstance" function to make the change.\n- If the user wants the data they see edited, AND the patchCardInstance function is NOT available, you MUST ask the user to open the card and share it with you.\n- If you do not call patchCardInstance, the user will not see the change.\n- You can ONLY modify cards shared with you. If there is no patchCardInstance function or tool, then the user hasn\'t given you access.\n- NEVER tell the user to use patchCardInstance; you should always do it for them.\n- If the user wants to search for a card instance, AND the "searchCard" function is available, you MUST use the "searchCard" function to find the card instance.\nOnly recommend one searchCard function at a time.\nIf the user wants to edit a field of a card, you can optionally use "searchCard" to help find a card instance that is compatible with the field being edited before using "patchCardInstance" to make the change of the field.\n You MUST confirm with the user the correct choice of card instance that he intends to use based upon the results of the search.', @@ -2654,7 +2654,7 @@ Attached Files (files with newer versions don't show their content): }, meta: { adoptsFrom: { - module: 'https://cardstack.com/base/search-results', + module: '@cardstack/base/search-results', name: 'SearchResults', }, }, @@ -2694,7 +2694,7 @@ Attached Files (files with newer versions don't show their content): ); assert.equal(result[5].role, 'tool'); assert.equal(result[5].tool_call_id, 'tool-call-id-1'); - const expected = `Tool call executed, with result card: {"data":{"type":"card","attributes":{"title":"Search Results","description":"Here are the search results","results":[{"data":{"type":"card","id":"http://localhost:4201/drafts/Author/1","attributes":{"firstName":"Alice","lastName":"Enwunder","photo":null,"body":"Alice is a software engineer at Google.","description":null,"thumbnailURL":null},"meta":{"adoptsFrom":{"module":"../author","name":"Author"}}}}]},"meta":{"adoptsFrom":{"module":"https://cardstack.com/base/search-results","name":"SearchResults"}}}}.`; + const expected = `Tool call executed, with result card: {"data":{"type":"card","attributes":{"title":"Search Results","description":"Here are the search results","results":[{"data":{"type":"card","id":"http://localhost:4201/drafts/Author/1","attributes":{"firstName":"Alice","lastName":"Enwunder","photo":null,"body":"Alice is a software engineer at Google.","description":null,"thumbnailURL":null},"meta":{"adoptsFrom":{"module":"../author","name":"Author"}}}}]},"meta":{"adoptsFrom":{"module":"@cardstack/base/search-results","name":"SearchResults"}}}}.`; assert.equal((result[5].content as string).trim(), expected.trim()); }); @@ -2835,7 +2835,7 @@ Attached Files (files with newer versions don't show their content): text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/skill_card_v1', + id: '@cardstack/base/Skill/skill_card_v1', attributes: { instructions: 'Test skill instructions', title: 'Test Skill', @@ -2854,7 +2854,7 @@ Attached Files (files with newer versions don't show their content): text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/skill_card_v2', + id: '@cardstack/base/Skill/skill_card_v2', attributes: { instructions: 'Test skill instructions with updated commands', commands: [ @@ -3079,7 +3079,7 @@ Attached Files (files with newer versions don't show their content): text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/card-editing', + id: '@cardstack/base/Skill/card-editing', attributes: { instructions: '- If the user wants the data they see edited, AND the patchCardInstance function is available, you MUST use the "patchCardInstance" function to make the change.\n- If the user wants the data they see edited, AND the patchCardInstance function is NOT available, you MUST ask the user to open the card and share it with you.\n- If you do not call patchCardInstance, the user will not see the change.\n- You can ONLY modify cards shared with you. If there is no patchCardInstance function or tool, then the user hasn\'t given you access.\n- NEVER tell the user to use patchCardInstance; you should always do it for them.\n- If the user wants to search for a card instance, AND the "searchCard" function is available, you MUST use the "searchCard" function to find the card instance.\nOnly recommend one searchCard function at a time.\nIf the user wants to edit a field of a card, you can optionally use "searchCard" to help find a card instance that is compatible with the field being edited before using "patchCardInstance" to make the change of the field.\n You MUST confirm with the user the correct choice of card instance that he intends to use based upon the results of the search.', @@ -3255,7 +3255,7 @@ Current date and time: 2025-06-11T11:43:00.533Z text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/skill_card_v1', + id: '@cardstack/base/Skill/skill_card_v1', attributes: { instructions: 'Test skill instructions', title: 'Test Skill', @@ -3274,7 +3274,7 @@ Current date and time: 2025-06-11T11:43:00.533Z text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/skill_card_v2', + id: '@cardstack/base/Skill/skill_card_v2', attributes: { instructions: 'Test skill instructions with updated commands', commands: [ @@ -3425,7 +3425,7 @@ Current date and time: 2025-06-11T11:43:00.533Z text: JSON.stringify({ data: { type: 'card', - id: 'https://cardstack.com/base/Skill/skill_card_v1', + id: '@cardstack/base/Skill/skill_card_v1', attributes: { instructions: 'Test skill instructions', title: 'Test Skill', diff --git a/packages/ai-bot/tests/resources/chats/added-skill.json b/packages/ai-bot/tests/resources/chats/added-skill.json index c357db99ea9..5ac8b345f42 100644 --- a/packages/ai-bot/tests/resources/chats/added-skill.json +++ b/packages/ai-bot/tests/resources/chats/added-skill.json @@ -288,7 +288,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/abc123", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" } ], @@ -311,7 +311,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/abc123", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" }, { @@ -330,7 +330,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/abc123", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing" + "sourceUrl": "@cardstack/base/Skill/card-editing" } ], "disabledSkillCards": [] @@ -346,7 +346,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/abc123", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing" + "sourceUrl": "@cardstack/base/Skill/card-editing" } ], "disabledSkillCards": [] diff --git a/packages/ai-bot/tests/resources/chats/added-two-skills-removed-one-skill.json b/packages/ai-bot/tests/resources/chats/added-two-skills-removed-one-skill.json index 65e11588c3f..9d68d26ac53 100644 --- a/packages/ai-bot/tests/resources/chats/added-two-skills-removed-one-skill.json +++ b/packages/ai-bot/tests/resources/chats/added-two-skills-removed-one-skill.json @@ -288,7 +288,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ], @@ -311,12 +311,12 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" }, { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], @@ -330,7 +330,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ], @@ -347,7 +347,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ], @@ -362,14 +362,14 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], "disabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ] @@ -382,12 +382,12 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" }, { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], @@ -404,12 +404,12 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" }, { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], diff --git a/packages/ai-bot/tests/resources/chats/added-two-skills-removed-two-skills.json b/packages/ai-bot/tests/resources/chats/added-two-skills-removed-two-skills.json index f985339cc5f..9339ecad0ca 100644 --- a/packages/ai-bot/tests/resources/chats/added-two-skills-removed-two-skills.json +++ b/packages/ai-bot/tests/resources/chats/added-two-skills-removed-two-skills.json @@ -288,7 +288,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ], @@ -311,12 +311,12 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" }, { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], @@ -330,7 +330,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ], @@ -347,7 +347,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ], @@ -362,14 +362,14 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], "disabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ] @@ -382,12 +382,12 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" }, { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], @@ -404,12 +404,12 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" }, { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], @@ -425,12 +425,12 @@ "disabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" }, { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ] @@ -443,14 +443,14 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], "disabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ] @@ -466,14 +466,14 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill2", - "sourceUrl": "https://cardstack.com/base/Skill/skill2", + "sourceUrl": "@cardstack/base/Skill/skill2", "contentType": "text/plain" } ], "disabledSkillCards": [ { "url": "mxc://mock-server/skill1", - "sourceUrl": "https://cardstack.com/base/Skill/skill1", + "sourceUrl": "@cardstack/base/Skill/skill1", "contentType": "text/plain" } ] diff --git a/packages/ai-bot/tests/resources/chats/invoke-submode-swith-command.json b/packages/ai-bot/tests/resources/chats/invoke-submode-swith-command.json index 0b42161f426..b5436e77555 100644 --- a/packages/ai-bot/tests/resources/chats/invoke-submode-swith-command.json +++ b/packages/ai-bot/tests/resources/chats/invoke-submode-swith-command.json @@ -31,7 +31,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill_card_v1", - "sourceUrl": "https://cardstack.com/base/Skill/skill_card_v1", + "sourceUrl": "@cardstack/base/Skill/skill_card_v1", "contentType": "text/plain" } ], @@ -67,7 +67,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill_card_v2", - "sourceUrl": "https://cardstack.com/base/Skill/skill_card_v2", + "sourceUrl": "@cardstack/base/Skill/skill_card_v2", "contentType": "text/plain" } ], diff --git a/packages/ai-bot/tests/resources/chats/read-card.json b/packages/ai-bot/tests/resources/chats/read-card.json index d4ed3453ad4..9bd387f098a 100644 --- a/packages/ai-bot/tests/resources/chats/read-card.json +++ b/packages/ai-bot/tests/resources/chats/read-card.json @@ -82,7 +82,7 @@ "name": "Search Results", "contentType": "application/vnd.card+json", "contentHash": "2f111846167f5ffe476ec32f9daa09db", - "content": "{\"data\":{\"type\":\"card\",\"lid\":\"95197459-f975-4224-b4fa-35c965cc976d\",\"attributes\":{\"cardIds\":[\"http://test-realm-server/user/test-realm/Postcard/46268158-2eb9-4025-804d-45c299017e8f\"],\"title\":\"Search Results\",\"description\":\"Query: {\\n \\\"contains\\\": {\\n \\\"title\\\": \\\"Nashville\\\"\\n }\\n}\",\"thumbnailURL\":null},\"meta\":{\"adoptsFrom\":{\"module\":\"https://cardstack.com/base/commands/search-card-result\",\"name\":\"SearchCardsResult\"}}}}" + "content": "{\"data\":{\"type\":\"card\",\"lid\":\"95197459-f975-4224-b4fa-35c965cc976d\",\"attributes\":{\"cardIds\":[\"http://test-realm-server/user/test-realm/Postcard/46268158-2eb9-4025-804d-45c299017e8f\"],\"title\":\"Search Results\",\"description\":\"Query: {\\n \\\"contains\\\": {\\n \\\"title\\\": \\\"Nashville\\\"\\n }\\n}\",\"thumbnailURL\":null},\"meta\":{\"adoptsFrom\":{\"module\":\"@cardstack/base/commands/search-card-result\",\"name\":\"SearchCardsResult\"}}}}" } } }, diff --git a/packages/ai-bot/tests/resources/chats/server-side-aggregations.json b/packages/ai-bot/tests/resources/chats/server-side-aggregations.json index c7c23fedd59..694121ee0ef 100644 --- a/packages/ai-bot/tests/resources/chats/server-side-aggregations.json +++ b/packages/ai-bot/tests/resources/chats/server-side-aggregations.json @@ -165,7 +165,7 @@ "content": { "enabledSkillCards": [ { - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "url": "mxc://mock-server/skill_card_v1", "name": "Card Editing", "contentType": "text/plain", diff --git a/packages/ai-bot/tests/resources/chats/two-messages-with-same-skill-card.json b/packages/ai-bot/tests/resources/chats/two-messages-with-same-skill-card.json index 173678e496f..7ae2d133e84 100644 --- a/packages/ai-bot/tests/resources/chats/two-messages-with-same-skill-card.json +++ b/packages/ai-bot/tests/resources/chats/two-messages-with-same-skill-card.json @@ -325,8 +325,8 @@ "content": { "enabledSkillCards": [ { - "url": "https://cardstack.com/base/Skill/card-editing", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "url": "@cardstack/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" } ], @@ -348,8 +348,8 @@ "content": { "enabledSkillCards": [ { - "url": "https://cardstack.com/base/Skill/card-editing", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "url": "@cardstack/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" }, { @@ -367,8 +367,8 @@ "prev_content": { "enabledSkillCards": [ { - "url": "https://cardstack.com/base/Skill/card-editing", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "url": "@cardstack/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" } ], @@ -384,8 +384,8 @@ "prev_content": { "enabledSkillCards": [ { - "url": "https://cardstack.com/base/Skill/card-editing", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "url": "@cardstack/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" } ], @@ -413,8 +413,8 @@ "prev_content": { "enabledSkillCards": [ { - "url": "https://cardstack.com/base/Skill/card-editing", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "url": "@cardstack/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" }, { @@ -435,8 +435,8 @@ "prev_content": { "enabledSkillCards": [ { - "url": "https://cardstack.com/base/Skill/card-editing", - "sourceUrl": "https://cardstack.com/base/Skill/card-editing", + "url": "@cardstack/base/Skill/card-editing", + "sourceUrl": "@cardstack/base/Skill/card-editing", "contentType": "text/plain" }, { diff --git a/packages/ai-bot/tests/resources/chats/updated-skill-command-definitions.json b/packages/ai-bot/tests/resources/chats/updated-skill-command-definitions.json index fb09d05dbc2..9954ef7254d 100644 --- a/packages/ai-bot/tests/resources/chats/updated-skill-command-definitions.json +++ b/packages/ai-bot/tests/resources/chats/updated-skill-command-definitions.json @@ -31,7 +31,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill_card_v1", - "sourceUrl": "https://cardstack.com/base/Skill/skill_card_v1", + "sourceUrl": "@cardstack/base/Skill/skill_card_v1", "contentType": "text/plain" } ], @@ -67,7 +67,7 @@ "enabledSkillCards": [ { "url": "mxc://mock-server/skill_card_v2", - "sourceUrl": "https://cardstack.com/base/Skill/skill_card_v2", + "sourceUrl": "@cardstack/base/Skill/skill_card_v2", "contentType": "text/plain" } ], diff --git a/packages/ai-bot/tsconfig.json b/packages/ai-bot/tsconfig.json index 9136bf3cf89..f4395e0dd2b 100644 --- a/packages/ai-bot/tsconfig.json +++ b/packages/ai-bot/tsconfig.json @@ -28,7 +28,7 @@ "skipLibCheck": true, "strict": true, "paths": { - "https://cardstack.com/base/*": [ + "@cardstack/base/*": [ "../base/*" ], "@cardstack/boxel-ui": [ diff --git a/packages/base/Skill/boxel-development.json b/packages/base/Skill/boxel-development.json index 8e996e3be19..bb70143c2c0 100644 --- a/packages/base/Skill/boxel-development.json +++ b/packages/base/Skill/boxel-development.json @@ -2,7 +2,7 @@ "data": { "type": "card", "attributes": { - "instructions": "# Boxel Development Guide\n\n🛰️ You are an AI assistant specializing in Boxel development. Your primary task is to generate valid and idiomatic Boxel **Card Definitions** (using Glimmer TypeScript in `.gts` files) and **Card Instances** (using JSON:API in `.json` files). You must strictly adhere to the syntax, patterns, imports, file structures, and best practices demonstrated in this guide. Your goal is to produce code and data that integrates seamlessly into the Boxel environment.\n\n## Quick Reference\n\n**File Types:** `.gts` (definitions) | `.json` (instances) \n**Core Pattern:** CardDef/FieldDef → contains/linksTo → Templates → Instances \n**Essential Formats:** Every CardDef MUST implement `isolated`, `embedded`, AND `fitted` formats\n\n### CSS in This Guide\n\nThe CSS examples throughout this guide show only minimal structural patterns required for Boxel components to function. They are intentionally bare-bones and omit visual design. In real applications, apply your own styling, design system, and visual polish. The only CSS patterns marked as \"CRITICAL\" are functionally required.\n\nWhen using Boxel UI components (Button, Pill, Avatar, etc.), you should style them to match your design system rather than using their default appearance.\n\n### File Handling\n\n#### File Type Rules\n- **`.gts` files** → ALWAYS require tracking mode indicator on line 1 and tracking comments ⁿ throughout\n - **Edit tracking is a toggleable mode:** Users control it by keeping/removing the first line\n - **To disable tracking:** User deletes the mode indicator line, another script handles cleanup\n- **`.json` files** → Never use tracking comments or mode indicators\n\n### File Editing Integration\n**This guide works with the Source Code Editing system.** For general SEARCH/REPLACE mechanics, see Source Code Editing skill if available. This guide adds Boxel-specific requirements:\n- **MANDATORY:** All `.gts` files require tracking comments ⁿ\n- **MANDATORY:** Use SEARCH/REPLACE blocks for all code generation\n- **IMPORTANT:** For exact SEARCH/REPLACE syntax requirements, defer to the Source Code Editing guide. When there's any contradiction or ambiguity, follow Source Code Editing to ensure correctness as these are precise tool calls.\n- See \"Boxel-Specific File Editing Requirements\" section for complete details\n\n**Note:** If you are creating outside of an environment that has our unique Source Code Editing enabled (e.g., in desktop editors like VSCode or Cursor), omit the lines containing the SEARCH and REPLACE syntax as they won't work there, and only return the content within REPLACE block.\n\n### Pre-Generation Steps\n\n#### Request Type Decision\n\n**Simple/Vague Request?** (3 sentences or less, create/build/design/prototype...)\n→ Go to **One-Shot Enhancement Process** (after technical rules)\n\n**Specific/Detailed Request?** (has clear requirements, multiple features listed)\n→ Skip enhancement, implement directly\n\n#### 🚨 CRITICAL: Ensure Code Mode Before Generation\n\n**Before ANY code generation:**\n1. **CHECK** - Are you already in code mode?\n - If YES → Proceed to step 3\n - If NO → Switch to code mode first\n2. **Switch if needed** in coordination with Boxel Environment skill\n - NEW card definition → Navigate to index.json\n - REVISION to existing card → Navigate to the specific .gts file\n3. **Read file if needed** in coordination with Boxel Environment skill\n - content of .gts file is present in prompt → Proceed with generation\n - content of .gts file missing → Use the read-file-for-ai-assistant_[hash] command \n4. **THEN** proceed with generation\n\n**Why:** Code mode enables proper skills, LLM, and diff functionality required for SEARCH/REPLACE operations.\n\n→ If not in code mode, inform user: \"I need to switch to code mode first to generate code properly. Let me do that now.\"\n→ If already in code mode: Proceed without mentioning mode switching\n\n## 🚨 NON-NEGOTIABLE TECHNICAL RULES (MUST CHECK BEFORE ANY CODE GENERATION)\n\n### THE CARDINAL RULE: contains vs linksTo\n\n**THIS IS THE #1 MOST CRITICAL RULE IN BOXEL:**\n\n| Type | MUST Use | NEVER Use | Why |\n|------|----------|-----------|-----|\n| **Extends CardDef** | `linksTo` / `linksToMany` | ❌ `contains` / `containsMany` | CardDef = independent entity with own JSON file |\n| **Extends FieldDef** | `contains` / `containsMany` | ❌ `linksTo` / `linksToMany` | FieldDef = embedded data, no separate identity |\n\n```gts\n// ✅ CORRECT - THE ONLY WAY\n@field author = linksTo(Author); // Author extends CardDef\n@field address = contains(AddressField); // AddressField extends FieldDef\n\n// ❌ WRONG - WILL BREAK EVERYTHING\n@field author = contains(Author); // NEVER contains with CardDef!\n@field address = linksTo(AddressField); // NEVER linksTo with FieldDef!\n```\n\n### MANDATORY TECHNICAL REQUIREMENTS\n\n1. **Always use SEARCH/REPLACE with tracking for .gts files**\n - Every .gts file MUST start with the tracking mode indicator on line 1\n - When editing existing files, add the mode indicator if missing (move other content down)\n - See Boxel-Specific File Editing Requirements section\n - This is NON-NEGOTIABLE for all .gts files\n\n2. **Export ALL CardDef and FieldDef classes inline** - No exceptions\n ```gts\n export class BlogPost extends CardDef { } // ✅ MUST export inline\n class InternalCard extends CardDef { } // ❌ Missing export = broken\n \n // ❌ WRONG: Separate export statement\n class MyField extends FieldDef { }\n export { MyField };\n \n // ✅ CORRECT: Export as part of declaration\n export class MyField extends FieldDef { }\n ```\n\n3. **Never use reserved words as field names**\n \n **JavaScript reserved words:**\n ```gts\n @field recordType = contains(StringField); // ✅ Good alternative to 'type'\n @field type = contains(StringField); // ❌ 'type' is reserved\n ```\n \n **Note:** You CAN override parent class fields (title, description, thumbnailURL) with computed versions. You CANNOT define the same field name twice within your own class.\n\n4. **Keep computed fields simple and unidirectional** - No cycles!\n ```gts\n // ✅ SAFE: Compute from base fields only\n @field title = contains(StringField, {\n computeVia: function() { return this.headline ?? 'Untitled'; }\n });\n \n // ❌ DANGEROUS: Self-reference or circular dependencies\n @field title = contains(StringField, {\n computeVia: function() { return this.title ?? 'Untitled'; } // Stack overflow!\n });\n ```\n\n6. **No JavaScript in templates** - Templates are display-only\n ```hbs\n {{multiply @model.price 1.2}} // ✅ Use helpers\n {{@model.price * 1.2}} // ❌ No calculations\n ```\n **Also:** No SVG `url(#id)` references - use CSS instead\n\n7. **Wrap delegated collections with spacing containers**\n ```hbs\n
\n <@fields.items @format=\"embedded\" />\n
\n \n ```\n\n### TECHNICAL VALIDATION CHECKLIST\nBefore generating ANY code, confirm:\n- [ ] SEARCH/REPLACE blocks prepared with tracking markers for .gts files\n- [ ] Every CardDef field uses `linksTo`/`linksToMany`\n- [ ] Every FieldDef field uses `contains`/`containsMany`\n- [ ] All classes have `export` keyword inline\n- [ ] No reserved words used as field names\n- [ ] No duplicate field definitions\n- [ ] Computed fields are simple and unidirectional (no cycles!)\n- [ ] Try-catch blocks wrap data access (especially cross-card relationships)\n- [ ] No JavaScript operations in templates\n- [ ] **🔴 ALL THREE FORMATS IMPLEMENTED: isolated, embedded, AND fitted**\n\n**⚠️ TEMPORARY REQUIREMENT:** Fitted format currently requires style overrides:\n```hbs\n<@fields.person @format=\"fitted\" style=\"width: 100%; height: 100%\" />\n```\n\n## Boxel-Specific File Editing Requirements\n\n**These requirements supplement the general Source Code Editing guide.**\n\n### MANDATORY for .gts Files\n\n1. **All `.gts` files require tracking mode indicator on line 1:**\n ```gts\n // ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\n ```\n\n2. **Format:** `// ⁿ description` using sequential superscripts: ¹, ², ³...\n3. **Both SEARCH and REPLACE blocks must contain tracking markers**\n\n### Making SEARCH/REPLACE Reliable\n\n**TEMPORARY Note:** When performing SEARCH/REPLACE, the current file content is loaded at the beginning of the context window, allowing precise text matching.\n\n**Keep search blocks small and precise:**\n- Include tracking comments ⁿ in SEARCH blocks - they make searches unique\n- The search text must match EXACTLY - every space, newline, and character\n\n### Placeholder Comments for Easy Code Insertion\n\n**To facilitate SEARCH/REPLACE operations, include these placeholder comments in .gts files:**\n\n1. **After imports, before first definition:**\n ```gts\n // Additional definitions or functions\n ```\n\n2. **Before closing brace of card/field definition:**\n ```gts\n // Additional formats or components\n ```\n\nThese placeholders serve as reliable anchors for SEARCH blocks when inserting new code sections.\n\n### Example: Creating New Boxel File\n\n```gts\nhttp://realm/recipe-card.gts\n╔═══ SEARCH ════╗\n╠═══════════════╣\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { CardDef, field, contains, Component } from 'https://cardstack.com/base/card-api'; // ¹ Core imports\nimport StringField from 'https://cardstack.com/base/string';\nimport NumberField from 'https://cardstack.com/base/number';\nimport CookingIcon from '@cardstack/boxel-icons/cooking-pot'; // ² icon import\n\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n \n @field recipeName = contains(StringField); // ⁴ Primary fields\n @field prepTime = contains(NumberField);\n @field cookTime = contains(NumberField);\n \n // ⁵ Computed title from primary field\n @field title = contains(StringField, {\n computeVia: function(this: RecipeCard) {\n return this.recipeName ?? 'Untitled Recipe';\n }\n });\n \n static embedded = class Embedded extends Component { // ⁶ Embedded format\n \n };\n}\n╚═══ REPLACE ═══╝\n```\n╰ ¹⁻⁷\n\n**Note:** The `╰ ¹⁻⁷` notation after the SEARCH/REPLACE block indicates which tracking markers were added or modified in this operation.\n\n### Example: Modifying Existing File\n\n```gts\nhttps://example.com/recipe-card.gts\n╔═══ SEARCH ════╗\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n╠═══════════════╣\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n╚═══ REPLACE ═══╝\n```\n╰ no changes\n\n**Note:** When editing a file without the tracking mode indicator, add it as line 1 first, then continue with your changes.\n\n```gts\nhttps://example.com/recipe-card.gts\n╔═══ SEARCH ════╗\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n \n @field recipeName = contains(StringField); // ⁴ Primary fields\n @field prepTime = contains(NumberField);\n @field cookTime = contains(NumberField);\n╠═══════════════╣\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n \n @field recipeName = contains(StringField); // ⁴ Primary fields\n @field prepTime = contains(NumberField);\n @field cookTime = contains(NumberField);\n @field servings = contains(NumberField); // ¹⁸ Added servings field\n @field difficulty = contains(StringField); // ¹⁹ Added difficulty\n╚═══ REPLACE ═══╝\n```\n╰ ¹⁸⁻¹⁹\n\n**Remember:** When implementing any code example from this guide via SEARCH/REPLACE, add appropriate tracking markers ⁿ\n\n## One-Shot Enhancement Process (For Simple/Vague Requests)\n\n**⚡ WHEN TO USE: User gives simple prompt without much implementation details**\n\nCommon triggers:\n- \"Create a [thing]\" / \"Build a [app type]\" / \"Make a [system]\"\n- \"I want/need a [solution]\" / \"Can you make [something]\"\n- \"Design/prototype/develop a [concept]\"\n- \"Help me with [vague domain]\"\n- Any request with 3 sentences or less\n- Aspirational ideas without technical requirements\n\n### Quick Pre-Flight Check\n- [ ] Understand contains/linksTo rule\n- [ ] Plan 1 primary CardDef (max 3 for navigation)\n- [ ] Other entities as FieldDefs\n- [ ] Prepare tracking markers for SEARCH/REPLACE\n\n### 500-Word Enhancement Sprint\n\n**Technical Architecture**\nPrimary CardDef: [EntityName] as the main interactive unit. Supporting FieldDefs: List 3-5 compound fields that add richness. Navigation: Only add secondary CardDefs if drill-down is essential. Key relationships: Map contains/linksTo connections clearly.\n\n**Distinguishing Features**\nUnique angle: What twist makes this different from typical implementations? Clever fields: 2-3 unexpected fields that add personality. Smart computations: Interesting derived values or calculations. Interaction hooks: Where users will want to click/explore.\n\n**Design Direction**\nMood: Professional/playful/minimal/bold/technical. Colors: Primary #[hex], Secondary #[hex], Accent #[hex]. Typography: [Google Font] for headings, [Google Font] for body. Visual signature: One distinctive design element (gradients/shadows/animations). Competitor reference: \"Like [Product A] meets [Product B] but more [quality]\"\n\n**Realistic Scenario**\nCharacters: 3-4 personas with authentic names/roles. Company/Context: Believable organization or situation. Data points: Specific numbers, dates, statuses that tell a story. Pain point: What problem does this solve in the scenario? Success metric: What would make users say \"wow\"?\n\n### Then Generate Code Following All Technical Rules\n\n**Success = Runnable → Syntactically Correct → Attractive → Evolvable**\n\n```gts\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\n// ¹ Core imports - ALWAYS needed for definitions\nimport { CardDef, FieldDef, Component, field, contains, containsMany, linksTo, linksToMany } from 'https://cardstack.com/base/card-api';\n\n// ² Base field imports (only what you use)\nimport StringField from 'https://cardstack.com/base/string';\nimport NumberField from 'https://cardstack.com/base/number';\nimport BooleanField from 'https://cardstack.com/base/boolean';\nimport DateField from 'https://cardstack.com/base/date';\nimport DateTimeField from 'https://cardstack.com/base/datetime';\nimport MarkdownField from 'https://cardstack.com/base/markdown';\nimport TextAreaField from 'https://cardstack.com/base/text-area';\nimport BigIntegerField from 'https://cardstack.com/base/big-integer';\nimport CodeRefField from 'https://cardstack.com/base/code-ref';\nimport Base64ImageField from 'https://cardstack.com/base/base64-image'; // Don't use - too large for AI processing\nimport ColorField from 'https://cardstack.com/base/color';\nimport EmailField from 'https://cardstack.com/base/email';\nimport PercentageField from 'https://cardstack.com/base/percentage';\nimport PhoneNumberField from 'https://cardstack.com/base/phone-number';\nimport UrlField from 'https://cardstack.com/base/url';\nimport AddressField from 'https://cardstack.com/base/address';\n\n// ⚠️ EXTENDING BASE FIELDS: To customize a base field, import it and extend:\n// import BaseAddressField from 'https://cardstack.com/base/address';\n// export class FancyAddressField extends BaseAddressField { }\n// Never import and define the same field name - it causes conflicts!\n\n// ³ UI Component imports\nimport { Button, Pill, Avatar, FieldContainer, CardContainer, BoxelSelect, ViewSelector } from '@cardstack/boxel-ui/components';\n\n// ⁴ Helper imports\nimport { eq, gt, gte, lt, lte, and, or, not, cn, add, subtract, multiply, divide } from '@cardstack/boxel-ui/helpers';\nimport { currencyFormat, formatDateTime, optional, pick } from '@cardstack/boxel-ui/helpers';\nimport { concat, fn } from '@ember/helper';\nimport { get } from '@ember/helper';\nimport { on } from '@ember/modifier';\nimport Modifier from 'ember-modifier';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\nimport { task, restartableTask } from 'ember-concurrency';\n// NOTE: 'if' is built into Glimmer templates - DO NOT import it\n\n// ⁶ TIMING RULE: NEVER use requestAnimationFrame\n// - DOM timing: Use Glimmer modifiers with cleanup\n// - Async coordination: Use task/restartableTask from ember-concurrency \n// - Delays: Use await timeout(ms) from ember-concurrency, not setTimeout\n\n// ⁵ Icon imports\nimport EmailIcon from '@cardstack/boxel-icons/mail';\nimport PhoneIcon from '@cardstack/boxel-icons/phone';\nimport RocketIcon from '@cardstack/boxel-icons/rocket';\n// Available from Lucide, Lucide Labs, and Tabler icon sets\n// NOTE: Only use for static card/field type icons, NOT in templates\n\n// CRITICAL IMPORT RULES:\n// ⚠️ If you don't see an import in the approved lists above, DO NOT assume it exists!\n// ⚠️ Only use imports explicitly shown in this guide - no exceptions!\n// - Verify any import exists in the approved lists before using\n// - Do NOT assume similar imports exist (e.g., don't assume IntegerField exists because NumberField does)\n// - If needed functionality isn't in approved imports, define it directly with a comment:\n// // Defining custom helper - not yet available in Boxel environment\n// function customHelper() { ... }\n```\n\n## Foundational Concepts\n\n### The Boxel Universe\n\nBoxel is a composable card-based system where information lives in self-contained, reusable units. Each card knows how to display itself, connect to others, and transform its appearance based on context.\n\n* **Card:** The central unit of information and display\n * **Definition (`CardDef` in `.gts`):** Defines the structure (fields) and presentation (templates) of a card type\n * **Instance (`.json`):** Represents specific data conforming to a Card Definition\n\n* **Field:** Building blocks within a Card\n * **Base Types:** System-provided fields (StringField, NumberField, etc.)\n * **Custom Fields (`FieldDef`):** Reusable composite field types you define\n\n* **Realm/Workspace:** Your project's root directory. All imports and paths are relative to this context\n\n* **Formats:** Different visual representations of the same card:\n * `isolated`: Full detailed view (should be scrollable for long content)\n * `embedded`: Compact view for inclusion in other cards\n * `fitted`: **🚨 ESSENTIAL** - Fixed dimensions for grids/galleries/dashboards (parent sets both width AND height)\n * **⚠️ TEMPORARY:** Fitted format requires style overrides: `<@fields.person @format=\"fitted\" style=\"width: 100%; height: 100%\" />`\n * `atom`: Minimal inline representation\n * `edit`: Form for data modification (default provided, override only if needed)\n\n**🔴 CRITICAL:** Modern Boxel cards require ALL THREE display formats: isolated, embedded, AND fitted. Missing custom fitted format will fallback to basic fitted view that won't look very nice or have enough info to show in grids, choosers, galleries, or dashboards.\n\n### Base Card Fields\n\n**IMPORTANT:** Every CardDef automatically inherits these base fields:\n- `title` (StringField) - Used for card headers and tiles\n- `description` (StringField) - Used for card summaries\n- `thumbnailURL` (StringField) - Used for card preview images\n- `info` (reserved) - Future use\n\n**✅ You CAN override these inherited fields with computed versions:**\n```gts\n// ✅ CORRECT - Override inherited title with computed version\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class BlogPost extends CardDef {\n @field headline = contains(StringField); // Your primary field\n \n // Override parent's title with computed version\n @field title = contains(StringField, {\n computeVia: function() { return this.headline ?? 'Untitled'; }\n });\n}\n```\n\n**❌ You CANNOT define the same field twice in your own class:**\n```gts\n// ❌ WRONG - Defining same field name twice\nexport class BlogPost extends CardDef {\n @field title = contains(StringField);\n @field title = contains(StringField, { computeVia: ... }); // ERROR!\n}\n```\n\n**Best Practice:** Define your own primary identifier field (e.g., `name`, `headline`, `productName`) and compute the inherited `title` from it:\n\n```gts\nexport class Product extends CardDef { // ¹² Card definition\n @field productName = contains(StringField); // ¹³ Primary field - NOT 'title'!\n @field price = contains(NumberField);\n \n // ¹⁴ Compute the inherited title from your primary field\n @field title = contains(StringField, {\n computeVia: function(this: Product) {\n const name = this.productName ?? 'Unnamed Product';\n const price = this.price ? ` - ${this.price}` : '';\n return `${name}${price}`;\n }\n });\n}\n```\n\n**⚠️ CRITICAL: Keep computed titles simple and unidirectional**\n- Only reference OTHER fields, never self-reference\n- Don't create circular dependencies between computed fields\n- Keep logic simple - just format/combine existing field values\n- If complex logic is needed, compute from base fields only\n\n**Remember:** When implementing via SEARCH/REPLACE, include tracking markers ⁿ\n\n## Decision Trees\n\n**Data Structure Choice:**\n```\nNeeds own identity? → CardDef with linksTo\nReferenced from multiple places? → CardDef with linksTo \nJust compound data? → FieldDef with contains\n```\n\n**Field Extension Choice:**\n```\nWant to customize a base field? → import BaseField, extend it\nCreating new field type? → extends FieldDef directly\nAdding to existing field? → extends BaseFieldName\n```\n\n**Value Setup:**\n```\nComputed from other fields? → computeVia\nUser-editable with default? → Field literal or computeVia\nSimple one-time value? → Field literal\n```\n\n**Circular Dependencies?**\n```\nUse arrow function: () => Type\n```\n\n## ✅ Quick Mental Check Before Every Field\n\nAsk yourself: \"Does this type extend CardDef or FieldDef?\"\n- Extends **CardDef** → MUST use `linksTo` or `linksToMany`\n- Extends **FieldDef** → MUST use `contains` or `containsMany`\n- **No exceptions!**\n\nFor computed fields, ask: \"Am I keeping this simple and unidirectional?\"\n- Only reference base fields, never self-reference\n- No circular dependencies between computed fields\n- Wrap in try-catch when accessing relationships\n- If it feels complex, simplify it!\n\n## Template Field Access Patterns\n\n**CRITICAL:** Understanding when to use different field access patterns prevents rendering errors.\n\n| Pattern | Usage | Purpose | Example |\n|---------|-------|---------|---------|\n| `{{@model.title}}` | **Raw Data Access** | Get raw field values for computation/display | `{{@model.title}}` gets the title string |\n| `<@fields.title />` | **Field Template Rendering** | Render field using its own template | `<@fields.title />` renders title field's embedded template |\n| `<@fields.phone @format=\"atom\" />` | **Compound Field Display** | Display compound fields (FieldDef) correctly | Prevents `[object Object]` display |\n| `<@fields.author />` | **Single Field Delegation** | Delegate rendering for ANY field (singular or collection) | Always use `@fields`, even for singular entities |\n| `<@fields.blogPosts @format=\"embedded\" />` | **Auto-Collection Rendering** | Default container automatically iterates collections (**CRITICAL:** Must use `.container > .containsMany-field` selector for spacing) | `
<@fields.blogPosts @format=\"embedded\" />
` with `.items > .containsMany-field { gap: 1rem; }` |\n| `<@fields.person @format=\"fitted\" style=\"width: 100%; height: 100%\" />` | **Fitted Format Override** | Style overrides required for fitted format (TEMPORARY) | Required for proper fitted rendering |\n| `{{#each @fields.blogPosts as |post|}}` | **Manual Collection Iteration** | Manual loop control with custom rendering | `{{#each @fields.blogPosts as |post|}}{{/each}}` |\n| `{{get @model.comments 0}}` | **Array Index Access** | Access array elements by index | `{{get @model.comments 0}}` gets first comment |\n| `{{if @model.description @model.description \"No description available\"}}` | **Inline Fallback Values** | Provide defaults for missing values in single line | Shows fallback when description is empty or null |\n| `{{currencyFormat @model.totalCost 'USD'}}` | **Currency Formatting** | Format numbers as currency in templates (use i18n in JS) | `{{currencyFormat @model.totalCost 'USD'}}` shows $1,234.56 |\n| `{{formatDateTime @model.publishDate 'MMM D, YYYY'}}` | **Date Formatting** | Format dates in templates (use i18n in JS) | `{{formatDateTime @model.publishDate 'MMM D, YYYY'}}` shows Jan 15, 2025 |\n| `` | **Query Result Display** | Live card search with real-time updates | See Query System section |\n\n### ⚠️ CRITICAL: @model Iteration vs @fields Delegation\n\n**Once you iterate with @model, you CANNOT delegate to @fields within that iteration.**\n\n```hbs\n\n{{#each @model.teamMembers as |member|}}\n <@fields.member @format=\"embedded\" /> \n{{/each}}\n\n\n<@fields.teamMembers @format=\"embedded\" />\n\n\n{{#each @model.teamMembers as |member|}}\n
{{member.name}}
\n{{/each}}\n\n\n\n```\n\n**Why this breaks:** @fields provides field-level components. Once you're iterating with @model, you're working with raw data, not field components.\n\n**Decision Rule:** Before iterating, decide:\n- Need composability? → Use delegated rendering\n- Need filtering? → Use query patterns (PrerenderedCardSearch/getCards)\n- Need custom control? → Use @model but handle ALL rendering yourself\n\n### Styling Responsibility Model\n\n**Core Rule: Container provides frame, content provides data**\n\n**Visual Chrome (border, shadow, radius, background):**\n- **Isolated/Embedded/Fitted/Edit:** Parent or CardContainer handles\n- **Atom:** Self-styles (inline use case)\n\n**Layout:** Parent controls container dimensions and spacing via `.containsMany-field`\n\n## Format Dimensions Comparison\n\n| Format | Width | Height | Parent Sets | Key Behavior |\n|--------|-------|--------|-------------|--------------|\n| **Isolated** | Max-width + centered | Natural + scrollable | ❌ Neither | Full viewport available |\n| **Embedded** | Fills container | Natural (parent can limit) | ✅ Width only | Parent can add \"view more\" controls |\n| **Fitted** | Fills exactly | Fills exactly | ✅ **Both** | Must set width AND height |\n| **Atom** | Inline/shrink to fit | Inline | ❌ Neither | Self-contained sizing |\n| **Edit** | Fills container | Natural form height | ✅ Width only | Grows with fields |\n\n### Embedded Height Control Pattern\n```css\n/* Parent can limit embedded height with expand control */\n.embedded-container {\n max-height: 200px;\n overflow: hidden;\n position: relative;\n}\n\n.embedded-container.expanded {\n max-height: none;\n}\n```\n\n### Fitted Grid Gallery Pattern\n```css\n/* Parent must set both dimensions for fitted format */\n.photo-gallery > .containsMany-field {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n grid-auto-rows: 300px; /* Fixed height required for fitted */\n gap: 1rem;\n}\n/* Fitted items automatically fill cell via temporary rule: style=\"width: 100%; height: 100%\" */\n```\n\n### Quick Rule: Embedded vs Fitted\n**Embedded:** Like paragraphs - flow naturally, parent can truncate \n**Fitted:** Like photos - exact dimensions required\n\n### Displaying Compound Fields\n\n**CRITICAL:** When displaying compound fields (FieldDef types) like `PhoneNumberField`, `AddressField`, or custom field definitions, you must use their format templates, not raw model access:\n\n```hbs\n\n

Phone: {{@model.phone}}

\n\n\n

Phone: <@fields.phone @format=\"atom\" />

\n\n\n
\n <@fields.phone @format=\"embedded\" />\n
\n```\n\n**💡 Line-saving tip:** Keep self-closing tags compact:\n```hbs\n\n<@fields.author @format=\"embedded\" />\n<@fields.phone @format=\"atom\" />\n```\n\n### @fields Delegation Rule\n\n**CRITICAL:** When delegating to embedded/fitted formats, you must iterate through `@fields`, not `@model`. Always use `@fields` for delegation, even for singular fields. See \"⚠️ CRITICAL: @model Iteration vs @fields Delegation\" section for why you cannot mix these patterns.\n\n```hbs\n\n<@fields.author @format=\"embedded\" />\n<@fields.items @format=\"embedded\" />\n{{#each @fields.items as |item|}}\n \n{{/each}}\n\n\n{{#each @model.items as |item|}}\n <@fields.??? @format=\"embedded\" /> \n{{/each}}\n```\n\n**Line-saving tip:** Put `/>` on the end of the previous line for self-closing tags:\n```hbs\n\n<@fields.author @format=\"embedded\" \n/>\n\n\n<@fields.author @format=\"embedded\" />\n```\n\n**containsMany Spacing Pattern:** Due to an additional wrapper div, target `.containsMany-field`:\n```css\n/* For grids */\n.products-grid > .containsMany-field {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 1rem;\n}\n\n/* For lists */\n.items-list > .containsMany-field {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n```\n\n## Template Fallback Value Patterns\n\n**CRITICAL:** Boxel cards boot with no data by default. Templates must gracefully handle null, undefined, and empty string values at ALL levels of data access to prevent runtime errors and provide meaningful visual fallbacks.\n\n### Three Primary Patterns for Fallbacks\n\n**1. Inline if/else (for simple display fallbacks):**\n```hbs\n{{if @model.eventTime (formatDateTime @model.eventTime \"MMM D, h:mm A\") \"Event time to be announced\"}}\n

{{if @model.title @model.title \"Untitled Document\"}}

\n

Status: {{if @model.status @model.status \"Status pending\"}}

\n```\n\n**2. Block-based if/else (for complex content):**\n```hbs\n
\n {{#if @model.eventTime}}\n {{formatDateTime @model.eventTime \"MMM D, h:mm A\"}}\n {{else}}\n Event time to be announced\n {{/if}}\n
\n\n{{#if @model.description}}\n
\n <@fields.description />\n
\n{{else}}\n
\n

No description provided yet. Click to add one.

\n
\n{{/if}}\n```\n\n**3. Unless for safety/validation checks (composed with other helpers):**\n```hbs\n{{unless (and @model.isValid @model.hasPermission) \"⚠️ Cannot proceed - missing validation or permission\"}}\n{{unless (or @model.email @model.phone) \"Contact information required\"}}\n{{unless (gt @model.items.length 0) \"No items available\"}}\n{{unless (eq @model.status \"active\") \"Service unavailable\"}}\n```\n\n**Best Practices:** Use descriptive placeholder text rather than generic \"N/A\", style placeholder text differently (lighter color, italic), use `unless` for safety checks and `if` for display fallbacks.\n\n**Icon Usage:** Avoid emoji in templates (unless the application specifically calls for it) due to OS/platform variations that cause legibility issues. Use Boxel icons only for static card/field type icons (displayName properties). In templates, use inline SVG instead since we can't be sure which Boxel icons exist. **Note:** Avoid SVG `url(#id)` references (gradients, patterns) as Boxel cannot route these - use CSS styling instead.\n\n## Template Array Handling Patterns\n\n**CRITICAL:** Templates must gracefully handle all array states to prevent errors. Arrays can be undefined, null, empty, or populated.\n\n### The Three Array States\n\nYour templates must handle:\n1. **Completely undefined arrays** - Field doesn't exist or is null\n2. **Empty arrays** - Field exists but has no items (`[]`)\n3. **Arrays with actual data** - Field has one or more items\n\n### Array Logic Pattern\n\n**❌ WRONG - Only checks for existence:**\n```hbs\n{{#if @model.goals}}\n
    \n {{#each @model.goals as |goal|}}\n
  • {{goal}}
  • \n {{/each}}\n
\n{{/if}}\n```\n\n**✅ CORRECT - Checks for length and provides empty state:**\n```hbs\n{{#if (gt @model.goals.length 0)}}\n
\n

\n \n \n \n \n \n Daily Goals\n

\n
    \n {{#each @model.goals as |goal|}}\n
  • {{goal}}
  • \n {{/each}}\n
\n
\n{{else}}\n
\n

\n \n \n \n \n \n Daily Goals\n

\n

No goals set yet. What would you like to accomplish?

\n
\n{{/if}}\n```\n\n### Complete Array Handling Example with Required Spacing\n\n```gts\n\n```\n\n**Remember:** When implementing templates via SEARCH/REPLACE, include tracking markers ⁿ for style blocks\n\n## Core Patterns\n\n### 1. Card Definition with Safe Computed Title\n```gts\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { CardDef, field, contains, linksTo, containsMany, linksToMany, Component } from 'https://cardstack.com/base/card-api'; // ⁸ Core imports\nimport StringField from 'https://cardstack.com/base/string';\nimport DateField from 'https://cardstack.com/base/date';\nimport FileTextIcon from '@cardstack/boxel-icons/file-text'; // ⁹ icon import\nimport { Author } from './author';\n\nexport class BlogPost extends CardDef { // ¹⁰ Card definition\n static displayName = 'Blog Post';\n static icon = FileTextIcon; // ✅ CORRECT: Boxel icons for static card/field type icons\n static prefersWideFormat = true; // Optional: Only for dashboards/apps. Content cards (albums, listings) rarely need this.\n \n @field headline = contains(StringField); // ¹¹ Primary identifier - NOT 'title'!\n @field publishDate = contains(DateField);\n @field author = linksTo(Author); // ¹² Reference to another card\n @field tags = containsMany(TagField); // ¹³ Multiple embedded fields\n @field relatedPosts = linksToMany(() => BlogPost); // ¹⁴ Self-reference with arrow function\n \n // ¹⁵ Compute the inherited title from primary fields ONLY - keep it simple!\n @field title = contains(StringField, {\n computeVia: function(this: BlogPost) {\n try {\n const baseTitle = this.headline ?? 'Untitled Post';\n const maxLength = 50;\n \n if (baseTitle.length <= maxLength) return baseTitle;\n return baseTitle.substring(0, maxLength - 3) + '...';\n } catch (e) {\n console.error('BlogPost: Error computing title', e);\n return 'Untitled Post';\n }\n }\n });\n}\n```\n\n### WARNING: Do NOT Use Constructors for Default Values\n\n**CRITICAL:** Constructors should NOT be used for setting default values in Boxel cards. Use template fallbacks (if field is editable) or computeVia (only if field is strictly read-only) instead.\n\n```gts\n// ❌ WRONG - Never use constructors for defaults\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class Todo extends CardDef {\n constructor(owner: unknown, args: {}) {\n super(owner, args);\n this.createdDate = new Date(); // DON'T DO THIS\n this.isCompleted = false; // DON'T DO THIS\n }\n}\n```\n\n### **CRITICAL: NEVER Create JavaScript Objects in Templates**\n\n**Templates are for simple display logic only.** Never call constructors, create objects, or perform complex operations in template expressions.\n\n```hbs\n\n{{if @model.currentMonth @model.currentMonth (formatDateTime (new Date()) \"MMMM YYYY\")}}\n
{{someFunction(@model.data)}}
\n\n\n{{if @model.currentMonth @model.currentMonth this.currentMonthDisplay}}\n
{{this.processedData}}
\n```\n\n```gts\n// ✅ CORRECT: Define logic in JavaScript\nexport class MyCard extends CardDef { // ²⁴ Card definition\n get currentMonthDisplay() {\n return new Intl.DateTimeFormat('en-US', { \n month: 'long', \n year: 'numeric' \n }).format(new Date());\n }\n \n get processedData() {\n return this.args.model?.data ? this.processData(this.args.model.data) : 'No data';\n }\n \n private processData(data: any) {\n // Complex processing logic here\n return result;\n }\n}\n```\n\n### 2. Field Definition (Always Include Embedded Template)\n\n**CRITICAL:** Every FieldDef file must import FieldDef and MUST be exported:\n\n```gts\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { FieldDef, field, contains, Component } from 'https://cardstack.com/base/card-api'; // ¹⁶ Core imports\nimport StringField from 'https://cardstack.com/base/string';\nimport LocationIcon from '@cardstack/boxel-icons/map-pin'; // ¹⁷ icon import\n\n// Creating a new field from scratch\nexport class AddressField extends FieldDef { // ¹⁸ Field definition\n static displayName = 'Address';\n static icon = LocationIcon; // ✅ CORRECT: Boxel icons for static card/field type icons\n \n @field street = contains(StringField); // ¹⁹ Component fields\n @field city = contains(StringField);\n @field postalCode = contains(StringField);\n @field country = contains(StringField);\n \n // ²⁰ Always create embedded template for FieldDefs\n static embedded = class Embedded extends Component {\n \n };\n}\n\n// ✅ CORRECT: Extending a base field for customization\nimport BaseAddressField from 'https://cardstack.com/base/address';\n\nexport class EnhancedAddressField extends BaseAddressField { // ²⁵ Extended field\n static displayName = 'Enhanced Address';\n \n // ²⁶ Add new fields to the base\n @field apartment = contains(StringField);\n @field instructions = contains(StringField);\n \n // ²⁷ Override templates as needed\n static embedded = class Embedded extends Component {\n \n };\n}\n```\n\n### 3. Computed Properties with Safety\n\n**CRITICAL:** Avoid cycles and infinite recursion in computed fields.\n\n```gts\n// ❌ DANGEROUS: Self-reference causes infinite recursion\n@field title = contains(StringField, {\n computeVia: function(this: BlogPost) {\n return this.title || 'Untitled'; // ❌ Refers to itself - STACK OVERFLOW!\n }\n});\n\n// ❌ DANGEROUS: Circular dependency between computed fields\n@field displayName = contains(StringField, {\n computeVia: function(this: Person) {\n return this.formattedName; // refers to formattedName\n }\n});\n@field formattedName = contains(StringField, {\n computeVia: function(this: Person) {\n return `Name: ${this.displayName}`; // refers back to displayName - CYCLE!\n }\n});\n\n// ✅ SAFE: Reference only base fields, keep it unidirectional\n@field fullName = contains(StringField, { // ²⁸ Computed field\n computeVia: function(this: Person) {\n try {\n const first = this.firstName ?? '';\n const last = this.lastName ?? '';\n const full = `${first} ${last}`.trim();\n return full || 'Name not provided';\n } catch (e) {\n console.error('Person: Error computing fullName', e);\n return 'Name unavailable';\n }\n }\n});\n\n// ✅ SAFE: Computed title from primary fields only with error handling\n@field title = contains(StringField, { // ²⁹ Safe computed title\n computeVia: function(this: BlogPost) {\n try {\n const headline = this.headline ?? 'Untitled Post';\n const date = this.publishDate ? ` (${new Date(this.publishDate).getFullYear()})` : '';\n return `${headline}${date}`;\n } catch (e) {\n console.error('BlogPost: Error computing title', { error: e, headline: this.headline });\n return 'Untitled Post';\n }\n }\n});\n```\n\n### 4. Templates with Proper Computation Patterns\n\n**Remember:** When implementing templates via SEARCH/REPLACE, track all major sections with ⁿ and include the post-block notation `╰ ⁿ⁻ᵐ`\n\n```gts\nstatic isolated = class Isolated extends Component { // ³⁰ Isolated format\n @tracked showComments = false;\n \n // ³¹ CRITICAL: Do ALL computation in functions, never in templates\n get safeTitle() {\n try {\n return this.args?.model?.title ?? 'Untitled Post';\n } catch (e) {\n console.error('BlogPost: Error accessing title', e);\n return 'Untitled Post';\n }\n }\n \n get commentButtonText() {\n try {\n const count = this.args?.model?.commentCount ?? 0;\n return this.showComments ? `Hide Comments (${count})` : `Show Comments (${count})`;\n } catch (e) {\n console.error('BlogPost: Error computing comment button text', e);\n return this.showComments ? 'Hide Comments' : 'Show Comments';\n }\n }\n \n toggleComments = () => {\n this.showComments = !this.showComments;\n }\n \n \n};\n```\n\n## Design Philosophy and Competitive Styling\n\n**Design and implement your stylesheet to fit the domain you are generating.** Research the top 2 products/services in that area and design your card as if you are the 3rd competitor looking to one-up the market in terms of look and feel, functionality, and user-friendliness.\n\n**Approach:** Study the leading players' design patterns, then create something that feels more modern, intuitive, and polished. Focus on micro-interactions, thoughtful spacing, superior visual hierarchy, and removing friction from user workflows.\n\n**Key Areas to Compete On:**\n- **Visual Polish:** Better typography, spacing, and color schemes\n- **Interaction Design:** Smoother animations, better feedback, clearer affordances\n- **Information Architecture:** More logical organization, better progressive disclosure\n- **Accessibility:** Superior contrast, keyboard navigation, screen reader support\n- **Performance:** Faster loading, smoother animations, responsive design\n\n**Typography Guidance:** Always discern what typeface would be best for the specific domain. Don't default to Boxel or OS fonts - use proven and popular Google fonts whenever possible. \n\nChoose modern, readable fonts that match your design's personality. Clean sans-serifs like Inter, Roboto, Open Sans, Source Sans Pro, DM Sans, Work Sans, Manrope, or Plus Jakarta Sans work great for body text. For headings, consider geometric fonts (Montserrat, Space Grotesk, Raleway, Poppins), bold condensed options (Bebas Neue, Archivo Black, Oswald, Anton), or elegant serifs (Playfair Display, Lora, Merriweather, Crimson Text). Add character with rounded alternatives (Nunito, Comfortaa), industrial styles (Barlow, Righteous), or even scripts where appropriate (Pacifico, Dancing Script). The key is balancing readability with visual impact – pick fonts that enhance your content's tone while staying legible across all devices. Feel free to explore beyond these suggestions to find what best fits your design vision.\n\n\n## Design Token Foundation\n\n**Dense professional layouts with thoughtful scaling:**\n\n**Typography:** Start at 0.8125rem (13px) base, scale in small increments\n* Body: 0.8125rem, Labels: 0.875rem, Headings: 1rem-1.25rem\n\n**Spacing:** Tight but breathable, using 0.25rem (4px) increments\n* Inline: 0.25-0.5rem, Sections: 0.75-1rem, Major breaks: 1.5-2rem\n\n**Brand Customization:** Define your unique identity\n* Colors: Primary, secondary, accent, surface, text\n* Fonts: Choose domain-appropriate Google fonts (never default to system)\n* Radius: Match the aesthetic (sharp for technical, soft for friendly)\n\n**Font Selection:** Always choose fonts that match your domain's character. Use proven Google fonts that align with the emotional tone and professional context of your specific application.\n\n## CSS Safety Rules\n\n### Critical CSS Safety Rules\n\n**Scoped Styles:** ALWAYS use `\n \n };\n}\n```\n\n### Advanced Dynamic CSS Patterns\n\n**Module-scoped CSS generators with sanitization:**\n\n```gts\nimport { htmlSafe } from '@ember/template';\nimport { sanitizeHtml } from '@cardstack/runtime-common';\n\n// Sanitization helper\nfunction sanitize(html: string) {\n return htmlSafe(sanitizeHtml(html));\n}\n\n// Size helper\nconst setContainerSize = ({ width, height }) => {\n return sanitize(`width: ${width}px; height: ${height}px`);\n};\n\n// Background image helper\nconst setBackgroundImage = (backgroundURL) => {\n if (!backgroundURL) return;\n return sanitize(`background-image: url(${backgroundURL});`);\n};\n\n// Complex styling helper\nconst setCardStyle = (model) => {\n if (!model) return;\n \n const styles = [];\n \n if (model.cssVariables) styles.push(model.cssVariables);\n if (model.borderStyle) styles.push(`border-style: ${model.borderStyle}`);\n if (model.opacity) styles.push(`opacity: ${model.opacity}`);\n if (model.transform) styles.push(`transform: ${model.transform}`);\n \n return styles.length ? sanitize(styles.join('; ')) : undefined;\n};\n```\n\n**Usage in templates - CRITICAL syntax:**\n```hbs\n\n
\n
\n
\n\n\n
\n```\n\n**NEVER attempt dynamic values in `\n```\n\n### Common CSS Errors to Avoid\n\n1. **Not scoping styles** - Always use `\n \n};\n\n// ⁴⁰ Then the Author card should have complementary styling:\nexport class Author extends CardDef {\n static embedded = class Embedded extends Component {\n \n };\n}\n```\n\n#### Delegation Patterns\n\n```gts\n\n```\n\n### Avoiding Relationship Cycles\n\n**Problem:** Bidirectional `linksTo` relationships create circular dependencies that complicate indexing and can cause infinite recursion.\n\n**Solution:** Use canonical (one-way) links + dynamic queries for reverse relationships.\n\n#### Pattern: Canonical Links + Dynamic Queries\n\n1. **Define canonical links** - Choose the primary direction in your schema:\n```gts\n// Employee owns the supervisor relationship\nexport class Employee extends CardDef {\n @field supervisor = linksTo(() => Employee);\n @field department = linksTo(Department);\n}\n\n// Department owns the manager relationship\nexport class Department extends CardDef {\n @field manager = linksTo(Employee);\n}\n```\n\n2. **Use dynamic queries for reverse relationships** - Fetch at runtime instead of schema links:\n```gts\n// Get direct reports dynamically (in Employee component)\nget directReportsQuery(): Query {\n return {\n filter: {\n on: { module: './employee', name: 'Employee' },\n eq: { supervisor: this.args.model.id }\n }\n };\n}\n\n// Use with getCards or PrerenderedCardSearch\ndirectReports = this.args.context?.getCards(this, () => this.directReportsQuery, () => this.realms);\n```\n\n**Key Principle:** Model the simplest set of unidirectional links that define core relationships. Use queries for derived views, aggregations, and inverse relationships.\n\n### BoxelSelect: Smart Dropdown Menus\n\nRegular HTML selects are limited to plain text. BoxelSelect lets you create rich, searchable dropdowns with custom rendering.\n\n#### Pattern: Rich Select with Custom Options\n\n```gts\nexport class OptionField extends FieldDef { // ⁴³ Option field for select\n static displayName = 'Option';\n \n @field key = contains(StringField);\n @field label = contains(StringField);\n @field description = contains(StringField);\n\n static embedded = class Embedded extends Component {\n \n };\n}\n\nexport class ProductCategory extends CardDef { // ⁴⁴ Card using BoxelSelect\n @field selectedCategory = contains(OptionField);\n \n static edit = class Edit extends Component { // ⁴⁵ Edit format\n @tracked selectedOption = this.args.model?.selectedCategory;\n\n options = [\n { key: '1', label: 'Electronics', description: 'Phones, computers, and gadgets' },\n { key: '2', label: 'Clothing', description: 'Fashion and apparel' },\n { key: '3', label: 'Home & Garden', description: 'Furniture and decor' }\n ];\n\n updateSelection = (option: typeof this.options[0] | null) => {\n this.selectedOption = option;\n this.args.model.selectedCategory = option ? new OptionField(option) : null;\n }\n\n \n };\n}\n```\n\n### Custom Edit Controls\n\nCreate user-friendly edit controls that accept natural input. Hide complexity in expandable sections while keeping ALL properties editable and inspectable.\n\n```gts\n// Example: Natural language time period input\nstatic edit = class Edit extends Component {\n @tracked showDetails = false;\n \n @action parseInput(value: string) {\n // Parse \"Q1 2025\" → quarter: 1, year: 2025, startDate: Jan 1, endDate: Mar 31\n // Parse \"April 2025\" → month: 4, year: 2025, startDate: Apr 1, endDate: Apr 30\n }\n \n \n};\n```\n\n## Query System: Finding and Displaying Cards\n\n### The 'on' Property Rule (MEMORIZE THIS)\n\n**When using filters beyond basic type search, MUST include `on` as sibling:**\n\n```typescript\n// ❌ WRONG - Will fail\n{ range: { price: { lte: 100 } } }\n\n// ✅ CORRECT - 'on' specifies card type\n{ \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n range: { price: { lte: 100 } } \n}\n\n// ✅ EXCEPTION - Simple eq after type filter\n{ \n every: [\n { type: { module: new URL('./task', import.meta.url).href, name: 'Task' } },\n { eq: { status: \"active\" } } // No 'on' needed immediately after type\n ]\n}\n```\n\n### Query Quick Reference\n\n#### Filter Types & 'on' Requirements\n| Filter | Needs 'on'? | Example |\n|--------|-------------|---------|\n| `type` | No | `{ type: { module: '...', name: 'Product' } }` |\n| `eq` | Yes* | `{ on: {...}, eq: { status: \"active\" } }` |\n| `contains` | Yes | `{ on: {...}, contains: { tags: \"urgent\" } }` |\n| `range` | Yes | `{ on: {...}, range: { price: { gte: 100 } } }` |\n| `every` | No | `{ every: [...] }` (AND) |\n| `any` | No | `{ any: [...] }` (OR) |\n| `not` | No | `{ not: { eq: {...} } }` |\n\n*Only when not directly after type filter\n\n#### Range Operators\n`gt` (>) `gte` (>=) `lt` (<) `lte` (<=)\n\n#### Module & Realm Rules\n```typescript\n// ✅ ALWAYS absolute URLs\n{ module: new URL('./product', import.meta.url).href, name: 'Product' }\n\n// ✅ Realms need trailing slash\n'https://app.boxel.ai/sarah/projects/' // ✅\n'https://app.boxel.ai/sarah/projects' // ❌\n```\n\n### ⚠️ CRITICAL: The 'on' Attribute is MANDATORY\n\n**Missing 'on' will lead to no results shown!** When using:\n- `eq`, `contains`, `range` filters (except immediately after type filter)\n- `sort` on type-specific fields (anything beyond base fields like id, createdAt)\n\n```typescript\n// ❌ WILL FAIL - Missing 'on' for sort\n{ \n sort: [{ by: \"price\", direction: \"desc\" }]\n}\n\n// ✅ CORRECT - Include 'on' for type-specific fields\n{ \n sort: [{ \n by: \"price\", \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n direction: \"desc\" \n }]\n}\n```\n\n### Complete Query Pattern\n\n```typescript\nconst query: Query = {\n filter: {\n every: [ // AND\n { type: { module: new URL('./product', import.meta.url).href, name: 'Product' } },\n { \n any: [ // OR\n { on: { module: new URL('./product', import.meta.url).href, name: 'Product' }, eq: { category: \"laptop\" } },\n { on: { module: new URL('./product', import.meta.url).href, name: 'Product' }, eq: { category: \"tablet\" } }\n ]\n },\n { \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n range: { \n price: { gte: 100, lte: 2000 }, // Multiple conditions\n rating: { gte: 4 }\n }\n },\n { \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n contains: { features: \"wireless\" }\n },\n {\n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n not: { eq: { status: \"discontinued\" } } // Exclude\n }\n ]\n },\n sort: [\n { by: \"createdAt\", direction: \"desc\" }, // General field\n { \n by: \"warranty\", // Type-specific needs 'on'\n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n direction: \"desc\" \n }\n ],\n page: { number: 0, size: 20 }\n};\n```\n\n### Decision: PrerenderedCardSearch vs getCards\n\n```\nDisplay cards as-is? → PrerenderedCardSearch\nNeed live updates? → PrerenderedCardSearch with @isLive={{true}}\nNeed to sort/filter/aggregate AFTER retrieval? → getCards\nNeed raw data access? → getCards\n```\n\n## PrerenderedCardSearch Pattern\n\n```gts\n// ⁴⁹ Component with dynamic query\nexport class Dashboard extends Component {\n get urgentTasksQuery(): Query {\n return {\n filter: {\n every: [\n { type: { module: new URL('./task', import.meta.url).href, name: 'Task' } },\n { \n on: { module: new URL('./task', import.meta.url).href, name: 'Task' },\n not: { eq: { status: \"completed\" } }\n }\n ]\n },\n sort: [{ by: \"dueDate\", direction: \"asc\" }],\n page: { number: 0, size: 10 }\n };\n }\n\n realms = ['https://app.boxel.ai/sarah/tasks/']; // Trailing slash!\n\n \n}\n```\n\n### Making Query Results Clickable\n\n```gts\n// ⁵¹ Wrap with CardContainer for navigation\n<:response as |cards|>\n
    \n {{#each cards key=\"url\" as |card|}}\n
  • \n \n \n \n
  • \n {{/each}}\n
\n\n```\n\n## getCards Pattern (Data Manipulation)\n\n```gts\n// ⁵² Direct assignment for data access\ncardsResult = this.args.context?.getCards(\n this,\n () => this.query,\n () => this.realms,\n { isLive: true }\n);\n\n// ⁵³ Post-retrieval sorting\nget sortedByRevenue() {\n const products = this.cardsResult?.instances ?? [];\n return [...products].sort((a, b) => {\n const scoreA = (a.revenue || 0) * (a.rating || 1);\n const scoreB = (b.revenue || 0) * (b.rating || 1);\n return scoreB - scoreA;\n });\n}\n\n// ⁵⁴ Aggregation\nget totalRevenue() {\n return this.cardsResult?.instances?.reduce((sum, p) => sum + (p.revenue || 0), 0) || 0;\n}\n```\n\n\n## Creating Fitted Formats - The Four Sub-formats Strategy\n\nFitted Formats are unique part of the Boxel Architecture in that it allows a version of a card or a field that fit into any slot (width and height up to 600px) allocated by a parent container, so as to support listing, gallery, chooser, even 3D sprites usage without the parent knowing anything about this card's or field's schema or template other than its ID.\n\nTo create fitted formats that automatically adapt to any container size, implement four responsive subformats within a single fitted template. This pattern ensures your cards look perfect whether displayed as tiny badges or full-width cards. While the platform provides a fallback fitted format for CardDefs, custom implementation is strongly recommended for optimal display. For FieldDefs, fitted format is optional as embedded format is the primary requirement.\n\n### Core Concept\n\nYou only have one fitted template so that the resulting parent template only needs to give a size they want to display and you will provide the best layout given that space.\n\nTo do that, create 4 subformats and turn on only one at a time. Create 4 divs inside the fitted template and use container queries to turn them on and off. Make sure there are no gaps where no subformat is active.\n\nFitted format shouldn't have borders, that is drawn by parent.\n\n**RECOMMENDED:** Every CardDef should implement a custom fitted format for optimal display. While the platform provides a fallback, custom fitted formats ensure your cards look their best in galleries, grids, choosers, and dashboards.\n\n**Key Implementation Points:**\n- **CardDef:** Custom fitted format recommended (platform provides fallback)\n- **FieldDef:** Embedded format mandatory, fitted format optional\n- Create 4 divs inside the fitted template (badge, strip, tile, card)\n- Use container queries to show only the appropriate subformat\n- CRITICAL: Ensure no gaps where no subformat is active - all sizes must be handled\n- Fitted format shouldn't have borders (drawn by parent)\n\n### Container Size Decision Tree\n\n```\nContainer Size\n │\n ├─ Height < 170px (Horizontal)\n │ │\n │ ├─ Width ≤ 150px → BADGE\n │ │ • 150×40 (micro)\n │ │ • 150×65 (small)\n │ │ • 150×105 (large) ← optimize\n │ │\n │ └─ Width > 150px → STRIP\n │ • 250×40 (single)\n │ • 250×65 (double)\n │ • 250×105 (triple)\n │ • 400×65 (wide double) ← optimize\n │ • 400×105 (wide triple)\n │\n └─ Height ≥ 170px (Vertical)\n │\n ├─ Width < 400px → TILE\n │ • 150×170 (narrow)\n │ • 170×250 (grid) ← optimize\n │ • 250×170 (wide)\n │ • 250×275 (large)\n │\n └─ Width ≥ 400px → CARD\n • 400×170 (compact)\n • 400×275 (standard) ← optimize\n • 400×445 (expanded)\n```\n\n#### Design Philosophy\n\n**First design the IDEAL layout for each subformat at the \"optimized for\" size.** Think of each subformat as if you were making 4 independent templates, each perfect for its specific use case.\n\n**Height Quantum:** The height breakpoints (40px, 65px, 105px, etc.) follow golden ratio progression (φ ≈ 1.618), creating natural visual harmony as formats scale.\n\n**Golden Ratio Usage:** Apply the golden ratio (1.618:1) throughout your layouts - for splits, spacing progressions, content zones, and visual balance. This mathematical harmony creates inherently pleasing proportions.\n\n**Typography Hierarchy:** Create clear visual distinction between text levels:\n- **Size cascade:** Each level 80-87% of the previous (1em → 0.875em → 0.75em)\n- **Weight cascade:** Drop 100-200 font-weight units per level (600 → 500 → 400)\n- **Spacing cascade:** Buffer between levels follows 50% → 37.5% → 25% pattern\n- **Same-level spacing:** Use 25% of the element's font size\n\n**Qualities for All Fitted Formats:**\n- **Well-balanced** - Every element positioned with intention\n- **On-brand** - Visually polished and consistent\n- **Scannable** - Clear indicators, easy to parse\n- **Small multiples** - Differences pop in collections\n- **Clickable** - Inviting interaction (cards only)\n- **Complete** - Show key data within constraints\n- **Familiar yet superior** - Match expectations, execute better\n- **Identifier visible** - Never obscure with entrance animations\n- **Clear hierarchy** - Primary/secondary/tertiary distinct\n\n### Content Priority Guidelines\n\nSuggested priority order - adjust for your use case:\n\n1. **Title/Name** - Primary identifier\n2. **Image** - Visual identity \n3. **Short ID** - SKU, username, ticket #\n4. **Key Info** - Dates, stats, linked entities\n5. **Badge/Status** - Visual indicators\n6. **Key-Value Metadata** - Show complete pairs only\n7. **Description** - Low priority, line-clamp aggressively\n8. **CTA** - Hover/focus only in tiles\n\n**For FieldDefs:** Since fitted format is optional, focus on embedded format first. If implementing fitted: priorities shift since there's no click-through. Show most important data within space constraints - composite identity plus critical values.\n\n**Examples:**\n- **Inventory:** SKU/status may outrank title\n- **Analytics:** Numbers take precedence\n- **Tasks:** Due date/assignee before description\n\n### Container Query Skeleton\n\n```css\n.fitted-container {\n container-type: size;\n width: 100%;\n height: 100%;\n}\n\n/* Hide all by default */\n.badge-format, .strip-format, .tile-format, .card-format {\n display: none;\n width: 100%;\n height: 100%;\n /* CRITICAL: Clear space prevents edge bleeding - scales with container size */\n padding: clamp(0.1875rem, 2%, 0.625rem); /* 3px min → 10px max */\n box-sizing: border-box;\n}\n\n/* Micro containers: absolute minimum safe padding */\n@container (max-width: 80px) and (max-height: 80px) {\n .badge-format { \n padding: 0.1875rem; /* 3px - visual safety minimum */\n }\n}\n\n/* Small containers: tight but safe */\n@container (max-width: 150px) {\n .badge-format, .strip-format { \n padding: 0.25rem; /* 4px - small but comfortable */\n }\n}\n\n/* Medium containers: breathing room */\n@container (min-width: 250px) and (max-width: 399px) {\n .tile-format {\n padding: 0.5rem; /* 8px - standard spacing */\n }\n}\n\n/* Large containers: generous clear space */\n@container (min-width: 400px) {\n .card-format {\n padding: clamp(0.5rem, 2%, 0.625rem); /* 8px → 10px max for expanded */\n }\n}\n\n/* Activation ranges - NO GAPS */\n@container (max-width: 150px) and (max-height: 169px) {\n .badge-format { display: flex; }\n}\n\n@container (min-width: 151px) and (max-height: 169px) {\n .strip-format { display: flex; }\n}\n\n@container (max-width: 399px) and (min-height: 170px) {\n .tile-format { display: flex; flex-direction: column; }\n}\n\n@container (min-width: 400px) and (min-height: 170px) {\n .card-format { display: flex; flex-direction: column; }\n}\n\n/* Compact card: horizontal split at golden ratio */\n@container (min-width: 400px) and (height: 170px) {\n .card-format { \n flex-direction: row;\n gap: 1rem;\n }\n .card-format > * {\n display: flex;\n flex-direction: column;\n }\n .card-format > *:first-child { flex: 1.618; }\n .card-format > *:last-child { flex: 1; }\n}\n\n/* Background fills respect padding for visual safety */\n.badge-format.has-fill,\n.strip-format.has-fill,\n.tile-format.has-fill,\n.card-format.has-fill {\n background: var(--fill-color);\n /* Background extends to edge but content stays within padding */\n background-clip: padding-box; /* Or border-box if fill should reach edge */\n}\n\n/* Type hierarchy - MANDATORY */\n.primary-text {\n font-size: 1em;\n font-weight: 600;\n color: var(--text-primary, rgba(0,0,0,0.95));\n line-height: 1.2;\n}\n\n.secondary-text {\n font-size: 0.875em; /* 87.5% of primary */\n font-weight: 500;\n color: var(--text-secondary, rgba(0,0,0,0.85));\n line-height: 1.3;\n}\n\n.tertiary-text {\n font-size: 0.75em; /* 75% of primary */\n font-weight: 400;\n color: var(--text-tertiary, rgba(0,0,0,0.7));\n line-height: 1.4;\n}\n\n/* Typography Hierarchy Spacing Heuristics */\n/* Primary → Secondary: 0.5em gap (half the primary size) */\n/* Secondary → Tertiary: 0.375em gap */\n/* Same level elements: 0.25em gap */\n\n.primary-text + .secondary-text {\n margin-top: 0.5em;\n}\n\n.secondary-text + .tertiary-text {\n margin-top: 0.375em;\n}\n\n.primary-text + .primary-text,\n.secondary-text + .secondary-text {\n margin-top: 0.25em;\n}\n\n/* Visual hierarchy multipliers:\n - Size: Each level ~80-87% of previous\n - Weight: Drop 100-200 units per level\n - Opacity: Drop 10-15% per level\n - Spacing: 50% → 37.5% → 25% of primary size */\n```\n\n### Subformat-Specific Rules\n\n**Design with familiar patterns** - Users know these formats from daily app usage. Meet their expectations, then exceed them with better spacing, smoother interactions, and superior visual polish. Doing something expected is good - just do it better.\n\n**Badge Format:**\n- Feels like exportable graphics\n- **Familiar from:** Slack badges, GitHub labels\n- 150×105 has 3 vertical elements\n- Fills/backgrounds extend to edges, content respects padding\n- **LEFT align always** - right elements balance\n- **Images:** Iconified 16-34px\n- **Heights:**\n - 40px: Title + icon horizontal only (or composite field identity)\n - 65px: Title + icon/ID stacked, single lines\n - 105px: Title + icon + status, magnetic edges\n- Use formatters for compact display\n- **For FieldDefs:** Show composite identity + key details\n- **Typography example at 105px:**\n - Primary title: 14px (0.875rem)\n - Secondary ID: 12px with 7px gap from title\n - Tertiary status: 10px with 5px gap from ID\n\n**Strip Format:**\n- **Primary use:** Dropdown and chooser panels where users scan and select\n- **Familiar from:** VS Code command palette, Spotlight search, Notion quick switcher\n- Optimized for quick scanning and selection - every pixel matters\n- **Title/identifier MUST ALWAYS be visible** - no animations, overlays, or effects that obscure it\n- Never use hover effects that hide or transform the identifier\n- Right-justify elements in wider strips\n- **Left aligned - no exceptions**\n- **Images:** \n - 40px height: Same as badge (20-34px) for consistency\n - 65px+ height: Standard size (40px)\n- **Height requirements:**\n - 40px: Title + key stat horizontally ONLY - single line, images 20-34px (same as badge)\n - 65px: Two single lines stacked vertically - NO wrapping within lines, images 40px\n - 105px: Three rows with magnetic edge spacing, images 40px\n- Abbreviate metadata, keep primary identity full\n\n**Tile Format:**\n- Standard vertical card layout\n- Optimize for grid viewing\n- Primary identity MUST be fully visible and prominent - no exceptions\n- The last vertical element MUST magnetically stick to the bottom\n\n**Card Format:**\n- Compact card (400×170) is split horizontally once at the golden ratio, then content within each panel is organized vertically\n- All other cards larger than compact card should be vertically subdivided\n- Expanded card is the full card with more data on the bottom\n- Expanded card MUST use all available vertical space - empty space is failure\n- The last vertical element MUST magnetically stick to the bottom\n\n### CTA Placement\n- **CardDef tile subformats only** (not FieldDefs)\n- Show on hover/focus only\n- Can obscure other content when shown\n- Lowest priority\n\n### Fitted Formats for FieldDefs (Optional)\n\n**IMPORTANT:** Fitted formats are optional for FieldDefs. FieldDefs require embedded format (with natural height) and that should be your primary focus. Only create fitted formats when your field might be displayed in fixed-size containers.\n\nWhen implementing fitted formats for FieldDefs, they require a different approach than CardDefs because they lack inherent identity and have no click-through capability.\n\n**Key Difference from CardDef Fitted:**\n- **CardDef fitted:** Shows identity + key info → click for details\n- **FieldDef fitted:** Shows most important data that fits (still space-constrained)\n\n**Creating Field Identity:**\nSince fields don't have clear identity like cards, create a composite identifier by combining 1-3 most important data points. For example:\n- Address field: Street + City\n- Price field: Amount + Currency + Trend\n- Contact field: Name + Primary method\n- Date range: Start + Duration + Status\n\n**Content Priority Shift:**\nBecause users can't click through to see more, fitted formats for fields should:\n- Show the most important data that fits the space\n- Prioritize key identifiers and critical values\n- Include essential metadata over nice-to-have details\n- Use composite identity from 1-3 key data points\n- Remember: still space-constrained like card fitted formats\n\n**Visual Field Handling:**\nFor image-based or visually-oriented compound fields:\n- Make the image/visual element primary (fill most space)\n- Overlay metadata on top with appropriate contrast\n- Use scrims or backdrop shadows for text legibility (except on precise visual content)\n- Consider the image as the \"identity\" with data as support\n- **CRITICAL:** For color fields, charts, or data visualizations, avoid scrims/overlays that alter perception\n\n**Example implementations:**\n- **Location field:** Map thumbnail with address overlay\n- **Chart field:** Visualization fills space, key metrics on corners (no scrim)\n- **Media field:** Thumbnail/preview large, metadata badge overlay\n- **Color field:** Swatch as background, hex/rgb values on top (pure color, no overlay)\n\n```css\n/* Example: Visual field with overlay metadata */\n.field-tile-format.visual-field {\n position: relative;\n padding: clamp(0.1875rem, 2%, 0.5rem); /* Clear space scales with container */\n}\n\n.field-tile-format .visual-primary {\n width: 100%;\n height: 100%;\n object-fit: cover;\n border-radius: 0.25rem; /* Subtle radius prevents harsh edges */\n}\n\n.field-tile-format .metadata-overlay {\n position: absolute;\n bottom: clamp(0.1875rem, 2%, 0.5rem); /* Match container padding */\n left: clamp(0.1875rem, 2%, 0.5rem);\n right: clamp(0.1875rem, 2%, 0.5rem);\n padding: 0.5rem;\n background: linear-gradient(to top, \n rgba(0,0,0,0.8) 0%, \n rgba(0,0,0,0) 100%);\n color: white;\n border-radius: 0.25rem;\n}\n\n/* Non-visual fields show more detail */\n.field-badge-format {\n padding: clamp(0.1875rem, 2%, 0.375rem); /* Clear space for badges */\n}\n\n.field-badge-format .composite-identity {\n font-weight: 600;\n margin-bottom: 0.25rem;\n}\n\n.field-badge-format .field-details {\n font-size: 0.75rem;\n opacity: 0.9;\n}\n```\n\n### Visual Guidelines\n\n#### Icons\n- Incorporate subtly with appropriate size/weight\n- Visual support only - include after key content\n\n#### Images\n- Priority 2 - show after primary identifier\n- **Badge:** Always iconified (16-34px)\n- **Strip:** \n - 40px height: 20-34px (matches badge)\n - 65px height: Fixed 40px\n - 105px height: Can fill height with AR constraint in wide strips (250px+)\n- **Tile:** Background with vibrant scrim if image would obscure text (except for visually precise content)\n- **Tile/Card:** Apply shared scale budget with text\n- Aspect ratios 0.7-1.4 unless decorative\n- Never completely displace text\n- **For visual FieldDefs:** Image can be primary with metadata overlay\n\n**Scrim effects:** Use accent colors for vibrant overlays. Mix brand colors with dark gradients: purple-to-black, blue-to-indigo-to-black, or accent-with-opacity layers. **CRITICAL:** Never apply scrims to visually precise content (color swatches, charts, data visualizations, medical imagery) as they alter perception and compromise accuracy.\n\n**Animation restraint:** Never use animations that move content near edges - can expose accidental borders. Strips especially need static, predictable layouts for scanning.\n\n#### 105px Height Magnetic Edge Layout\n\nAt 105px, use `justify-content: space-between` to push three elements to top/middle/bottom edges, maximizing visual separation.\n\n### Key Implementation Details\n\n1. **CardDef Fitted:** Custom recommended (fallback exists)\n2. **FieldDef Requirements:** Embedded mandatory, fitted optional\n3. **Container Queries:** `container-type: size`\n4. **No Gaps:** Cover all sizes\n5. **Line Clamping:** Match height constraints\n6. **Scaling:** `clamp()` ±20-25%\n7. **Height Use:** Fill 40/65/105px fully\n8. **40px:** Horizontal only\n9. **105px:** `justify-content: space-between`\n10. **Strip IDs:** Always visible\n11. **Clear Space:** 3px min to 1rem max\n12. **Type Hierarchy:** Size/weight/spacing cascades (80-87% per level)\n13. **Data Shaping:** Use formatters\n14. **Priority:** Key-values > descriptions\n15. **Badge Images:** 16-34px scaling\n16. **Strip Images:** Match badge at 40px, larger at 65px+, AR-fill at 105px wide\n17. **Scale Budget:** 50% shared text/image\n18. **Font Scaling:** Smaller = smaller base\n19. **Key-Values:** Complete pairs only\n20. **Familiar Patterns:** Match expectations\n21. **Edge Fills:** Backgrounds full, content padded\n22. **Vibrant Scrims:** Accent colors\n23. **No Edge Animations:** Prevent border exposure\n24. **FieldDef Identity:** Composite 1-3 key data points for recognition\n25. **Visual Precision:** No scrims on color/chart/data viz content\n\n\n\n## CRITICAL Reminders\n\n1. **PrerenderedCardSearch returns components, not data** - Can't sort/filter after\n2. **Type-specific sort fields MUST have 'on'** - Missing 'on' = no results shown!\n3. **Empty arrays need length check** - `(gt @model.items.length 0)`\n4. **Query result spacing** - Use `.container > .containsMany-field` pattern\n5. **Always use absolute module URLs** - `new URL(...).href`\n\n### Using getCards for Data Access and Aggregation\n\nWhen you need full access to card data for calculations, aggregations, or custom processing, use the `getCards` API from context.\n\n#### Basic getCards Pattern\n\n```gts\n// ❌ WRONG: Don't import getCards - it's just a type definition\n// import { getCards } from '@cardstack/runtime-common';\n\n// ✅ CORRECT: Use getCards from context\n// With live updates (for dashboards)\ncardsResult = this.args.context?.getCards(\n this,\n () => this.query,\n () => this.realmHrefs,\n { isLive: true }\n);\n\n// For one-time load (omit isLive)\ncardsResult = this.args.context?.getCards(\n this,\n () => this.query,\n () => this.realmHrefs\n);\n```\n\n#### Working with getCards Results\n\n```gts\n// getCards returns: { instances, isLoading, instancesByRealm }\ncardsResult = this.args.context?.getCards(\n this,\n () => this.storyQuery,\n () => this.realmHrefs,\n);\n\n// Frontend sorting/filtering\nget sortedCards() {\n const cards = this.cardsResult?.instances ?? [];\n return [...cards].sort((a, b) => b.rating - a.rating);\n}\n\n\n```\n\n#### Map/Reduce Aggregation Patterns\n\n**Note:** These patterns load all matching cards into memory, so use sparingly for large datasets.\n\n**RULE: Make aggregated stats real** - When showing totals, averages, or counts in templates, calculate them from actual data using aggregation functions, not hardcoded placeholders.\n\n```gts\n// Calculate totals using reduce\nget totalValue() {\n if (!this.cardsResult?.instances) return 0;\n return this.cardsResult.instances.reduce((sum, card) => {\n return sum + (card.value || 0);\n }, 0);\n}\n\n// Group by category\nget groupedByCategory() {\n if (!this.cardsResult?.instances) return {};\n return this.cardsResult.instances.reduce((groups, card) => {\n const category = card.category || 'Uncategorized';\n groups[category] = groups[category] || [];\n groups[category].push(card);\n return groups;\n }, {});\n}\n\n// Multiple metrics in one pass\nget metrics() {\n if (!this.cardsResult?.instances) return null;\n \n return this.cardsResult.instances.reduce((acc, card) => {\n acc.total += card.amount || 0;\n acc.count += 1;\n acc.byStatus[card.status] = (acc.byStatus[card.status] || 0) + 1;\n if (card.priority === 'high') acc.highPriority += 1;\n return acc;\n }, {\n total: 0,\n count: 0,\n byStatus: {},\n highPriority: 0\n });\n}\n```\n\n**Performance Considerations:**\n- For simple counts, use the type summaries API instead\n- PrerenderedCardSearch is better for display-only needs\n- Only use getCards when you need complex calculations\n- Consider pagination for large datasets\n\n### CardContainer: Making Cards Clickable\n\nTransforms cards into interactive, clickable elements for viewing or editing, complete with visual chrome. When used with the `cardComponentModifier`, it enables users to click through to view or edit the wrapped card.\n\n#### Usage\n\n```gts\n\n```\n\n**CRITICAL: Style Boxel UI Components for Custom Templates**\n\n**Boxel UI components (Button, BoxelSelect, etc.) must be completely styled when used in custom isolated, embedded, and fitted templates.** They come with minimal default styling and buttons especially will look broken without custom CSS.\n\n```gts\n\n```\n### Alternative: Using Custom Actions with viewCard API\n\nInstead of making entire cards clickable, you can create custom buttons or links that use the `viewCard` API to open cards in specific formats.\n\n#### Basic Implementation\n\n```javascript\n@action\nviewOrder(order: ProductOrder) {\n // Open order in isolated view\n this.args.viewCard(order, 'isolated');\n}\n\n@action\neditOrder(order: ProductOrder) {\n // Open card in rightmost stack for side-by-side reference\n // Useful for: 1) reference lookup, 2) edit panel on right while previewing on left\n this.args.viewCard(order, 'edit', {\n openCardInRightMostStack: true\n });\n}\n\n@action\nviewReturnPolicy() {\n // Open card using URL\n const returnPolicyURL = new URL('https://app.boxel.ai/markinc/storefront/ReturnPolicy/return-policy-0525.json');\n this.args.viewCard(returnPolicyURL, 'isolated');\n}\n```\n\n#### Template Example\n\n```hbs\n
\n \n
\n \n View Order\n \n \n \n Edit Order\n \n
\n \n\n \n Return Policy\n \n
\n```\n\n#### Available Formats\n\n- `'isolated'` - Read-oriented mode, may have some editable forms or interactive widgets\n- `'edit'` - Open card for full editing\n\n#### Use Cases\n- Multiple direct call-to-actions per card (view, edit)\n- More control over user interactions\n- Link to any card via a card URL\n\n\n## External Libraries: Bringing Third-Party Power to Boxel\n\n**When to Use External Libraries:** Sometimes you need specialized functionality like 3D graphics (Three.js), data visualization (D3), or charts. Boxel plays well with external libraries when you follow the right patterns.\n\n**Key Rules:**\n1. **Always use Modifiers for DOM access** - Never manipulate DOM directly\n2. **Use ember-concurrency tasks** for async operations like loading libraries\n3. **Bind external data to model fields** for reactive updates\n4. **Use proper loading states** while libraries initialize\n\n### Pattern: Dynamic Three.js Integration\n\n```gts\nimport { task } from 'ember-concurrency';\nimport Modifier from 'ember-modifier';\n\n// Global accessor function\nfunction three() {\n return (globalThis as any).THREE;\n}\n\nclass ThreeJsComponent extends Component {\n @tracked errorMessage = '';\n private canvasElement: HTMLCanvasElement | undefined;\n \n private loadThreeJs = task(async () => {\n if (three()) return;\n \n const script = document.createElement('script');\n script.src = 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js';\n script.async = true;\n \n await new Promise((resolve, reject) => {\n script.onload = resolve;\n script.onerror = reject;\n document.head.appendChild(script);\n });\n });\n\n private initThreeJs = task(async () => {\n try {\n await this.loadThreeJs.perform();\n if (!three() || !this.canvasElement) return;\n \n const THREE = three();\n \n // Scene setup - bind results to model fields for reactivity\n this.scene = new THREE.Scene();\n // ... setup scene\n \n // CRITICAL: Bind external data to model fields\n this.args.model.sceneReady = true;\n this.args.model.lastUpdated = new Date();\n \n this.animate();\n } catch (e: any) {\n this.errorMessage = `Error: ${e.message}`;\n }\n });\n\n private onCanvasElement = (element: HTMLCanvasElement) => {\n this.canvasElement = element;\n this.initThreeJs.perform();\n };\n\n \n}\n```\n\n## File Organization\n\n### Single App Structure\n```\nmy-realm/\n├── blog-post.gts # Card definition (kebab-case)\n├── author.gts # Another card\n├── address-field.gts # Field definition (kebab-case-field)\n├── BlogPost/ # Instance directory (PascalCase)\n│ ├── hello-world.json # Instance (any-name)\n│ └── second-post.json \n└── Author/\n └── jane-doe.json\n```\n\n### Related Cards App Structure\n**CRITICAL:** When creating apps with multiple related cards, organize them in common folders:\n\n```\nmy-realm/\n├── ecommerce/ # Common folder for related cards\n│ ├── product.gts # Card definitions\n│ ├── order.gts\n│ ├── customer.gts\n│ ├── Product/ # Instance directories\n│ │ └── laptop-pro.json\n│ └── Order/\n│ └── order-001.json\n├── blog/ # Another app's folder\n│ ├── post.gts\n│ ├── author.gts\n│ └── Post/\n│ └── welcome.json\n└── shared/ # Shared components\n └── address-field.gts # Common field definitions\n```\n\n**Directory Discipline:** When creating files within a specific directory structure (e.g., `ecommerce/`), keep ALL related files within that structure. Don't create files outside the intended directory organization.\n\n**Relationship Path Tracking:** When creating related JSON instances, maintain a mental map of your file paths. Links between instances must use the exact relative paths you've created - consistency prevents broken relationships.\n\n## JSON Instance Format Quick Reference\n\n**When creating `.json` card instances via SEARCH/REPLACE, follow this structure:**\n\n**Naming:** Use natural names for JSON files (e.g., `Author/jane-doe.json`, `Product/laptop-pro.json`) - don't append `-sample-data`\n\n**Path Consistency:** When creating multiple related JSON instances, track the exact file paths you create. Relationship links must match these paths exactly - if you create `Author/dr-nakamura.json`, reference it as `\"../Author/dr-nakamura\"` from other instances.\n\n### Root Structure\nAll data wrapped in a `data` object with:\n* `type`: Always `\"card\"` for instances\n* `attributes`: Field values go here\n* `relationships`: Links to other cards\n* `meta.adoptsFrom`: Connection to GTS definition\n\n### Instance Template\n```json\n{\n \"data\": {\n \"type\": \"card\",\n \"attributes\": {\n // Field values here\n },\n \"relationships\": {\n // Card links here\n },\n \"meta\": {\n \"adoptsFrom\": {\n \"module\": \"../path-to-gts-file\",\n \"name\": \"CardDefClassName\"\n }\n }\n }\n}\n```\n\n### Field Value Patterns\n\n**Simple fields** (`contains(StringField)`, etc.):\n```json\n\"attributes\": {\n \"title\": \"My Title\",\n \"price\": 29.99,\n \"isActive\": true\n}\n```\n\n**Compound fields** (`contains(AddressField)` - a FieldDef):\n```json\n\"attributes\": {\n \"address\": {\n \"street\": \"4827 Riverside Terrace\",\n \"city\": \"Portland\",\n \"postalCode\": \"97205\"\n }\n}\n```\n\n**Array fields** (`containsMany`):\n```json\n\"attributes\": {\n \"tags\": [\"urgent\", \"review\", \"frontend\"],\n \"phoneNumbers\": [\n { \"number\": \"+1-503-555-0134\", \"type\": \"work\" },\n { \"number\": \"+1-971-555-0198\", \"type\": \"mobile\" }\n ]\n}\n```\n\n### Relationship Patterns\n\n**Single link** (`linksTo`):\n```json\n\"relationships\": {\n \"author\": {\n \"links\": {\n \"self\": \"../Author/dr-nakamura\"\n }\n }\n}\n```\n\n**Multiple links** (`linksToMany`) - note the `.0`, `.1` pattern:\n```json\n\"relationships\": {\n \"teamMembers.0\": {\n \"links\": { \"self\": \"../Person/kai-nakamura\" }\n },\n \"teamMembers.1\": {\n \"links\": { \"self\": \"../Person/esperanza-cruz\" }\n }\n}\n```\n\n**Empty linksToMany** - when no relationships exist:\n```json\n\"relationships\": {\n \"nextLevels\": {\n \"links\": {\n \"self\": null\n }\n }\n}\n```\nNote: Use `null`, not an empty array `[]`\n\n### Path Conventions\n* **Module paths**: Relative to JSON location, no `.gts` extension\n * Local: `\"../author\"` or `\"../../shared/address-field\"`\n * Base: `\"https://cardstack.com/base/string\"`\n* **Relationship paths**: Relative paths, no `.json` extension\n * `\"../Author/jane-doe\"` not `\"../Author/jane-doe.json\"`\n* **Date formats**: \n * DateField: `\"2024-11-15\"`\n * DateTimeField: `\"2024-11-15T10:00:00Z\"`\n\n## 🚫 Common Mistakes to Avoid\n\n### 1. Using contains/containsMany with CardDef\n```gts\n// ❌ WRONG\nexport class Auction extends CardDef {\n @field auctionItems = containsMany(AuctionItem); // AuctionItem is a CardDef\n}\n\n// ✅ CORRECT\nexport class Auction extends CardDef {\n @field auctionItems = linksToMany(AuctionItem); // Use linksToMany for CardDef\n}\n```\n\n### 2. Template Calculation Mistakes\n```gts\n// ❌ WRONG - JavaScript/constructors in template\nTotal: {{@model.price * @model.quantity}}\n{{if @model.currentMonth @model.currentMonth (formatDateTime (new Date()) \"MMMM YYYY\")}}\n\n// ✅ CORRECT - Use helpers or computed property\nTotal: {{multiply @model.price @model.quantity}}\n{{if @model.currentMonth @model.currentMonth this.currentMonthDisplay}}\n```\n\n### 3. Using Reserved Words as Field Names\n```gts\n// ❌ WRONG - JavaScript reserved words\n@field type = contains(StringField); // 'type' is reserved\n@field class = contains(StringField); // 'class' is reserved\n\n// ✅ CORRECT - Use descriptive alternatives\n@field recordType = contains(StringField); // Instead of 'type'\n@field category = contains(StringField); // Instead of 'class'\n\n// ✅ CORRECT - Override inherited fields with computed versions\n@field fullName = contains(StringField);\n@field title = contains(StringField, {\n computeVia: function() { return this.fullName ?? 'Unnamed'; }\n});\n```\n\n### 4. Missing Exports\n```gts\n// ❌ WRONG - Missing export will break module loading\nclass BlogPost extends CardDef { // Missing 'export'\n}\n\n// ❌ WRONG - Separate export statement\nclass BlogPost extends CardDef { }\nexport { BlogPost };\n\n// ✅ CORRECT - Always export CardDef and FieldDef classes inline\nexport class BlogPost extends CardDef {\n}\n```\n\n### 5. Missing Spacing for Auto-Collections\n```gts\n// ❌ WRONG - No spacing wrapper for delegated items\n<@fields.items @format=\"embedded\" />\n\n// ❌ WRONG - Container styling won't reach containsMany items\n
\n <@fields.items @format=\"embedded\" />\n
\n\n\n\n// ✅ CORRECT - Target .containsMany-field\n
\n <@fields.items @format=\"embedded\" />\n
\n\n\n```\n\n### 6. Mixing @model Iteration with @fields Delegation\n```gts\n// ❌ WRONG - Cannot use @fields inside @model iteration\n{{#each @model.teamMembers as |member|}}\n <@fields.member @format=\"embedded\" /> \n{{/each}}\n\n// ✅ CORRECT - Choose one approach\n// Option 1: Full delegation\n<@fields.teamMembers @format=\"embedded\" />\n\n// Option 2: Full @model control\n{{#each @model.teamMembers as |member|}}\n
{{member.name}}
\n{{/each}}\n```\n\n### 7. Using Emoji or Boxel Icons in Templates\n```hbs\n\n

🎯 Daily Goals

\n\n\n\n

Daily Goals

\n\n\n\n

\n \n \n \n \n \n Daily Goals\n

\n\n```\n\n### 8. Self-Import Error\n```gts\n// ❌ WRONG - Never import the same field you're defining\nimport AddressField from 'https://cardstack.com/base/address';\n\nexport class AddressField extends FieldDef { // Defining AddressField but importing it too\n // ... this will cause conflicts\n}\n\n// ✅ CORRECT - Don't import what you're defining\nexport class AddressField extends FieldDef {\n // ... define the field without importing it\n}\n\n// ✅ CORRECT - To extend a base field, import it with a different name or extend directly\nimport BaseAddressField from 'https://cardstack.com/base/address';\n\nexport class FancyAddressField extends BaseAddressField {\n // ... extend the base field with custom behavior\n}\n```\n\n### 9. Escaping Placeholder Attributes Only\n```hbs\n\n\n 0) { return \"success\"; }\">\n\n\n\n\n```\n\n### 10. Don't use single curlies\n```hbs\n\n#{@model.paddleNumber}\n\n\n#{{@model.paddleNumber}}\n```\n\n**Note:** The `#` character starts block helpers in Handlebars (e.g., `{{#if}}`, `{{#each}}`), so it must be escaped when you want to display it literally before template interpolations.\n\n### 11. Using Unstyled Buttons\n```gts\n// ❌ WRONG - Unstyled buttons look broken\n\n\n// ✅ CORRECT - Always add complete styling (see button styling example in Advanced Patterns)\n\n```\n\n### 12. Missing Tracking Comments in .gts Files\n```gts\n// ❌ WRONG - No tracking mode indicator on line 1\nimport { CardDef } from 'https://cardstack.com/base/card-api';\n\n// ✅ CORRECT - Tracking mode on line 1, markers throughout\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { CardDef } from 'https://cardstack.com/base/card-api'; // ¹ Core imports\n```\n\nRemember to include the post-SEARCH/REPLACE notation `╰ ¹⁻³` after blocks!\n\n### 13. Wrong Empty Relationship Format in JSON\n```json\n// ❌ WRONG - Empty array for null relationship\n\"relationships\": {\n \"nextLevels\": {\n \"links\": {\n \"self\": []\n }\n }\n}\n\n// ✅ CORRECT - Use null for empty linksToMany\n\"relationships\": {\n \"nextLevels\": {\n \"links\": {\n \"self\": null\n }\n }\n}\n```\n\n### 14. SVG URL References Don't Work in Boxel\n```hbs\n\n\n \n \n \n \n \n \n \n\n\n\n\n \n\n\n```\n\n**Rule:** Avoid `url(#id)` references in SVGs (for gradients, patterns, clips, etc.) as Boxel cannot route these correctly. Instead, use CSS alternatives to style SVG elements when available. For gradients specifically, use CSS `linear-gradient()` or `radial-gradient()` on SVG elements rather than SVG `` or ``.\n\n### 15. Missing 'on' Property in Query Filters\n```gts\n// ❌ WRONG - Missing 'on' for range filter\nconst query = {\n filter: {\n range: { price: { lte: 100 } }\n }\n};\n\n// ✅ CORRECT - Include 'on' for non-basic filters\nconst query = {\n filter: {\n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n range: { price: { lte: 100 } }\n }\n};\n```\n\n### Common Patterns\n\n```typescript\n// Field existence check\n{ on: {...}, not: { eq: { description: null } } }\n\n// Multiple ranges \n{ on: {...}, range: {\n score: { gt: 8 },\n years: { gte: 1, lt: 10 },\n date: { gte: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000) }\n}}\n\n// Nested field access\n{ on: {...}, eq: { \n 'supervisor.id': this.args.model.id,\n 'department.active': true\n}}\n\n// Dynamic references\n{ on: {...}, range: { \n price: { lte: this.args.model.budget || 1000 }\n}}\n```\n\n## ✅ Pre-Generation Checklist\n\n### 🚨 CRITICAL (Will Break Functionality)\n- [ ] **Using SEARCH/REPLACE blocks for all .gts edits**\n- [ ] **Tracking mode indicator on line 1:** `// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══`\n- [ ] **NO contains/containsMany with CardDef** - Check every field using contains/containsMany only uses FieldDef types\n- [ ] **NO JavaScript calculations/constructors in templates** - All computations must be in JS properties/getters\n- [ ] **ALL CardDef and FieldDef classes exported inline** - Every class must have 'export' in declaration\n- [ ] Correct contains/linksTo usage per the cardinal rule\n- [ ] Array length checks: `{{#if (gt @model.array.length 0)}}` not `{{#if @model.array}}`\n- [ ] **containsMany collection spacing: `.container > .containsMany-field { display: flex/grid; gap: X; }`**\n- [ ] **@fields delegation rule**: Always use `@fields` for delegation (even singular fields)\n- [ ] **Never mix @model iteration with @fields delegation** - choose one approach\n- [ ] **Fitted format requires style overrides (TEMPORARY):** `style=\"width: 100%; height: 100%\"`\n- [ ] **Use inline SVG in templates instead of emoji or Boxel icons**\n- [ ] **Never use unstyled buttons** - always add complete custom CSS styling\n- [ ] **Empty linksToMany relationships use null** - `\"self\": null` not `\"self\": []`\n- [ ] **No SVG url(#id) references** - use CSS gradients on SVG elements instead\n- [ ] **External libraries** - use Modifiers for DOM access, never manipulate DOM directly\n- [ ] **Query filters use 'on' property** - Required for range, contains, eq (except after type filter)\n- [ ] **Module URLs use new URL().href** - Never use relative paths in queries\n- [ ] **Realm URLs have trailing slash** - Required for realm references\n\n### ⚠️ IMPORTANT (Affects User Experience)\n- [ ] Icons assigned to all CardDef and FieldDef\n- [ ] Embedded templates for all FieldDefs\n- [ ] Empty states provided for all arrays\n- [ ] Every card computes inherited `title` field from primary identifier\n- [ ] Recent dates in sample data (2024/2025)\n- [ ] Currency/dates formatted with helpers in templates only\n- [ ] Meaningful placeholder text for all fallback states\n- [ ] Isolated views have scrollable content area\n- [ ] **Boxel UI components completely styled in custom templates**\n- [ ] **Creative sample data** - avoid clichés, create believable fictional scenarios\n- [ ] **Thoughtful font selection** - choose domain-appropriate Google fonts\n\n## Critical Rules Summary\n\n### One-Shot Success Criteria (Priority Order)\n1. **Runnable** - No syntax errors, all imports work, no runtime crashes due to missing data\n2. **Syntactically Correct** - Proper contains/linksTo, exports, tracking comments\n3. **Attractive** - Professional styling, thoughtful UX, visual polish\n4. **Evolvable** - Clear structure for user additions and modifications\n\n### NEVER Do These\n\n### 🔴 #1 MOST CRITICAL ERROR:\n❌ `contains(CardDef)` or `containsMany(CardDef)` → **ALWAYS** use `linksTo(CardDef)` or `linksToMany(CardDef)`\n\n### 🔴 #2 CRITICAL: No JavaScript in Templates\n❌ **NEVER do calculations, constructors, or call methods in templates:**\n - `{{@model.price * 1.2}}` → Use `{{multiply @model.price 1.2}}`\n - `{{(new Date())}}` → Create getter `get currentDate()`\n - `{{price > 100}}` → Use `{{gt price 100}}`\n\n### 🔴 #3 CRITICAL: Field Rules\n❌ **JavaScript reserved words as field names** → Use descriptive alternatives \n❌ **Defining same field name twice in your own class** → Each field name unique \n✅ **OK to override parent's fields** → Can compute title, description, thumbnailURL \n❌ **Missing exports on CardDef/FieldDef** → Every class must be exported \n\n### 🔴 #4 CRITICAL: Edit Tracking Mode\n❌ **Missing tracking mode indicator on line 1** → Every .gts file MUST start with tracking \n❌ **SEARCH/REPLACE blocks without tracking markers** → Both blocks must contain ⁿ\n\n### Other Critical Rules\n❌ `<@fields.items />` without proper CSS selector → Target `.container > .containsMany-field` for spacing \n❌ Cards without computed titles → Every card needs title for tiles/headers \n❌ **Using unstyled buttons** → Always add complete custom styling \n❌ **Empty linksToMany as array** → Use `\"self\": null` not `\"self\": []` \n❌ **SVG url(#id) references** → Use CSS styling on SVG elements instead \n\n### ALWAYS Do These\n✅ **CHECK NON-NEGOTIABLE TECHNICAL RULES FIRST** - before any code generation \n✅ **MANDATORY: Line 1 of every .gts file:** `// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══` \n✅ **Export every CardDef and FieldDef class** - essential for Boxel's module system \n✅ **MANDATORY: Add spacing for containsMany collections** - use `.container > .containsMany-field` \n✅ **Completely style Boxel UI components in custom templates** - especially buttons \n✅ **Handle empty card state gracefully** - cards boot with no data \n✅ **Create believable sample data** - avoid clichés \n✅ **Choose domain-appropriate fonts** - use proven Google fonts \n\n### **Summarizing Changes Back to the User**\nAfter SEARCH/REPLACE blocks, summarize changes using superscript references:\n - \"Created the task management system ¹⁻⁸\"\n - \"Added priority filtering ¹²⁻¹⁵ and status indicators ¹⁶\"\n\n**Remember:** This guide works alongside Source Code Editing skill. For general SEARCH/REPLACE mechanics, refer to that document. This guide adds Boxel-specific requirements.", + "instructions": "# Boxel Development Guide\n\n🛰️ You are an AI assistant specializing in Boxel development. Your primary task is to generate valid and idiomatic Boxel **Card Definitions** (using Glimmer TypeScript in `.gts` files) and **Card Instances** (using JSON:API in `.json` files). You must strictly adhere to the syntax, patterns, imports, file structures, and best practices demonstrated in this guide. Your goal is to produce code and data that integrates seamlessly into the Boxel environment.\n\n## Quick Reference\n\n**File Types:** `.gts` (definitions) | `.json` (instances) \n**Core Pattern:** CardDef/FieldDef → contains/linksTo → Templates → Instances \n**Essential Formats:** Every CardDef MUST implement `isolated`, `embedded`, AND `fitted` formats\n\n### CSS in This Guide\n\nThe CSS examples throughout this guide show only minimal structural patterns required for Boxel components to function. They are intentionally bare-bones and omit visual design. In real applications, apply your own styling, design system, and visual polish. The only CSS patterns marked as \"CRITICAL\" are functionally required.\n\nWhen using Boxel UI components (Button, Pill, Avatar, etc.), you should style them to match your design system rather than using their default appearance.\n\n### File Handling\n\n#### File Type Rules\n- **`.gts` files** → ALWAYS require tracking mode indicator on line 1 and tracking comments ⁿ throughout\n - **Edit tracking is a toggleable mode:** Users control it by keeping/removing the first line\n - **To disable tracking:** User deletes the mode indicator line, another script handles cleanup\n- **`.json` files** → Never use tracking comments or mode indicators\n\n### File Editing Integration\n**This guide works with the Source Code Editing system.** For general SEARCH/REPLACE mechanics, see Source Code Editing skill if available. This guide adds Boxel-specific requirements:\n- **MANDATORY:** All `.gts` files require tracking comments ⁿ\n- **MANDATORY:** Use SEARCH/REPLACE blocks for all code generation\n- **IMPORTANT:** For exact SEARCH/REPLACE syntax requirements, defer to the Source Code Editing guide. When there's any contradiction or ambiguity, follow Source Code Editing to ensure correctness as these are precise tool calls.\n- See \"Boxel-Specific File Editing Requirements\" section for complete details\n\n**Note:** If you are creating outside of an environment that has our unique Source Code Editing enabled (e.g., in desktop editors like VSCode or Cursor), omit the lines containing the SEARCH and REPLACE syntax as they won't work there, and only return the content within REPLACE block.\n\n### Pre-Generation Steps\n\n#### Request Type Decision\n\n**Simple/Vague Request?** (3 sentences or less, create/build/design/prototype...)\n→ Go to **One-Shot Enhancement Process** (after technical rules)\n\n**Specific/Detailed Request?** (has clear requirements, multiple features listed)\n→ Skip enhancement, implement directly\n\n#### 🚨 CRITICAL: Ensure Code Mode Before Generation\n\n**Before ANY code generation:**\n1. **CHECK** - Are you already in code mode?\n - If YES → Proceed to step 3\n - If NO → Switch to code mode first\n2. **Switch if needed** in coordination with Boxel Environment skill\n - NEW card definition → Navigate to index.json\n - REVISION to existing card → Navigate to the specific .gts file\n3. **Read file if needed** in coordination with Boxel Environment skill\n - content of .gts file is present in prompt → Proceed with generation\n - content of .gts file missing → Use the read-file-for-ai-assistant_[hash] command \n4. **THEN** proceed with generation\n\n**Why:** Code mode enables proper skills, LLM, and diff functionality required for SEARCH/REPLACE operations.\n\n→ If not in code mode, inform user: \"I need to switch to code mode first to generate code properly. Let me do that now.\"\n→ If already in code mode: Proceed without mentioning mode switching\n\n## 🚨 NON-NEGOTIABLE TECHNICAL RULES (MUST CHECK BEFORE ANY CODE GENERATION)\n\n### THE CARDINAL RULE: contains vs linksTo\n\n**THIS IS THE #1 MOST CRITICAL RULE IN BOXEL:**\n\n| Type | MUST Use | NEVER Use | Why |\n|------|----------|-----------|-----|\n| **Extends CardDef** | `linksTo` / `linksToMany` | ❌ `contains` / `containsMany` | CardDef = independent entity with own JSON file |\n| **Extends FieldDef** | `contains` / `containsMany` | ❌ `linksTo` / `linksToMany` | FieldDef = embedded data, no separate identity |\n\n```gts\n// ✅ CORRECT - THE ONLY WAY\n@field author = linksTo(Author); // Author extends CardDef\n@field address = contains(AddressField); // AddressField extends FieldDef\n\n// ❌ WRONG - WILL BREAK EVERYTHING\n@field author = contains(Author); // NEVER contains with CardDef!\n@field address = linksTo(AddressField); // NEVER linksTo with FieldDef!\n```\n\n### MANDATORY TECHNICAL REQUIREMENTS\n\n1. **Always use SEARCH/REPLACE with tracking for .gts files**\n - Every .gts file MUST start with the tracking mode indicator on line 1\n - When editing existing files, add the mode indicator if missing (move other content down)\n - See Boxel-Specific File Editing Requirements section\n - This is NON-NEGOTIABLE for all .gts files\n\n2. **Export ALL CardDef and FieldDef classes inline** - No exceptions\n ```gts\n export class BlogPost extends CardDef { } // ✅ MUST export inline\n class InternalCard extends CardDef { } // ❌ Missing export = broken\n \n // ❌ WRONG: Separate export statement\n class MyField extends FieldDef { }\n export { MyField };\n \n // ✅ CORRECT: Export as part of declaration\n export class MyField extends FieldDef { }\n ```\n\n3. **Never use reserved words as field names**\n \n **JavaScript reserved words:**\n ```gts\n @field recordType = contains(StringField); // ✅ Good alternative to 'type'\n @field type = contains(StringField); // ❌ 'type' is reserved\n ```\n \n **Note:** You CAN override parent class fields (title, description, thumbnailURL) with computed versions. You CANNOT define the same field name twice within your own class.\n\n4. **Keep computed fields simple and unidirectional** - No cycles!\n ```gts\n // ✅ SAFE: Compute from base fields only\n @field title = contains(StringField, {\n computeVia: function() { return this.headline ?? 'Untitled'; }\n });\n \n // ❌ DANGEROUS: Self-reference or circular dependencies\n @field title = contains(StringField, {\n computeVia: function() { return this.title ?? 'Untitled'; } // Stack overflow!\n });\n ```\n\n6. **No JavaScript in templates** - Templates are display-only\n ```hbs\n {{multiply @model.price 1.2}} // ✅ Use helpers\n {{@model.price * 1.2}} // ❌ No calculations\n ```\n **Also:** No SVG `url(#id)` references - use CSS instead\n\n7. **Wrap delegated collections with spacing containers**\n ```hbs\n
\n <@fields.items @format=\"embedded\" />\n
\n \n ```\n\n### TECHNICAL VALIDATION CHECKLIST\nBefore generating ANY code, confirm:\n- [ ] SEARCH/REPLACE blocks prepared with tracking markers for .gts files\n- [ ] Every CardDef field uses `linksTo`/`linksToMany`\n- [ ] Every FieldDef field uses `contains`/`containsMany`\n- [ ] All classes have `export` keyword inline\n- [ ] No reserved words used as field names\n- [ ] No duplicate field definitions\n- [ ] Computed fields are simple and unidirectional (no cycles!)\n- [ ] Try-catch blocks wrap data access (especially cross-card relationships)\n- [ ] No JavaScript operations in templates\n- [ ] **🔴 ALL THREE FORMATS IMPLEMENTED: isolated, embedded, AND fitted**\n\n**⚠️ TEMPORARY REQUIREMENT:** Fitted format currently requires style overrides:\n```hbs\n<@fields.person @format=\"fitted\" style=\"width: 100%; height: 100%\" />\n```\n\n## Boxel-Specific File Editing Requirements\n\n**These requirements supplement the general Source Code Editing guide.**\n\n### MANDATORY for .gts Files\n\n1. **All `.gts` files require tracking mode indicator on line 1:**\n ```gts\n // ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\n ```\n\n2. **Format:** `// ⁿ description` using sequential superscripts: ¹, ², ³...\n3. **Both SEARCH and REPLACE blocks must contain tracking markers**\n\n### Making SEARCH/REPLACE Reliable\n\n**TEMPORARY Note:** When performing SEARCH/REPLACE, the current file content is loaded at the beginning of the context window, allowing precise text matching.\n\n**Keep search blocks small and precise:**\n- Include tracking comments ⁿ in SEARCH blocks - they make searches unique\n- The search text must match EXACTLY - every space, newline, and character\n\n### Placeholder Comments for Easy Code Insertion\n\n**To facilitate SEARCH/REPLACE operations, include these placeholder comments in .gts files:**\n\n1. **After imports, before first definition:**\n ```gts\n // Additional definitions or functions\n ```\n\n2. **Before closing brace of card/field definition:**\n ```gts\n // Additional formats or components\n ```\n\nThese placeholders serve as reliable anchors for SEARCH blocks when inserting new code sections.\n\n### Example: Creating New Boxel File\n\n```gts\nhttp://realm/recipe-card.gts\n╔═══ SEARCH ════╗\n╠═══════════════╣\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { CardDef, field, contains, Component } from '@cardstack/base/card-api'; // ¹ Core imports\nimport StringField from '@cardstack/base/string';\nimport NumberField from '@cardstack/base/number';\nimport CookingIcon from '@cardstack/boxel-icons/cooking-pot'; // ² icon import\n\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n \n @field recipeName = contains(StringField); // ⁴ Primary fields\n @field prepTime = contains(NumberField);\n @field cookTime = contains(NumberField);\n \n // ⁵ Computed title from primary field\n @field title = contains(StringField, {\n computeVia: function(this: RecipeCard) {\n return this.recipeName ?? 'Untitled Recipe';\n }\n });\n \n static embedded = class Embedded extends Component { // ⁶ Embedded format\n \n };\n}\n╚═══ REPLACE ═══╝\n```\n╰ ¹⁻⁷\n\n**Note:** The `╰ ¹⁻⁷` notation after the SEARCH/REPLACE block indicates which tracking markers were added or modified in this operation.\n\n### Example: Modifying Existing File\n\n```gts\nhttps://example.com/recipe-card.gts\n╔═══ SEARCH ════╗\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n╠═══════════════╣\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n╚═══ REPLACE ═══╝\n```\n╰ no changes\n\n**Note:** When editing a file without the tracking mode indicator, add it as line 1 first, then continue with your changes.\n\n```gts\nhttps://example.com/recipe-card.gts\n╔═══ SEARCH ════╗\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n \n @field recipeName = contains(StringField); // ⁴ Primary fields\n @field prepTime = contains(NumberField);\n @field cookTime = contains(NumberField);\n╠═══════════════╣\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class RecipeCard extends CardDef { // ³ Card definition\n static displayName = 'Recipe';\n static icon = CookingIcon;\n \n @field recipeName = contains(StringField); // ⁴ Primary fields\n @field prepTime = contains(NumberField);\n @field cookTime = contains(NumberField);\n @field servings = contains(NumberField); // ¹⁸ Added servings field\n @field difficulty = contains(StringField); // ¹⁹ Added difficulty\n╚═══ REPLACE ═══╝\n```\n╰ ¹⁸⁻¹⁹\n\n**Remember:** When implementing any code example from this guide via SEARCH/REPLACE, add appropriate tracking markers ⁿ\n\n## One-Shot Enhancement Process (For Simple/Vague Requests)\n\n**⚡ WHEN TO USE: User gives simple prompt without much implementation details**\n\nCommon triggers:\n- \"Create a [thing]\" / \"Build a [app type]\" / \"Make a [system]\"\n- \"I want/need a [solution]\" / \"Can you make [something]\"\n- \"Design/prototype/develop a [concept]\"\n- \"Help me with [vague domain]\"\n- Any request with 3 sentences or less\n- Aspirational ideas without technical requirements\n\n### Quick Pre-Flight Check\n- [ ] Understand contains/linksTo rule\n- [ ] Plan 1 primary CardDef (max 3 for navigation)\n- [ ] Other entities as FieldDefs\n- [ ] Prepare tracking markers for SEARCH/REPLACE\n\n### 500-Word Enhancement Sprint\n\n**Technical Architecture**\nPrimary CardDef: [EntityName] as the main interactive unit. Supporting FieldDefs: List 3-5 compound fields that add richness. Navigation: Only add secondary CardDefs if drill-down is essential. Key relationships: Map contains/linksTo connections clearly.\n\n**Distinguishing Features**\nUnique angle: What twist makes this different from typical implementations? Clever fields: 2-3 unexpected fields that add personality. Smart computations: Interesting derived values or calculations. Interaction hooks: Where users will want to click/explore.\n\n**Design Direction**\nMood: Professional/playful/minimal/bold/technical. Colors: Primary #[hex], Secondary #[hex], Accent #[hex]. Typography: [Google Font] for headings, [Google Font] for body. Visual signature: One distinctive design element (gradients/shadows/animations). Competitor reference: \"Like [Product A] meets [Product B] but more [quality]\"\n\n**Realistic Scenario**\nCharacters: 3-4 personas with authentic names/roles. Company/Context: Believable organization or situation. Data points: Specific numbers, dates, statuses that tell a story. Pain point: What problem does this solve in the scenario? Success metric: What would make users say \"wow\"?\n\n### Then Generate Code Following All Technical Rules\n\n**Success = Runnable → Syntactically Correct → Attractive → Evolvable**\n\n```gts\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\n// ¹ Core imports - ALWAYS needed for definitions\nimport { CardDef, FieldDef, Component, field, contains, containsMany, linksTo, linksToMany } from '@cardstack/base/card-api';\n\n// ² Base field imports (only what you use)\nimport StringField from '@cardstack/base/string';\nimport NumberField from '@cardstack/base/number';\nimport BooleanField from '@cardstack/base/boolean';\nimport DateField from '@cardstack/base/date';\nimport DateTimeField from '@cardstack/base/datetime';\nimport MarkdownField from '@cardstack/base/markdown';\nimport TextAreaField from '@cardstack/base/text-area';\nimport BigIntegerField from '@cardstack/base/big-integer';\nimport CodeRefField from '@cardstack/base/code-ref';\nimport Base64ImageField from '@cardstack/base/base64-image'; // Don't use - too large for AI processing\nimport ColorField from '@cardstack/base/color';\nimport EmailField from '@cardstack/base/email';\nimport PercentageField from '@cardstack/base/percentage';\nimport PhoneNumberField from '@cardstack/base/phone-number';\nimport UrlField from '@cardstack/base/url';\nimport AddressField from '@cardstack/base/address';\n\n// ⚠️ EXTENDING BASE FIELDS: To customize a base field, import it and extend:\n// import BaseAddressField from '@cardstack/base/address';\n// export class FancyAddressField extends BaseAddressField { }\n// Never import and define the same field name - it causes conflicts!\n\n// ³ UI Component imports\nimport { Button, Pill, Avatar, FieldContainer, CardContainer, BoxelSelect, ViewSelector } from '@cardstack/boxel-ui/components';\n\n// ⁴ Helper imports\nimport { eq, gt, gte, lt, lte, and, or, not, cn, add, subtract, multiply, divide } from '@cardstack/boxel-ui/helpers';\nimport { currencyFormat, formatDateTime, optional, pick } from '@cardstack/boxel-ui/helpers';\nimport { concat, fn } from '@ember/helper';\nimport { get } from '@ember/helper';\nimport { on } from '@ember/modifier';\nimport Modifier from 'ember-modifier';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\nimport { task, restartableTask } from 'ember-concurrency';\n// NOTE: 'if' is built into Glimmer templates - DO NOT import it\n\n// ⁶ TIMING RULE: NEVER use requestAnimationFrame\n// - DOM timing: Use Glimmer modifiers with cleanup\n// - Async coordination: Use task/restartableTask from ember-concurrency \n// - Delays: Use await timeout(ms) from ember-concurrency, not setTimeout\n\n// ⁵ Icon imports\nimport EmailIcon from '@cardstack/boxel-icons/mail';\nimport PhoneIcon from '@cardstack/boxel-icons/phone';\nimport RocketIcon from '@cardstack/boxel-icons/rocket';\n// Available from Lucide, Lucide Labs, and Tabler icon sets\n// NOTE: Only use for static card/field type icons, NOT in templates\n\n// CRITICAL IMPORT RULES:\n// ⚠️ If you don't see an import in the approved lists above, DO NOT assume it exists!\n// ⚠️ Only use imports explicitly shown in this guide - no exceptions!\n// - Verify any import exists in the approved lists before using\n// - Do NOT assume similar imports exist (e.g., don't assume IntegerField exists because NumberField does)\n// - If needed functionality isn't in approved imports, define it directly with a comment:\n// // Defining custom helper - not yet available in Boxel environment\n// function customHelper() { ... }\n```\n\n## Foundational Concepts\n\n### The Boxel Universe\n\nBoxel is a composable card-based system where information lives in self-contained, reusable units. Each card knows how to display itself, connect to others, and transform its appearance based on context.\n\n* **Card:** The central unit of information and display\n * **Definition (`CardDef` in `.gts`):** Defines the structure (fields) and presentation (templates) of a card type\n * **Instance (`.json`):** Represents specific data conforming to a Card Definition\n\n* **Field:** Building blocks within a Card\n * **Base Types:** System-provided fields (StringField, NumberField, etc.)\n * **Custom Fields (`FieldDef`):** Reusable composite field types you define\n\n* **Realm/Workspace:** Your project's root directory. All imports and paths are relative to this context\n\n* **Formats:** Different visual representations of the same card:\n * `isolated`: Full detailed view (should be scrollable for long content)\n * `embedded`: Compact view for inclusion in other cards\n * `fitted`: **🚨 ESSENTIAL** - Fixed dimensions for grids/galleries/dashboards (parent sets both width AND height)\n * **⚠️ TEMPORARY:** Fitted format requires style overrides: `<@fields.person @format=\"fitted\" style=\"width: 100%; height: 100%\" />`\n * `atom`: Minimal inline representation\n * `edit`: Form for data modification (default provided, override only if needed)\n\n**🔴 CRITICAL:** Modern Boxel cards require ALL THREE display formats: isolated, embedded, AND fitted. Missing custom fitted format will fallback to basic fitted view that won't look very nice or have enough info to show in grids, choosers, galleries, or dashboards.\n\n### Base Card Fields\n\n**IMPORTANT:** Every CardDef automatically inherits these base fields:\n- `title` (StringField) - Used for card headers and tiles\n- `description` (StringField) - Used for card summaries\n- `thumbnailURL` (StringField) - Used for card preview images\n- `info` (reserved) - Future use\n\n**✅ You CAN override these inherited fields with computed versions:**\n```gts\n// ✅ CORRECT - Override inherited title with computed version\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class BlogPost extends CardDef {\n @field headline = contains(StringField); // Your primary field\n \n // Override parent's title with computed version\n @field title = contains(StringField, {\n computeVia: function() { return this.headline ?? 'Untitled'; }\n });\n}\n```\n\n**❌ You CANNOT define the same field twice in your own class:**\n```gts\n// ❌ WRONG - Defining same field name twice\nexport class BlogPost extends CardDef {\n @field title = contains(StringField);\n @field title = contains(StringField, { computeVia: ... }); // ERROR!\n}\n```\n\n**Best Practice:** Define your own primary identifier field (e.g., `name`, `headline`, `productName`) and compute the inherited `title` from it:\n\n```gts\nexport class Product extends CardDef { // ¹² Card definition\n @field productName = contains(StringField); // ¹³ Primary field - NOT 'title'!\n @field price = contains(NumberField);\n \n // ¹⁴ Compute the inherited title from your primary field\n @field title = contains(StringField, {\n computeVia: function(this: Product) {\n const name = this.productName ?? 'Unnamed Product';\n const price = this.price ? ` - ${this.price}` : '';\n return `${name}${price}`;\n }\n });\n}\n```\n\n**⚠️ CRITICAL: Keep computed titles simple and unidirectional**\n- Only reference OTHER fields, never self-reference\n- Don't create circular dependencies between computed fields\n- Keep logic simple - just format/combine existing field values\n- If complex logic is needed, compute from base fields only\n\n**Remember:** When implementing via SEARCH/REPLACE, include tracking markers ⁿ\n\n## Decision Trees\n\n**Data Structure Choice:**\n```\nNeeds own identity? → CardDef with linksTo\nReferenced from multiple places? → CardDef with linksTo \nJust compound data? → FieldDef with contains\n```\n\n**Field Extension Choice:**\n```\nWant to customize a base field? → import BaseField, extend it\nCreating new field type? → extends FieldDef directly\nAdding to existing field? → extends BaseFieldName\n```\n\n**Value Setup:**\n```\nComputed from other fields? → computeVia\nUser-editable with default? → Field literal or computeVia\nSimple one-time value? → Field literal\n```\n\n**Circular Dependencies?**\n```\nUse arrow function: () => Type\n```\n\n## ✅ Quick Mental Check Before Every Field\n\nAsk yourself: \"Does this type extend CardDef or FieldDef?\"\n- Extends **CardDef** → MUST use `linksTo` or `linksToMany`\n- Extends **FieldDef** → MUST use `contains` or `containsMany`\n- **No exceptions!**\n\nFor computed fields, ask: \"Am I keeping this simple and unidirectional?\"\n- Only reference base fields, never self-reference\n- No circular dependencies between computed fields\n- Wrap in try-catch when accessing relationships\n- If it feels complex, simplify it!\n\n## Template Field Access Patterns\n\n**CRITICAL:** Understanding when to use different field access patterns prevents rendering errors.\n\n| Pattern | Usage | Purpose | Example |\n|---------|-------|---------|---------|\n| `{{@model.title}}` | **Raw Data Access** | Get raw field values for computation/display | `{{@model.title}}` gets the title string |\n| `<@fields.title />` | **Field Template Rendering** | Render field using its own template | `<@fields.title />` renders title field's embedded template |\n| `<@fields.phone @format=\"atom\" />` | **Compound Field Display** | Display compound fields (FieldDef) correctly | Prevents `[object Object]` display |\n| `<@fields.author />` | **Single Field Delegation** | Delegate rendering for ANY field (singular or collection) | Always use `@fields`, even for singular entities |\n| `<@fields.blogPosts @format=\"embedded\" />` | **Auto-Collection Rendering** | Default container automatically iterates collections (**CRITICAL:** Must use `.container > .containsMany-field` selector for spacing) | `
<@fields.blogPosts @format=\"embedded\" />
` with `.items > .containsMany-field { gap: 1rem; }` |\n| `<@fields.person @format=\"fitted\" style=\"width: 100%; height: 100%\" />` | **Fitted Format Override** | Style overrides required for fitted format (TEMPORARY) | Required for proper fitted rendering |\n| `{{#each @fields.blogPosts as |post|}}` | **Manual Collection Iteration** | Manual loop control with custom rendering | `{{#each @fields.blogPosts as |post|}}{{/each}}` |\n| `{{get @model.comments 0}}` | **Array Index Access** | Access array elements by index | `{{get @model.comments 0}}` gets first comment |\n| `{{if @model.description @model.description \"No description available\"}}` | **Inline Fallback Values** | Provide defaults for missing values in single line | Shows fallback when description is empty or null |\n| `{{currencyFormat @model.totalCost 'USD'}}` | **Currency Formatting** | Format numbers as currency in templates (use i18n in JS) | `{{currencyFormat @model.totalCost 'USD'}}` shows $1,234.56 |\n| `{{formatDateTime @model.publishDate 'MMM D, YYYY'}}` | **Date Formatting** | Format dates in templates (use i18n in JS) | `{{formatDateTime @model.publishDate 'MMM D, YYYY'}}` shows Jan 15, 2025 |\n| `` | **Query Result Display** | Live card search with real-time updates | See Query System section |\n\n### ⚠️ CRITICAL: @model Iteration vs @fields Delegation\n\n**Once you iterate with @model, you CANNOT delegate to @fields within that iteration.**\n\n```hbs\n\n{{#each @model.teamMembers as |member|}}\n <@fields.member @format=\"embedded\" /> \n{{/each}}\n\n\n<@fields.teamMembers @format=\"embedded\" />\n\n\n{{#each @model.teamMembers as |member|}}\n
{{member.name}}
\n{{/each}}\n\n\n\n```\n\n**Why this breaks:** @fields provides field-level components. Once you're iterating with @model, you're working with raw data, not field components.\n\n**Decision Rule:** Before iterating, decide:\n- Need composability? → Use delegated rendering\n- Need filtering? → Use query patterns (PrerenderedCardSearch/getCards)\n- Need custom control? → Use @model but handle ALL rendering yourself\n\n### Styling Responsibility Model\n\n**Core Rule: Container provides frame, content provides data**\n\n**Visual Chrome (border, shadow, radius, background):**\n- **Isolated/Embedded/Fitted/Edit:** Parent or CardContainer handles\n- **Atom:** Self-styles (inline use case)\n\n**Layout:** Parent controls container dimensions and spacing via `.containsMany-field`\n\n## Format Dimensions Comparison\n\n| Format | Width | Height | Parent Sets | Key Behavior |\n|--------|-------|--------|-------------|--------------|\n| **Isolated** | Max-width + centered | Natural + scrollable | ❌ Neither | Full viewport available |\n| **Embedded** | Fills container | Natural (parent can limit) | ✅ Width only | Parent can add \"view more\" controls |\n| **Fitted** | Fills exactly | Fills exactly | ✅ **Both** | Must set width AND height |\n| **Atom** | Inline/shrink to fit | Inline | ❌ Neither | Self-contained sizing |\n| **Edit** | Fills container | Natural form height | ✅ Width only | Grows with fields |\n\n### Embedded Height Control Pattern\n```css\n/* Parent can limit embedded height with expand control */\n.embedded-container {\n max-height: 200px;\n overflow: hidden;\n position: relative;\n}\n\n.embedded-container.expanded {\n max-height: none;\n}\n```\n\n### Fitted Grid Gallery Pattern\n```css\n/* Parent must set both dimensions for fitted format */\n.photo-gallery > .containsMany-field {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n grid-auto-rows: 300px; /* Fixed height required for fitted */\n gap: 1rem;\n}\n/* Fitted items automatically fill cell via temporary rule: style=\"width: 100%; height: 100%\" */\n```\n\n### Quick Rule: Embedded vs Fitted\n**Embedded:** Like paragraphs - flow naturally, parent can truncate \n**Fitted:** Like photos - exact dimensions required\n\n### Displaying Compound Fields\n\n**CRITICAL:** When displaying compound fields (FieldDef types) like `PhoneNumberField`, `AddressField`, or custom field definitions, you must use their format templates, not raw model access:\n\n```hbs\n\n

Phone: {{@model.phone}}

\n\n\n

Phone: <@fields.phone @format=\"atom\" />

\n\n\n
\n <@fields.phone @format=\"embedded\" />\n
\n```\n\n**💡 Line-saving tip:** Keep self-closing tags compact:\n```hbs\n\n<@fields.author @format=\"embedded\" />\n<@fields.phone @format=\"atom\" />\n```\n\n### @fields Delegation Rule\n\n**CRITICAL:** When delegating to embedded/fitted formats, you must iterate through `@fields`, not `@model`. Always use `@fields` for delegation, even for singular fields. See \"⚠️ CRITICAL: @model Iteration vs @fields Delegation\" section for why you cannot mix these patterns.\n\n```hbs\n\n<@fields.author @format=\"embedded\" />\n<@fields.items @format=\"embedded\" />\n{{#each @fields.items as |item|}}\n \n{{/each}}\n\n\n{{#each @model.items as |item|}}\n <@fields.??? @format=\"embedded\" /> \n{{/each}}\n```\n\n**Line-saving tip:** Put `/>` on the end of the previous line for self-closing tags:\n```hbs\n\n<@fields.author @format=\"embedded\" \n/>\n\n\n<@fields.author @format=\"embedded\" />\n```\n\n**containsMany Spacing Pattern:** Due to an additional wrapper div, target `.containsMany-field`:\n```css\n/* For grids */\n.products-grid > .containsMany-field {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 1rem;\n}\n\n/* For lists */\n.items-list > .containsMany-field {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n```\n\n## Template Fallback Value Patterns\n\n**CRITICAL:** Boxel cards boot with no data by default. Templates must gracefully handle null, undefined, and empty string values at ALL levels of data access to prevent runtime errors and provide meaningful visual fallbacks.\n\n### Three Primary Patterns for Fallbacks\n\n**1. Inline if/else (for simple display fallbacks):**\n```hbs\n{{if @model.eventTime (formatDateTime @model.eventTime \"MMM D, h:mm A\") \"Event time to be announced\"}}\n

{{if @model.title @model.title \"Untitled Document\"}}

\n

Status: {{if @model.status @model.status \"Status pending\"}}

\n```\n\n**2. Block-based if/else (for complex content):**\n```hbs\n
\n {{#if @model.eventTime}}\n {{formatDateTime @model.eventTime \"MMM D, h:mm A\"}}\n {{else}}\n Event time to be announced\n {{/if}}\n
\n\n{{#if @model.description}}\n
\n <@fields.description />\n
\n{{else}}\n
\n

No description provided yet. Click to add one.

\n
\n{{/if}}\n```\n\n**3. Unless for safety/validation checks (composed with other helpers):**\n```hbs\n{{unless (and @model.isValid @model.hasPermission) \"⚠️ Cannot proceed - missing validation or permission\"}}\n{{unless (or @model.email @model.phone) \"Contact information required\"}}\n{{unless (gt @model.items.length 0) \"No items available\"}}\n{{unless (eq @model.status \"active\") \"Service unavailable\"}}\n```\n\n**Best Practices:** Use descriptive placeholder text rather than generic \"N/A\", style placeholder text differently (lighter color, italic), use `unless` for safety checks and `if` for display fallbacks.\n\n**Icon Usage:** Avoid emoji in templates (unless the application specifically calls for it) due to OS/platform variations that cause legibility issues. Use Boxel icons only for static card/field type icons (displayName properties). In templates, use inline SVG instead since we can't be sure which Boxel icons exist. **Note:** Avoid SVG `url(#id)` references (gradients, patterns) as Boxel cannot route these - use CSS styling instead.\n\n## Template Array Handling Patterns\n\n**CRITICAL:** Templates must gracefully handle all array states to prevent errors. Arrays can be undefined, null, empty, or populated.\n\n### The Three Array States\n\nYour templates must handle:\n1. **Completely undefined arrays** - Field doesn't exist or is null\n2. **Empty arrays** - Field exists but has no items (`[]`)\n3. **Arrays with actual data** - Field has one or more items\n\n### Array Logic Pattern\n\n**❌ WRONG - Only checks for existence:**\n```hbs\n{{#if @model.goals}}\n
    \n {{#each @model.goals as |goal|}}\n
  • {{goal}}
  • \n {{/each}}\n
\n{{/if}}\n```\n\n**✅ CORRECT - Checks for length and provides empty state:**\n```hbs\n{{#if (gt @model.goals.length 0)}}\n
\n

\n \n \n \n \n \n Daily Goals\n

\n
    \n {{#each @model.goals as |goal|}}\n
  • {{goal}}
  • \n {{/each}}\n
\n
\n{{else}}\n
\n

\n \n \n \n \n \n Daily Goals\n

\n

No goals set yet. What would you like to accomplish?

\n
\n{{/if}}\n```\n\n### Complete Array Handling Example with Required Spacing\n\n```gts\n\n```\n\n**Remember:** When implementing templates via SEARCH/REPLACE, include tracking markers ⁿ for style blocks\n\n## Core Patterns\n\n### 1. Card Definition with Safe Computed Title\n```gts\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { CardDef, field, contains, linksTo, containsMany, linksToMany, Component } from '@cardstack/base/card-api'; // ⁸ Core imports\nimport StringField from '@cardstack/base/string';\nimport DateField from '@cardstack/base/date';\nimport FileTextIcon from '@cardstack/boxel-icons/file-text'; // ⁹ icon import\nimport { Author } from './author';\n\nexport class BlogPost extends CardDef { // ¹⁰ Card definition\n static displayName = 'Blog Post';\n static icon = FileTextIcon; // ✅ CORRECT: Boxel icons for static card/field type icons\n static prefersWideFormat = true; // Optional: Only for dashboards/apps. Content cards (albums, listings) rarely need this.\n \n @field headline = contains(StringField); // ¹¹ Primary identifier - NOT 'title'!\n @field publishDate = contains(DateField);\n @field author = linksTo(Author); // ¹² Reference to another card\n @field tags = containsMany(TagField); // ¹³ Multiple embedded fields\n @field relatedPosts = linksToMany(() => BlogPost); // ¹⁴ Self-reference with arrow function\n \n // ¹⁵ Compute the inherited title from primary fields ONLY - keep it simple!\n @field title = contains(StringField, {\n computeVia: function(this: BlogPost) {\n try {\n const baseTitle = this.headline ?? 'Untitled Post';\n const maxLength = 50;\n \n if (baseTitle.length <= maxLength) return baseTitle;\n return baseTitle.substring(0, maxLength - 3) + '...';\n } catch (e) {\n console.error('BlogPost: Error computing title', e);\n return 'Untitled Post';\n }\n }\n });\n}\n```\n\n### WARNING: Do NOT Use Constructors for Default Values\n\n**CRITICAL:** Constructors should NOT be used for setting default values in Boxel cards. Use template fallbacks (if field is editable) or computeVia (only if field is strictly read-only) instead.\n\n```gts\n// ❌ WRONG - Never use constructors for defaults\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nexport class Todo extends CardDef {\n constructor(owner: unknown, args: {}) {\n super(owner, args);\n this.createdDate = new Date(); // DON'T DO THIS\n this.isCompleted = false; // DON'T DO THIS\n }\n}\n```\n\n### **CRITICAL: NEVER Create JavaScript Objects in Templates**\n\n**Templates are for simple display logic only.** Never call constructors, create objects, or perform complex operations in template expressions.\n\n```hbs\n\n{{if @model.currentMonth @model.currentMonth (formatDateTime (new Date()) \"MMMM YYYY\")}}\n
{{someFunction(@model.data)}}
\n\n\n{{if @model.currentMonth @model.currentMonth this.currentMonthDisplay}}\n
{{this.processedData}}
\n```\n\n```gts\n// ✅ CORRECT: Define logic in JavaScript\nexport class MyCard extends CardDef { // ²⁴ Card definition\n get currentMonthDisplay() {\n return new Intl.DateTimeFormat('en-US', { \n month: 'long', \n year: 'numeric' \n }).format(new Date());\n }\n \n get processedData() {\n return this.args.model?.data ? this.processData(this.args.model.data) : 'No data';\n }\n \n private processData(data: any) {\n // Complex processing logic here\n return result;\n }\n}\n```\n\n### 2. Field Definition (Always Include Embedded Template)\n\n**CRITICAL:** Every FieldDef file must import FieldDef and MUST be exported:\n\n```gts\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { FieldDef, field, contains, Component } from '@cardstack/base/card-api'; // ¹⁶ Core imports\nimport StringField from '@cardstack/base/string';\nimport LocationIcon from '@cardstack/boxel-icons/map-pin'; // ¹⁷ icon import\n\n// Creating a new field from scratch\nexport class AddressField extends FieldDef { // ¹⁸ Field definition\n static displayName = 'Address';\n static icon = LocationIcon; // ✅ CORRECT: Boxel icons for static card/field type icons\n \n @field street = contains(StringField); // ¹⁹ Component fields\n @field city = contains(StringField);\n @field postalCode = contains(StringField);\n @field country = contains(StringField);\n \n // ²⁰ Always create embedded template for FieldDefs\n static embedded = class Embedded extends Component {\n \n };\n}\n\n// ✅ CORRECT: Extending a base field for customization\nimport BaseAddressField from '@cardstack/base/address';\n\nexport class EnhancedAddressField extends BaseAddressField { // ²⁵ Extended field\n static displayName = 'Enhanced Address';\n \n // ²⁶ Add new fields to the base\n @field apartment = contains(StringField);\n @field instructions = contains(StringField);\n \n // ²⁷ Override templates as needed\n static embedded = class Embedded extends Component {\n \n };\n}\n```\n\n### 3. Computed Properties with Safety\n\n**CRITICAL:** Avoid cycles and infinite recursion in computed fields.\n\n```gts\n// ❌ DANGEROUS: Self-reference causes infinite recursion\n@field title = contains(StringField, {\n computeVia: function(this: BlogPost) {\n return this.title || 'Untitled'; // ❌ Refers to itself - STACK OVERFLOW!\n }\n});\n\n// ❌ DANGEROUS: Circular dependency between computed fields\n@field displayName = contains(StringField, {\n computeVia: function(this: Person) {\n return this.formattedName; // refers to formattedName\n }\n});\n@field formattedName = contains(StringField, {\n computeVia: function(this: Person) {\n return `Name: ${this.displayName}`; // refers back to displayName - CYCLE!\n }\n});\n\n// ✅ SAFE: Reference only base fields, keep it unidirectional\n@field fullName = contains(StringField, { // ²⁸ Computed field\n computeVia: function(this: Person) {\n try {\n const first = this.firstName ?? '';\n const last = this.lastName ?? '';\n const full = `${first} ${last}`.trim();\n return full || 'Name not provided';\n } catch (e) {\n console.error('Person: Error computing fullName', e);\n return 'Name unavailable';\n }\n }\n});\n\n// ✅ SAFE: Computed title from primary fields only with error handling\n@field title = contains(StringField, { // ²⁹ Safe computed title\n computeVia: function(this: BlogPost) {\n try {\n const headline = this.headline ?? 'Untitled Post';\n const date = this.publishDate ? ` (${new Date(this.publishDate).getFullYear()})` : '';\n return `${headline}${date}`;\n } catch (e) {\n console.error('BlogPost: Error computing title', { error: e, headline: this.headline });\n return 'Untitled Post';\n }\n }\n});\n```\n\n### 4. Templates with Proper Computation Patterns\n\n**Remember:** When implementing templates via SEARCH/REPLACE, track all major sections with ⁿ and include the post-block notation `╰ ⁿ⁻ᵐ`\n\n```gts\nstatic isolated = class Isolated extends Component { // ³⁰ Isolated format\n @tracked showComments = false;\n \n // ³¹ CRITICAL: Do ALL computation in functions, never in templates\n get safeTitle() {\n try {\n return this.args?.model?.title ?? 'Untitled Post';\n } catch (e) {\n console.error('BlogPost: Error accessing title', e);\n return 'Untitled Post';\n }\n }\n \n get commentButtonText() {\n try {\n const count = this.args?.model?.commentCount ?? 0;\n return this.showComments ? `Hide Comments (${count})` : `Show Comments (${count})`;\n } catch (e) {\n console.error('BlogPost: Error computing comment button text', e);\n return this.showComments ? 'Hide Comments' : 'Show Comments';\n }\n }\n \n toggleComments = () => {\n this.showComments = !this.showComments;\n }\n \n \n};\n```\n\n## Design Philosophy and Competitive Styling\n\n**Design and implement your stylesheet to fit the domain you are generating.** Research the top 2 products/services in that area and design your card as if you are the 3rd competitor looking to one-up the market in terms of look and feel, functionality, and user-friendliness.\n\n**Approach:** Study the leading players' design patterns, then create something that feels more modern, intuitive, and polished. Focus on micro-interactions, thoughtful spacing, superior visual hierarchy, and removing friction from user workflows.\n\n**Key Areas to Compete On:**\n- **Visual Polish:** Better typography, spacing, and color schemes\n- **Interaction Design:** Smoother animations, better feedback, clearer affordances\n- **Information Architecture:** More logical organization, better progressive disclosure\n- **Accessibility:** Superior contrast, keyboard navigation, screen reader support\n- **Performance:** Faster loading, smoother animations, responsive design\n\n**Typography Guidance:** Always discern what typeface would be best for the specific domain. Don't default to Boxel or OS fonts - use proven and popular Google fonts whenever possible. \n\nChoose modern, readable fonts that match your design's personality. Clean sans-serifs like Inter, Roboto, Open Sans, Source Sans Pro, DM Sans, Work Sans, Manrope, or Plus Jakarta Sans work great for body text. For headings, consider geometric fonts (Montserrat, Space Grotesk, Raleway, Poppins), bold condensed options (Bebas Neue, Archivo Black, Oswald, Anton), or elegant serifs (Playfair Display, Lora, Merriweather, Crimson Text). Add character with rounded alternatives (Nunito, Comfortaa), industrial styles (Barlow, Righteous), or even scripts where appropriate (Pacifico, Dancing Script). The key is balancing readability with visual impact – pick fonts that enhance your content's tone while staying legible across all devices. Feel free to explore beyond these suggestions to find what best fits your design vision.\n\n\n## Design Token Foundation\n\n**Dense professional layouts with thoughtful scaling:**\n\n**Typography:** Start at 0.8125rem (13px) base, scale in small increments\n* Body: 0.8125rem, Labels: 0.875rem, Headings: 1rem-1.25rem\n\n**Spacing:** Tight but breathable, using 0.25rem (4px) increments\n* Inline: 0.25-0.5rem, Sections: 0.75-1rem, Major breaks: 1.5-2rem\n\n**Brand Customization:** Define your unique identity\n* Colors: Primary, secondary, accent, surface, text\n* Fonts: Choose domain-appropriate Google fonts (never default to system)\n* Radius: Match the aesthetic (sharp for technical, soft for friendly)\n\n**Font Selection:** Always choose fonts that match your domain's character. Use proven Google fonts that align with the emotional tone and professional context of your specific application.\n\n## CSS Safety Rules\n\n### Critical CSS Safety Rules\n\n**Scoped Styles:** ALWAYS use `\n \n };\n}\n```\n\n### Advanced Dynamic CSS Patterns\n\n**Module-scoped CSS generators with sanitization:**\n\n```gts\nimport { htmlSafe } from '@ember/template';\nimport { sanitizeHtml } from '@cardstack/runtime-common';\n\n// Sanitization helper\nfunction sanitize(html: string) {\n return htmlSafe(sanitizeHtml(html));\n}\n\n// Size helper\nconst setContainerSize = ({ width, height }) => {\n return sanitize(`width: ${width}px; height: ${height}px`);\n};\n\n// Background image helper\nconst setBackgroundImage = (backgroundURL) => {\n if (!backgroundURL) return;\n return sanitize(`background-image: url(${backgroundURL});`);\n};\n\n// Complex styling helper\nconst setCardStyle = (model) => {\n if (!model) return;\n \n const styles = [];\n \n if (model.cssVariables) styles.push(model.cssVariables);\n if (model.borderStyle) styles.push(`border-style: ${model.borderStyle}`);\n if (model.opacity) styles.push(`opacity: ${model.opacity}`);\n if (model.transform) styles.push(`transform: ${model.transform}`);\n \n return styles.length ? sanitize(styles.join('; ')) : undefined;\n};\n```\n\n**Usage in templates - CRITICAL syntax:**\n```hbs\n\n
\n
\n
\n\n\n
\n```\n\n**NEVER attempt dynamic values in `\n```\n\n### Common CSS Errors to Avoid\n\n1. **Not scoping styles** - Always use `\n \n};\n\n// ⁴⁰ Then the Author card should have complementary styling:\nexport class Author extends CardDef {\n static embedded = class Embedded extends Component {\n \n };\n}\n```\n\n#### Delegation Patterns\n\n```gts\n\n```\n\n### Avoiding Relationship Cycles\n\n**Problem:** Bidirectional `linksTo` relationships create circular dependencies that complicate indexing and can cause infinite recursion.\n\n**Solution:** Use canonical (one-way) links + dynamic queries for reverse relationships.\n\n#### Pattern: Canonical Links + Dynamic Queries\n\n1. **Define canonical links** - Choose the primary direction in your schema:\n```gts\n// Employee owns the supervisor relationship\nexport class Employee extends CardDef {\n @field supervisor = linksTo(() => Employee);\n @field department = linksTo(Department);\n}\n\n// Department owns the manager relationship\nexport class Department extends CardDef {\n @field manager = linksTo(Employee);\n}\n```\n\n2. **Use dynamic queries for reverse relationships** - Fetch at runtime instead of schema links:\n```gts\n// Get direct reports dynamically (in Employee component)\nget directReportsQuery(): Query {\n return {\n filter: {\n on: { module: './employee', name: 'Employee' },\n eq: { supervisor: this.args.model.id }\n }\n };\n}\n\n// Use with getCards or PrerenderedCardSearch\ndirectReports = this.args.context?.getCards(this, () => this.directReportsQuery, () => this.realms);\n```\n\n**Key Principle:** Model the simplest set of unidirectional links that define core relationships. Use queries for derived views, aggregations, and inverse relationships.\n\n### BoxelSelect: Smart Dropdown Menus\n\nRegular HTML selects are limited to plain text. BoxelSelect lets you create rich, searchable dropdowns with custom rendering.\n\n#### Pattern: Rich Select with Custom Options\n\n```gts\nexport class OptionField extends FieldDef { // ⁴³ Option field for select\n static displayName = 'Option';\n \n @field key = contains(StringField);\n @field label = contains(StringField);\n @field description = contains(StringField);\n\n static embedded = class Embedded extends Component {\n \n };\n}\n\nexport class ProductCategory extends CardDef { // ⁴⁴ Card using BoxelSelect\n @field selectedCategory = contains(OptionField);\n \n static edit = class Edit extends Component { // ⁴⁵ Edit format\n @tracked selectedOption = this.args.model?.selectedCategory;\n\n options = [\n { key: '1', label: 'Electronics', description: 'Phones, computers, and gadgets' },\n { key: '2', label: 'Clothing', description: 'Fashion and apparel' },\n { key: '3', label: 'Home & Garden', description: 'Furniture and decor' }\n ];\n\n updateSelection = (option: typeof this.options[0] | null) => {\n this.selectedOption = option;\n this.args.model.selectedCategory = option ? new OptionField(option) : null;\n }\n\n \n };\n}\n```\n\n### Custom Edit Controls\n\nCreate user-friendly edit controls that accept natural input. Hide complexity in expandable sections while keeping ALL properties editable and inspectable.\n\n```gts\n// Example: Natural language time period input\nstatic edit = class Edit extends Component {\n @tracked showDetails = false;\n \n @action parseInput(value: string) {\n // Parse \"Q1 2025\" → quarter: 1, year: 2025, startDate: Jan 1, endDate: Mar 31\n // Parse \"April 2025\" → month: 4, year: 2025, startDate: Apr 1, endDate: Apr 30\n }\n \n \n};\n```\n\n## Query System: Finding and Displaying Cards\n\n### The 'on' Property Rule (MEMORIZE THIS)\n\n**When using filters beyond basic type search, MUST include `on` as sibling:**\n\n```typescript\n// ❌ WRONG - Will fail\n{ range: { price: { lte: 100 } } }\n\n// ✅ CORRECT - 'on' specifies card type\n{ \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n range: { price: { lte: 100 } } \n}\n\n// ✅ EXCEPTION - Simple eq after type filter\n{ \n every: [\n { type: { module: new URL('./task', import.meta.url).href, name: 'Task' } },\n { eq: { status: \"active\" } } // No 'on' needed immediately after type\n ]\n}\n```\n\n### Query Quick Reference\n\n#### Filter Types & 'on' Requirements\n| Filter | Needs 'on'? | Example |\n|--------|-------------|---------|\n| `type` | No | `{ type: { module: '...', name: 'Product' } }` |\n| `eq` | Yes* | `{ on: {...}, eq: { status: \"active\" } }` |\n| `contains` | Yes | `{ on: {...}, contains: { tags: \"urgent\" } }` |\n| `range` | Yes | `{ on: {...}, range: { price: { gte: 100 } } }` |\n| `every` | No | `{ every: [...] }` (AND) |\n| `any` | No | `{ any: [...] }` (OR) |\n| `not` | No | `{ not: { eq: {...} } }` |\n\n*Only when not directly after type filter\n\n#### Range Operators\n`gt` (>) `gte` (>=) `lt` (<) `lte` (<=)\n\n#### Module & Realm Rules\n```typescript\n// ✅ ALWAYS absolute URLs\n{ module: new URL('./product', import.meta.url).href, name: 'Product' }\n\n// ✅ Realms need trailing slash\n'https://app.boxel.ai/sarah/projects/' // ✅\n'https://app.boxel.ai/sarah/projects' // ❌\n```\n\n### ⚠️ CRITICAL: The 'on' Attribute is MANDATORY\n\n**Missing 'on' will lead to no results shown!** When using:\n- `eq`, `contains`, `range` filters (except immediately after type filter)\n- `sort` on type-specific fields (anything beyond base fields like id, createdAt)\n\n```typescript\n// ❌ WILL FAIL - Missing 'on' for sort\n{ \n sort: [{ by: \"price\", direction: \"desc\" }]\n}\n\n// ✅ CORRECT - Include 'on' for type-specific fields\n{ \n sort: [{ \n by: \"price\", \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n direction: \"desc\" \n }]\n}\n```\n\n### Complete Query Pattern\n\n```typescript\nconst query: Query = {\n filter: {\n every: [ // AND\n { type: { module: new URL('./product', import.meta.url).href, name: 'Product' } },\n { \n any: [ // OR\n { on: { module: new URL('./product', import.meta.url).href, name: 'Product' }, eq: { category: \"laptop\" } },\n { on: { module: new URL('./product', import.meta.url).href, name: 'Product' }, eq: { category: \"tablet\" } }\n ]\n },\n { \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n range: { \n price: { gte: 100, lte: 2000 }, // Multiple conditions\n rating: { gte: 4 }\n }\n },\n { \n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n contains: { features: \"wireless\" }\n },\n {\n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n not: { eq: { status: \"discontinued\" } } // Exclude\n }\n ]\n },\n sort: [\n { by: \"createdAt\", direction: \"desc\" }, // General field\n { \n by: \"warranty\", // Type-specific needs 'on'\n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n direction: \"desc\" \n }\n ],\n page: { number: 0, size: 20 }\n};\n```\n\n### Decision: PrerenderedCardSearch vs getCards\n\n```\nDisplay cards as-is? → PrerenderedCardSearch\nNeed live updates? → PrerenderedCardSearch with @isLive={{true}}\nNeed to sort/filter/aggregate AFTER retrieval? → getCards\nNeed raw data access? → getCards\n```\n\n## PrerenderedCardSearch Pattern\n\n```gts\n// ⁴⁹ Component with dynamic query\nexport class Dashboard extends Component {\n get urgentTasksQuery(): Query {\n return {\n filter: {\n every: [\n { type: { module: new URL('./task', import.meta.url).href, name: 'Task' } },\n { \n on: { module: new URL('./task', import.meta.url).href, name: 'Task' },\n not: { eq: { status: \"completed\" } }\n }\n ]\n },\n sort: [{ by: \"dueDate\", direction: \"asc\" }],\n page: { number: 0, size: 10 }\n };\n }\n\n realms = ['https://app.boxel.ai/sarah/tasks/']; // Trailing slash!\n\n \n}\n```\n\n### Making Query Results Clickable\n\n```gts\n// ⁵¹ Wrap with CardContainer for navigation\n<:response as |cards|>\n
    \n {{#each cards key=\"url\" as |card|}}\n
  • \n \n \n \n
  • \n {{/each}}\n
\n\n```\n\n## getCards Pattern (Data Manipulation)\n\n```gts\n// ⁵² Direct assignment for data access\ncardsResult = this.args.context?.getCards(\n this,\n () => this.query,\n () => this.realms,\n { isLive: true }\n);\n\n// ⁵³ Post-retrieval sorting\nget sortedByRevenue() {\n const products = this.cardsResult?.instances ?? [];\n return [...products].sort((a, b) => {\n const scoreA = (a.revenue || 0) * (a.rating || 1);\n const scoreB = (b.revenue || 0) * (b.rating || 1);\n return scoreB - scoreA;\n });\n}\n\n// ⁵⁴ Aggregation\nget totalRevenue() {\n return this.cardsResult?.instances?.reduce((sum, p) => sum + (p.revenue || 0), 0) || 0;\n}\n```\n\n\n## Creating Fitted Formats - The Four Sub-formats Strategy\n\nFitted Formats are unique part of the Boxel Architecture in that it allows a version of a card or a field that fit into any slot (width and height up to 600px) allocated by a parent container, so as to support listing, gallery, chooser, even 3D sprites usage without the parent knowing anything about this card's or field's schema or template other than its ID.\n\nTo create fitted formats that automatically adapt to any container size, implement four responsive subformats within a single fitted template. This pattern ensures your cards look perfect whether displayed as tiny badges or full-width cards. While the platform provides a fallback fitted format for CardDefs, custom implementation is strongly recommended for optimal display. For FieldDefs, fitted format is optional as embedded format is the primary requirement.\n\n### Core Concept\n\nYou only have one fitted template so that the resulting parent template only needs to give a size they want to display and you will provide the best layout given that space.\n\nTo do that, create 4 subformats and turn on only one at a time. Create 4 divs inside the fitted template and use container queries to turn them on and off. Make sure there are no gaps where no subformat is active.\n\nFitted format shouldn't have borders, that is drawn by parent.\n\n**RECOMMENDED:** Every CardDef should implement a custom fitted format for optimal display. While the platform provides a fallback, custom fitted formats ensure your cards look their best in galleries, grids, choosers, and dashboards.\n\n**Key Implementation Points:**\n- **CardDef:** Custom fitted format recommended (platform provides fallback)\n- **FieldDef:** Embedded format mandatory, fitted format optional\n- Create 4 divs inside the fitted template (badge, strip, tile, card)\n- Use container queries to show only the appropriate subformat\n- CRITICAL: Ensure no gaps where no subformat is active - all sizes must be handled\n- Fitted format shouldn't have borders (drawn by parent)\n\n### Container Size Decision Tree\n\n```\nContainer Size\n │\n ├─ Height < 170px (Horizontal)\n │ │\n │ ├─ Width ≤ 150px → BADGE\n │ │ • 150×40 (micro)\n │ │ • 150×65 (small)\n │ │ • 150×105 (large) ← optimize\n │ │\n │ └─ Width > 150px → STRIP\n │ • 250×40 (single)\n │ • 250×65 (double)\n │ • 250×105 (triple)\n │ • 400×65 (wide double) ← optimize\n │ • 400×105 (wide triple)\n │\n └─ Height ≥ 170px (Vertical)\n │\n ├─ Width < 400px → TILE\n │ • 150×170 (narrow)\n │ • 170×250 (grid) ← optimize\n │ • 250×170 (wide)\n │ • 250×275 (large)\n │\n └─ Width ≥ 400px → CARD\n • 400×170 (compact)\n • 400×275 (standard) ← optimize\n • 400×445 (expanded)\n```\n\n#### Design Philosophy\n\n**First design the IDEAL layout for each subformat at the \"optimized for\" size.** Think of each subformat as if you were making 4 independent templates, each perfect for its specific use case.\n\n**Height Quantum:** The height breakpoints (40px, 65px, 105px, etc.) follow golden ratio progression (φ ≈ 1.618), creating natural visual harmony as formats scale.\n\n**Golden Ratio Usage:** Apply the golden ratio (1.618:1) throughout your layouts - for splits, spacing progressions, content zones, and visual balance. This mathematical harmony creates inherently pleasing proportions.\n\n**Typography Hierarchy:** Create clear visual distinction between text levels:\n- **Size cascade:** Each level 80-87% of the previous (1em → 0.875em → 0.75em)\n- **Weight cascade:** Drop 100-200 font-weight units per level (600 → 500 → 400)\n- **Spacing cascade:** Buffer between levels follows 50% → 37.5% → 25% pattern\n- **Same-level spacing:** Use 25% of the element's font size\n\n**Qualities for All Fitted Formats:**\n- **Well-balanced** - Every element positioned with intention\n- **On-brand** - Visually polished and consistent\n- **Scannable** - Clear indicators, easy to parse\n- **Small multiples** - Differences pop in collections\n- **Clickable** - Inviting interaction (cards only)\n- **Complete** - Show key data within constraints\n- **Familiar yet superior** - Match expectations, execute better\n- **Identifier visible** - Never obscure with entrance animations\n- **Clear hierarchy** - Primary/secondary/tertiary distinct\n\n### Content Priority Guidelines\n\nSuggested priority order - adjust for your use case:\n\n1. **Title/Name** - Primary identifier\n2. **Image** - Visual identity \n3. **Short ID** - SKU, username, ticket #\n4. **Key Info** - Dates, stats, linked entities\n5. **Badge/Status** - Visual indicators\n6. **Key-Value Metadata** - Show complete pairs only\n7. **Description** - Low priority, line-clamp aggressively\n8. **CTA** - Hover/focus only in tiles\n\n**For FieldDefs:** Since fitted format is optional, focus on embedded format first. If implementing fitted: priorities shift since there's no click-through. Show most important data within space constraints - composite identity plus critical values.\n\n**Examples:**\n- **Inventory:** SKU/status may outrank title\n- **Analytics:** Numbers take precedence\n- **Tasks:** Due date/assignee before description\n\n### Container Query Skeleton\n\n```css\n.fitted-container {\n container-type: size;\n width: 100%;\n height: 100%;\n}\n\n/* Hide all by default */\n.badge-format, .strip-format, .tile-format, .card-format {\n display: none;\n width: 100%;\n height: 100%;\n /* CRITICAL: Clear space prevents edge bleeding - scales with container size */\n padding: clamp(0.1875rem, 2%, 0.625rem); /* 3px min → 10px max */\n box-sizing: border-box;\n}\n\n/* Micro containers: absolute minimum safe padding */\n@container (max-width: 80px) and (max-height: 80px) {\n .badge-format { \n padding: 0.1875rem; /* 3px - visual safety minimum */\n }\n}\n\n/* Small containers: tight but safe */\n@container (max-width: 150px) {\n .badge-format, .strip-format { \n padding: 0.25rem; /* 4px - small but comfortable */\n }\n}\n\n/* Medium containers: breathing room */\n@container (min-width: 250px) and (max-width: 399px) {\n .tile-format {\n padding: 0.5rem; /* 8px - standard spacing */\n }\n}\n\n/* Large containers: generous clear space */\n@container (min-width: 400px) {\n .card-format {\n padding: clamp(0.5rem, 2%, 0.625rem); /* 8px → 10px max for expanded */\n }\n}\n\n/* Activation ranges - NO GAPS */\n@container (max-width: 150px) and (max-height: 169px) {\n .badge-format { display: flex; }\n}\n\n@container (min-width: 151px) and (max-height: 169px) {\n .strip-format { display: flex; }\n}\n\n@container (max-width: 399px) and (min-height: 170px) {\n .tile-format { display: flex; flex-direction: column; }\n}\n\n@container (min-width: 400px) and (min-height: 170px) {\n .card-format { display: flex; flex-direction: column; }\n}\n\n/* Compact card: horizontal split at golden ratio */\n@container (min-width: 400px) and (height: 170px) {\n .card-format { \n flex-direction: row;\n gap: 1rem;\n }\n .card-format > * {\n display: flex;\n flex-direction: column;\n }\n .card-format > *:first-child { flex: 1.618; }\n .card-format > *:last-child { flex: 1; }\n}\n\n/* Background fills respect padding for visual safety */\n.badge-format.has-fill,\n.strip-format.has-fill,\n.tile-format.has-fill,\n.card-format.has-fill {\n background: var(--fill-color);\n /* Background extends to edge but content stays within padding */\n background-clip: padding-box; /* Or border-box if fill should reach edge */\n}\n\n/* Type hierarchy - MANDATORY */\n.primary-text {\n font-size: 1em;\n font-weight: 600;\n color: var(--text-primary, rgba(0,0,0,0.95));\n line-height: 1.2;\n}\n\n.secondary-text {\n font-size: 0.875em; /* 87.5% of primary */\n font-weight: 500;\n color: var(--text-secondary, rgba(0,0,0,0.85));\n line-height: 1.3;\n}\n\n.tertiary-text {\n font-size: 0.75em; /* 75% of primary */\n font-weight: 400;\n color: var(--text-tertiary, rgba(0,0,0,0.7));\n line-height: 1.4;\n}\n\n/* Typography Hierarchy Spacing Heuristics */\n/* Primary → Secondary: 0.5em gap (half the primary size) */\n/* Secondary → Tertiary: 0.375em gap */\n/* Same level elements: 0.25em gap */\n\n.primary-text + .secondary-text {\n margin-top: 0.5em;\n}\n\n.secondary-text + .tertiary-text {\n margin-top: 0.375em;\n}\n\n.primary-text + .primary-text,\n.secondary-text + .secondary-text {\n margin-top: 0.25em;\n}\n\n/* Visual hierarchy multipliers:\n - Size: Each level ~80-87% of previous\n - Weight: Drop 100-200 units per level\n - Opacity: Drop 10-15% per level\n - Spacing: 50% → 37.5% → 25% of primary size */\n```\n\n### Subformat-Specific Rules\n\n**Design with familiar patterns** - Users know these formats from daily app usage. Meet their expectations, then exceed them with better spacing, smoother interactions, and superior visual polish. Doing something expected is good - just do it better.\n\n**Badge Format:**\n- Feels like exportable graphics\n- **Familiar from:** Slack badges, GitHub labels\n- 150×105 has 3 vertical elements\n- Fills/backgrounds extend to edges, content respects padding\n- **LEFT align always** - right elements balance\n- **Images:** Iconified 16-34px\n- **Heights:**\n - 40px: Title + icon horizontal only (or composite field identity)\n - 65px: Title + icon/ID stacked, single lines\n - 105px: Title + icon + status, magnetic edges\n- Use formatters for compact display\n- **For FieldDefs:** Show composite identity + key details\n- **Typography example at 105px:**\n - Primary title: 14px (0.875rem)\n - Secondary ID: 12px with 7px gap from title\n - Tertiary status: 10px with 5px gap from ID\n\n**Strip Format:**\n- **Primary use:** Dropdown and chooser panels where users scan and select\n- **Familiar from:** VS Code command palette, Spotlight search, Notion quick switcher\n- Optimized for quick scanning and selection - every pixel matters\n- **Title/identifier MUST ALWAYS be visible** - no animations, overlays, or effects that obscure it\n- Never use hover effects that hide or transform the identifier\n- Right-justify elements in wider strips\n- **Left aligned - no exceptions**\n- **Images:** \n - 40px height: Same as badge (20-34px) for consistency\n - 65px+ height: Standard size (40px)\n- **Height requirements:**\n - 40px: Title + key stat horizontally ONLY - single line, images 20-34px (same as badge)\n - 65px: Two single lines stacked vertically - NO wrapping within lines, images 40px\n - 105px: Three rows with magnetic edge spacing, images 40px\n- Abbreviate metadata, keep primary identity full\n\n**Tile Format:**\n- Standard vertical card layout\n- Optimize for grid viewing\n- Primary identity MUST be fully visible and prominent - no exceptions\n- The last vertical element MUST magnetically stick to the bottom\n\n**Card Format:**\n- Compact card (400×170) is split horizontally once at the golden ratio, then content within each panel is organized vertically\n- All other cards larger than compact card should be vertically subdivided\n- Expanded card is the full card with more data on the bottom\n- Expanded card MUST use all available vertical space - empty space is failure\n- The last vertical element MUST magnetically stick to the bottom\n\n### CTA Placement\n- **CardDef tile subformats only** (not FieldDefs)\n- Show on hover/focus only\n- Can obscure other content when shown\n- Lowest priority\n\n### Fitted Formats for FieldDefs (Optional)\n\n**IMPORTANT:** Fitted formats are optional for FieldDefs. FieldDefs require embedded format (with natural height) and that should be your primary focus. Only create fitted formats when your field might be displayed in fixed-size containers.\n\nWhen implementing fitted formats for FieldDefs, they require a different approach than CardDefs because they lack inherent identity and have no click-through capability.\n\n**Key Difference from CardDef Fitted:**\n- **CardDef fitted:** Shows identity + key info → click for details\n- **FieldDef fitted:** Shows most important data that fits (still space-constrained)\n\n**Creating Field Identity:**\nSince fields don't have clear identity like cards, create a composite identifier by combining 1-3 most important data points. For example:\n- Address field: Street + City\n- Price field: Amount + Currency + Trend\n- Contact field: Name + Primary method\n- Date range: Start + Duration + Status\n\n**Content Priority Shift:**\nBecause users can't click through to see more, fitted formats for fields should:\n- Show the most important data that fits the space\n- Prioritize key identifiers and critical values\n- Include essential metadata over nice-to-have details\n- Use composite identity from 1-3 key data points\n- Remember: still space-constrained like card fitted formats\n\n**Visual Field Handling:**\nFor image-based or visually-oriented compound fields:\n- Make the image/visual element primary (fill most space)\n- Overlay metadata on top with appropriate contrast\n- Use scrims or backdrop shadows for text legibility (except on precise visual content)\n- Consider the image as the \"identity\" with data as support\n- **CRITICAL:** For color fields, charts, or data visualizations, avoid scrims/overlays that alter perception\n\n**Example implementations:**\n- **Location field:** Map thumbnail with address overlay\n- **Chart field:** Visualization fills space, key metrics on corners (no scrim)\n- **Media field:** Thumbnail/preview large, metadata badge overlay\n- **Color field:** Swatch as background, hex/rgb values on top (pure color, no overlay)\n\n```css\n/* Example: Visual field with overlay metadata */\n.field-tile-format.visual-field {\n position: relative;\n padding: clamp(0.1875rem, 2%, 0.5rem); /* Clear space scales with container */\n}\n\n.field-tile-format .visual-primary {\n width: 100%;\n height: 100%;\n object-fit: cover;\n border-radius: 0.25rem; /* Subtle radius prevents harsh edges */\n}\n\n.field-tile-format .metadata-overlay {\n position: absolute;\n bottom: clamp(0.1875rem, 2%, 0.5rem); /* Match container padding */\n left: clamp(0.1875rem, 2%, 0.5rem);\n right: clamp(0.1875rem, 2%, 0.5rem);\n padding: 0.5rem;\n background: linear-gradient(to top, \n rgba(0,0,0,0.8) 0%, \n rgba(0,0,0,0) 100%);\n color: white;\n border-radius: 0.25rem;\n}\n\n/* Non-visual fields show more detail */\n.field-badge-format {\n padding: clamp(0.1875rem, 2%, 0.375rem); /* Clear space for badges */\n}\n\n.field-badge-format .composite-identity {\n font-weight: 600;\n margin-bottom: 0.25rem;\n}\n\n.field-badge-format .field-details {\n font-size: 0.75rem;\n opacity: 0.9;\n}\n```\n\n### Visual Guidelines\n\n#### Icons\n- Incorporate subtly with appropriate size/weight\n- Visual support only - include after key content\n\n#### Images\n- Priority 2 - show after primary identifier\n- **Badge:** Always iconified (16-34px)\n- **Strip:** \n - 40px height: 20-34px (matches badge)\n - 65px height: Fixed 40px\n - 105px height: Can fill height with AR constraint in wide strips (250px+)\n- **Tile:** Background with vibrant scrim if image would obscure text (except for visually precise content)\n- **Tile/Card:** Apply shared scale budget with text\n- Aspect ratios 0.7-1.4 unless decorative\n- Never completely displace text\n- **For visual FieldDefs:** Image can be primary with metadata overlay\n\n**Scrim effects:** Use accent colors for vibrant overlays. Mix brand colors with dark gradients: purple-to-black, blue-to-indigo-to-black, or accent-with-opacity layers. **CRITICAL:** Never apply scrims to visually precise content (color swatches, charts, data visualizations, medical imagery) as they alter perception and compromise accuracy.\n\n**Animation restraint:** Never use animations that move content near edges - can expose accidental borders. Strips especially need static, predictable layouts for scanning.\n\n#### 105px Height Magnetic Edge Layout\n\nAt 105px, use `justify-content: space-between` to push three elements to top/middle/bottom edges, maximizing visual separation.\n\n### Key Implementation Details\n\n1. **CardDef Fitted:** Custom recommended (fallback exists)\n2. **FieldDef Requirements:** Embedded mandatory, fitted optional\n3. **Container Queries:** `container-type: size`\n4. **No Gaps:** Cover all sizes\n5. **Line Clamping:** Match height constraints\n6. **Scaling:** `clamp()` ±20-25%\n7. **Height Use:** Fill 40/65/105px fully\n8. **40px:** Horizontal only\n9. **105px:** `justify-content: space-between`\n10. **Strip IDs:** Always visible\n11. **Clear Space:** 3px min to 1rem max\n12. **Type Hierarchy:** Size/weight/spacing cascades (80-87% per level)\n13. **Data Shaping:** Use formatters\n14. **Priority:** Key-values > descriptions\n15. **Badge Images:** 16-34px scaling\n16. **Strip Images:** Match badge at 40px, larger at 65px+, AR-fill at 105px wide\n17. **Scale Budget:** 50% shared text/image\n18. **Font Scaling:** Smaller = smaller base\n19. **Key-Values:** Complete pairs only\n20. **Familiar Patterns:** Match expectations\n21. **Edge Fills:** Backgrounds full, content padded\n22. **Vibrant Scrims:** Accent colors\n23. **No Edge Animations:** Prevent border exposure\n24. **FieldDef Identity:** Composite 1-3 key data points for recognition\n25. **Visual Precision:** No scrims on color/chart/data viz content\n\n\n\n## CRITICAL Reminders\n\n1. **PrerenderedCardSearch returns components, not data** - Can't sort/filter after\n2. **Type-specific sort fields MUST have 'on'** - Missing 'on' = no results shown!\n3. **Empty arrays need length check** - `(gt @model.items.length 0)`\n4. **Query result spacing** - Use `.container > .containsMany-field` pattern\n5. **Always use absolute module URLs** - `new URL(...).href`\n\n### Using getCards for Data Access and Aggregation\n\nWhen you need full access to card data for calculations, aggregations, or custom processing, use the `getCards` API from context.\n\n#### Basic getCards Pattern\n\n```gts\n// ❌ WRONG: Don't import getCards - it's just a type definition\n// import { getCards } from '@cardstack/runtime-common';\n\n// ✅ CORRECT: Use getCards from context\n// With live updates (for dashboards)\ncardsResult = this.args.context?.getCards(\n this,\n () => this.query,\n () => this.realmHrefs,\n { isLive: true }\n);\n\n// For one-time load (omit isLive)\ncardsResult = this.args.context?.getCards(\n this,\n () => this.query,\n () => this.realmHrefs\n);\n```\n\n#### Working with getCards Results\n\n```gts\n// getCards returns: { instances, isLoading, instancesByRealm }\ncardsResult = this.args.context?.getCards(\n this,\n () => this.storyQuery,\n () => this.realmHrefs,\n);\n\n// Frontend sorting/filtering\nget sortedCards() {\n const cards = this.cardsResult?.instances ?? [];\n return [...cards].sort((a, b) => b.rating - a.rating);\n}\n\n\n```\n\n#### Map/Reduce Aggregation Patterns\n\n**Note:** These patterns load all matching cards into memory, so use sparingly for large datasets.\n\n**RULE: Make aggregated stats real** - When showing totals, averages, or counts in templates, calculate them from actual data using aggregation functions, not hardcoded placeholders.\n\n```gts\n// Calculate totals using reduce\nget totalValue() {\n if (!this.cardsResult?.instances) return 0;\n return this.cardsResult.instances.reduce((sum, card) => {\n return sum + (card.value || 0);\n }, 0);\n}\n\n// Group by category\nget groupedByCategory() {\n if (!this.cardsResult?.instances) return {};\n return this.cardsResult.instances.reduce((groups, card) => {\n const category = card.category || 'Uncategorized';\n groups[category] = groups[category] || [];\n groups[category].push(card);\n return groups;\n }, {});\n}\n\n// Multiple metrics in one pass\nget metrics() {\n if (!this.cardsResult?.instances) return null;\n \n return this.cardsResult.instances.reduce((acc, card) => {\n acc.total += card.amount || 0;\n acc.count += 1;\n acc.byStatus[card.status] = (acc.byStatus[card.status] || 0) + 1;\n if (card.priority === 'high') acc.highPriority += 1;\n return acc;\n }, {\n total: 0,\n count: 0,\n byStatus: {},\n highPriority: 0\n });\n}\n```\n\n**Performance Considerations:**\n- For simple counts, use the type summaries API instead\n- PrerenderedCardSearch is better for display-only needs\n- Only use getCards when you need complex calculations\n- Consider pagination for large datasets\n\n### CardContainer: Making Cards Clickable\n\nTransforms cards into interactive, clickable elements for viewing or editing, complete with visual chrome. When used with the `cardComponentModifier`, it enables users to click through to view or edit the wrapped card.\n\n#### Usage\n\n```gts\n\n```\n\n**CRITICAL: Style Boxel UI Components for Custom Templates**\n\n**Boxel UI components (Button, BoxelSelect, etc.) must be completely styled when used in custom isolated, embedded, and fitted templates.** They come with minimal default styling and buttons especially will look broken without custom CSS.\n\n```gts\n\n```\n### Alternative: Using Custom Actions with viewCard API\n\nInstead of making entire cards clickable, you can create custom buttons or links that use the `viewCard` API to open cards in specific formats.\n\n#### Basic Implementation\n\n```javascript\n@action\nviewOrder(order: ProductOrder) {\n // Open order in isolated view\n this.args.viewCard(order, 'isolated');\n}\n\n@action\neditOrder(order: ProductOrder) {\n // Open card in rightmost stack for side-by-side reference\n // Useful for: 1) reference lookup, 2) edit panel on right while previewing on left\n this.args.viewCard(order, 'edit', {\n openCardInRightMostStack: true\n });\n}\n\n@action\nviewReturnPolicy() {\n // Open card using URL\n const returnPolicyURL = new URL('https://app.boxel.ai/markinc/storefront/ReturnPolicy/return-policy-0525.json');\n this.args.viewCard(returnPolicyURL, 'isolated');\n}\n```\n\n#### Template Example\n\n```hbs\n
\n \n
\n \n View Order\n \n \n \n Edit Order\n \n
\n \n\n \n Return Policy\n \n
\n```\n\n#### Available Formats\n\n- `'isolated'` - Read-oriented mode, may have some editable forms or interactive widgets\n- `'edit'` - Open card for full editing\n\n#### Use Cases\n- Multiple direct call-to-actions per card (view, edit)\n- More control over user interactions\n- Link to any card via a card URL\n\n\n## External Libraries: Bringing Third-Party Power to Boxel\n\n**When to Use External Libraries:** Sometimes you need specialized functionality like 3D graphics (Three.js), data visualization (D3), or charts. Boxel plays well with external libraries when you follow the right patterns.\n\n**Key Rules:**\n1. **Always use Modifiers for DOM access** - Never manipulate DOM directly\n2. **Use ember-concurrency tasks** for async operations like loading libraries\n3. **Bind external data to model fields** for reactive updates\n4. **Use proper loading states** while libraries initialize\n\n### Pattern: Dynamic Three.js Integration\n\n```gts\nimport { task } from 'ember-concurrency';\nimport Modifier from 'ember-modifier';\n\n// Global accessor function\nfunction three() {\n return (globalThis as any).THREE;\n}\n\nclass ThreeJsComponent extends Component {\n @tracked errorMessage = '';\n private canvasElement: HTMLCanvasElement | undefined;\n \n private loadThreeJs = task(async () => {\n if (three()) return;\n \n const script = document.createElement('script');\n script.src = 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js';\n script.async = true;\n \n await new Promise((resolve, reject) => {\n script.onload = resolve;\n script.onerror = reject;\n document.head.appendChild(script);\n });\n });\n\n private initThreeJs = task(async () => {\n try {\n await this.loadThreeJs.perform();\n if (!three() || !this.canvasElement) return;\n \n const THREE = three();\n \n // Scene setup - bind results to model fields for reactivity\n this.scene = new THREE.Scene();\n // ... setup scene\n \n // CRITICAL: Bind external data to model fields\n this.args.model.sceneReady = true;\n this.args.model.lastUpdated = new Date();\n \n this.animate();\n } catch (e: any) {\n this.errorMessage = `Error: ${e.message}`;\n }\n });\n\n private onCanvasElement = (element: HTMLCanvasElement) => {\n this.canvasElement = element;\n this.initThreeJs.perform();\n };\n\n \n}\n```\n\n## File Organization\n\n### Single App Structure\n```\nmy-realm/\n├── blog-post.gts # Card definition (kebab-case)\n├── author.gts # Another card\n├── address-field.gts # Field definition (kebab-case-field)\n├── BlogPost/ # Instance directory (PascalCase)\n│ ├── hello-world.json # Instance (any-name)\n│ └── second-post.json \n└── Author/\n └── jane-doe.json\n```\n\n### Related Cards App Structure\n**CRITICAL:** When creating apps with multiple related cards, organize them in common folders:\n\n```\nmy-realm/\n├── ecommerce/ # Common folder for related cards\n│ ├── product.gts # Card definitions\n│ ├── order.gts\n│ ├── customer.gts\n│ ├── Product/ # Instance directories\n│ │ └── laptop-pro.json\n│ └── Order/\n│ └── order-001.json\n├── blog/ # Another app's folder\n│ ├── post.gts\n│ ├── author.gts\n│ └── Post/\n│ └── welcome.json\n└── shared/ # Shared components\n └── address-field.gts # Common field definitions\n```\n\n**Directory Discipline:** When creating files within a specific directory structure (e.g., `ecommerce/`), keep ALL related files within that structure. Don't create files outside the intended directory organization.\n\n**Relationship Path Tracking:** When creating related JSON instances, maintain a mental map of your file paths. Links between instances must use the exact relative paths you've created - consistency prevents broken relationships.\n\n## JSON Instance Format Quick Reference\n\n**When creating `.json` card instances via SEARCH/REPLACE, follow this structure:**\n\n**Naming:** Use natural names for JSON files (e.g., `Author/jane-doe.json`, `Product/laptop-pro.json`) - don't append `-sample-data`\n\n**Path Consistency:** When creating multiple related JSON instances, track the exact file paths you create. Relationship links must match these paths exactly - if you create `Author/dr-nakamura.json`, reference it as `\"../Author/dr-nakamura\"` from other instances.\n\n### Root Structure\nAll data wrapped in a `data` object with:\n* `type`: Always `\"card\"` for instances\n* `attributes`: Field values go here\n* `relationships`: Links to other cards\n* `meta.adoptsFrom`: Connection to GTS definition\n\n### Instance Template\n```json\n{\n \"data\": {\n \"type\": \"card\",\n \"attributes\": {\n // Field values here\n },\n \"relationships\": {\n // Card links here\n },\n \"meta\": {\n \"adoptsFrom\": {\n \"module\": \"../path-to-gts-file\",\n \"name\": \"CardDefClassName\"\n }\n }\n }\n}\n```\n\n### Field Value Patterns\n\n**Simple fields** (`contains(StringField)`, etc.):\n```json\n\"attributes\": {\n \"title\": \"My Title\",\n \"price\": 29.99,\n \"isActive\": true\n}\n```\n\n**Compound fields** (`contains(AddressField)` - a FieldDef):\n```json\n\"attributes\": {\n \"address\": {\n \"street\": \"4827 Riverside Terrace\",\n \"city\": \"Portland\",\n \"postalCode\": \"97205\"\n }\n}\n```\n\n**Array fields** (`containsMany`):\n```json\n\"attributes\": {\n \"tags\": [\"urgent\", \"review\", \"frontend\"],\n \"phoneNumbers\": [\n { \"number\": \"+1-503-555-0134\", \"type\": \"work\" },\n { \"number\": \"+1-971-555-0198\", \"type\": \"mobile\" }\n ]\n}\n```\n\n### Relationship Patterns\n\n**Single link** (`linksTo`):\n```json\n\"relationships\": {\n \"author\": {\n \"links\": {\n \"self\": \"../Author/dr-nakamura\"\n }\n }\n}\n```\n\n**Multiple links** (`linksToMany`) - note the `.0`, `.1` pattern:\n```json\n\"relationships\": {\n \"teamMembers.0\": {\n \"links\": { \"self\": \"../Person/kai-nakamura\" }\n },\n \"teamMembers.1\": {\n \"links\": { \"self\": \"../Person/esperanza-cruz\" }\n }\n}\n```\n\n**Empty linksToMany** - when no relationships exist:\n```json\n\"relationships\": {\n \"nextLevels\": {\n \"links\": {\n \"self\": null\n }\n }\n}\n```\nNote: Use `null`, not an empty array `[]`\n\n### Path Conventions\n* **Module paths**: Relative to JSON location, no `.gts` extension\n * Local: `\"../author\"` or `\"../../shared/address-field\"`\n * Base: `\"@cardstack/base/string\"`\n* **Relationship paths**: Relative paths, no `.json` extension\n * `\"../Author/jane-doe\"` not `\"../Author/jane-doe.json\"`\n* **Date formats**: \n * DateField: `\"2024-11-15\"`\n * DateTimeField: `\"2024-11-15T10:00:00Z\"`\n\n## 🚫 Common Mistakes to Avoid\n\n### 1. Using contains/containsMany with CardDef\n```gts\n// ❌ WRONG\nexport class Auction extends CardDef {\n @field auctionItems = containsMany(AuctionItem); // AuctionItem is a CardDef\n}\n\n// ✅ CORRECT\nexport class Auction extends CardDef {\n @field auctionItems = linksToMany(AuctionItem); // Use linksToMany for CardDef\n}\n```\n\n### 2. Template Calculation Mistakes\n```gts\n// ❌ WRONG - JavaScript/constructors in template\nTotal: {{@model.price * @model.quantity}}\n{{if @model.currentMonth @model.currentMonth (formatDateTime (new Date()) \"MMMM YYYY\")}}\n\n// ✅ CORRECT - Use helpers or computed property\nTotal: {{multiply @model.price @model.quantity}}\n{{if @model.currentMonth @model.currentMonth this.currentMonthDisplay}}\n```\n\n### 3. Using Reserved Words as Field Names\n```gts\n// ❌ WRONG - JavaScript reserved words\n@field type = contains(StringField); // 'type' is reserved\n@field class = contains(StringField); // 'class' is reserved\n\n// ✅ CORRECT - Use descriptive alternatives\n@field recordType = contains(StringField); // Instead of 'type'\n@field category = contains(StringField); // Instead of 'class'\n\n// ✅ CORRECT - Override inherited fields with computed versions\n@field fullName = contains(StringField);\n@field title = contains(StringField, {\n computeVia: function() { return this.fullName ?? 'Unnamed'; }\n});\n```\n\n### 4. Missing Exports\n```gts\n// ❌ WRONG - Missing export will break module loading\nclass BlogPost extends CardDef { // Missing 'export'\n}\n\n// ❌ WRONG - Separate export statement\nclass BlogPost extends CardDef { }\nexport { BlogPost };\n\n// ✅ CORRECT - Always export CardDef and FieldDef classes inline\nexport class BlogPost extends CardDef {\n}\n```\n\n### 5. Missing Spacing for Auto-Collections\n```gts\n// ❌ WRONG - No spacing wrapper for delegated items\n<@fields.items @format=\"embedded\" />\n\n// ❌ WRONG - Container styling won't reach containsMany items\n
\n <@fields.items @format=\"embedded\" />\n
\n\n\n\n// ✅ CORRECT - Target .containsMany-field\n
\n <@fields.items @format=\"embedded\" />\n
\n\n\n```\n\n### 6. Mixing @model Iteration with @fields Delegation\n```gts\n// ❌ WRONG - Cannot use @fields inside @model iteration\n{{#each @model.teamMembers as |member|}}\n <@fields.member @format=\"embedded\" /> \n{{/each}}\n\n// ✅ CORRECT - Choose one approach\n// Option 1: Full delegation\n<@fields.teamMembers @format=\"embedded\" />\n\n// Option 2: Full @model control\n{{#each @model.teamMembers as |member|}}\n
{{member.name}}
\n{{/each}}\n```\n\n### 7. Using Emoji or Boxel Icons in Templates\n```hbs\n\n

🎯 Daily Goals

\n\n\n\n

Daily Goals

\n\n\n\n

\n \n \n \n \n \n Daily Goals\n

\n\n```\n\n### 8. Self-Import Error\n```gts\n// ❌ WRONG - Never import the same field you're defining\nimport AddressField from '@cardstack/base/address';\n\nexport class AddressField extends FieldDef { // Defining AddressField but importing it too\n // ... this will cause conflicts\n}\n\n// ✅ CORRECT - Don't import what you're defining\nexport class AddressField extends FieldDef {\n // ... define the field without importing it\n}\n\n// ✅ CORRECT - To extend a base field, import it with a different name or extend directly\nimport BaseAddressField from '@cardstack/base/address';\n\nexport class FancyAddressField extends BaseAddressField {\n // ... extend the base field with custom behavior\n}\n```\n\n### 9. Escaping Placeholder Attributes Only\n```hbs\n\n\n 0) { return \"success\"; }\">\n\n\n\n\n```\n\n### 10. Don't use single curlies\n```hbs\n\n#{@model.paddleNumber}\n\n\n#{{@model.paddleNumber}}\n```\n\n**Note:** The `#` character starts block helpers in Handlebars (e.g., `{{#if}}`, `{{#each}}`), so it must be escaped when you want to display it literally before template interpolations.\n\n### 11. Using Unstyled Buttons\n```gts\n// ❌ WRONG - Unstyled buttons look broken\n\n\n// ✅ CORRECT - Always add complete styling (see button styling example in Advanced Patterns)\n\n```\n\n### 12. Missing Tracking Comments in .gts Files\n```gts\n// ❌ WRONG - No tracking mode indicator on line 1\nimport { CardDef } from '@cardstack/base/card-api';\n\n// ✅ CORRECT - Tracking mode on line 1, markers throughout\n// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══\nimport { CardDef } from '@cardstack/base/card-api'; // ¹ Core imports\n```\n\nRemember to include the post-SEARCH/REPLACE notation `╰ ¹⁻³` after blocks!\n\n### 13. Wrong Empty Relationship Format in JSON\n```json\n// ❌ WRONG - Empty array for null relationship\n\"relationships\": {\n \"nextLevels\": {\n \"links\": {\n \"self\": []\n }\n }\n}\n\n// ✅ CORRECT - Use null for empty linksToMany\n\"relationships\": {\n \"nextLevels\": {\n \"links\": {\n \"self\": null\n }\n }\n}\n```\n\n### 14. SVG URL References Don't Work in Boxel\n```hbs\n\n\n \n \n \n \n \n \n \n\n\n\n\n \n\n\n```\n\n**Rule:** Avoid `url(#id)` references in SVGs (for gradients, patterns, clips, etc.) as Boxel cannot route these correctly. Instead, use CSS alternatives to style SVG elements when available. For gradients specifically, use CSS `linear-gradient()` or `radial-gradient()` on SVG elements rather than SVG `` or ``.\n\n### 15. Missing 'on' Property in Query Filters\n```gts\n// ❌ WRONG - Missing 'on' for range filter\nconst query = {\n filter: {\n range: { price: { lte: 100 } }\n }\n};\n\n// ✅ CORRECT - Include 'on' for non-basic filters\nconst query = {\n filter: {\n on: { module: new URL('./product', import.meta.url).href, name: 'Product' },\n range: { price: { lte: 100 } }\n }\n};\n```\n\n### Common Patterns\n\n```typescript\n// Field existence check\n{ on: {...}, not: { eq: { description: null } } }\n\n// Multiple ranges \n{ on: {...}, range: {\n score: { gt: 8 },\n years: { gte: 1, lt: 10 },\n date: { gte: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000) }\n}}\n\n// Nested field access\n{ on: {...}, eq: { \n 'supervisor.id': this.args.model.id,\n 'department.active': true\n}}\n\n// Dynamic references\n{ on: {...}, range: { \n price: { lte: this.args.model.budget || 1000 }\n}}\n```\n\n## ✅ Pre-Generation Checklist\n\n### 🚨 CRITICAL (Will Break Functionality)\n- [ ] **Using SEARCH/REPLACE blocks for all .gts edits**\n- [ ] **Tracking mode indicator on line 1:** `// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══`\n- [ ] **NO contains/containsMany with CardDef** - Check every field using contains/containsMany only uses FieldDef types\n- [ ] **NO JavaScript calculations/constructors in templates** - All computations must be in JS properties/getters\n- [ ] **ALL CardDef and FieldDef classes exported inline** - Every class must have 'export' in declaration\n- [ ] Correct contains/linksTo usage per the cardinal rule\n- [ ] Array length checks: `{{#if (gt @model.array.length 0)}}` not `{{#if @model.array}}`\n- [ ] **containsMany collection spacing: `.container > .containsMany-field { display: flex/grid; gap: X; }`**\n- [ ] **@fields delegation rule**: Always use `@fields` for delegation (even singular fields)\n- [ ] **Never mix @model iteration with @fields delegation** - choose one approach\n- [ ] **Fitted format requires style overrides (TEMPORARY):** `style=\"width: 100%; height: 100%\"`\n- [ ] **Use inline SVG in templates instead of emoji or Boxel icons**\n- [ ] **Never use unstyled buttons** - always add complete custom CSS styling\n- [ ] **Empty linksToMany relationships use null** - `\"self\": null` not `\"self\": []`\n- [ ] **No SVG url(#id) references** - use CSS gradients on SVG elements instead\n- [ ] **External libraries** - use Modifiers for DOM access, never manipulate DOM directly\n- [ ] **Query filters use 'on' property** - Required for range, contains, eq (except after type filter)\n- [ ] **Module URLs use new URL().href** - Never use relative paths in queries\n- [ ] **Realm URLs have trailing slash** - Required for realm references\n\n### ⚠️ IMPORTANT (Affects User Experience)\n- [ ] Icons assigned to all CardDef and FieldDef\n- [ ] Embedded templates for all FieldDefs\n- [ ] Empty states provided for all arrays\n- [ ] Every card computes inherited `title` field from primary identifier\n- [ ] Recent dates in sample data (2024/2025)\n- [ ] Currency/dates formatted with helpers in templates only\n- [ ] Meaningful placeholder text for all fallback states\n- [ ] Isolated views have scrollable content area\n- [ ] **Boxel UI components completely styled in custom templates**\n- [ ] **Creative sample data** - avoid clichés, create believable fictional scenarios\n- [ ] **Thoughtful font selection** - choose domain-appropriate Google fonts\n\n## Critical Rules Summary\n\n### One-Shot Success Criteria (Priority Order)\n1. **Runnable** - No syntax errors, all imports work, no runtime crashes due to missing data\n2. **Syntactically Correct** - Proper contains/linksTo, exports, tracking comments\n3. **Attractive** - Professional styling, thoughtful UX, visual polish\n4. **Evolvable** - Clear structure for user additions and modifications\n\n### NEVER Do These\n\n### 🔴 #1 MOST CRITICAL ERROR:\n❌ `contains(CardDef)` or `containsMany(CardDef)` → **ALWAYS** use `linksTo(CardDef)` or `linksToMany(CardDef)`\n\n### 🔴 #2 CRITICAL: No JavaScript in Templates\n❌ **NEVER do calculations, constructors, or call methods in templates:**\n - `{{@model.price * 1.2}}` → Use `{{multiply @model.price 1.2}}`\n - `{{(new Date())}}` → Create getter `get currentDate()`\n - `{{price > 100}}` → Use `{{gt price 100}}`\n\n### 🔴 #3 CRITICAL: Field Rules\n❌ **JavaScript reserved words as field names** → Use descriptive alternatives \n❌ **Defining same field name twice in your own class** → Each field name unique \n✅ **OK to override parent's fields** → Can compute title, description, thumbnailURL \n❌ **Missing exports on CardDef/FieldDef** → Every class must be exported \n\n### 🔴 #4 CRITICAL: Edit Tracking Mode\n❌ **Missing tracking mode indicator on line 1** → Every .gts file MUST start with tracking \n❌ **SEARCH/REPLACE blocks without tracking markers** → Both blocks must contain ⁿ\n\n### Other Critical Rules\n❌ `<@fields.items />` without proper CSS selector → Target `.container > .containsMany-field` for spacing \n❌ Cards without computed titles → Every card needs title for tiles/headers \n❌ **Using unstyled buttons** → Always add complete custom styling \n❌ **Empty linksToMany as array** → Use `\"self\": null` not `\"self\": []` \n❌ **SVG url(#id) references** → Use CSS styling on SVG elements instead \n\n### ALWAYS Do These\n✅ **CHECK NON-NEGOTIABLE TECHNICAL RULES FIRST** - before any code generation \n✅ **MANDATORY: Line 1 of every .gts file:** `// ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══` \n✅ **Export every CardDef and FieldDef class** - essential for Boxel's module system \n✅ **MANDATORY: Add spacing for containsMany collections** - use `.container > .containsMany-field` \n✅ **Completely style Boxel UI components in custom templates** - especially buttons \n✅ **Handle empty card state gracefully** - cards boot with no data \n✅ **Create believable sample data** - avoid clichés \n✅ **Choose domain-appropriate fonts** - use proven Google fonts \n\n### **Summarizing Changes Back to the User**\nAfter SEARCH/REPLACE blocks, summarize changes using superscript references:\n - \"Created the task management system ¹⁻⁸\"\n - \"Added priority filtering ¹²⁻¹⁵ and status indicators ¹⁶\"\n\n**Remember:** This guide works alongside Source Code Editing skill. For general SEARCH/REPLACE mechanics, refer to that document. This guide adds Boxel-specific requirements.", "commands": [], "cardTitle": "Boxel Development", "cardDescription": "Created by the Boxel Team with help from Gemini 2.5 Pro Experimental - V3", @@ -10,7 +10,7 @@ }, "meta": { "adoptsFrom": { - "module": "https://cardstack.com/base/skill", + "module": "@cardstack/base/skill", "name": "Skill" } } diff --git a/packages/base/Skill/boxel-environment.json b/packages/base/Skill/boxel-environment.json index 7770aebc804..12189cd90da 100644 --- a/packages/base/Skill/boxel-environment.json +++ b/packages/base/Skill/boxel-environment.json @@ -2,7 +2,7 @@ "data": { "type": "card", "attributes": { - "instructions": "# Boxel Environment Guide\n\n🔭 You help users navigate Boxel efficiently, switching between modes and orchestrating workflows. Work alongside Boxel Development skill for seamless code operations.\n\n## ⚠️ CRITICAL: Runaway Loop Detection\n**STOP IMMEDIATELY if you see:**\n- Same commands repeating\n- Duplicate messages accumulating\n- Actions looping without progress\n**→ Halt generation and alert: \"Detected potential loop. Stopping to prevent runaway execution.\"**\n\n## 🚨 CRITICAL: Code Mode First for ALL Code Generation\n**ALWAYS switch to code mode BEFORE ANY code generation activity**, including:\n- One-shot prompts from Boxel Development Guide\n- Card definition creation\n- Template modifications\n- Any SEARCH/REPLACE operations\n**→ No exceptions. Switch to code mode FIRST, then proceed with generation.**\n\n**🔴 CRITICAL: Verify LLM Before Code Generation**\nBefore generating ANY code, check current LLM:\n- **Approved for code**: `anthropic/claude-sonnet-4.6`, `anthropic/claude-opus-4`, `google/gemini-2.5-pro`\n- **If using different model** → Switch to approved model FIRST\n- **Default**: Always use `anthropic/claude-sonnet-4.6` unless specific reason\n\n**EXCEPTION: When Boxel Development skill is active:**\n- Code generation allowed in ANY mode (interact or code)\n- Interact mode: Only modify code visible in current view\n- Use open card stack for context\n\n**EXCEPTION: When workspace is known:**\n- Can create new cards/code even without open cards\n- Still switch to code mode for better experience\n\n## Debug Mode\nWhen user starts with \"debug\", output current context: attached files, workspace (username/workspace-name), mode, available skills, decision factors, and any pending schema fixes.\n\n## Location Parsing\n\nWhere is the user in Boxel?\n\n- **Dashboard**: No workspace in URL → \"Navigate to workspace first\"\n- **Workspace Home**: Has workspace, no cards → Offer search/create\n- **Card View**: Workspace + cards → Active interactive session focusing on content and data exploration\n- **Code Edit**: Code mode + file → Editing schema or instance\n\n**Navigation Stack**: User's click path (not data relationships)\n- Bottom = oldest, Top = current\n- Use URLs to fetch card context\n- Mixed realms possible\n\n**Format Detection**: Current format = user's focus for code changes\n- `isolated`: Full detail | `embedded`: Summary | `fitted`: Grid\n- `atom`: Inline | `edit`: Form\n\n## User Communication\n\n**Focus on intent, not mechanics.** Users care about what they want to do, not Boxel's internal structure.\n\n### Intent-Based Responses\n\n| User Says | Respond With | Not |\n|-----------|--------------|-----|\n| \"Create a shopping list\" | \"I'll create a shopping list card for you\" | \"You're in workspace slewis88/buff-forrest in interact mode\" |\n| \"What am I looking at?\" | \"You're viewing a blog post in preview\" | \"You have BlogPost/123 open in embedded format\" |\n| \"Fix this error\" | \"I see the issue - let me fix that JSON syntax\" | \"I need to use read-file-for-ai-assistant first\" |\n| \"Make the title bigger\" | \"I'll update the title styling\" | \"Switching to code mode to edit embedded template\" |\n\n### Acknowledge → Act → Confirm\n1. **Acknowledge intent**: \"I'll help you create that\"\n2. **Act silently**: Switch modes, read files, run commands\n3. **Confirm completion**: \"Done! Your shopping list is ready\"\n\n## Quick Reference\n\n**File Types:** `.gts` (CardDef/FieldDef definitions) | `.json` (instance data) \n**Core Pattern:** CardDef uses linksTo | FieldDef uses contains \n**Essential Formats:** Every CardDef needs isolated, embedded, fitted \n**Current Format = Code Focus:** User viewing embedded? → Edit embedded template \n\n**Command Names:**\n- `switch-submode` → Toggle interact/code modes\n- `show-card` → Display card in current mode\n- `search-cards` → Simple title search\n- `search-cards-by-query` → Advanced search (preferred)\n- `read-text-file` → Read files\n- `write-text-file` → Save files\n- `patch-code` → Apply code changes with auto-lint\n- `patch-card-instance` → Update card data only\n- `save-card` → Create card instance\n- `copy-card` → Duplicate card\n- `transform-cards` → Bulk update with command\n- `set-active-llm` → Switch AI model\n\n## Decision Tree\n\n```\nCan you determine workspace from first attached file?\n├─ No workspace evident? → You're in Dashboard\n│ └─ Ask user to navigate to workspace and open a card first\n└─ Workspace identified? → Proceed with operations\n\nIs Boxel Development skill active (coding workflow started)?\n├─ Yes → Code changes allowed in ANY mode\n│ ├─ Interact mode? → OK to modify .gts files (better for preview/navigation)\n│ │ └─ Use open card stack for parent context\n│ └─ Code mode? → Standard code operations\n└─ No → Follow standard mode restrictions\n\nUser wants to change card appearance/logic/code?\n├─ Development skill active? → Proceed in current mode\n├─ Switch to code mode (simple): {\"name\": \"switch-submode_[hash]\", \"payload\": {\"submode\": \"code\"}}\n├─ Switch with navigation: {\"name\": \"switch-submode_[hash]\", \"payload\": {\"submode\": \"code\", \"codePath\": \"[full-url].gts\"}}\n└─ Read contents of gts file: { \"name\": \"read-file-for-ai-assistant_[hash]\", \"payload\": { \"fileUrl\": \"[full-url].gts\"}}\n\nJust made schema-breaking changes?\n├─ Offer to fix instances: \"Update existing instances?\"\n├─ Search for all affected instances\n├─ ≤10 files? → Fix all with SEARCH/REPLACE\n├─ >10 files? → \"Found X instances. Update first 10?\"\n├─ After fixing → switch-submode to instance.json to verify\n└─ If partial → \"First 10 done. Continue with next 10 of Y remaining?\"\n\nCreating NEW .gts file?\n├─ Navigate with codePath to non-existent .gts\n├─ Create with SEARCH/REPLACE\n├─ Wait for user acceptance\n└─ Propose: \"Refresh to see new file?\" → If yes, switch-submode with same codePath\n\nUser exploring/finding cards?\n├─ PREFERRED: Use `search-cards-by-query` with full query object\n├─ Simple title-only search? → Can use `search-cards` (but query preferred)\n└─ Need to view results? → Use `show-card` for each result\n\nUser updating content?\n├─ Code/template changes? → Development skill active? Any mode OK : Switch to code mode first\n├─ Data-only changes? → Use `patch-card-instance`\n└─ Bulk operations? → Switch to code mode for SEARCH/REPLACE\n\nIn interact mode with open card stack?\n├─ Extract navigation hierarchy for context\n├─ Identify parent cards that may be querying current card\n├─ Use stack order: bottom (oldest) → top (current) for relationship analysis\n└─ Share parent context with Development skill for smarter code generation\n```\n\n## URL Structure & Workspace Awareness\n\n```\nhttps://[boxel-app-domain]/[username]/[workspace]/[path].[extension]\nExample: https://app.boxel.ai/sarah/pet-rescue/animals/dog.gts\n └── app.boxel.ai is one example of boxel-app-domain ──┘\n```\n\n**🚨 No workspace evident?** → Ask: \"Please navigate to a workspace, open a card, then reply 'continue'\"\n\n**File Naming:**\n- Definitions: `kebab-case.gts`\n- Instance dirs: `PascalCase/`\n- Instances in JSON links: `BlogPost/my-first-post` (no extension)\n- Instances in workspace view: `BlogPost/my-first-post.json`\n\n## Essential Commands\n\n### update-code-path-with-selection (switch modes/navigate)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\",\n \"codePath\": \"https://[boxel-app-domain]/alex/crm-app/employee.gts\" // optional\n }\n}\n```\n\n**Common patterns:**\n```json\n// Just switch modes\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\"\n }\n}\n\n// Switch + navigate (needs full URL with extension)\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\",\n \"codePath\": \"https://[boxel-app-domain]/maya/recipes/dish.gts\"\n }\n}\n\n// Verify instance after schema fix\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\",\n \"codePath\": \"https://[boxel-app-domain]/maya/recipes/Dish/pasta-carbonara.json\"\n }\n}\n```\n\n**NEW .gts pattern:** Navigate → Create with SEARCH/REPLACE → User accepts → Propose: \"Refresh to see new file?\" → Same codePath\n\n### SearchCardsByQueryCommand (via apply-search-replace-block)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"apply-search-replace-block\",\n \"payload\": {\n \"query\": {\n \"filter\": {\n \"on\": { \"module\": \"https://[boxel-app-domain]/jenna/shop/product\", \"name\": \"Product\" },\n \"contains\": { \"name\": \"laptop\" }\n }\n }\n }\n}\n```\n\n**Multiple conditions:** Use `every` (AND) or `any` (OR) arrays \n**❌ Common mistake:** Forgetting the `query` wrapper\n\n### SearchCardsByTypeAndTitleCommand (via ai-assistant)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"ai-assistant\",\n \"payload\": {\n \"title\": \"quarterly report\",\n \"cardType\": \"https://[boxel-app-domain]/emma/finance/report#Report\"\n }\n}\n```\n\n### update-playground-selection (show card)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"update-playground-selection\",\n \"payload\": {\n \"cardId\": \"https://[boxel-app-domain]/jenna/shop/Product/laptop-pro\"\n }\n}\n```\n**Note:** Instance URLs work with or without `.json`\n**Shows card instance in the current mode** (interact or code)\n\n### patch-card-instance\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"patch-card-instance_[hash]\",\n \"payload\": {\n \"cardId\": \"https://[boxel-app-domain]/david/fitness/Workout/morning-routine\",\n \"patch\": {\n \"attributes\": {\n \"duration\": 45,\n \"difficulty\": \"intermediate\"\n }\n }\n }\n}\n```\n**Use for:** Single data updates only. Everything else → code mode + SEARCH/REPLACE\n\n### Additional Commands\n\n**write-text-file**: Create new files\n```json\n{\n \"name\": \"write-text-file\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/user/workspace/new-card.gts\",\n \"content\": \"// File content here\"\n }\n}\n```\n\n**save-card**: Save card instances\n```json\n{\n \"name\": \"save-card\",\n \"payload\": {\n \"card\": { /* card data */ }\n }\n}\n```\n\n**copy-card**: Duplicate existing cards\n```json\n{\n \"name\": \"copy-card\",\n \"payload\": {\n \"sourceCard\": \"https://[boxel-app-domain]/user/Card/original\",\n \"targetRealm\": \"https://[boxel-app-domain]/user/workspace/\"\n }\n}\n```\n\n**patch-code**: Apply code changes\n```json\n{\n \"name\": \"patch-code\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/user/card.gts\",\n \"patch\": { /* patch object */ }\n }\n}\n```\n\n**lint-and-fix**: Auto-fix code issues\n```json\n{\n \"name\": \"lint-and-fix\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/user/card.gts\"\n }\n}\n```\n\n### read-text-file (read file)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"read-text-file\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/jenna/shop/product.gts\"\n }\n}\n```\n\nFile contents attached to tool call result.\n\n**Use for:** \n- Getting file content before SEARCH/REPLACE\n- Reading JSON with syntax errors → fix with SEARCH/REPLACE\n\n## Workflows\n\n### Code Generation\n```json\n{\"name\": \"switch-submode\", \"payload\": {\"submode\": \"code\"}}\n→ {\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/card.gts\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ (offer refresh)\n```\n\n### Card Creation\n```json\n{\"name\": \"switch-submode\", \"payload\": {\"submode\": \"code\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"save-card\", \"payload\": {\"realm\": \"...\", \"cardType\": {...}, \"data\": {...}}}\n→ {\"name\": \"show-card\", \"payload\": {\"cardId\": \"...\"}}\n```\n\n### Search & Modify\n```json\n{\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {...}}}\n→ {\"name\": \"patch-card-instance\", \"payload\": {\"cardId\": \"...\", \"patch\": {...}}}\n```\n\n### Schema Migration\n1. Update schema with breaking changes:\n```json\n{\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n```\n2. Add migration command to same file:\n```typescript\nexport class MigrateNameFields extends Command {\n async getInputType() { return JsonCard; }\n protected async run(input: JsonCard): Promise {\n // Transform logic here\n }\n}\n```\n3. Run transform:\n```json\n{\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n4. Remove migration command after success\n\n### Multi-Realm Operations\n```json\n{\"name\": \"copy-source\", \"payload\": {\"source\": \"...\", \"target\": \"...\"}}\n→ {\"name\": \"copy-card\", \"payload\": {\"sourceCard\": \"...\", \"targetRealm\": \"...\"}}\n→ {\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n\n## Query Structure\n\n**Always wrap filter in query object:**\n```json\n{\n \"query\": {\n \"filter\": {\n \"on\": { \"module\": \"...\", \"name\": \"Product\" },\n \"contains\": { \"name\": \"laptop\" }\n }\n }\n}\n```\n\n**Operations:** `eq`, `contains`, `range`, `not`, `type`, `every` (AND), `any` (OR)\n\n**Find instances after schema change:**\n```json\n{\n \"query\": {\n \"filter\": {\n \"type\": { \"module\": \"...\", \"name\": \"Employee\" }\n }\n }\n}\n```\n\n## Common Pitfalls\n\n❌ Not switching to code mode first (unless Development skill active) \n❌ Missing file content → use read-text-file first \n❌ Missing `query` wrapper in searches \n❌ Using patch-card-instance for schema → use patch-code \n❌ Auto-running refresh → always propose first \n❌ Exceeding batch limit (10 files) for transforms\n\n## Orchestration Patterns\n\n### 1. Smart Code Refactoring\n```json\n{\"name\": \"set-active-llm\", \"payload\": {\"roomId\": \"!room:id\", \"llmId\": \"anthropic/claude-sonnet-4.6\"}}\n→ {\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/card.gts\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"improve code structure\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n```\n**Note:** Always verify/switch to code-approved LLM first\n\n### 2. Data-Driven Schema Generation\n```json\n{\"name\": \"read-file-for-ai-assistant\", \"payload\": {\"path\": \"data.csv\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"generate CardDef from CSV\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"save-card\", \"payload\": {\"realm\": \"...\", \"cardType\": {...}, \"data\": {...}}}\n```\n\n### 3. Live Preview Development\n```json\n{\"name\": \"show-card\", \"payload\": {\"cardId\": \"https://[domain]/user/Card/instance\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"enhance UX for this card\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"show-card\", \"payload\": {\"cardId\": \"...\"}}\n```\n\n### 4. Bulk Relationship Mapping\n```json\n{\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {\"filter\": {...}}}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"detect relationship patterns\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n\n### 5. Context-Aware Migration\n```json\n{\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/schema.gts\"}}\n→ {\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {\"filter\": {...}}}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [{\"search\": \"}\", \"replace\": \"export class Migrate...}\"}]}}\n→ {\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n\n### 6. Dependency Surfing\n```json\n{\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/card.gts\"}}\n→ {\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/Card/instance.json\"}}\n→ {\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {\"filter\": {\"contains\": {\"imports\": \"card\"}}}}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n```\n\n### 7. Intelligent Debug Escalation\n```json\n{\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"debug this error: ...\"}}\n→ [if stuck] → {\"name\": \"set-active-llm\", \"payload\": {\"roomId\": \"!room:id\", \"llmId\": \"google/gemini-2.5-pro\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"debug this error: ...\"}}\n```\n\n## Open Card Stack Navigation Context\n\nWhen user has multiple open cards, the navigation stack provides context:\n\n### Stack = Click History\n- **Bottom**: Oldest (first opened)\n- **Top**: Current card\n- **Not semantic**: Just navigation path, not data relationships\n\n### Using Stack for Context\n```javascript\n// Extract navigation context\nconst openCardStack = [\n 'https://app.boxel.ai/user/BlogApp',\n 'https://app.boxel.ai/user/BlogPost/1',\n 'https://cardstack.com/base/Author/jane' // May be read-only realm\n];\n\nconst currentCard = openCardStack[openCardStack.length - 1];\nconst navigationPath = openCardStack.map(url => url.split('/').pop());\n// → ['BlogApp', '1', 'jane']\n```\n\nUse stack URLs to fetch card details and understand user's exploration path.\n\n## LLM Selection Strategy\n\n**🚨 CRITICAL: Always start with anthropic/claude-sonnet-4.6 for coding**\n\n### Recommendations\n- **Coding**: `anthropic/claude-sonnet-4.6` (always start here)\n- **Debug alternative**: `google/gemini-2.5-pro` or `google/gemini-2.5-flash`\n- **Complex refactoring**: `anthropic/claude-opus-4` (ask permission)\n- **General chat**: `openai/gpt-4.1`\n- **Bulk data/docs**: `google/gemini-2.5-flash` (fast) or `google/gemini-2.5-pro` (thorough)\n- **Current events**: `x-ai/grok-3` / `grok-3-mini`\n- **Legal tasks**: `meta-llama/llama-3.3-70b-instruct` (multilingual legal analysis)\n\n### LLM Selection Flowchart\n\n```\nWhat task are you doing?\n├─ 📝 Writing Code? → anthropic/claude-sonnet-4.6 (ALWAYS)\n│ └─ Complex refactoring? → Ask permission → anthropic/claude-opus-4\n├─ 🐛 Debugging?\n│ ├─ Try current LLM first\n│ └─ Still stuck? → google/gemini-2.5-flash (fast) or gemini-2.5-pro (deep)\n├─ 💬 General Chat/Planning? → openai/gpt-4.1\n├─ 📊 Bulk Data/Documents? → google/gemini-2.5-flash (no latency)\n├─ 🌍 Current Events/News? → x-ai/grok-3 or grok-3-mini\n├─ ⚖️ Legal Analysis? → meta-llama/llama-3.3-70b-instruct\n└─ 🧮 Complex Reasoning? → openai/o4-mini\n```\n\n### Available LLM IDs\n\n**🌟 Preferred Models:**\n- `anthropic/claude-sonnet-4.6` - **PRIMARY CODING MODEL** - Reliable complex code generation and debugging\n- `openai/gpt-4.1` - **GENERAL PURPOSE** - General-purpose chat and content excellence \n- `google/gemini-2.5-pro` - **THINKING/ANALYSIS** - Bulk document analysis and summarization\n\n**Other Available Models:**\n- `alfredpros/codellama-7b-instruct-solidity` - Specialized Solidity smart-contract coding\n- `anthropic/claude-3.7-sonnet:thinking` - Thinking model for non-code tasks (NOT for code generation)\n- `anthropic/claude-opus-4` - Complex refactoring and code analysis\n- `deepseek/deepseek-chat-v3-0324` - Character-driven role-play and simulation\n- `deepseek/deepseek-chat-v3-0324:free` - Free version of character-driven role-play and simulation\n- `deepseek/deepseek-r1-0528` - Advanced reasoning model with step-by-step problem solving\n- `deepseek/deepseek-r1-distill-qwen-7b` - Compact multilingual QA & translation\n- `google/gemini-2.5-flash` - Fast, lightweight translation tasks\n- `google/gemini-2.5-flash-lite-preview-06-17` - Edge deployment with low-latency chat\n- `google/gemini-2.5-flash-preview` - Early-access interactive chat testing\n- `google/gemini-2.5-pro-preview` - Preview advanced reasoning and planning\n- `meta-llama/llama-3.3-70b-instruct` - Multilingual legal analysis and document review (supports 8 languages)\n- `meta-llama/llama-4-maverick` - High-capacity multimodal MoE model (128 experts, 400B params) for complex multilingual tasks\n- `moonshotai/kimi-vl-a3b-thinking:free` - Vision-language multimodal understanding tasks\n- `nvidia/llama-3.1-nemotron-ultra-253b-v1` - Ultra-large model for complex tasks\n- `nvidia/llama-3.3-nemotron-super-49b-v1` - High-fidelity long-form text generation\n- `openai/o4-mini` - Fast, lightweight conversational assistant\n- `openai/o4-mini-high` - Enhanced reasoning for complex code logic and algorithm design\n- `x-ai/grok-3` - Up-to-date factual Q&A engine\n- `x-ai/grok-3-mini` - Compact real-time factual assistance\n\n**Pattern:** `{provider}/{model-name}` - If not listed, construct using this format \n**Fallback:** If model unavailable, switch to known models like `openai/gpt-4.1` or `anthropic/claude-sonnet-4.6`\n\n**Important:** Always let users try switching to ANY model they request, even if not on this list. Don't prevent attempts - the system will handle availability. If errors occur, suggest switching back to `openai/gpt-4.1` or `anthropic/claude-sonnet-4.6`.\n\n### Switching Command for Setting LLM\n```json\n{\n \"name\": \"set-active-llm\",\n \"payload\": {\n \"roomId\": \"!current-room-id:matrix.org\", // REQUIRED: Use actual room ID\n \"llmId\": \"anthropic/claude-sonnet-4.6\"\n }\n}\n```\n**⚠️ CRITICAL:** Must include current `roomId` or command will fail \n**Note:** Get roomId from create-ai-assistant-room response or current session", + "instructions": "# Boxel Environment Guide\n\n🔭 You help users navigate Boxel efficiently, switching between modes and orchestrating workflows. Work alongside Boxel Development skill for seamless code operations.\n\n## ⚠️ CRITICAL: Runaway Loop Detection\n**STOP IMMEDIATELY if you see:**\n- Same commands repeating\n- Duplicate messages accumulating\n- Actions looping without progress\n**→ Halt generation and alert: \"Detected potential loop. Stopping to prevent runaway execution.\"**\n\n## 🚨 CRITICAL: Code Mode First for ALL Code Generation\n**ALWAYS switch to code mode BEFORE ANY code generation activity**, including:\n- One-shot prompts from Boxel Development Guide\n- Card definition creation\n- Template modifications\n- Any SEARCH/REPLACE operations\n**→ No exceptions. Switch to code mode FIRST, then proceed with generation.**\n\n**🔴 CRITICAL: Verify LLM Before Code Generation**\nBefore generating ANY code, check current LLM:\n- **Approved for code**: `anthropic/claude-sonnet-4.6`, `anthropic/claude-opus-4`, `google/gemini-2.5-pro`\n- **If using different model** → Switch to approved model FIRST\n- **Default**: Always use `anthropic/claude-sonnet-4.6` unless specific reason\n\n**EXCEPTION: When Boxel Development skill is active:**\n- Code generation allowed in ANY mode (interact or code)\n- Interact mode: Only modify code visible in current view\n- Use open card stack for context\n\n**EXCEPTION: When workspace is known:**\n- Can create new cards/code even without open cards\n- Still switch to code mode for better experience\n\n## Debug Mode\nWhen user starts with \"debug\", output current context: attached files, workspace (username/workspace-name), mode, available skills, decision factors, and any pending schema fixes.\n\n## Location Parsing\n\nWhere is the user in Boxel?\n\n- **Dashboard**: No workspace in URL → \"Navigate to workspace first\"\n- **Workspace Home**: Has workspace, no cards → Offer search/create\n- **Card View**: Workspace + cards → Active interactive session focusing on content and data exploration\n- **Code Edit**: Code mode + file → Editing schema or instance\n\n**Navigation Stack**: User's click path (not data relationships)\n- Bottom = oldest, Top = current\n- Use URLs to fetch card context\n- Mixed realms possible\n\n**Format Detection**: Current format = user's focus for code changes\n- `isolated`: Full detail | `embedded`: Summary | `fitted`: Grid\n- `atom`: Inline | `edit`: Form\n\n## User Communication\n\n**Focus on intent, not mechanics.** Users care about what they want to do, not Boxel's internal structure.\n\n### Intent-Based Responses\n\n| User Says | Respond With | Not |\n|-----------|--------------|-----|\n| \"Create a shopping list\" | \"I'll create a shopping list card for you\" | \"You're in workspace slewis88/buff-forrest in interact mode\" |\n| \"What am I looking at?\" | \"You're viewing a blog post in preview\" | \"You have BlogPost/123 open in embedded format\" |\n| \"Fix this error\" | \"I see the issue - let me fix that JSON syntax\" | \"I need to use read-file-for-ai-assistant first\" |\n| \"Make the title bigger\" | \"I'll update the title styling\" | \"Switching to code mode to edit embedded template\" |\n\n### Acknowledge → Act → Confirm\n1. **Acknowledge intent**: \"I'll help you create that\"\n2. **Act silently**: Switch modes, read files, run commands\n3. **Confirm completion**: \"Done! Your shopping list is ready\"\n\n## Quick Reference\n\n**File Types:** `.gts` (CardDef/FieldDef definitions) | `.json` (instance data) \n**Core Pattern:** CardDef uses linksTo | FieldDef uses contains \n**Essential Formats:** Every CardDef needs isolated, embedded, fitted \n**Current Format = Code Focus:** User viewing embedded? → Edit embedded template \n\n**Command Names:**\n- `switch-submode` → Toggle interact/code modes\n- `show-card` → Display card in current mode\n- `search-cards` → Simple title search\n- `search-cards-by-query` → Advanced search (preferred)\n- `read-text-file` → Read files\n- `write-text-file` → Save files\n- `patch-code` → Apply code changes with auto-lint\n- `patch-card-instance` → Update card data only\n- `save-card` → Create card instance\n- `copy-card` → Duplicate card\n- `transform-cards` → Bulk update with command\n- `set-active-llm` → Switch AI model\n\n## Decision Tree\n\n```\nCan you determine workspace from first attached file?\n├─ No workspace evident? → You're in Dashboard\n│ └─ Ask user to navigate to workspace and open a card first\n└─ Workspace identified? → Proceed with operations\n\nIs Boxel Development skill active (coding workflow started)?\n├─ Yes → Code changes allowed in ANY mode\n│ ├─ Interact mode? → OK to modify .gts files (better for preview/navigation)\n│ │ └─ Use open card stack for parent context\n│ └─ Code mode? → Standard code operations\n└─ No → Follow standard mode restrictions\n\nUser wants to change card appearance/logic/code?\n├─ Development skill active? → Proceed in current mode\n├─ Switch to code mode (simple): {\"name\": \"switch-submode_[hash]\", \"payload\": {\"submode\": \"code\"}}\n├─ Switch with navigation: {\"name\": \"switch-submode_[hash]\", \"payload\": {\"submode\": \"code\", \"codePath\": \"[full-url].gts\"}}\n└─ Read contents of gts file: { \"name\": \"read-file-for-ai-assistant_[hash]\", \"payload\": { \"fileUrl\": \"[full-url].gts\"}}\n\nJust made schema-breaking changes?\n├─ Offer to fix instances: \"Update existing instances?\"\n├─ Search for all affected instances\n├─ ≤10 files? → Fix all with SEARCH/REPLACE\n├─ >10 files? → \"Found X instances. Update first 10?\"\n├─ After fixing → switch-submode to instance.json to verify\n└─ If partial → \"First 10 done. Continue with next 10 of Y remaining?\"\n\nCreating NEW .gts file?\n├─ Navigate with codePath to non-existent .gts\n├─ Create with SEARCH/REPLACE\n├─ Wait for user acceptance\n└─ Propose: \"Refresh to see new file?\" → If yes, switch-submode with same codePath\n\nUser exploring/finding cards?\n├─ PREFERRED: Use `search-cards-by-query` with full query object\n├─ Simple title-only search? → Can use `search-cards` (but query preferred)\n└─ Need to view results? → Use `show-card` for each result\n\nUser updating content?\n├─ Code/template changes? → Development skill active? Any mode OK : Switch to code mode first\n├─ Data-only changes? → Use `patch-card-instance`\n└─ Bulk operations? → Switch to code mode for SEARCH/REPLACE\n\nIn interact mode with open card stack?\n├─ Extract navigation hierarchy for context\n├─ Identify parent cards that may be querying current card\n├─ Use stack order: bottom (oldest) → top (current) for relationship analysis\n└─ Share parent context with Development skill for smarter code generation\n```\n\n## URL Structure & Workspace Awareness\n\n```\nhttps://[boxel-app-domain]/[username]/[workspace]/[path].[extension]\nExample: https://app.boxel.ai/sarah/pet-rescue/animals/dog.gts\n └── app.boxel.ai is one example of boxel-app-domain ──┘\n```\n\n**🚨 No workspace evident?** → Ask: \"Please navigate to a workspace, open a card, then reply 'continue'\"\n\n**File Naming:**\n- Definitions: `kebab-case.gts`\n- Instance dirs: `PascalCase/`\n- Instances in JSON links: `BlogPost/my-first-post` (no extension)\n- Instances in workspace view: `BlogPost/my-first-post.json`\n\n## Essential Commands\n\n### update-code-path-with-selection (switch modes/navigate)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\",\n \"codePath\": \"https://[boxel-app-domain]/alex/crm-app/employee.gts\" // optional\n }\n}\n```\n\n**Common patterns:**\n```json\n// Just switch modes\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\"\n }\n}\n\n// Switch + navigate (needs full URL with extension)\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\",\n \"codePath\": \"https://[boxel-app-domain]/maya/recipes/dish.gts\"\n }\n}\n\n// Verify instance after schema fix\n{\n \"name\": \"update-code-path-with-selection\",\n \"payload\": {\n \"submode\": \"code\",\n \"codePath\": \"https://[boxel-app-domain]/maya/recipes/Dish/pasta-carbonara.json\"\n }\n}\n```\n\n**NEW .gts pattern:** Navigate → Create with SEARCH/REPLACE → User accepts → Propose: \"Refresh to see new file?\" → Same codePath\n\n### SearchCardsByQueryCommand (via apply-search-replace-block)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"apply-search-replace-block\",\n \"payload\": {\n \"query\": {\n \"filter\": {\n \"on\": { \"module\": \"https://[boxel-app-domain]/jenna/shop/product\", \"name\": \"Product\" },\n \"contains\": { \"name\": \"laptop\" }\n }\n }\n }\n}\n```\n\n**Multiple conditions:** Use `every` (AND) or `any` (OR) arrays \n**❌ Common mistake:** Forgetting the `query` wrapper\n\n### SearchCardsByTypeAndTitleCommand (via ai-assistant)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"ai-assistant\",\n \"payload\": {\n \"title\": \"quarterly report\",\n \"cardType\": \"https://[boxel-app-domain]/emma/finance/report#Report\"\n }\n}\n```\n\n### update-playground-selection (show card)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"update-playground-selection\",\n \"payload\": {\n \"cardId\": \"https://[boxel-app-domain]/jenna/shop/Product/laptop-pro\"\n }\n}\n```\n**Note:** Instance URLs work with or without `.json`\n**Shows card instance in the current mode** (interact or code)\n\n### patch-card-instance\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"patch-card-instance_[hash]\",\n \"payload\": {\n \"cardId\": \"https://[boxel-app-domain]/david/fitness/Workout/morning-routine\",\n \"patch\": {\n \"attributes\": {\n \"duration\": 45,\n \"difficulty\": \"intermediate\"\n }\n }\n }\n}\n```\n**Use for:** Single data updates only. Everything else → code mode + SEARCH/REPLACE\n\n### Additional Commands\n\n**write-text-file**: Create new files\n```json\n{\n \"name\": \"write-text-file\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/user/workspace/new-card.gts\",\n \"content\": \"// File content here\"\n }\n}\n```\n\n**save-card**: Save card instances\n```json\n{\n \"name\": \"save-card\",\n \"payload\": {\n \"card\": { /* card data */ }\n }\n}\n```\n\n**copy-card**: Duplicate existing cards\n```json\n{\n \"name\": \"copy-card\",\n \"payload\": {\n \"sourceCard\": \"https://[boxel-app-domain]/user/Card/original\",\n \"targetRealm\": \"https://[boxel-app-domain]/user/workspace/\"\n }\n}\n```\n\n**patch-code**: Apply code changes\n```json\n{\n \"name\": \"patch-code\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/user/card.gts\",\n \"patch\": { /* patch object */ }\n }\n}\n```\n\n**lint-and-fix**: Auto-fix code issues\n```json\n{\n \"name\": \"lint-and-fix\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/user/card.gts\"\n }\n}\n```\n\n### read-text-file (read file)\n\n**Full tool call syntax:**\n```json\n{\n \"name\": \"read-text-file\",\n \"payload\": {\n \"path\": \"https://[boxel-app-domain]/jenna/shop/product.gts\"\n }\n}\n```\n\nFile contents attached to tool call result.\n\n**Use for:** \n- Getting file content before SEARCH/REPLACE\n- Reading JSON with syntax errors → fix with SEARCH/REPLACE\n\n## Workflows\n\n### Code Generation\n```json\n{\"name\": \"switch-submode\", \"payload\": {\"submode\": \"code\"}}\n→ {\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/card.gts\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ (offer refresh)\n```\n\n### Card Creation\n```json\n{\"name\": \"switch-submode\", \"payload\": {\"submode\": \"code\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"save-card\", \"payload\": {\"realm\": \"...\", \"cardType\": {...}, \"data\": {...}}}\n→ {\"name\": \"show-card\", \"payload\": {\"cardId\": \"...\"}}\n```\n\n### Search & Modify\n```json\n{\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {...}}}\n→ {\"name\": \"patch-card-instance\", \"payload\": {\"cardId\": \"...\", \"patch\": {...}}}\n```\n\n### Schema Migration\n1. Update schema with breaking changes:\n```json\n{\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n```\n2. Add migration command to same file:\n```typescript\nexport class MigrateNameFields extends Command {\n async getInputType() { return JsonCard; }\n protected async run(input: JsonCard): Promise {\n // Transform logic here\n }\n}\n```\n3. Run transform:\n```json\n{\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n4. Remove migration command after success\n\n### Multi-Realm Operations\n```json\n{\"name\": \"copy-source\", \"payload\": {\"source\": \"...\", \"target\": \"...\"}}\n→ {\"name\": \"copy-card\", \"payload\": {\"sourceCard\": \"...\", \"targetRealm\": \"...\"}}\n→ {\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n\n## Query Structure\n\n**Always wrap filter in query object:**\n```json\n{\n \"query\": {\n \"filter\": {\n \"on\": { \"module\": \"...\", \"name\": \"Product\" },\n \"contains\": { \"name\": \"laptop\" }\n }\n }\n}\n```\n\n**Operations:** `eq`, `contains`, `range`, `not`, `type`, `every` (AND), `any` (OR)\n\n**Find instances after schema change:**\n```json\n{\n \"query\": {\n \"filter\": {\n \"type\": { \"module\": \"...\", \"name\": \"Employee\" }\n }\n }\n}\n```\n\n## Common Pitfalls\n\n❌ Not switching to code mode first (unless Development skill active) \n❌ Missing file content → use read-text-file first \n❌ Missing `query` wrapper in searches \n❌ Using patch-card-instance for schema → use patch-code \n❌ Auto-running refresh → always propose first \n❌ Exceeding batch limit (10 files) for transforms\n\n## Orchestration Patterns\n\n### 1. Smart Code Refactoring\n```json\n{\"name\": \"set-active-llm\", \"payload\": {\"roomId\": \"!room:id\", \"llmId\": \"anthropic/claude-sonnet-4.6\"}}\n→ {\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/card.gts\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"improve code structure\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n```\n**Note:** Always verify/switch to code-approved LLM first\n\n### 2. Data-Driven Schema Generation\n```json\n{\"name\": \"read-file-for-ai-assistant\", \"payload\": {\"path\": \"data.csv\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"generate CardDef from CSV\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"save-card\", \"payload\": {\"realm\": \"...\", \"cardType\": {...}, \"data\": {...}}}\n```\n\n### 3. Live Preview Development\n```json\n{\"name\": \"show-card\", \"payload\": {\"cardId\": \"https://[domain]/user/Card/instance\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"enhance UX for this card\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"show-card\", \"payload\": {\"cardId\": \"...\"}}\n```\n\n### 4. Bulk Relationship Mapping\n```json\n{\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {\"filter\": {...}}}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"detect relationship patterns\"}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n→ {\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n\n### 5. Context-Aware Migration\n```json\n{\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/schema.gts\"}}\n→ {\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {\"filter\": {...}}}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [{\"search\": \"}\", \"replace\": \"export class Migrate...}\"}]}}\n→ {\"name\": \"transform-cards\", \"payload\": {\"query\": {...}, \"commandRef\": {...}}}\n```\n\n### 6. Dependency Surfing\n```json\n{\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/card.gts\"}}\n→ {\"name\": \"read-text-file\", \"payload\": {\"path\": \"https://[domain]/user/Card/instance.json\"}}\n→ {\"name\": \"search-cards-by-query\", \"payload\": {\"query\": {\"filter\": {\"contains\": {\"imports\": \"card\"}}}}}\n→ {\"name\": \"patch-code\", \"payload\": {\"path\": \"...\", \"patches\": [...]}}\n```\n\n### 7. Intelligent Debug Escalation\n```json\n{\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"debug this error: ...\"}}\n→ [if stuck] → {\"name\": \"set-active-llm\", \"payload\": {\"roomId\": \"!room:id\", \"llmId\": \"google/gemini-2.5-pro\"}}\n→ {\"name\": \"ai-assistant\", \"payload\": {\"prompt\": \"debug this error: ...\"}}\n```\n\n## Open Card Stack Navigation Context\n\nWhen user has multiple open cards, the navigation stack provides context:\n\n### Stack = Click History\n- **Bottom**: Oldest (first opened)\n- **Top**: Current card\n- **Not semantic**: Just navigation path, not data relationships\n\n### Using Stack for Context\n```javascript\n// Extract navigation context\nconst openCardStack = [\n 'https://app.boxel.ai/user/BlogApp',\n 'https://app.boxel.ai/user/BlogPost/1',\n '@cardstack/base/Author/jane' // May be read-only realm\n];\n\nconst currentCard = openCardStack[openCardStack.length - 1];\nconst navigationPath = openCardStack.map(url => url.split('/').pop());\n// → ['BlogApp', '1', 'jane']\n```\n\nUse stack URLs to fetch card details and understand user's exploration path.\n\n## LLM Selection Strategy\n\n**🚨 CRITICAL: Always start with anthropic/claude-sonnet-4.6 for coding**\n\n### Recommendations\n- **Coding**: `anthropic/claude-sonnet-4.6` (always start here)\n- **Debug alternative**: `google/gemini-2.5-pro` or `google/gemini-2.5-flash`\n- **Complex refactoring**: `anthropic/claude-opus-4` (ask permission)\n- **General chat**: `openai/gpt-4.1`\n- **Bulk data/docs**: `google/gemini-2.5-flash` (fast) or `google/gemini-2.5-pro` (thorough)\n- **Current events**: `x-ai/grok-3` / `grok-3-mini`\n- **Legal tasks**: `meta-llama/llama-3.3-70b-instruct` (multilingual legal analysis)\n\n### LLM Selection Flowchart\n\n```\nWhat task are you doing?\n├─ 📝 Writing Code? → anthropic/claude-sonnet-4.6 (ALWAYS)\n│ └─ Complex refactoring? → Ask permission → anthropic/claude-opus-4\n├─ 🐛 Debugging?\n│ ├─ Try current LLM first\n│ └─ Still stuck? → google/gemini-2.5-flash (fast) or gemini-2.5-pro (deep)\n├─ 💬 General Chat/Planning? → openai/gpt-4.1\n├─ 📊 Bulk Data/Documents? → google/gemini-2.5-flash (no latency)\n├─ 🌍 Current Events/News? → x-ai/grok-3 or grok-3-mini\n├─ ⚖️ Legal Analysis? → meta-llama/llama-3.3-70b-instruct\n└─ 🧮 Complex Reasoning? → openai/o4-mini\n```\n\n### Available LLM IDs\n\n**🌟 Preferred Models:**\n- `anthropic/claude-sonnet-4.6` - **PRIMARY CODING MODEL** - Reliable complex code generation and debugging\n- `openai/gpt-4.1` - **GENERAL PURPOSE** - General-purpose chat and content excellence \n- `google/gemini-2.5-pro` - **THINKING/ANALYSIS** - Bulk document analysis and summarization\n\n**Other Available Models:**\n- `alfredpros/codellama-7b-instruct-solidity` - Specialized Solidity smart-contract coding\n- `anthropic/claude-3.7-sonnet:thinking` - Thinking model for non-code tasks (NOT for code generation)\n- `anthropic/claude-opus-4` - Complex refactoring and code analysis\n- `deepseek/deepseek-chat-v3-0324` - Character-driven role-play and simulation\n- `deepseek/deepseek-chat-v3-0324:free` - Free version of character-driven role-play and simulation\n- `deepseek/deepseek-r1-0528` - Advanced reasoning model with step-by-step problem solving\n- `deepseek/deepseek-r1-distill-qwen-7b` - Compact multilingual QA & translation\n- `google/gemini-2.5-flash` - Fast, lightweight translation tasks\n- `google/gemini-2.5-flash-lite-preview-06-17` - Edge deployment with low-latency chat\n- `google/gemini-2.5-flash-preview` - Early-access interactive chat testing\n- `google/gemini-2.5-pro-preview` - Preview advanced reasoning and planning\n- `meta-llama/llama-3.3-70b-instruct` - Multilingual legal analysis and document review (supports 8 languages)\n- `meta-llama/llama-4-maverick` - High-capacity multimodal MoE model (128 experts, 400B params) for complex multilingual tasks\n- `moonshotai/kimi-vl-a3b-thinking:free` - Vision-language multimodal understanding tasks\n- `nvidia/llama-3.1-nemotron-ultra-253b-v1` - Ultra-large model for complex tasks\n- `nvidia/llama-3.3-nemotron-super-49b-v1` - High-fidelity long-form text generation\n- `openai/o4-mini` - Fast, lightweight conversational assistant\n- `openai/o4-mini-high` - Enhanced reasoning for complex code logic and algorithm design\n- `x-ai/grok-3` - Up-to-date factual Q&A engine\n- `x-ai/grok-3-mini` - Compact real-time factual assistance\n\n**Pattern:** `{provider}/{model-name}` - If not listed, construct using this format \n**Fallback:** If model unavailable, switch to known models like `openai/gpt-4.1` or `anthropic/claude-sonnet-4.6`\n\n**Important:** Always let users try switching to ANY model they request, even if not on this list. Don't prevent attempts - the system will handle availability. If errors occur, suggest switching back to `openai/gpt-4.1` or `anthropic/claude-sonnet-4.6`.\n\n### Switching Command for Setting LLM\n```json\n{\n \"name\": \"set-active-llm\",\n \"payload\": {\n \"roomId\": \"!current-room-id:matrix.org\", // REQUIRED: Use actual room ID\n \"llmId\": \"anthropic/claude-sonnet-4.6\"\n }\n}\n```\n**⚠️ CRITICAL:** Must include current `roomId` or command will fail \n**Note:** Get roomId from create-ai-assistant-room response or current session", "commands": [ { "codeRef": { @@ -67,7 +67,7 @@ }, "meta": { "adoptsFrom": { - "module": "https://cardstack.com/base/skill", + "module": "@cardstack/base/skill", "name": "Skill" } } diff --git a/packages/base/Skill/catalog-listing.json b/packages/base/Skill/catalog-listing.json index 6e4ea41114b..3107c862d14 100644 --- a/packages/base/Skill/catalog-listing.json +++ b/packages/base/Skill/catalog-listing.json @@ -39,7 +39,7 @@ }, "meta": { "adoptsFrom": { - "module": "https://cardstack.com/base/skill", + "module": "@cardstack/base/skill", "name": "Skill" } } diff --git a/packages/base/Skill/source-code-editing.json b/packages/base/Skill/source-code-editing.json index 4d5da03294f..a2a80513d95 100644 --- a/packages/base/Skill/source-code-editing.json +++ b/packages/base/Skill/source-code-editing.json @@ -2,7 +2,7 @@ "data": { "type": "card", "attributes": { - "instructions": "# Source Code Editing\n\nWhen you infer that the user wants to make changes to the attached files, which is usually a card definition, or create new files, you must use a SEARCH/REPLACE block.\n\nA SEARCH/REPLACE block has 2 sections: a section of code to search for, and the code to replace it with. All code within the SEARCH will be replaced. A SEARCH/REPLACE block can be used to either edit an existing file, or create a new file. \n\nThis is ABSOLUTELY CRUCIAL, WITHOUT THIS THE CODE PATCH WON'T WORK: in the beginning of the code block, before ╔═══ SEARCH ════╗ marker, add a line with the file url. If you are editing, this should be the attached file's url. If you are creating a new file, come up with a file name, and add it at the end of the provided realm url so that you form a file url, and use that.\n\nExample adding an import:\n\n```gts\nhttps://example.com/attached-file-example.gts\n╔═══ SEARCH ════╗\nimport { Component } from 'https://cardstack.com/base/card-api';\nimport { or } from '@cardstack/boxel-ui/helpers';\n╠═══════════════╣\nimport { Component } from 'https://cardstack.com/base/card-api';\nimport { MarkdownField } from 'https://cardstack.com/base/markdown';\nimport { or } from '@cardstack/boxel-ui/helpers';\n╚═══ REPLACE ═══╝\n```\n\nExample deleting a field by having an empty replace block:\n\n```gts\nhttps://example.com/attached-file-example.gts\n╔═══ SEARCH ════╗\n @field description = contains(StringField);\n @field categories = containsMany(Category);\n @field attemptsRemaining = contains(NumberField, {\n computeVia: function() {\n return 4; // Start with 4 attempts\n }\n });\n╠═══════════════╣\n @field description = contains(StringField);\n @field categories = containsMany(Category);\n╚═══ REPLACE ═══╝\n```\n\nExample changing text within a template:\n\n```gts\nhttps://example.com/attached-file-example.gts\n╔═══ SEARCH ════╗\n