From 680bfe0e7b01ed3e47c25dbac7d11252df95e46e Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Tue, 14 Jul 2026 16:28:47 -0400 Subject: [PATCH] Regenerate from boxel-workspaces main Picks up the boxel-skill-authoring skill and its index/glossary references (workspaces #20), the theme/UI skills update (#19), and the distilled portable learnings sweep (#21), which folds technical-rules.md into its successors. Co-Authored-By: Claude Fable 5 --- commands/boxel-create-card.md | 217 +++---------- commands/boxel-create-instance.md | 66 ++-- commands/boxel-design-card.md | 14 +- commands/boxel-develop-theme.md | 50 +-- commands/boxel-edit-template.md | 26 +- commands/boxel-search-cards.md | 6 +- commands/boxel-sync-workspace.md | 62 ++-- commands/distill-from-boxel-skills.md | 2 +- index.md | 41 +-- skills/boxel-create-edit-cards/SKILL.md | 50 +-- skills/boxel-design/SKILL.md | 2 +- skills/boxel-environment/SKILL.md | 7 +- .../references/card-tool-selection.md | 46 +++ .../references/fresh-realm-push-integrity.md | 32 ++ .../references/using-filedef-in-cards.md | 8 + skills/boxel-patterns/SKILL.md | 194 +---------- .../containsmany-sorted-render/README.md | 2 +- .../integrate-one-shot-llm/example.gts | 1 + .../README.md | 75 +++++ .../example.gts | 306 ++++++++++++++++++ .../layout-kanban-drag-drop/README.md | 2 +- .../patterns/link-host-mode-paths/README.md | 6 + .../organize-atomic-field-factory/example.gts | 1 + .../references/integration-surfaces.md | 12 +- .../references/pattern-authoring.md | 99 ++++++ .../references/pattern-backlog.md | 90 ++++++ skills/boxel-skill-authoring/SKILL.md | 69 ++++ skills/boxel-theme-development/SKILL.md | 2 +- .../references/design-md-adapter.md | 2 +- skills/boxel-ui-guidelines/SKILL.md | 5 +- .../references/checklist.md | 2 +- .../references/delegated-render-control.md | 26 +- .../references/print-and-published-output.md | 55 ++++ .../references/template-patterns.md | 18 ++ .../use-boxel-design-tokens-for-theming.md | 9 +- .../references/use-boxel-ui-components.md | 1 + ...se-container-queries-not-viewport-units.md | 6 +- skills/boxel/SKILL.md | 5 +- .../container-query-fitted-layout.md | 275 ++++++++-------- skills/boxel/references/core-patterns.md | 10 +- skills/boxel/references/design-playbook.md | 18 +- skills/boxel/references/fitted-formats.md | 6 +- skills/boxel/references/lint-workflow.md | 15 +- skills/boxel/references/quick-reference.md | 2 +- skills/boxel/references/styling-design.md | 10 +- skills/boxel/references/technical-rules.md | 87 ----- skills/glossary.md | 32 +- skills/source-code-editing/SKILL.md | 6 +- 48 files changed, 1230 insertions(+), 848 deletions(-) create mode 100644 skills/boxel-environment/references/card-tool-selection.md create mode 100644 skills/boxel-environment/references/fresh-realm-push-integrity.md create mode 100644 skills/boxel-patterns/patterns/integrate-three-js-3mf-fabrication/README.md create mode 100644 skills/boxel-patterns/patterns/integrate-three-js-3mf-fabrication/example.gts create mode 100644 skills/boxel-patterns/references/pattern-authoring.md create mode 100644 skills/boxel-patterns/references/pattern-backlog.md create mode 100644 skills/boxel-skill-authoring/SKILL.md create mode 100644 skills/boxel-ui-guidelines/references/print-and-published-output.md delete mode 100644 skills/boxel/references/technical-rules.md diff --git a/commands/boxel-create-card.md b/commands/boxel-create-card.md index 79ac585..b1d2614 100644 --- a/commands/boxel-create-card.md +++ b/commands/boxel-create-card.md @@ -20,191 +20,46 @@ boxel: ## Read (REQUIRED, in order) -1. **`skills/boxel/references/design-playbook.md`** — THE process. Four stages: mockup → extract → tokenize → derive. The verbatim Pentagram-art-director framing IS stage 1. Read this in full before any other skill. -2. **`skills/boxel-ui-guidelines/references/delegated-render-control.md`** — REQUIRED if this card embeds other cards via `<@fields.X @format='...' />`. Covers: format choice (fitted vs embedded — *upstream* of any CSS; pick the wrong one and you get empty boxes), the plural-field wrapper trap (`linksToMany` ≠ `containsMany` in class names), atom-on-dark-background invisibility, stagger via CSS-variable cascade through `display: contents`, and the per-format chrome contract. -3. **`skills/boxel-patterns/patterns/app-card-home-with-search/README.md`** — REQUIRED if this build is a card *family* (2+ related CardDefs). Build a Home CardDef alongside them so the realm has an entry point. -4. `skills/boxel-patterns/patterns/cardinfo-override-title/README.md` — the `cardTitle` override that respects user input. -5. `skills/boxel/SKILL.md` (focus on `references/core-concept.md` — CardInfo + computed pass-throughs). -6. `skills/boxel/references/base-field-catalog.md` — reach past `StringField` when the value has a known shape (`EmailField`, `DateRangeField`, `PercentageField`, etc.). -7. `skills/boxel/references/icons.md` — verify icons against the CDN HEAD before importing. -8. `skills/boxel/references/lint-workflow.md` — mandatory installed npm `boxel` lint gate. -9. `skills/source-code-editing/SKILL.md`. -10. Check `skills/boxel-patterns/SKILL.md` for a ready pattern matching the outcome (`automate-linked-to-me-lookup`, `format-morph-shared-component`, `polymorphic-field-subclass`, `resource-for-state`). - -## Procedure — apply the design playbook - -The playbook is mandatory for any user-facing card. Skip only for utility cards (lookup tables, internal config). For **card families** (2+ related CardDefs), stage 0 below is REQUIRED before any real schema is written — without it, fitted views end up pedestrian because the data model isn't rich enough to compose with. - -### Stage 0 — Planning artifacts (for card families) - -Produce these BEFORE writing any production CardDef. Each can ship as a CardDef whose `static isolated` IS the plan document, pushed to the realm so the user can review visually. See `boxel/references/design-playbook.md` for ASCII templates of each artifact. - -| Artifact | What it answers | -|---|---| -| Goal | What the family does in one paragraph | -| Brief | What makes this unique + important for users | -| DataModelPlan | ASCII data-flow diagram + schema sketch (CardDef list, FieldDef list, relationships, sensitive vs operational realm split) | -| Sample data | 3-5 dossiers per CardDef — real names, real prices, real photo captions | -| ASCII layout | Box layout for each CardDef's `isolated` view — where the hero image goes, where the eyebrow vs headline vs meta lives | -| MicroMockups | Hi-fi mockup of each format (`isolated` / `embedded` / `fitted` / `edit`) at desktop AND mobile, with the divider strategy decided. All cards responsive in `isolated` AND `edit`. | - -The source pattern is in `app.boxel.ai/.../actual-duck-82/{architecture-plan,data-model-plan,micro-mockups}.gts` — three CardDefs whose isolated templates ARE the plan documents. Without stage 0, agents reach for thin schemas (name + description + date) and produce fitted views with nothing to show. Stage 0 forces the rich-enough data model and the signature-field decision (which field is the visual hero of fitted: the photo? the price? the badge?). - -### Stage 1 — Mockup pass (NO variables) - -Write `static isolated` as an HTML/CSS mockup with hardcoded values: real fonts, real `#hex`, real px sizes. The verbatim brief: - -> Do a design exploration and generate only the above-the-fold view in isolated, framed as such. Write sample content for this use case and fit the elements in there as a design challenge executed by a brand-focused art director of Pentagram, judged by the preeminent taste maker in that field. (Specify who in your thinking, not your final summary.) - -Hold an internal taste-maker in mind. Schema must be rich enough that the design has something to compose with — add fields (`subtitle`, `eyebrow`, `cuisine`, `keyIngredient`, `rating`, `reviews`, etc.) the design demands. - -Apply intrinsic design taste: serif/sans pairing, weight rhythm (large light + tiny bold), letter-spaced micro-labels, ONE accent color in ≤2 places, editorial micro-objects (avatar with initial, drawn rule, eyebrow + section heading, rule-bracketed data slab). Write evocative sample content in the publication's voice (NYT, Cherry Bombe, Apartamento, Pitchfork — pick the register). - -### Stage 2 — Extract theme - -Scan stage-1 CSS and pull every distinct decision into named tokens. Write a Theme card whose `cssVariables` ARE the design's palette. Rule of two: tokenize if used twice+; leave inline if used once. Don't add tokens the mockup didn't earn. - -### Stage 3 — Tokenize isolated - -Replace hardcoded values with `var(--*)` references to the new theme. Pixel-identical to stage 1. Link `cardInfo.theme` on the instance. - -### Stage 4 — Derive fitted + embedded - -Use the established visual identity to author `static embedded` and `static fitted`. **Fitted MUST feature the card's media** (hero image, illustration, brand mark) if any. Same fonts, same accents, less content. - -### When embedding child cards (linksTo/linksToMany) - -If any format renders `<@fields.X @format='...' />`, read `delegated-render-control.md` BEFORE writing the parent's CSS. The host injects CardContainer chrome (rounded corners, halo, padding, `overflow: hidden`). The parent overrides via: -- Theme cascade (cleanest — when the child has its own `cardInfo.theme`) -- `:deep(.boxel-card-container)` from the parent's scoped CSS (workhorse) -- `@displayContainer={{false}}` (kills chrome entirely; pair with parent-owned chip span) - -For atoms in prose or in a bill list, the canonical pattern is `<@fields.X @format='atom' @displayContainer={{false}} />` inside a parent-owned `` so the parent controls borders, alignment, padding. - -### Other procedure steps - -- Inspect the realm to see existing conventions. -- Decide CardDef vs FieldDef and the relationship type (`contains` vs `linksTo`). -- **Override `cardTitle`** if the card has a natural primary identifier field. Use the canonical form (cardInfo.name → primary field → `Untitled `). Also override `cardDescription` if you can compute it from other fields. -- Assign a `static icon` on every CardDef and FieldDef (CDN-verified name). - -### Sample instances - -Create one or two `.json` instances when requested: - -- Link the Theme via `"cardInfo.theme": { "links": { "self": "../Theme/" } }` (relationship key has a literal dot). -- Include an explicit `attributes.cardInfo` object with `notes`, `name`, `summary`, `cardThumbnailURL` whenever a theme is linked. -- For nested app folders, prefer an absolute Theme URL in `cardInfo.theme.links.self` unless the relative path has been verified in the live app. -- **Theme JSON itself**: include `attributes.cardInfo` but OMIT `relationships["cardInfo.theme"]` entirely (no self-loop). -- Use `"self": null` for empty `linksTo`/`linksToMany`, NEVER `[]`. -- Include `meta.adoptsFrom.module` + `name`. -- Content should match the design's editorial voice — evocative and real-sounding, never Lorem ipsum. - -### Lint/compile verification - -- `npx boxel check ` is sync state only, NOT a compile or lint check. -- Use the installed npm `@cardstack/boxel-cli` 0.2.0+ lint surface: - - Before push: `npx boxel file lint --realm --file `. - - After push: `npx boxel lint --realm `. -- Clean lint means `No lint issues found` or JSON `messages: []`; `ok: true` with lint messages is not clean. -- Run server-side render validation after push too: hit `/_search-prerendered` for `embedded`/`fitted`/`atom`/`head` formats, and open the card in the live app to exercise `isolated`. - -### Final gate — verify via search, not via lint - -**Lint passing ≠ kit shipped.** Lint is a smoke test of file structure; it does NOT exercise the realm's actual module-load path. A kit can lint clean with 0 of N instances actually indexed. - -For every CardDef built in this command, run BOTH gates before declaring done: - -```sh -REALM="" - -# Gate A: module-load probe (the realm-server actually evaluates the .gts) -npx boxel run-command @cardstack/boxel-host/tools/get-card-type-schema/default \ - --realm "$REALM" \ - --input "{\"codeRef\":{\"module\":\"${REALM}\",\"name\":\"\"}}" \ - --json -# Must return: status: "ready" -# `cardOrThunk was undefined` → bad import or unresolved cycle somewhere in the chain. - -# Gate B: typed-search count (instances are recognized as the type) -npx boxel search --realm "$REALM" \ - --query "{\"filter\":{\"type\":{\"module\":\"${REALM}\",\"name\":\"\"}}}" \ - --json -# Count must equal the number of instances you intended to push. -# Use absolute module URLs in --query; relative paths like ./ don't resolve from the CLI. -``` - -If Gate A returns error OR Gate B count is short, the kit is NOT shipped. Do NOT report done. - -### Icon — CDN-verify before assignment - -Workspace source-file grep is NOT proof. Probe every icon you plan to use: - -```sh -for icon in ; do - code=$(curl -s -o /dev/null -w "%{http_code}" \ - "https://boxel-icons.boxel.ai/@cardstack/boxel-icons/v1/icons/${icon}.js") - printf " %-20s HTTP %s\n" "$icon" "$code" -done -``` - -Require 200 before adding `static icon = X`. See `boxel/references/icons.md` "CDN verification is the only proof an icon exists." +1. `skills/boxel/references/design-playbook.md` — THE process: stage 0 planning (mandatory for card families) + mockup → extract → tokenize → derive. Read in full first. +2. `skills/boxel-ui-guidelines/references/delegated-render-control.md` — REQUIRED if this card embeds other cards via `<@fields.X @format='...' />`. +3. `skills/boxel-patterns/patterns/app-card-home-with-search/README.md` — REQUIRED for card families (2+ related CardDefs). +4. `skills/boxel-patterns/patterns/cardinfo-override-title/README.md` — the `cardTitle` override. +5. `skills/boxel/SKILL.md` (cardinal rules; focus `references/core-concept.md`). +6. `skills/boxel/references/base-field-catalog.md` — reach past `StringField` when the value has a known shape. +7. `skills/boxel/references/icons.md` — CDN verification is the only proof an icon exists. +8. `skills/boxel/references/lint-workflow.md` — the lint gate, and what lint does NOT catch. +9. `skills/boxel-environment/references/indexing-operations.md` — post-push verification gates. +10. `skills/source-code-editing/SKILL.md`. +11. Check `skills/boxel-patterns/SKILL.md` for a ready pattern matching the outcome. + +## Procedure + +1. Inspect the realm for existing conventions. Decide CardDef vs FieldDef and `contains` vs `linksTo`. +2. **Card family?** Produce the Stage 0 artifacts from `design-playbook.md` (Goal · Brief · DataModelPlan with thunk marks · Sample Data · ASCII Layouts · MicroMockups with content matrices) BEFORE any production schema. +3. **Run design-playbook stages 1-4** (the verbatim art-director brief lives there). Mandatory for any user-facing card; skip only for utility cards. Push after each stage. +4. Implement every `fitted` template per `boxel/references/container-query-fitted-layout.md`; walk all 16 sizes per `boxel/references/fitted-formats.md`. +5. If any format embeds children, apply `delegated-render-control.md` before writing the parent's CSS. +6. Per CardDef: override `cardTitle` (and `cardDescription` when computable); decide `static prefersWideFormat` now (`boxel/references/prefers-wide-format.md`); assign a `static icon` only after a CDN probe returns 200. +7. For families, build the Home card per `app-card-home-with-search`. +8. Sample instances when requested: follow `/boxel-create-instance`. Content in the design's editorial voice — never Lorem ipsum. +9. Lint gate per `lint-workflow.md`: `npx boxel file lint` before push, `npx boxel lint` after. +10. **Verification gates per `indexing-operations.md` — lint is NOT the gate.** Per CardDef: module-load probe (`get-card-type-schema` → `status: ready`) AND typed-search count matches intended instance count. +11. Render smoke test per CardDef in the live app before claiming done. ## Done Criteria (self-verify) -**Design playbook (the spine):** -- [ ] Stage 1 mockup was a real designed surface — internal taste-maker held in mind, schema rich enough to compose with, evocative sample content, two-family typography pairing, weight rhythm (large light + tiny bold), letter-spaced eyebrow, ONE accent in ≤2 places, 2-3 editorial micro-objects. -- [ ] Stage 2 theme was EXTRACTED from the mockup (rule of two — tokenize if used twice+), not picked from a generic shadcn palette before the design existed. -- [ ] Stage 3 isolated is pixel-identical to stage 1 but uses `var(--*)` for every value that appears twice+. -- [ ] Stage 4 fitted **features the card's media** if the card has any (hero image, illustration, brand mark, color swatch). Fitted is not a text-only metadata strip when imagery is available. -- [ ] Fitted view **renders correctly at ALL 16 named sizes** (Small/Medium/Large Badge; Single/Double/Triple Strip; Double-Wide/Triple-Wide Strip; Small/Regular/CardsGrid/Tall/Large Tile; Compact/Full/Expanded Card). No content overflow, no clipped text, type hierarchy legible at every cell, sub-format routing (badge/strip/tile/card) hits correctly. Walk the table in `boxel/references/fitted-formats.md` and confirm each row. Verify via the live app's fitted preview — not by `npx boxel check`. - -**Chrome contract (every format):** -- [ ] No format's outermost element decorates with `border-radius`, `border`, `box-shadow`, opaque `background`, or `overflow` — those belong to the host's CardContainer or the parent's `:deep()` override. Brand outer-treatment goes on the Theme card via `--radius` / `--background` / `--border`. -- [ ] `fitted` outermost doesn't set `width`, `height`, `min/max-height`, `container-type`, or `container-name` — the host's `.field-component-card.fitted-format` sets those. -- [ ] If this card embeds children via `<@fields.X @format='...' />`, the parent's CSS overrides CardContainer chrome (via `:deep()`, theme cascade, or `@displayContainer={{false}}`) so embedded children look native to the parent's design language — no orphan rounded corners, no misaligned atoms. - -**Embedding contract (every place `<@fields.X @format='...' />` appears):** -- [ ] Format chosen by who owns the cell size — `embedded` for natural-height lists/feeds (clubs, events, results), `fitted` for uniform tile grids (portraits, calendar cells). Never `fitted` into a flex column with short content — that produces empty box space below each row. See "Picking the format" in `delegated-render-control.md`. -- [ ] Plural-field grid selectors target `:deep(> .plural-field)` AND `:deep(.linksToMany-itemContainer), :deep(.containsMany-item)` with `display: contents`. Targeting only `.containsMany-field` silently skips `linksToMany` and the grid collapses to one column. -- [ ] Atoms inside dark surfaces use `@displayContainer={{false}}` (or the parent recolors `:deep(.field-component-card.atom-format)` chrome). Default atom chrome has near-white background and will hide light-on-dark text. -- [ ] Staggered animations on plural-field cards use the CSS-variable cascade (`--stagger-d` on the wrapper's `:nth-child(N)`, `animation-delay: var(--stagger-d)` on the card). Direct `:nth-child` on `.field-component-card` always matches `:nth-child(1)` because of the per-item wrapper. -- [ ] Divider strategy chosen explicitly. If the parent draws lines between cards (`border-bottom`, `border-right`, outer-frame borders), the parent ALSO sets `:deep(.boxel-card-container--boundaries) { box-shadow: none; }`. If the child halo IS the boundary, the parent uses `gap` and no parent borders. Mixing produces the "double rule" / "drop shadow fighting border" bug. -- [ ] No duplicate `:deep(.boxel-card-container--boundaries)` rules per section. When switching strategies, DELETE the stale rule — don't just add an override below it. `grep -c "boundaries" ` should equal one rule per styled section. - -**Home app (mandatory for card families):** -- [ ] If this build introduces 2+ related CardDefs, a `Home` CardDef exists alongside them with `prefersWideFormat = true` and one `@context.searchResultsComponent` section per CardDef. The realm has a clear entry point — no manual indexing. See `app-card-home-with-search`. - -**`prefersWideFormat` decision (most-forgotten static property):** -- [ ] For each new CardDef, decided **at creation time** whether `static prefersWideFormat = true` is right. Set true for app-card homes, long-record cards with side nav, dashboards, document / multi-column cards, 3D/spatial layouts, routed pages, spreadsheets, slide decks. Leave false for detail / form / note / settings cards. **Don't defer — discovering you needed it later means rewriting the layout you already wrote against the narrow column.** See `boxel/references/prefers-wide-format.md`. - -**Framework rules:** -- [ ] `@field cardTitle` is overridden to compute from a primary field (when one exists) AND respects `cardInfo?.name?.trim()?.length` first. -- [ ] `grep -E '@field [a-z]+ = contains\(CardDef' ` returns nothing (no `contains(CardDef)`). -- [ ] `grep -E 'static (isolated|embedded|fitted) =' ` returns 3 hits per CardDef. -- [ ] Every exported class is preceded by `export class`. -- [ ] Edit-tracking banner is on line 1 if the file uses tracking mode. -- [ ] Icons imported are CDN-verified (HEAD 200 against `https://boxel-icons.boxel.ai/@cardstack/boxel-icons/v1/icons/.js`). -- [ ] No `{{#if (this.x)}}` parens around bare property access in templates (parens turn it into a helper invocation; class getters fail silently). -- [ ] Entrance animations have their `from` state INSIDE the keyframe and use `animation-fill-mode: both` — never `opacity: 0` in base CSS + `animation: ... forwards`. Otherwise the card goes invisible after a format flip (`isolated → edit → isolated`) or any animation cancellation. See `boxel-ui-guidelines/references/template-patterns.md` → "Entrance animations". -- [ ] Queries use `filter: { type: ref }` to select all of a type (NOT `filter: { on: ref }` — `on` is a scope, not a filter). Custom-field sorts include `on: ref` (only `lastModified`, `createdAt`, `cardURL` work without it). Refs built with `codeRef(here, path, name)` from `@cardstack/runtime-common`; `realmURL` imported as a Symbol from the same module (not `Symbol.for('realmURL')`). See `boxel/references/query-systems.md`. -- [ ] `containsMany(FieldDef)` is used ONLY when the list stays small (≤2 entries typical) OR each entry has very few fields (≤2). For multi-field entries that accumulate (results, line items, history rows, etc.), use `linksToMany(CardDef)` instead — otherwise the edit form gets one inline editor per (entry × field), producing ~1s/keystroke at 5 entries × 6 fields. See boxel/SKILL.md "containsMany edit-form perf trap." - -**Instances:** -- [ ] A Theme card exists and is linked from every non-Theme sample instance (`cardInfo.theme` with the dotted key). -- [ ] Sample JSON instances include `attributes.cardInfo` with `notes`, `name`, `summary`, `cardThumbnailURL` when a theme is linked. -- [ ] Theme JSON itself does NOT include `relationships["cardInfo.theme"]` (no self-loop). -- [ ] `meta.adoptsFrom.module` + `name` present on every instance. -- [ ] Sample content matches the design's editorial voice — evocative, real-sounding, never Lorem ipsum. - -**Validation:** -- [ ] Local lint was clean before push: `npx boxel file lint --realm --file `. -- [ ] Remote lint was clean after push: `npx boxel lint --realm `. -- [ ] A real server-side render check was run (`_search-prerendered` for `embedded`/`fitted`/`atom`/`head` + open in app for `isolated`). `npx boxel check` alone does not satisfy this. +- [ ] Playbook stages ran in order; stage-1 self-critique passed; fitted features the card's media and renders cleanly at all 16 sizes (`fitted-formats.md`). +- [ ] Chrome contract honored — no format's outermost element decorates itself (per-format table, `design-playbook.md` stage 4). +- [ ] Every embed site passes the `delegated-render-control.md` checkpoints (format choice, plural-field selectors, atom chrome, divider strategy). +- [ ] `cardTitle` overridden, `prefersWideFormat` decided, icons CDN-verified, queries follow `query-systems.md`, templates pass the cardinal rules. +- [ ] Instances pass the `/boxel-create-instance` done criteria; a Theme card exists and is linked from every non-Theme sample instance. +- [ ] Home card exists if this build introduced 2+ related CardDefs. +- [ ] Lint clean before and after push, both `indexing-operations.md` gates pass, and a render smoke test ran. ## Failure Recovery - SEARCH/REPLACE didn't match: read the file again, include a unique nearby marker, retry with a smaller window. -- Card renders as "Untitled " → you skipped the `cardTitle` override. Add it per `cardinfo-override-title`. -- Card looks unstyled in preview → the instance is missing `cardInfo.theme` link, OR the Theme card itself has empty `cssVariables`. Verify both. -- "instanceof check fails after import" → the constructor was passed unboxed through `{{...}}`. Box it (see pattern `organize-resource-class-data-loader`). +- Card renders as "Untitled " → add the `cardTitle` override per `cardinfo-override-title`. +- Card looks unstyled → missing `cardInfo.theme` link OR the Theme card has empty `cssVariables`. +- `cardOrThunk was undefined` → bad import or unresolved cycle; see the error table in `indexing-operations.md`. +- "instanceof check fails after import" → constructor passed unboxed through `{{...}}`; box it (pattern `organize-resource-class-data-loader`). diff --git a/commands/boxel-create-instance.md b/commands/boxel-create-instance.md index 270e899..c4b27c4 100644 --- a/commands/boxel-create-instance.md +++ b/commands/boxel-create-instance.md @@ -20,63 +20,35 @@ boxel: ## Read -1. `skills/boxel/SKILL.md` (focus: `references/data-management.md`) -2. `skills/boxel-create-edit-cards/SKILL.md` -3. `skills/source-code-editing/SKILL.md` (if going via SEARCH/REPLACE) +1. `skills/boxel/SKILL.md` — cardinal rules 13-14 (external URLs in `links.self`, `linksToMany` indexed keys) plus `references/data-management.md` for the full JSON:API instance shapes (`containsMany` arrays, empty links, `adoptsFrom`). +2. `skills/boxel/references/card-references.md` — how every `links.self` resolves (`./` / `../` / absolute; bare paths throw). Also `references/core-concept.md` "Relationship path resolution" and "`adoptsFrom.module` — URL of the .gts, NOT including the export name". +3. `skills/boxel/references/base-field-catalog.md` — DateField vs DateTimeField value contract; the image URL/ImageDef pair pattern. +4. `skills/boxel-patterns/patterns/theme-first-workflow/README.md` — theme linking via `relationships["cardInfo.theme"]`, absolute URLs for nested folders, and why Theme cards omit the self-theme relationship. +5. `skills/boxel-create-edit-cards/SKILL.md` — choosing the host-command combination. +6. `skills/source-code-editing/SKILL.md` (if going via SEARCH/REPLACE). ## Procedure 1. Decide the tool: `write-text-file` (new instance, simple shape), `patch-fields` (existing instance, surgical), `patchCardInstance` (existing instance, full replace), or SEARCH/REPLACE (large or structural). -2. Compose the JSON. Use `"data"` envelope, `"type": "card"`, `"meta.adoptsFrom"`, `"attributes"`, `"relationships"`. -3. For empty `linksTo`/`linksToMany` use `"self": null`, NEVER `[]`. -4. Save to `//.json`. - -## Instance shape — JSON:API correctness rules - -- **`adoptsFrom.module` is the URL of the .gts, NOT including the export name.** Use `"module": "https://cardstack.com/base/brand-guide"`, `"name": "default"` — never `"module": "https://cardstack.com/base/brand-guide/default"` (that treats the export name as a URL path and returns 404). -- **Every `links.self` MUST start with `./`, `../`, or `http`.** Bare paths like `"BrandGuide/foo"` are treated as npm-style package specifiers and fail with `Cannot resolve bare package specifier`. Use `"./BrandGuide/foo"` for root-level instances, `"../BrandGuide/foo"` for subfolder instances, or absolute URLs for cross-realm references. See `boxel/references/core-concept.md` "Relationship path resolution." -- **`linksToMany` uses INDEXED KEYS, not an array.** Each linked item is its own top-level relationship key: - ```json - "relationships": { - "materials.0": { "links": { "self": "../Material/white-oak" } }, - "materials.1": { "links": { "self": "../Material/walnut" } } - } - ``` - NEVER `"materials": { "links": { "self": ["a", "b"] } }` — the host rejects the instance as "not a card resource document" and it never loads. -- **`containsMany` is an ARRAY in attributes.** `"attributes": { "cabinets": [ {...}, {...} ] }` — opposite of `linksToMany`. Easy to mix up. -- **Date fields match their schema type.** `DateField` ↔ `"YYYY-MM-DD"` (no `T`). `DateTimeField` ↔ `"YYYY-MM-DDTHH:MM:SS.sssZ"` (with `T` and `Z`). Mismatch passes lint, crashes at render with `RangeError: Invalid time value`. -- **Image URLs go in the paired `*URL` field, NEVER in a `linksTo` relationship's `links.self`.** Putting an external URL there bricks the realm (Postgres NULL-byte rejection rolls back the whole indexing batch). See Cardinal Rule in CLAUDE.md. +2. Compose the JSON: `"data"` envelope, `"type": "card"`, `meta.adoptsFrom` (module + name), `attributes`, `relationships` — shapes per the Read list above. +3. Include `attributes.cardInfo` unless there's a specific reason not to (it's the user's edit surface); set `relationships["cardInfo.theme"]` when this instance should carry its own theme. Theme instances themselves omit that relationship. +4. Match date string formats to the schema's field type (DateField vs DateTimeField); put external image URLs in the paired `*URL` attribute, never in a relationship. +5. Save to `//.json`. ## Done Criteria (self-verify) -- [ ] `"data"` envelope at top level. -- [ ] `"meta.adoptsFrom"` has both `module` and `name`. Module URL does NOT include `/default` or any export-name suffix. -- [ ] Every `links.self` value starts with `./`, `../`, or `http`. No bare paths. -- [ ] `linksToMany` uses indexed-key shape (`"field.0"`, `"field.1"`), NOT array in `links.self`. -- [ ] `containsMany` is an array in `attributes.`. -- [ ] Empty relationships use `"self": null`. -- [ ] `attributes` is an object, NEVER a JSON-string. -- [ ] `relationships` is a sibling of `attributes`, not nested inside it. -- [ ] Include `attributes.cardInfo` (the four-key object: `name`, `summary`, `cardThumbnailURL`, `notes`; nulls fine) UNLESS the CardDef has a meaningful computed `cardTheme` AND there's no name/summary the user might want to edit. When in doubt, include it — it's cheap and gives the user an edit surface. -- [ ] **Per-instance theme override.** If you want THIS instance to override whatever `cardTheme` would otherwise compute, set `relationships["cardInfo.theme"]` with the dotted key. For CardDefs that use the default `cardTheme` pass-through, this is also the only way to install a theme at all. For CardDefs that override `cardTheme` (e.g. Task inherits from Project), it's optional. -- [ ] Exception for Theme card instances themselves: they include `attributes.cardInfo` but OMIT `relationships["cardInfo.theme"]` entirely (no circular self-link). -- [ ] Date string formats match the schema's field type (DateField vs DateTimeField). -- [ ] Image URLs are in `attributes.`, NOT in `relationships..links.self`. +- [ ] `"data"` envelope; `meta.adoptsFrom` has `module` (no export-name suffix) + `name`. +- [ ] Every `links.self` starts with `./`, `../`, or `http` (see `card-references.md`). +- [ ] `linksToMany` uses indexed keys, `containsMany` is an array in attributes, empty links use `"self": null` (cardinal rule 14 + `data-management.md`). +- [ ] `attributes` is an object (never a JSON string); `relationships` is its sibling, not nested inside it. +- [ ] `attributes.cardInfo` present; theme linkage decided per `theme-first-workflow`; no self-theme on Theme instances. +- [ ] Date values match field type; image URLs in the `*URL` attribute (cardinal rule 13 + `base-field-catalog.md`). ## Verification after push -`npx boxel file lint` does NOT confirm the instance is indexed. Run: - -```sh -npx boxel search --realm \ - --query "{\"filter\":{\"type\":{\"module\":\"\",\"name\":\"\"}}}" \ - --json | python3 -c "import json,sys;raw=sys.stdin.read();s=raw.find('[');e=raw.rfind(']')+1;print(len(json.loads(raw[s:e])))" -``` - -Count must match the number of `` instances you intended to push. If short, look for: bare `links.self` path, malformed `linksToMany` array, wrong `adoptsFrom.module` URL. +`npx boxel file lint` does NOT confirm the instance is indexed. Run the typed-search count gate from `skills/boxel-environment/references/indexing-operations.md`: `npx boxel search` filtered on the CardDef's absolute module URL must return the number of instances you pushed. If short, look for: bare `links.self` path, malformed `linksToMany` array, wrong `adoptsFrom.module` URL. ## Failure Recovery -- "Error: attributes is a string" → `attributes` must be an object literal, not a stringified JSON. -- "Error: relationships inside attributes" → move `relationships` to be a sibling at the top level of `data`. -- See `skills/boxel-environment/references/common-errors.md` for the full error catalogue. +- "attributes is a string" / "relationships inside attributes" → see `skills/boxel-environment/references/common-errors.md` for the full error catalogue with fixes. +- Instance never loads ("not a card resource document") → `linksToMany` array shape; use indexed keys. diff --git a/commands/boxel-design-card.md b/commands/boxel-design-card.md index 52831f3..32e178a 100644 --- a/commands/boxel-design-card.md +++ b/commands/boxel-design-card.md @@ -1,6 +1,6 @@ --- name: boxel-design-card -description: Improve a card's visual design — colors, typography, mood, asset direction, theme tokens. +description: Workflow to improve an EXISTING card's visual design end-to-end — Theme check, design discovery, tokenization. Routes into boxel-design (visual decisions) and boxel-ui-guidelines (template implementation); use this for the procedure, those skills for the rules. boxel: kind: skill --- @@ -35,15 +35,7 @@ boxel: 2. Run the design discovery process from `boxel-design`: mood, audience, references, distinctive angle. Use those to inform the Theme card's `visualDNA` + `cssVariables`. -3. Link `cardInfo.theme` on every relevant instance: - ```json - "relationships": { - "cardInfo.theme": { - "links": { "self": "../Theme/" } - } - } - ``` - The relationship key has a literal dot — `"cardInfo.theme"`, not nested. +3. Link `cardInfo.theme` on every relevant instance — the relationship key has a literal dot (`"cardInfo.theme"`, not nested). Exact JSON shape: `theme-first-workflow/README.md`. 4. Update templates' ` +``` + +(Providing `:placeholder` instead renders the image column with your icon/content on the `--fc-image-background` gradient — use that when the card *usually* has media and this instance just lacks it; omit all three when the card type never has media.) + +Source: [`packages/boxel-ui/addon/src/components/fitted-card/`](https://github.com/cardstack/boxel/tree/main/packages/boxel-ui/addon/src/components/fitted-card) — read the component's doc comment for the full named-block / arg / `--fc-*` API (including `@layout` to force vertical/horizontal and the `--fc-*-display` visibility overrides). + +Tune, don't fork: when the composition IS standard, prefer setting `--fc-*` variables and `@container fitted-card (...)` overrides from your card's scoped CSS over forking the layout. When it isn't — see the special-template rule above — hand-roll from the start; the File Inventory references show what that looks like. + +## Query the Host Container (`fitted-card`) + +**CRITICAL RULE:** CSS container queries cannot style the container element itself — only its descendants. The host wrapper IS the container, so your entire template — including its root `.fit` grid — is a descendant and can be styled by `@container fitted-card (...)` rules: ```gts ``` ```css -.cq { - container-type: size; /* enables both width AND height queries */ - container-name: card; - width: 100%; - height: 100%; - overflow: hidden; -} - .fit { width: 100%; height: 100%; display: grid; - overflow: hidden; - box-sizing: border-box; } ``` -**Why `container-type: size`?** Fitted cards have fixed width AND height set by the parent. We need both `@container (width ...)` and `@container (height ...)` queries. `container-type: size` enables both. This is safe because the parent guarantees both dimensions. +**Why does this work without a local container?** The host sets `container-type: size; container-name: fitted-card` on the wrapper. `size` enables both `@container (width ...)` and `@container (height ...)` queries — safe because the parent guarantees both dimensions. -**If you use a single element** (the grid IS the container), none of your `@container` rules for grid-template, padding, gap, or CSS custom properties on that element will work. The layout will be completely broken. This was the #1 mistake in the initial implementation. +**If you create your own container on the root** (`container-type` on `.fit`), none of your `@container` rules for grid-template, padding, gap, or CSS custom properties on that element will work — a container cannot be styled by its own queries — and you'd be competing with the host wrapper in violation of the child contract in `delegated-render-control.md`. Earlier versions of this guide prescribed a local two-element `.cq` → `.fit` skeleton; that is superseded — query the host's `fitted-card` container instead. ## Size Classification @@ -90,16 +115,18 @@ This guide teaches how to build Boxel card fitted views using **only CSS contain | medium | 170-260px | Hide tags, clamp meta to 1 line | | wide | >260px | Show tags, horizontal thumbnails at h40/h65/h105 | +> ⚠️ **Why tags hide below 260px:** tag pills with `flex-wrap: wrap` consume unpredictable height, destabilizing the layout. `@container card (width <= 260px) { .r-tags { display: none; } }` + ### Container Query Syntax ```css /* Height quantum */ -@container card (height <= 50px) { /* h40 */ } -@container card (50px < height <= 80px) { /* h65 */ } -@container card (80px < height <= 130px) { /* h105 */ } +@container fitted-card (height <= 50px) { /* h40 */ } +@container fitted-card (50px < height <= 80px) { /* h65 */ } +@container fitted-card (80px < height <= 130px) { /* h105 */ } /* Compound: width + height */ -@container card (width > 260px) and (200px < height <= 320px) { /* wide h275 */ } +@container fitted-card (width > 260px) and (200px < height <= 320px) { /* wide h275 */ } ``` ## Grid Template Patterns @@ -117,13 +144,13 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; /* hero head body tags meta */ ``` -**Why:** With `auto`, the body row takes its full content height regardless of available space, pushing tags and meta off the bottom. With `minmax(0, 1fr)`, the body gets whatever space remains after hero (percentage), head (auto = line-clamped content), tags (auto), and meta (auto). If that's 0px, the body collapses gracefully — its `overflow: hidden` clips any content. +**Why:** With `auto`, the body row takes its full content height regardless of available space, pushing tags and meta off the bottom — even with `overflow: hidden` on the grid, each `auto` row still gets its content size. With `minmax(0, 1fr)`, the body gets whatever space remains after hero (percentage), head (auto = line-clamped content), tags (auto), and meta (auto). If that's 0px, the body collapses gracefully — its `overflow: hidden` clips any content. ### Grid Templates Per Height Quantum ```css /* h40: head only */ -@container card (height <= 50px) { +@container fitted-card (height <= 50px) { .fit { grid-template-rows: 1fr; grid-template-areas: "head"; @@ -134,7 +161,7 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; } /* h65: head + meta */ -@container card (50px < height <= 80px) { +@container fitted-card (50px < height <= 80px) { .fit { grid-template-rows: 1fr auto; grid-template-areas: "head" "meta"; @@ -143,7 +170,7 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; } /* h105: head + body + meta */ -@container card (80px < height <= 130px) { +@container fitted-card (80px < height <= 130px) { .fit { grid-template-rows: auto auto 1fr; grid-template-areas: "head" "body" "meta"; @@ -152,7 +179,7 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; } /* h170: head + body + meta (no hero) */ -@container card (width <= 260px) and (130px < height <= 200px) { +@container fitted-card (width <= 260px) and (130px < height <= 200px) { .fit { grid-template-rows: auto minmax(0, 1fr) auto; grid-template-areas: "head" "body" "meta"; @@ -160,7 +187,7 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; .r-hero { display: none; } .r-tags { display: none; } } -@container card (width > 260px) and (130px < height <= 200px) { +@container fitted-card (width > 260px) and (130px < height <= 200px) { .fit { grid-template-rows: auto minmax(0, 1fr) auto auto; grid-template-areas: "head" "body" "tags" "meta"; @@ -169,7 +196,7 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; } /* h275: hero + all content */ -@container card (width > 260px) and (200px < height <= 320px) { +@container fitted-card (width > 260px) and (200px < height <= 320px) { .fit { grid-template-rows: minmax(0, 30%) auto minmax(0, 1fr) auto auto; grid-template-areas: "hero" "head" "body" "tags" "meta"; @@ -177,7 +204,7 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; } /* h445: hero (larger) + all content */ -@container card (width > 260px) and (height > 320px) { +@container fitted-card (width > 260px) and (height > 320px) { .fit { grid-template-rows: minmax(0, 38%) auto minmax(0, 1fr) auto auto; grid-template-areas: "hero" "head" "body" "tags" "meta"; @@ -185,6 +212,8 @@ grid-template-rows: minmax(0, 35%) auto minmax(0, 1fr) auto auto; } ``` +> ⚠️ **Hero percentage must be conservative.** A percentage hero shrinks with the container: at h275, `35%` = 96px at 275px tall but only 70px at 200px tall. Use 30% for h275 and 35-38% for h445 so text still fits at the short end of each range (as the templates above do). + ### Handling Missing Images with `:not(:has())` When the hero image is optional, provide alternative templates: @@ -201,7 +230,7 @@ When the hero image is optional, provide alternative templates: For wide containers at h40/h65/h105, switch to 2-column layout with thumbnail: ```css -@container card (width > 260px) and (50px < height <= 80px) { +@container fitted-card (width > 260px) and (50px < height <= 80px) { .fit:has(.r-hero) { grid-template-columns: 50px 1fr; grid-template-rows: 1fr auto; @@ -247,21 +276,21 @@ Cards progress through layout modes as width increases. Each mode has a minimum ```css /* ✅ Thumbnail sidebar at >260px — fixed image column */ -@container card (width > 260px) and (50px < height <= 80px) { +@container fitted-card (width > 260px) and (50px < height <= 80px) { .fit:has(.r-hero) { grid-template-columns: 55px 1fr; } } /* ✅ Magazine spread at >370px — percentage image column */ -@container card (width > 370px) and (130px < height <= 200px) { +@container fitted-card (width > 370px) and (130px < height <= 200px) { .fit:has(.r-hero) { grid-template-columns: 45% 1fr; } } /* ❌ WRONG — magazine spread at >260px, content column too narrow */ -@container card (width > 260px) and (130px < height <= 200px) { +@container fitted-card (width > 260px) and (130px < height <= 200px) { .fit:has(.r-hero) { grid-template-columns: 45% 1fr; /* at 265px: content = 134px! */ } @@ -304,7 +333,7 @@ When the image is optional, always provide a fallback grid that uses stacked lay ```css /* Magazine spread only when image exists AND width is sufficient */ -@container card (width > 370px) and (200px < height <= 320px) { +@container fitted-card (width > 370px) and (200px < height <= 320px) { .fit:has(.r-hero) { grid-template-columns: 45% 1fr; /* ... 2-column areas ... */ @@ -352,6 +381,8 @@ The body region should use flex column layout for internal spacing: } ``` +> ⚠️ **Meta row is `auto`, not `1fr`.** The bottom meta row should take its content size; `1fr` wastes space or opens a gap between body text and meta. Use `align-self: end` on the meta region to dock it. + ## Line Clamping ### Boilerplate @@ -367,23 +398,25 @@ Every text element that needs clamping requires this base: } ``` +> ⚠️ Missing any of these three declarations and `-webkit-line-clamp` silently does nothing. + ### Pre-Declared Clamp Values Instead of the JS modifier computing line allocation at runtime, you declare the clamp value at each size breakpoint: ```css -@container card (height <= 50px) { +@container fitted-card (height <= 50px) { .headline { -webkit-line-clamp: 1; } } -@container card (50px < height <= 80px) { +@container fitted-card (50px < height <= 80px) { .headline { -webkit-line-clamp: 2; } } -@container card (80px < height <= 130px) { +@container fitted-card (80px < height <= 130px) { .headline { -webkit-line-clamp: 2; } .subhead { -webkit-line-clamp: 1; } .excerpt { display: none; } } -@container card (130px < height <= 200px) { +@container fitted-card (130px < height <= 200px) { .headline { -webkit-line-clamp: 3; } .subhead { -webkit-line-clamp: 1; } .excerpt { -webkit-line-clamp: 2; } @@ -407,7 +440,7 @@ The JS system's "priority-1 gets 3x more lines" is encoded directly: at every br ```css /* Narrow h170: headline needs more lines, hide subhead */ -@container card (width <= 170px) and (130px < height <= 200px) { +@container fitted-card (width <= 170px) and (130px < height <= 200px) { .headline { -webkit-line-clamp: 4; } .subhead { display: none; } .excerpt { -webkit-line-clamp: 2; } @@ -435,13 +468,15 @@ Example for 250×275 (medium, h275) with hero: - Excerpt at 12px × 1.35 = 16px/line → 2 lines = 32px - Total body: 48px ← fits in 90px with breathing room -**If the math doesn't work at the smallest size in a range, reduce the clamp or font size.** The h275 range (200-320px) is wide — values must work at 200px tall, not just 275px. +**If the math doesn't work at the smallest size in a range, reduce the clamp.** The h275 range (200-320px) is wide — values must work at 200px tall, not just 275px. Always calculate line budgets for the WORST case (smallest height in the range). The `pow()`-based scale + per-role `max()` floors (next section) handle font sizes automatically — but the line-clamp values you pick per quantum still need to be conservative for the smallest size in the band. ## Comfort-Scored Typography — `pow()`-based hierarchical scale ### Approach: One Base, One Ratio, `pow()` Hierarchy -**All fitted cards MUST use continuous container-query unit scaling, driven by a single hierarchical scale.** Anchor the whole type system on ONE base size that grows with the container, then derive each role (headline, body, meta, etc.) by raising/lowering it via `pow()` with a typographic ratio. Each derived size gets a `max()` floor so it stays readable at the smallest container sizes. +**Scope: this mandate applies to HAND-ROLLED fitted templates.** `FittedCard`-based templates handle typography through the component's stepped theme-token sizes (`--boxel-font-size-*`) and its `--fc-*-font-size` tuning variables — do not retrofit the `pow()` hierarchy onto them. + +**All hand-rolled fitted templates MUST use continuous container-query unit scaling, driven by a single hierarchical scale.** Anchor the whole type system on ONE base size that grows with the container, then derive each role (headline, body, meta, etc.) by raising/lowering it via `pow()` with a typographic ratio. Each derived size gets a `max()` floor so it stays readable at the smallest container sizes. This replaces both (a) the original 18-block stepped `@container` font-size grid, and (b) the additive `4cqi + 1.5cqb` formula per role. One base, one ratio, hierarchical exponents, explicit minimums — fewer variables to tune, and the *relative* hierarchy is preserved automatically as the base scales. @@ -492,7 +527,7 @@ This replaces both (a) the original 18-block stepped `@container` font-size grid - **Single source of truth.** Want a denser scale? Change `--type-ratio` to 1.2. Want all text bigger? Bump the base's clamp. Every role moves together, preserving the visual hierarchy. - **Floors per role, not globally.** The body and headline shouldn't have the same minimum — the headline can drop to 11px in a tight strip, but its derived base might want to clamp at a different floor than meta. `max()` per role handles that without re-deriving from a different base. - **Cleaner mental model.** "Headline is 2 ratio-steps above body" is a single sentence. The previous additive formula encoded the same hierarchy through 6 separate hand-tuned coefficients, harder to keep consistent. -- **CSS `pow()` is real.** Supported in Chrome 117+, Safari 17+, Firefox 118+ (late 2023). Boxel runs in modern browsers; this is safe. +- **CSS `pow()` is real.** Supported in Chrome 117+, Safari 17+, Firefox 118+ (late 2023). Boxel runs in modern browsers; this is safe. (If you ever must support older browsers, replace `pow(var(--type-ratio), 2)` with the literal multiplication `var(--type-ratio) * var(--type-ratio)` — uglier, same result.) **Tuning knobs:** @@ -521,30 +556,9 @@ The `pow()`-based formulas approximate the original hand-tuned table at standard Match the table for "good enough" cases at small/medium sizes; the formulas give slightly larger values at very wide containers (where the table caps), which is generally desirable — large cards can afford bigger type. -### Full Comfort Table (Reference Only — Don't Implement Stepped) - -The table below documents the original stepped values. Preserved for reference and validation, but **do not implement stepped `@container` blocks for font sizes** — use the `pow()`-based continuous formulas above instead. - -| Width | Height | Headline | Body | Subhead | Meta | Tag | Pad | Gap | -|-------|--------|----------|------|---------|------|-----|-----|-----| -| ≤170 | ≤50 | 11px | 9px | 9px | 8px | 7px | 5px | 2px | -| ≤170 | 50-80 | 12px | 9px | 9px | 8px | 8px | 5px | 2px | -| ≤170 | 80-130 | 13px | 10px | 10px | 8px | 8px | 5px | 2px | -| ≤170 | 130-200 | 14px | 10px | 10px | 9px | 8px | 5px | 2px | -| ≤170 | 200-320 | 14px | 11px | 11px | 9px | 8px | 5px | 2px | -| ≤170 | >320 | 14px | 11px | 11px | 9px | 8px | 5px | 2px | -| 170-260 | ≤50 | 12px | 9px | 9px | 8px | 8px | 7px | 4px | -| 170-260 | 50-80 | 13px | 10px | 10px | 9px | 9px | 7px | 4px | -| 170-260 | 80-130 | 14px | 11px | 11px | 9px | 9px | 7px | 4px | -| 170-260 | 130-200 | 16px | 11px | 11px | 9px | 9px | 7px | 4px | -| 170-260 | 200-320 | 18px | 12px | 12px | 10px | 10px | 7px | 4px | -| 170-260 | >320 | 18px | 12px | 12px | 10px | 10px | 7px | 4px | -| >260 | ≤50 | 15px | 11px | 11px | 9px | 9px | 10px | 6px | -| >260 | 50-80 | 15px | 11px | 11px | 9px | 9px | 10px | 6px | -| >260 | 80-130 | 16px | 12px | 12px | 10px | 10px | 10px | 6px | -| >260 | 130-200 | 18px | 12px | 12px | 10px | 10px | 10px | 6px | -| >260 | 200-320 | 18px | 12px | 12px | 10px | 10px | 10px | 6px | -| >260 | >320 | 24px | 14px | 14px | 11px | 11px | 10px | 7px | +### Comfort Thresholds (Historical — Superseded by `pow()`) + +An earlier version of this system used an 18-cell stepped table of hand-tuned comfort sizes — one row per width-class × height-quantum combination, roughly headline 11px → 24px, body 9px → 14px, pad 5px → 10px from the smallest cell to the largest. The continuous `pow()` formulas above supersede it: they reproduce those comfort values at standard sizes (the spot checks above quote the table's values) while scaling smoothly between them. **Do not implement stepped `@container` blocks for font sizes.** ### Behavioral Overrides (Stepped Rules That Remain) @@ -552,12 +566,12 @@ While font sizes and spacing scale continuously, some **behavioral rules** still ```css /* Hide elements at small sizes — binary decision, not gradual */ -@container card (width <= 170px) and (height <= 50px) { +@container fitted-card (width <= 170px) and (height <= 50px) { .chg { display: none; } } /* Adjust line clamps at specific thresholds */ -@container card (width <= 170px) and (130px < height <= 200px) { +@container fitted-card (width <= 170px) and (130px < height <= 200px) { .headline { -webkit-line-clamp: 4; } .subhead { display: none; } } @@ -567,20 +581,20 @@ While font sizes and spacing scale continuously, some **behavioral rules** still ### Using the Variables -Content styles reference the variables with fallbacks: +Content styles reference the variables bare — `.fit` (the parent) already declares every `--fit-*` variable with its floor, so descendants never carry inline fallbacks: ```css .headline { - font-size: var(--fit-headline-size, 14px); - line-height: var(--fit-headline-lh, 1.18); + font-size: var(--fit-headline-size); + line-height: var(--fit-headline-lh); } .subhead { - font-size: var(--fit-subhead-size, 12px); - line-height: var(--fit-body-lh, 1.35); + font-size: var(--fit-subhead-size); + line-height: var(--fit-body-lh); } .excerpt { - font-size: var(--fit-body-size, 11px); - line-height: var(--fit-body-lh, 1.4); + font-size: var(--fit-body-size); + line-height: var(--fit-body-lh); } ``` @@ -596,15 +610,15 @@ Some "metadata" is actually **key info** — price, status, availability — tha ```css /* Key info: bigger, bolder, colored — not buried in meta */ .price { - font-size: var(--fit-keyinfo-size, 14px); + font-size: var(--fit-keyinfo-size); font-weight: 800; - color: var(--primary, #6366f1); + color: var(--primary); } /* Regular meta: small, muted, subordinate */ .author, .date, .source { - font-size: var(--fit-meta-size, 9px); - color: var(--muted-foreground, #9b8bb0); + font-size: var(--fit-meta-size); + color: var(--muted-foreground); } ``` @@ -616,7 +630,7 @@ Some "metadata" is actually **key info** — price, status, availability — tha ### 1. Container Cannot Style Itself -`@container card (...) { .fit { ... } }` only works if `.fit` is a DESCENDANT of the element with `container-name: card`. If `.fit` IS the container, nothing happens. Always use the two-element pattern. +`@container fitted-card (...) { .fit { ... } }` only works because `.fit` is a DESCENDANT of the host wrapper carrying `container-name: fitted-card`. If you make `.fit` a container itself, nothing happens — a container cannot be styled by its own queries. Never create your own container on the fitted root; query the host's `fitted-card` container. ### 2. `auto` Grid Rows Overflow @@ -643,7 +657,7 @@ At h275, `35%` hero = 96px at 275px tall but only 70px at 200px tall. Use 30% fo Tag pills with `flex-wrap: wrap` consume unpredictable height. Hide them at narrow/medium widths to avoid layout instability: ```css -@container card (width <= 260px) { .r-tags { display: none; } } +@container fitted-card (width <= 260px) { .r-tags { display: none; } } ``` ### 8. Meta Should Be `auto`, Not `1fr` @@ -676,32 +690,29 @@ Every fitted card has regions. Common patterns: ```gts ``` -No modifiers. No `data-line-*` attributes. No `{{this.fitGrid}}`. +No modifiers. No `data-line-*` attributes. No `{{this.fitGrid}}`. No local container — the host wrapper provides the `fitted-card` size container. ### Step 3: Write the Base CSS ```css -.cq { container-type: size; container-name: card; width: 100%; height: 100%; overflow: hidden; } -.fit { width: 100%; height: 100%; display: grid; overflow: hidden; box-sizing: border-box; } +.fit { width: 100%; height: 100%; display: grid; } .r-hero, .r-head, .r-body, .r-tags, .r-meta { overflow: hidden; min-height: 0; } .headline, .excerpt { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; margin: 0; } ``` @@ -720,7 +731,7 @@ Start from the smallest height quantum and work up. For each quantum: 2. Set the line clamp values 3. Add behavioral overrides (element hiding, line-clamp adjustments at specific width×height combos) -**Do NOT set `--fit-*` font/spacing variables inside `@container` blocks** — the `pow()` hierarchy handles this automatically. +**Do NOT set `--fit-*` font/spacing variables inside `@container` blocks** — the `pow()` hierarchy is computed once on `.fit`; per-quantum blocks override only structure, line clamps, and visibility. If you find yourself overriding `--fit-headline-size` inside a `@container` rule, adjust the base clamp range or `--type-ratio` instead. ### Step 6: Test in Format Preview @@ -748,13 +759,11 @@ Open the Format Preview card, link your card instance, and check all 16 sizes. U ### After (Container Queries) ```gts -
-
-
-

{{@model.headline}}

-
-
-
+
+
+

{{@model.headline}}

+
+
``` No modifiers, no data attributes. All layout logic lives in CSS. @@ -775,8 +784,10 @@ No modifiers, no data attributes. All layout logic lives in CSS. ## Quick Checklist for New CQ Fitted Views -- [ ] Two-element pattern: `.cq` (container) wraps `.fit` (grid) -- [ ] `container-type: size` on the outer element +> This checklist is for **hand-rolled** fitted templates. A `FittedCard`-based template skips it — the component covers these internally; verify it at the 16 sizes and tune `--fc-*` variables instead. + +- [ ] Single root `.fit` grid; all `@container` rules query the host's `fitted-card` container +- [ ] No `container-type` / `container-name` anywhere in the template — the host wrapper provides the size container - [ ] All regions have `overflow: hidden; min-height: 0` - [ ] Body region uses `minmax(0, 1fr)` grid row, not `auto` - [ ] Line clamp boilerplate on all clamped text elements @@ -833,15 +844,21 @@ A card looks art-directed when these six moves are all present. Missing any one One serif (the body voice: headline, dek, lede, stat values) and one sans-serif (the micro-labels: eyebrows, stat labels, byline meta, review counts). Never mix three families. Never set the eyebrow in the serif. Never set a stat value in the sans. ```css -.headline { font-family: var(--font-serif, 'Playfair Display', Georgia, serif); } -.dek { font-family: var(--font-serif, 'Lora', Georgia, serif); font-style: italic; } -.stat-val { font-family: var(--font-serif, ...); } +/* Declare the pairing ONCE on the composition root — --font-serif has no + default value, so this local declaration is where its one fallback lives */ +.masthead { + --serif: var(--font-serif, Georgia, serif); + --sans: var(--font-sans, system-ui, sans-serif); +} +.headline { font-family: var(--serif); } +.dek { font-family: var(--serif); font-style: italic; } +.stat-val { font-family: var(--serif); } .eyebrow, .stat-lbl, -.review-count { font-family: var(--font-sans, 'Inter', sans-serif); } +.review-count { font-family: var(--sans); } ``` -The theme card supplies `--font-serif` and `--font-sans`. Always reference them. Always provide a safe fallback (`Georgia, serif` / `system-ui, sans-serif`). +The theme card supplies `--font-serif` and `--font-sans`. Always reference them. `--font-serif` has no default, so it needs a safe fallback (`Georgia, serif`) — given once, in the composition root's local-variable declaration, never inline per selector. #### 2. Weight rhythm — pair a large, light serif with tiny, bold sans diff --git a/skills/boxel/references/core-patterns.md b/skills/boxel/references/core-patterns.md index bd066fb..8030b89 100644 --- a/skills/boxel/references/core-patterns.md +++ b/skills/boxel/references/core-patterns.md @@ -42,7 +42,7 @@ export class BlogPost extends CardDef { static icon = FileTextIcon; // ✅ CORRECT: Boxel icons for static card/field type icons // prefersWideFormat = true is RIGHT for blog posts (long-form reading + hero // imagery want viewport width). Default is `false` — see - // `references/prefers-wide-format.md` for the full when-to-set-it rule. + // `prefers-wide-format.md` for the full when-to-set-it rule. static prefersWideFormat = true; @field headline = contains(StringField); @@ -111,6 +111,14 @@ export class AddressField extends FieldDef { } ``` +### Schema hygiene (quick checklist) + +Beyond the cardinal rules in `SKILL.md` (contains vs linksTo, exports, three formats): + +- **Never use JS/TS reserved words as field names** (`class`, `new`, `default`, `static`, …). +- **No duplicate field definitions** — one `@field name = ...` per name per class. +- **Keep computed fields simple and unidirectional** — no self-reference or cycles (see next section); wrap cross-card access in try-catch. + ### 3. Computed Properties with Safety **CRITICAL:** Avoid cycles and infinite recursion in computed fields. diff --git a/skills/boxel/references/design-playbook.md b/skills/boxel/references/design-playbook.md index d4206c0..7ad8b0b 100644 --- a/skills/boxel/references/design-playbook.md +++ b/skills/boxel/references/design-playbook.md @@ -318,22 +318,26 @@ Verify visually that stage 3 matches stage 1. If anything shifted, your theme ex Now you have the design language (in the theme) and the flagship layout (in isolated). Derive the constrained formats from the visual identity. -**Critical — every format leaves outer chrome to the host.** Read `boxel-ui-guidelines/references/delegated-render-control.md` ("The flip side — what the CHILD card MUST NOT do"). The host wraps every format with a CardContainer that provides `border-radius`, `background`, `border`/`box-shadow`, `overflow: hidden`. The child format must NOT decorate its outermost element with these — the parent and host own the box; the child owns what's inside it. +**Critical — every format leaves outer chrome to the host.** Read `boxel-ui-guidelines/references/delegated-render-control.md` ("The flip side — what the CHILD card MUST NOT do"). The host wraps every format with a CardContainer that provides `border-radius`, `border`, `box-shadow`, `overflow: hidden`. The child format must NOT decorate its outermost element with these — the parent and host own the box boundaries; the child owns what's inside it. This rule is what lets a parent (like the Row & Rail Programme showcase) embed your card and override the chrome to match its design language. If your `isolated` adds `border-radius: 8px` to its outer `
`, it fights every parent that tries to override. -Per format, outer-element rules: +Per format, outer-element rules. `CardContainer` — which wraps every card render — already applies the theme's `background-color`, `color`, `font-family` (`--font-sans`, falling back to the Boxel sans stack), and body `font-size`; all inherited for free. Don't declare these on the root unless deviating: `font-family` only for a non-sans card voice (such as `--font-serif`); `background-color`/`color` only when a pairing other than the theme's main background/foreground is preferred. `fitted` and `embedded` may make that switch (e.g. `--card` + `--card-foreground`); `isolated` and CardDef `edit` keep the theme's pair. | Format | OK on outermost | NOT OK | |---|---|---| -| `isolated` | `color`, `font-family`, inner padding, inner grid/flex | `border-radius`, `border`, `box-shadow`, opaque `background`, `overflow` | -| `embedded` | same | same — plus no `width/height/max-width` | -| `fitted` | `color`, `font-family`, inner padding, inner grid template | `border-radius`, `border`, `box-shadow`, `background`, `width/height/min/max-height`, `overflow`, `container-type`, `container-name` | +| `isolated` | `font-family` (only if not `--font-sans`), inner padding, inner grid/flex | `border-radius`, `border`, `box-shadow`, `overflow`, background/foreground overrides (use the theme's) | +| `embedded` | same — plus a different background/foreground pairing (e.g. `--card` + `--card-foreground`) | `border-radius`, `border`, `box-shadow`, `overflow`, `width/height/max-width` | +| `fitted` | a different background/foreground pairing (e.g. `--card` + `--card-foreground`), `font-family` (only if not `--font-sans`), inner padding, inner grid template | `border-radius`, `border`, `box-shadow`, `width/height/min/max-height`, `container-type`, `container-name` | | `atom` | inline content only | `padding`, `border`, `border-radius`, `background`, any `display:` other than default | -| `edit` | form field spacing, internal layout | same as embedded | +| `edit` | form field spacing, internal layout | same as isolated (keep the theme's background/foreground) | + +Compound-field `embedded`/`edit` templates are the exception: nested inside a card surface, they may choose a different background/foreground combo to distinguish themselves — usually `--card` + `--card-foreground`. **If the brand demands a specific outer treatment** (sharp corners, custom border), put it on the **Theme card** (`--radius`, `--background`, `--border`). The wrapper's `--themed` cascade picks it up. Every linked card gets it for free — without format CSS contention. +**Fitted — start from `` for standard compositions.** For standard compositions (image + eyebrow + title + subtitle + meta + footer + badges), use the `FittedCard` component from `@cardstack/boxel-ui/components` and carry the design identity through its `--fc-*` variables and slot content — see the "Prefer ``" section of `container-query-fitted-layout.md`. When the design calls for a special fitted template (barcode ticket stub, terminal ticker, boarding pass, magazine spread — the kind in that guide's File Inventory), skip `FittedCard` and hand-roll per the guide. + **Fitted — the one rule that's actually a rule: FEATURE THE MEDIA.** If the card has a hero image, illustration, brand mark, or generated graphic, the fitted view leads with it. The thumbnail-sidebar / magazine-spread / stacked-with-hero patterns all qualify — what disqualifies is hiding the media under metadata. Container queries are FINE if you need them to switch layout modes (e.g., featured-image vs. text-only at different widths), but you can also just write three sub-templates by hand — whichever is clearer for THIS card. @@ -344,7 +348,7 @@ Layout latitude in fitted: - Keep one editorial micro-object if it scales (eyebrow with tracking does; drop cap doesn't) - Drop the schema-rich content; show only the 3-4 fields that survive at 200×200 -**Before declaring fitted done, walk all 16 named sizes.** The host previews fitted at 16 specific size boxes (3 badges + 5 strips + 5 tiles + 3 cards). A fitted layout that nails one size and breaks at another isn't done. The full table — Small/Medium/Large Badge, Single/Double/Triple/Double-Wide/Triple-Wide Strip, Small/Regular/CardsGrid/Tall/Large Tile, Compact/Full/Expanded Card — and the per-cell verification checklist live in [`fitted-formats.md`](fitted-formats.md). Use the live app's format preview to walk them visually; `npx boxel check` is not a substitute. +**Before declaring fitted done, walk all 16 named sizes.** The host previews fitted at 16 specific size boxes (3 badges + 5 strips + 5 tiles + 3 cards). A fitted layout that nails one size and breaks at another isn't done. The full table — Small/Medium/Large Badge, Single/Double/Triple/Double-Wide/Triple-Wide Strip, Small/Regular/CardsGrid/Tall/Large Tile, Compact/Full/Expanded Card — and the per-cell verification checklist live in [`fitted-formats.md`](fitted-formats.md). Use the live app's format preview to walk them visually. **Embedded — compressed isolated.** diff --git a/skills/boxel/references/fitted-formats.md b/skills/boxel/references/fitted-formats.md index 36fcb5e..0d3426c 100644 --- a/skills/boxel/references/fitted-formats.md +++ b/skills/boxel/references/fitted-formats.md @@ -1,6 +1,8 @@ ## Fitted Format Essentials -> 🔴 **REQUIRED for every fitted view.** All fitted templates MUST be built using the approach documented in [`container-query-fitted-layout.md`](./container-query-fitted-layout.md). The two-element `.cq` → `.fit` pattern, six height quanta with `minmax(0, 1fr)` body rows, `pow()`-based hierarchical typography with per-role minimums, line-budget math, and the magazine-spread / thumbnail-sidebar width thresholds are **not optional advice** — they're the standard. Hand-rolling per-format CSS without container queries, using `auto` rows for body content, or skipping `min-height: 0` on grid children will produce fitted views that overflow at edge sizes. +> 🔴 **REQUIRED for every fitted view.** All fitted templates MUST be built using the approach documented in [`container-query-fitted-layout.md`](./container-query-fitted-layout.md) — either via the `FittedCard` component (preferred for standard compositions; implements the standard internally) or hand-rolled. For hand-rolled templates, the single-root `.fit` grid querying the host-provided `fitted-card` container (never create your own container on the root), six height quanta with `minmax(0, 1fr)` body rows, `pow()`-based hierarchical typography with per-role minimums, line-budget math, and the magazine-spread / thumbnail-sidebar width thresholds are **not optional advice** — they're the standard. Hand-rolling per-format CSS without container queries, using `auto` rows for body content, or skipping `min-height: 0` on grid children will produce fitted views that overflow at edge sizes. +> +> For standard compositions, prefer the `FittedCard` component from `@cardstack/boxel-ui/components` — it implements the standard internally and adapts across all 16 sizes (see the "Prefer ``" section of the long guide). > > This file (fitted-formats.md) is a **quick reference for size classification + the 16 named sizes you must verify against**. For the actual implementation, read the long guide. @@ -92,4 +94,4 @@ The fitted view must render correctly in all 16 sizes above. For each row in the - [ ] No `auto` rows for body content (use `minmax(0, 1fr)` per the container-query guide). - [ ] No overflowing scrollbars or missing visual rhythm at any cell. -Server-side validation: hit `/_search-prerendered` with `format=fitted` for the card; the response includes pre-rendered HTML for each of the 16 named sizes. Open the live app's format preview to walk through them visually. `npx boxel check` does not do this — it's sync-state only. +Server-side validation: run a typed `npx boxel search --json` and inspect the fitted HTML relationship returned for the card, then open the live app's format preview to walk through all 16 sizes visually. Direct `/_search-prerendered` access is deployment-specific; where exposed, it uses HTTP `QUERY`, not `GET`. diff --git a/skills/boxel/references/lint-workflow.md b/skills/boxel/references/lint-workflow.md index db5d944..adced1f 100644 --- a/skills/boxel/references/lint-workflow.md +++ b/skills/boxel/references/lint-workflow.md @@ -2,9 +2,16 @@ Linting is part of the core Boxel code workflow. Every `.gts` change needs a real lint result before it is reported as done. -## In the AI assistant (no shell) — use `checkCorrectness` +**Which context are you in?** Two execution contexts run this workflow, and they use different lint transports: -The CLI sections below assume a shell. In the AI assistant there is no shell, so **do not** try to run `npx boxel file lint`. After every applied code patch the assistant automatically issues a `checkCorrectness` command against each patched file (and each affected card instance). It runs the same server-side ESLint + `ember-template-lint` gate and returns the result inline as a card with `correct` (boolean), `errors` (string array), and `warnings` (string array). Clean means `correct: true` with an empty `errors` array — the same bar as `No lint issues found` / `messages: []` below. +- **Shell contexts** (Claude Code, a terminal, CI, any agent with Bash): use the `npx boxel` CLI commands below. This is what CLAUDE.md and `boxel/SKILL.md` Cardinal Rule 10 refer to. +- **The in-app Boxel AI assistant** (no shell): use `checkCorrectness` (next section). The CLI commands are unavailable there — do not attempt them. + +Both transports hit the same server-side lint gate; only the invocation differs. + +## In the in-app Boxel AI assistant (no shell) — use `checkCorrectness` + +The CLI sections below assume a shell. In the in-app assistant there is no shell, so **do not** try to run `npx boxel file lint`. After every applied code patch the assistant automatically issues a `checkCorrectness` command against each patched file (and each affected card instance). It runs the same server-side ESLint + `ember-template-lint` gate and returns the result inline as a card with `correct` (boolean), `errors` (string array), and `warnings` (string array). Clean means `correct: true` with an empty `errors` array — the same bar as `No lint issues found` / `messages: []` below. The auto-fix loop is bounded (currently 3 attempts per target). Aim to emit clean code on the first pass rather than leaning on the retries — the recurring first-pass offenders (unused/duplicated imports, concatenated inline `style`, unlabeled inputs) are in the `SKILL.md` common-mistakes list. Everything under "What Lint Does NOT Catch" and "Completion Gate" applies to `checkCorrectness` too; the CLI-specific setup and command syntax do not. @@ -67,8 +74,8 @@ Do not treat transport success or `ok: true` as clean by itself. The lint endpoi ## What Does Not Count -- `npx boxel check ` does not count. It reports sync state and can miss compile/lint failures. -- `_search-prerendered` does not replace lint. It is a server render check for supported formats. +- The current monorepo CLI has no `npx boxel check`. It was a legacy standalone sync-state command; use `npx boxel file lint` / `npx boxel lint`. +- `_search-prerendered` does not replace lint. It is a server render check for supported formats. A direct `/_search-prerendered` probe is deployment-specific and, where exposed, uses HTTP `QUERY` rather than `GET` — for a server-side render check, prefer `npx boxel search --json` and inspect the returned HTML relationships, then open the card in the app to exercise `isolated`. - Opening the card in the app does not replace lint. It exercises runtime rendering, especially isolated format. ## What Lint Does NOT Catch diff --git a/skills/boxel/references/quick-reference.md b/skills/boxel/references/quick-reference.md index 1fda7fa..c846b8e 100644 --- a/skills/boxel/references/quick-reference.md +++ b/skills/boxel/references/quick-reference.md @@ -90,7 +90,7 @@ import { task, restartableTask, timeout } from 'ember-concurrency'; // timeout i // Import preflight: if a template uses (fn ...), {{on ...}}, // concat/get/array/hash, Boxel UI predicates, or formatters, the import must -// be present explicitly. See references/common-imports.md. +// be present explicitly. See common-imports.md. // ⁶ TIMING RULE: NEVER use requestAnimationFrame // - DOM timing: Use Glimmer modifiers with cleanup diff --git a/skills/boxel/references/styling-design.md b/skills/boxel/references/styling-design.md index 09b60a9..d7864e9 100644 --- a/skills/boxel/references/styling-design.md +++ b/skills/boxel/references/styling-design.md @@ -124,7 +124,7 @@ body { margin: 0; } {{formatNumber @model.count size="tiny"}} ``` -Canonical formatter imports live in `@cardstack/boxel-ui/helpers`, not the catalog realm. See `references/formatters.md` for the full list. For static `markdown` templates, use `https://cardstack.com/base/markdown-helpers` instead. +Canonical formatter imports live in `@cardstack/boxel-ui/helpers`, not the catalog realm. See `formatters.md` for the full list. For static `markdown` templates, use `https://cardstack.com/base/markdown-helpers` instead. ## Design Philosophy and Competitive Styling @@ -151,13 +151,13 @@ Dense professional layouts with thoughtful scaling: - Radius: match the aesthetic (sharp for technical, soft for friendly) - Shadows: subtle elevation for interactive elements; keep z-index conservative (<10) -Implementation tip: Define CSS variables at component root and use fallbacks. +Implementation tip: Define local CSS variables at the component root, referencing theme tokens directly — no hardcoded fallbacks (the `--boxel-*` tokens are always defined). ```css .component { - --card-padding: var(--boxel-sp, 1rem); - --card-radius: var(--boxel-border-radius-sm, 0.5rem); - --card-shadow: var(--boxel-box-shadow, 0 2px 4px rgba(0,0,0,0.1)); + --card-padding: var(--boxel-sp); + --card-radius: var(--boxel-border-radius-sm); + --card-shadow: var(--boxel-box-shadow); padding: var(--card-padding); border-radius: var(--card-radius); box-shadow: var(--card-shadow); diff --git a/skills/boxel/references/technical-rules.md b/skills/boxel/references/technical-rules.md deleted file mode 100644 index 3c4f958..0000000 --- a/skills/boxel/references/technical-rules.md +++ /dev/null @@ -1,87 +0,0 @@ -### The Cardinal Rule - -**MOST CRITICAL RULE:** -```gts -// ✅ CORRECT -@field author = linksTo(Author); // CardDef -@field address = contains(AddressField); // FieldDef - -// ❌ WRONG - Will break everything -@field author = contains(Author); // NEVER! -@field address = linksTo(AddressField); // NEVER! -``` - -**Must export ALL classes:** -```gts -export class MyCard extends CardDef { } // ✅ -class MyCard extends CardDef { } // ❌ Missing export -``` - -**Computed fields:** -- Keep simple and unidirectional -- No self-reference or cycles -- Wrap cross-card access in try-catch - -## Technical Rules - -### THE CARDINAL RULE: contains vs linksTo - -**THIS IS THE #1 MOST CRITICAL RULE IN BOXEL:** - -| Type | MUST Use | NEVER Use | Why | -|------|----------|-----------|-----| -| **Extends CardDef** | `linksTo` / `linksToMany` | ❌ `contains` / `containsMany` | CardDef = independent entity with own JSON file | -| **Extends FieldDef** | `contains` / `containsMany` | ❌ `linksTo` / `linksToMany` | FieldDef = embedded data, no separate identity | - -```gts -// ✅ CORRECT -@field author = linksTo(Author); // Author extends CardDef -@field address = contains(AddressField); // AddressField extends FieldDef - -// ❌ WRONG -@field author = contains(Author); // NEVER! -@field address = linksTo(AddressField); // NEVER! -``` - -### MANDATORY TECHNICAL REQUIREMENTS - -1. **Always use SEARCH/REPLACE with tracking for .gts files** -2. **Export ALL CardDef and FieldDef classes inline** -3. **Never use reserved words as field names** -4. **Keep computed fields simple and unidirectional** -5. **No JavaScript in templates** -6. **Wrap delegated collections with spacing containers** - -### TECHNICAL VALIDATION CHECKLIST - -Before generating ANY code: -- [ ] SEARCH/REPLACE blocks with tracking markers -- [ ] Every CardDef field uses `linksTo`/`linksToMany` -- [ ] Every FieldDef field uses `contains`/`containsMany` -- [ ] All classes have `export` keyword inline -- [ ] No reserved words as field names -- [ ] No duplicate field definitions -- [ ] Computed fields are simple (no cycles!) -- [ ] Try-catch blocks wrap cross-card data access -- [ ] No JavaScript operations in templates -- [ ] ALL THREE FORMATS: isolated, embedded, fitted - -### Common Mistakes - -#### Using contains with CardDef -```gts -// ❌ WRONG -@field items = containsMany(Item); // Item is CardDef - -// ✅ CORRECT -@field items = linksToMany(Item); -``` - -#### Missing Exports -```gts -// ❌ WRONG -class BlogPost extends CardDef { } - -// ✅ CORRECT -export class BlogPost extends CardDef { } -``` \ No newline at end of file diff --git a/skills/glossary.md b/skills/glossary.md index 5b057df..f76ceac 100644 --- a/skills/glossary.md +++ b/skills/glossary.md @@ -59,6 +59,7 @@ Entry shape: `**Term** — one-sentence definition + (optional) where it's cover - **divider strategy (binary)** — Either parent draws dividers AND kills child halo (`.boxel-card-container--boundaries { box-shadow: none; }`), OR child halo IS the boundary AND parent skips borders. Doing both = "drop shadow fighting thin border." - **block param shadow trap** — `as |s|`, `as |section|`, `as |option|` etc. that shadow HTML tag names throw `TypeError: Cannot read properties of null (reading 'manager')`. Use unambiguous names. - **`{{#if this.x}}`** — Guard a block by a class getter / tracked property. **Don't write `{{#if (this.x)}}`** — wrapping in parens makes Glimmer treat it as a helper invocation; class getters fail silently. +- **keyed single-item `{{#each}}` remount** — Wrap a visual subtree in `{{#each (array trackedValue) key='@identity'}}` when changing tracked state must recreate its DOM and replay entrance animation; keep persistent controls outside the keyed subtree. → `boxel-ui-guidelines/references/template-patterns.md` - **template helpers (importable from `@ember/helper`)** — `fn`, `hash`, `array`, `get`, `concat`. Must be imported in `.gts`. - **template helpers (Boxel UI)** — `eq`, `not`, `and`, `or`, `gt`, `gte`, `lt`, `lte`, `add`, `subtract`, `multiply`, `divide`; `cn` (class names), `cssVar`, `element` (dynamic tag), `optional`, `pick`. → `boxel/references/common-imports.md` - **template formatters (Boxel UI helpers)** — `formatDateTime`, `formatNumber`, `formatCurrency`, `formatRelativeTime`, `formatDuration`, `formatCountdown`, `formatFileSize`, `formatList`, `formatNames`, `formatOrdinal`, `formatPeriod`, `formatAge`. → `boxel/references/formatters.md` @@ -136,7 +137,7 @@ The 4-stage recommended process for any user-facing card: - **`npx boxel file lint --realm --file `** — Local lint before push. Use during development. - **`npx boxel lint --realm `** — Remote lint after push. Authoritative check. -- **`npx boxel check`** — Sync-state report. **NOT a lint check.** Don't confuse the two. +- **`npx boxel check`** — Removed from the current monorepo CLI. It was a legacy standalone sync-state command; use `npx boxel file lint` / `npx boxel lint` for lint. - **Clean lint output** — `No lint issues found` (text) or `{ "messages": [] }` (JSON). - Lint is **mandatory** before declaring `.gts` work done. → `boxel/references/lint-workflow.md` @@ -151,6 +152,7 @@ The 4-stage recommended process for any user-facing card: - **No-inline-binary rule** — Never `data:`, `blob:`, base64, image bytes, MP3 bytes in `StringField`, `outputText`, notes, or any JSON attribute. Use FileDef subtypes; for generated bytes, write to a realm file via `WriteBinaryFileCommand` first. → `boxel-file-def/references/no-inline-binary.md` - **`linksTo(FileDef)`** — How a card references a file-backed asset. - **Relationship-path-with-extension rule** — When linking to a `MarkdownDef` etc., the relationship path needs the actual file extension. +- **published raw FileDef URL** — Public host HTML should point raw ``/media elements at the published mount path, not an authenticated source-realm URL; reindex after changing it so cached HTML is refreshed. → `boxel-file-def/references/using-filedef-in-cards.md` ## 10. BFM — Boxel Flavored Markdown @@ -176,7 +178,8 @@ The dialect Boxel reads and writes. See [bfm.boxel.site](https://bfm.boxel.site) ## 11. Container queries + fitted layout - **`container-name: fitted-card`** — Already set by the host on every fitted-card wrapper; reference it in `@container fitted-card (...)` queries. -- **two-element `.cq` → `.fit` pattern** — The mandatory fitted template skeleton. → `boxel/references/container-query-fitted-layout.md` +- **single-root `.fit` pattern** — The mandatory fitted template skeleton: one root `.fit` grid queried against the host's `fitted-card` container; never create your own container on the root. (Supersedes the old two-element `.cq` → `.fit` pattern.) → `boxel/references/container-query-fitted-layout.md` +- **`FittedCard`** — boxel-ui slot-fill component; the preferred starting point for standard fitted templates (implements the `fitted-card` queries internally, tuned via `--fc-*` variables). → `boxel-ui-guidelines/references/use-boxel-ui-components.md` + the "Prefer ``" section of `container-query-fitted-layout.md` - **six height quanta** — Six discrete `@container` ranges for fitted body content. - **`minmax(0, 1fr)` body row** — Required for grid-children inside fitted cards; `auto` rows for body content overflow at edge sizes. - **`min-height: 0` on grid children** — Required so flex/grid children can shrink rather than overflow. @@ -191,7 +194,8 @@ The dialect Boxel reads and writes. See [bfm.boxel.site](https://bfm.boxel.site) - **`hostRoutingRules`** — `containsMany(RoutingRuleField)` on `RealmConfig`. Maps clean paths (`/`, `/about`, `/blog`) to cards in the same realm. - **same-realm guard** — Defensive read-time filter that drops routing rules pointing at cards in *other* realms. Prevents private-realm card surfacing via a public realm's URL. - **published realm** — Realm reachable by anonymous visitors over HTTPS at a public domain. -- **`/_search-prerendered`** — Realm-server endpoint returning prerendered card HTML for the 16 fitted sizes (used by host preview). +- **print and published-output hardening** — Unclip host wrappers in print media, use semantic SVG fill attributes, validate Chromium and Firefox print output, and reindex before checking published HTML. → `boxel-ui-guidelines/references/print-and-published-output.md` +- **`/_search-prerendered`** — Deployment-specific realm-server endpoint for prerendered HTML. Where exposed, call it with HTTP `QUERY`, not `GET`; otherwise use `npx boxel search --json` and inspect `relationships.html`. - **`.boxel-history/`** — Per-realm git repo for change history; managed by `boxel-cli`. - **`.boxel-sync.json`** — File → md5 checksum manifest the CLI compares against during pull/push/sync. - **public permissions** — Realm-level setting that determines whether anonymous host-mode requests resolve. @@ -219,6 +223,8 @@ Substantial JS libraries shipped *inside* a realm filesystem rather than via npm Direct browser ESM imports for libraries Boxel realms don't bundle. - **`https://esm.run/three@`** — Three.js for 3D / WebGL. Same modifier-lifecycle pattern covers Babylon.js and raw WebGL. → `integrate-three-js-via-cdn` +- **raised relief vs flat/flush inlay** — Raised features overlap an uncut backing; flat/flush features occupy a deliberately over-cut cavity and end at the backing surface. → `integrate-three-js-3mf-fabrication` +- **welded manifold export mesh** — Quantize transformed vertices, reuse indices, remove collapsed triangles, and require every undirected edge exactly twice before 3MF serialization. → `integrate-three-js-3mf-fabrication` - **`https://esm.sh/leaflet@`** — Leaflet maps. Requires CSS link + explicit container size. → `integrate-leaflet-via-cdn` - **`https://esm.run/chess.js@`** + **`cm-chessboard`** — Chess engine + board. FEN persistence. → `integrate-chess-js-via-cdn` - **`https://esm.run/tone@14`** — Tone.js music toolkit. Polyphonic synths, transports, effects chains. → `integrate-tone-js-via-cdn` @@ -297,21 +303,27 @@ Available only inside the running Boxel app. Each is a default-export `Command` ## 21. `boxel-cli` (the local CLI) -- **`boxel profile `** — Profile / environment management. +Use the namespaced CLI published from the Boxel monorepo through `npx boxel`. The standalone flat-command CLI is legacy and its command shapes must not be copied into current guidance. + +- **`npx boxel profile `** — Profile / environment management. `switch` changes the global active profile; restore it after a temporary environment change. - **`npx boxel realm `** — Realm lifecycle. - **`npx boxel realm pull `** — Realm → local. - **`npx boxel realm push `** — Local → realm. - **`npx boxel realm sync `** — Bidirectional. (Has known hang issue on some realms; fall back to push/pull.) +- **fresh-realm push ordering** — Push definitions, wait for schemas, then write instances. Mixed first pushes can preserve card counts while replacing nested realm-defined field values with `null`. → `boxel-environment/references/fresh-realm-push-integrity.md` - **`npx boxel realm status `** — Classify local changes vs. manifest. +- **`npx boxel realm publish ` / `unpublish`** — Create or remove an anonymous host-mode copy. +- **`npx boxel realm indexing-errors --realm `** — List cards that failed to index when supported by the installed CLI. - **`npx boxel realm history`** — List/restore/tag checkpoints. - **`npx boxel realm milestone`** — Tag checkpoints. -- **`npx boxel realm watch `** — Auto-sync on file changes. +- **`npx boxel realm watch `** — Pull server-side realm changes into the local workspace; not a local auto-push loop. - **`npx boxel file `** — Per-file realm operations. - **`npx boxel file lint --realm --file `** — Local lint. - **`npx boxel lint [path] --realm `** — Remote lint (single file or whole realm). +- **`npx boxel parse [path]`** — Local Glint type-check plus JSON document validation. - **`npx boxel search '' --realms `** — Federated search. - **`npx boxel run-command [--realm ] [--input ] [--json]`** — Execute a host command via the prerenderer. CLI invocation mode for Commands. → `automate-run-command-cli` -- **`boxel consolidate-workspaces`** — Merge multiple watched workspaces (interactive). +- **`npx boxel consolidate-workspaces`** — Merge multiple watched workspaces (interactive). → `boxel-patterns/references/integration-surfaces.md` §10 @@ -336,8 +348,8 @@ Available only inside the running Boxel app. Each is a default-export `Command` ## 23. Skill catalog -- **`boxel`** — Core framework rules. 18+ references (`core-concept.md`, `technical-rules.md`, `query-systems.md`, `fitted-formats.md`, `design-playbook.md`, `lint-workflow.md`, `command-development.md`, `command-invocation-modes.md`, `container-query-fitted-layout.md`, `delegated-rendering.md`, `theme-design-system.md`, `styling-design.md`, `formatters.md`, `enumerations.md`, `date-math.md`, `base-field-catalog.md`, `imagedef.md`, `external-libraries.md`, `template-syntax.md`, `data-management.md`, `defensive-programming.md`, `common-imports.md`, `core-patterns.md`, `file-editing.md`, `icons.md`, `quick-reference.md`, `spec-usage.md`). -- **`boxel-patterns`** — Outcome-indexed catalogue of 40+ working patterns + `integration-surfaces.md` (capability cheatsheet) + `libraries.md` (import-path catalogue) + `ai-image-models.md` (verified model IDs for image generation). +- **`boxel`** — Core framework rules. 18+ references (`core-concept.md`, `query-systems.md`, `fitted-formats.md`, `design-playbook.md`, `lint-workflow.md`, `command-development.md`, `command-invocation-modes.md`, `container-query-fitted-layout.md`, `delegated-rendering.md`, `theme-design-system.md`, `styling-design.md`, `formatters.md`, `enumerations.md`, `date-math.md`, `base-field-catalog.md`, `imagedef.md`, `external-libraries.md`, `template-syntax.md`, `data-management.md`, `defensive-programming.md`, `common-imports.md`, `core-patterns.md`, `file-editing.md`, `icons.md`, `quick-reference.md`, `spec-usage.md`). +- **`boxel-patterns`** — Outcome-indexed catalogue of 50+ working patterns + `integration-surfaces.md` (capability cheatsheet) + `libraries.md` (import-path catalogue) + `ai-image-models.md` (verified model IDs for image generation) + `pattern-authoring.md` (README template, naming conventions, `validated:` ladder, promotion bar) + `pattern-backlog.md` (reserved-but-unextracted slugs — do not chase). - **`boxel-ui-guidelines`** — Template UI rules, `delegated-render-control.md`, `template-patterns.md`, `style-budget.md`, `prevent-content-overflow.md`. - **`boxel-design`** — Visual design + asset selection + critical anti-LLM-cliché rules. - **`boxel-theme-development`** — Theme/StyleReference/BrandGuide creation, DESIGN.md mapping, brand tokens, logo/mark capture, and audit workflow. @@ -345,7 +357,8 @@ Available only inside the running Boxel app. Each is a default-export `Command` - **`boxel-file-def`** — File-backed fields (`FileDef`, `ImageDef`, etc.). - **`boxel-flavored-markdown`** — BFM authoring with directives + fenced renderers. - **`boxel-markdown-format`** — Static `markdown` template output format. -- **`boxel-create-edit-cards`** — Command-path selection for card creation/editing. +- **`boxel-create-edit-cards`** — Thin pointer skill; content lives at `boxel-environment/references/card-tool-selection.md` (create/edit tool tables, file naming, path rules). +- **`boxel-skill-authoring`** — SKILL.md format contract for user-authored skills: `boxel.kind: skill` frontmatter, tool declarations, verify loop. - **`catalog-listing`** — Catalog operations + submission via `SubmissionWorkflowCard`. - **`source-code-editing`** — Canonical SEARCH/REPLACE edit transport. @@ -439,6 +452,7 @@ Ready patterns live at `boxel-patterns/patterns//{README.md, example.gts}` - **`integrate-thumbnail-card-ai`** — AI thumbnail via `GenerateThumbnailCommand` (with `cardInfo.cardThumbnail` auto-patch). - **`integrate-send-request-via-proxy`** — Generic third-party HTTP through the host proxy. - **`integrate-three-js-via-cdn`** — Three.js / Babylon.js / raw WebGL via ESM CDN + modifier lifecycle. +- **`integrate-three-js-3mf-fabrication`** — Raised versus flat/flush fabrication geometry, manifold mesh validation, and multicolor component planning for 3MF slicers. - **`integrate-leaflet-via-cdn`** — Leaflet map. - **`integrate-chess-js-via-cdn`** — chess.js + cm-chessboard combo. - **`integrate-tone-js-via-cdn`** — Tone.js music toolkit. diff --git a/skills/source-code-editing/SKILL.md b/skills/source-code-editing/SKILL.md index aa833bb..27a1286 100644 --- a/skills/source-code-editing/SKILL.md +++ b/skills/source-code-editing/SKILL.md @@ -1,6 +1,6 @@ --- name: source-code-editing -description: Use when editing existing .gts files via SEARCH/REPLACE blocks. Defines exact block format, matching rules, the .gts edit-tracking convention, and recovery from failed matches. Required before issuing any .gts code edit. +description: Use when editing existing .gts or .json files via SEARCH/REPLACE blocks. Defines exact block format, matching rules, the .gts edit-tracking convention, and recovery from failed matches. Required before issuing any code edit. boxel: kind: skill --- @@ -143,9 +143,9 @@ Boxel `.gts` files carry an edit-tracking convention so changes stay visible in // ═══ [EDIT TRACKING: ON] Mark all changes with ⁿ ═══ ``` -When you create a new `.gts` file, this banner is the first line of your REPLACE section. +When you create a new Boxel `.gts` file, this banner is the first line of your REPLACE section (some earlier generic examples in this document omit tracking markers; for Boxel `.gts` files in this repo, always include the banner and markers described below). -**When editing an existing `.gts` file, mark every line you add or change** with a sequential superscript comment — `// ¹ description`, `// ²`, `// ³` … — continuing from the highest marker already present in the file. Keep existing markers intact when they appear in your SEARCH section (they also help make the match unique). +**Mark every line you add or change** with a sequential superscript comment — `// ¹ description`, `// ²`, `// ³` … — continuing from the highest marker already present in the file. Keep existing markers intact when they appear in your SEARCH section (they also help make the match unique). Example creating a new file: