Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
907cba7
Workspace-run patches: seed overwrite, bootstrap NoOpValidator, cross…
christse Jul 17, 2026
877260b
Declare lodash as explicit boxel-ui dependency (CS-12187)
christse Jul 17, 2026
38fc6b2
Add on-demand skill tools and HTML screenshot tool to the factory agent
christse Jul 17, 2026
e9dc7d5
Add --v2 lean design-first mode to factory:go
christse Jul 17, 2026
e3f304b
V2 skill/prompt: Spec must populate catalog-facing title + description
christse Jul 17, 2026
2902016
Add RunLog live blog to --v2 runs; single-page mockups; no curl-polling
christse Jul 17, 2026
dc50089
Add --fork-context: prime once per brief, fork every implementation turn
christse Jul 17, 2026
133a8f3
Redesign RunLog to Boxel Workspace design language — alive under LIVE…
christse Jul 17, 2026
6305cde
RunLog: full activity coverage, show-me chips, throbber, live elapsed…
christse Jul 17, 2026
1a03291
Fix RunLog image field type: FileDef, not CardDef
christse Jul 17, 2026
f60ebc7
RunLog cold-start: intro/legend signage, top-pinned feed, warmup hint…
christse Jul 17, 2026
8916060
RunLog: raw re-write instance after sync to heal atomic FieldDef strip
christse Jul 17, 2026
bacab73
V2 live blog: stream tool events + agent post_update to the run log
christse Jul 17, 2026
6105e68
Ship moments carry live card links: stream handler tracks instance wr…
christse Jul 17, 2026
3ca6e7d
Validation entries link their step card (atom chip); v2 bootstrap sto…
christse Jul 17, 2026
3669071
Heal run-log after EVERY workspace sync (postSyncHeal hook)
christse Jul 17, 2026
b7ffcb0
Per-turn model/effort budget policy, orchestrator-owned
christse Jul 17, 2026
278e535
Cache-family heuristic: default fix budget tunes effort, not model; o…
christse Jul 17, 2026
d3dcc0b
Phase-split model mapping: DESIGN turn (flagship) + BUILD turn (cheap…
christse Jul 17, 2026
8d01aed
v2 skill: host tool import path rule + drag-drop items fallback rule
christse Jul 17, 2026
0c7e81a
v2 skill: searchResultsComponent wire-query contract rule
christse Jul 17, 2026
04fa079
v3: RunMonitor — orchestrator-authored run-log entries
christse Jul 17, 2026
54c7de5
v3: control/product realm split (--control-realm)
christse Jul 17, 2026
c8576cd
v3: host-import manifest gate (contract-drift elimination)
christse Jul 17, 2026
8713484
v3: render gate + acceptance walkthrough turn; v3 defaults
christse Jul 17, 2026
aab5219
v3: runtime-surface docs shipped as on-demand factory skills
christse Jul 17, 2026
2bc45fc
v3: GitHub-link briefs — port-analysis issue ahead of bootstrap
christse Jul 17, 2026
d5fd4bf
v3: --repo-url flag; port analysis goes INTO the code
christse Jul 17, 2026
729566a
v3: bootstrap prompt — repo-url stands in lieu of a wiki brief
christse Jul 17, 2026
cf3c875
Factory v2/v3: resume-safe restarts, session reuse, bug-fix path, run…
christse Jul 18, 2026
aafe8ae
docs: factory-v2-experiment branch overview (vs main)
christse Jul 18, 2026
e796601
docs: expand on-demand skill discovery/loading section
christse Jul 18, 2026
4c95090
Merge remote-tracking branch 'origin/main' into cs-12246-factory-v2-e…
jurgenwerk Jul 20, 2026
87d85fc
Update factory-entrypoint tests for the repo-url/control-realm arg su…
jurgenwerk Jul 20, 2026
092c276
Apply prettier formatting across software-factory
jurgenwerk Jul 20, 2026
da9bec4
Align seed e2e spec with resume-guard semantics
jurgenwerk Jul 20, 2026
580b9f2
Fix Spec realm routing and run-log render assertion
jurgenwerk Jul 20, 2026
49087c7
Drop the boxel-ui lodash declaration from this branch
jurgenwerk Jul 20, 2026
2a9bfd4
Add run-trace span telemetry across the factory loop
jurgenwerk Jul 21, 2026
3071755
Trace in-turn tool time, SDK lifecycle events, and full token usage
jurgenwerk Jul 21, 2026
6bbc7cc
Merge remote-tracking branch 'origin/main' into cs-12246-factory-v2-e…
jurgenwerk Jul 24, 2026
765af72
Leave factory-generated polish issues on the board by default
jurgenwerk Jul 24, 2026
9429358
Add --review-model / --review-effort for the post-issue review turn
jurgenwerk Jul 24, 2026
9d0c9af
Default the bootstrap turn to claude-sonnet-5
jurgenwerk Jul 24, 2026
8eac3df
Link the design-foundation and port-analysis seeds to the Project
jurgenwerk Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .agents/skills/boxel-workspace-cardinal-rules/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Boxel cardinal rules — silent-failure traps

Rules discovered the hard way in a downstream Boxel workspace: each one passes lint and
often passes indexing too, then breaks silently — corrupting the realm's index,
crashing at render, or dropping data with no error. Check every card/field you write
against this list before finishing an issue.

## 1. DateField vs DateTimeField value format

`DateField` values are `YYYY-MM-DD` (no `T`). `DateTimeField` values are full ISO
datetimes (`2026-07-16T14:30:00.000Z`, with `T`). Putting a datetime string in a
`DateField` (or vice versa) passes lint and indexes fine, then **crashes at render**
with `RangeError: Invalid time value` when a user actually opens the card. Naming
convention to follow when picking the field type: a `*At` suffix (`createdAt`,
`publishedAt`) means `DateTimeField`; a `*Date`/`*On` suffix or bare `dob` means
`DateField`.

## 2. Never put an external URL in `relationships.<field>.links.self`

If a `linksTo`/`linksToMany` field's JSON `links.self` points at a URL the indexer
can't parse as a card (an external website, an image CDN URL, anything not a card
resource), **the failed parse poisons the JSONB write and rolls back the WHOLE
REALM's indexing transaction** — every other file in the same push silently fails to
index too, with no error pointing at the actual bad file. For an external image/URL,
use the pair pattern instead: `linksTo(ImageDef)` (or a similar file/media field) +
`contains(UrlField)` as two separate fields, never one relationship pointing straight
at an external URL.

## 3. `linksToMany` JSON uses indexed top-level keys, never an array

Correct:

```json
"relationships": {
"items.0": { "links": { "self": "../foo" } },
"items.1": { "links": { "self": "../bar" } }
}
```

Wrong (rejected outright — "instance ... is not a card resource document"):

```json
"relationships": {
"items": { "links": { "self": ["../foo", "../bar"] } }
}
```

## 4. Never inline media or binary bytes in card JSON

No `data:` URIs, `blob:` URIs, base64, or raw media bytes in any JSON string field or
attribute. Store media as a realm file linked via `linksTo(FileDef)` (or a FileDef
subtype: `ImageDef`, `CsvFileDef`, etc.) instead — never embed the bytes directly in
the instance JSON.

## 5. Every query needs a realm scope, and don't start it before the realm is known

A card-owned query with a missing or empty `realm` argument silently falls back to
searching **every realm the server can see**, not just the current one. Always scope
queries to the current card's own realm, and don't kick off the query before that
realm URL is actually resolved. Cap general-purpose result sets (~100) rather than
pulling unbounded result sets.

## 6. Query-filter shape: `type` selects instances, `on` only scopes predicates

To select every instance of a type, filter on `{ type: <ref> }` directly — never wrap
it in `{ on: <ref> }` alone (`on` only scopes _other_ predicates like `eq`/`contains`;
a bare `{ on: ref }` with nothing else matches nothing). Build refs with a `codeRef()`
helper, not a manually-constructed object.

## 7. Don't push more than ~30 files through one atomic batch to a fresh realm

Large atomic pushes (30+ files via a single bulk-write endpoint) can report success
while silently dropping some files' indexing jobs. For bulk kit/asset installs, push
in smaller batches and verify each batch's expected file count actually shows up in a
realm search before pushing the next batch.

## 8. NEVER curl / HTTP-GET a `https://cardstack.com/base/*` module URL to inspect a base card

Base card module URLs (`https://cardstack.com/base/theme`, `.../base/card-api`,
`.../base/cards/structured-theme`, etc.) are **loader-resolved module references, not
fetchable HTTP resources.** `cardstack.com` is a marketing site — a direct GET or a
realm op (`_mtimes`, `boxel file read`) against `cardstack.com/base/...` returns a
generic Webflow **404 HTML page** (`data-wf-domain=... %%PUBLISH_URL_REPLACEMENT%%`),
NOT the card. Do not keep retrying it — that page will never become the schema. To
learn a base card's fields/shape, use the **`get_card_schema` tool** (it resolves
through the realm server), or read an existing instance of that card already in the
target realm. Same rule for any published `*.boxel.site` / `*.boxel.build` URL: those
are Webflow-published sites, not realms — never point realm operations at them.

Also: many base cards are **default exports**, so the schema ref is `name: "default"`,
NOT the class name. The base **Theme** card is the default export of
`https://cardstack.com/base/theme` (the module is `export default Theme`) — query it as
module `https://cardstack.com/base/theme`, name `default` (querying `#Theme` fails).
`StructuredTheme` is likewise the default export of `base/structured-theme`. When a
`get_card_schema` call fails with "named export is a CardDef", retry with `name:
"default"` before assuming the card is unreachable — do NOT fall back to curling the URL.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
name: boxel-delegated-render-control
description: Controlling child-card renders from the parent — fitted vs embedded format choice, the host-injected CardContainer chrome, and the plural-field/atom/stagger/divider traps. Use when embedding cards via <@fields.X @format='...' /> or when an embedded/fitted child looks wrong (empty boxes, collapsed grids, double borders, invisible atoms).
---

# Delegated Render Control

When a parent card renders a child via `<@fields.X @format='...' />`, the
host wraps the child in a `CardContainer` with chrome you didn't write
(rounded corners, 1px halo, light background, `overflow: hidden`). Two
decisions dominate the outcome: **which format** you pick, and **who owns
the chrome**. The full mechanics — exact injected DOM/CSS, override layers,
recipes, and the child-side contract — live in
`references/delegated-render-control.md`.

## Format choice = who owns the cell size (decide BEFORE styling)

| Format | Who controls the box size? | Use when |
| ---------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `embedded` | **The child.** Width fluid, height = the card's natural content. | Vertical lists, feeds, roster rows, variable-height items. |
| `fitted` | **The parent.** Child fills the box you give it (`width/height: 100%`, `container-type: size`). | Uniform tile grids (calendar cells, portraits, badge strips) where you deliberately set the cell size. |

**The single most common rendering bug:** picking `fitted` for a list of
variable-height cards — short content leaves a big empty box below each row.
The fix is the format choice, upstream of any CSS. Decision rule: _did you
set the cell size?_ Yes → `fitted` (+ `min-height`/`aspect-ratio` on the
cell). No → `embedded`.

## Who owns the chrome — three override layers (pick the lowest that works)

1. **Theme cascade** — if the linked card has `cardInfo.theme`, the theme's
`--background`, `--foreground`, `--border`, `--radius` flow into the
wrapper. Cleanest, but only when the child instance actually has a theme.
2. **`:deep()` from the parent's `<style scoped>`** — the workhorse.
Target `:deep(.boxel-card-container)` for radius/background,
`:deep(.boxel-card-container--boundaries)` for the 1px halo.
3. **`@displayContainer={{false}}`** — kills the chrome entirely
(`display: contents`); pair with a parent-owned wrapper element.

## The high-frequency traps

1. **Plural-field wrapper.** Rendering a plural field with one tag
(`<@fields.items @format='fitted' />`) inserts wrapper divs between your
grid and the cards — the grid sees ONE child and collapses to a single
column. Fix: `:deep(> .plural-field) { display: contents; }` plus
`:deep(.linksToMany-itemContainer), :deep(.containsMany-item) { display:
contents; }`. Targeting only `.containsMany-field` is the most common
bug — `linksToMany` ships `.linksToMany-field`, which never matches.
2. **Atoms on dark backgrounds disappear.** The atom chip's own near-white
background + halo win over your `color: inherit`. Fix: either
`@displayContainer={{false}}` (plain inline text), or keep the chip and
recolor via `:deep(.field-component-card.atom-format) { background:
transparent; box-shadow: none; color: var(--accent); }`.
3. **Stagger through `display: contents`.** `:nth-child` on the cards
themselves always resolves to 1 (each is the only child of its wrapper).
Set `--stagger-d` on `:deep(.linksToMany-itemContainer:nth-child(N))` /
`:deep(.containsMany-item:nth-child(N))` and read
`animation-delay: var(--stagger-d)` on `.field-component-card` — custom
properties inherit through `display: contents`.
4. **Divider strategy — parent draws OR child halo, never both.** Either
the parent draws dividers and kills the child halo
(`:deep(.boxel-card-container--boundaries) { box-shadow: none; }`), or
the halo IS the boundary and the parent adds no borders. Leaving both
yields the "drop shadow fighting a thin border" double-rule. When
switching strategies, DELETE the stale rule — same-selector CSS resolves
by source order.

## Don't override

`width/height: 100%` on `.fitted-format`, or `container-type` /
`container-name` on embedded/fitted — the child's container queries depend
on them. Style chrome (radius, background, border, shadow, padding), not
layout primitives.

## The child-side contract

The child draws ONLY inside the box; host or parent draws the box. No
`border-radius`, `border`, `box-shadow`, `overflow`, or size properties on
any format's outermost element — see the reference for the per-format table
and self-check.
Loading