Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Get it running first, so you know the environment is sound:

```bash
npm install
npm test # 347 of them, ~1.5s, no network
npm test # 362 of them, ~1.5s, no network
npm run dev # demo mode; no keys needed
```

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,12 @@ The audit needs a running server and `npx playwright install chromium`, so it st


```bash
npm test # 347 of them, ~1.5s, no network
npm test # 362 of them, ~1.5s, no network
```

They cover **the three rules taken back from the model** — selling-point count decides grid vs list, tier count decides single vs comparison, whether there is a UI screenshot decides the hero layout. If any of these drift, the decision quietly goes back to a model that cannot make it, so they are the ones that must not move.

Also covered: readiness gating (an empty array counts as missing, not ready), `asSettled` completion ordering, and the JSX serializer's sharp edges — custom properties need `as CSSProperties`, semicolons inside a gradient are not separators, text containing braces must be wrapped, and the `blk-in` animation class and `<style>` block must not reach the export. The agent-facing surfaces get the same treatment: `bundle.zip` is read back by a second, deliberately slow zip reader written in the test file rather than by the writer that produced it, `/llms.txt` is parsed against the llmstxt.org shape and checked line by line against the real block, theme and source tables, and the MCP server is driven through the handshake, every tool it advertises, and a series of messages that arrive broken.
Also covered: the catalog contract, now enforced rather than declared — a block whose props do not satisfy the schema `lib/catalog.ts` states for it holds its place as a skeleton instead of reaching a renderer that casts; readiness gating (an empty array counts as missing, not ready), `asSettled` completion ordering, and the JSX serializer's sharp edges — custom properties need `as CSSProperties`, semicolons inside a gradient are not separators, text containing braces must be wrapped, and the `blk-in` animation class and `<style>` block must not reach the export. The agent-facing surfaces get the same treatment: `bundle.zip` is read back by a second, deliberately slow zip reader written in the test file rather than by the writer that produced it, `/llms.txt` is parsed against the llmstxt.org shape and checked line by line against the real block, theme and source tables, and the MCP server is driven through the handshake, every tool it advertises, and a series of messages that arrive broken.

Plus structural invariants: every slot an archetype references must exist, required and optional must not overlap, `SLOT_ORDER` must cover every slot exactly once, and every variant must declare the fields it needs.

Expand Down
9 changes: 8 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSONUIProvider, Renderer } from "@json-render/react";
import type { Spec } from "@json-render/core";
import { registry } from "./registry";
import { THEMES } from "@/lib/themes";
import { validateProps } from "@/lib/catalog";
import { elementsFor, type SiteContent } from "@/lib/content";
import { isReady } from "@/lib/content-parallel";
import { layoutRules, SLOTS, SLOT_ORDER, type SlotKey } from "@/lib/plan";
Expand Down Expand Up @@ -241,6 +242,12 @@ export default function Page() {
}
const element = elementsFor(content)[plan.variant];
if (!element) return;
// compose.ts checks every element against its catalog schema before the spec leaves the server, and these two branches splice past that: the element is assembled here, from content this client happens to be holding. isReady above says the fields are there; only the catalog says they are the right shape. A restyle onto a variant the copy cannot fill is the same non-event either way, and saying so beats swapping in a block whose props the renderer will cast rather than check.
const problem = validateProps(element.type, element.props ?? {});
if (problem) {
setEditLog((log) => [...log, ` ${t.notReady}`]);
return;
}
setVariants((current) => ({ ...current, [plan.slot]: plan.variant }));
setSpec((current) => {
if (!current) return current;
Expand Down Expand Up @@ -269,7 +276,7 @@ export default function Page() {
return;
}
const element = elementsFor(content)[id as keyof ReturnType<typeof elementsFor>];
if (!element) {
if (!element || validateProps(element.type, element.props ?? {})) {
setEditLog((log) => [...log, ` ${t.notReady}`]);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

```bash
npm install
npm test # 347 个,约 1.5 秒,不碰网络
npm test # 362 个,约 1.5 秒,不碰网络
npm run dev # 演示模式,不需要 key
```

Expand Down
4 changes: 2 additions & 2 deletions docs/README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,12 @@ Die Prüfung braucht einen laufenden Server und `npx playwright install chromium


```bash
npm test # 347 Stück, rund 1,5 s, ohne Netz
npm test # 362 Stück, rund 1,5 s, ohne Netz
```

Sie decken **die drei dem Modell abgenommenen Regeln** ab: Die Anzahl der Verkaufsargumente entscheidet Raster oder Liste, die Anzahl der Preisstufen entscheidet Einzelkarte oder Vergleich, und ob es einen Oberflächen-Screenshot gibt, entscheidet das Layout des Kopfbereichs. Driftet eine davon, geht die Entscheidung still an ein Modell zurück, das sie nicht treffen kann — sie dürfen sich also nicht bewegen.

Ebenfalls abgedeckt: die Freigabe nach Datenlage (ein leeres Array zählt als fehlend, nicht als bereit), die Abschlussreihenfolge von `asSettled` und die Kanten des JSX-Serialisierers — Custom Properties brauchen `as CSSProperties`, Semikolons in einem Farbverlauf sind keine Trennzeichen, Text mit geschweiften Klammern muss eingefasst werden, und weder die Animationsklasse `blk-in` noch der `<style>`-Block dürfen in den Export gelangen. Die an Agenten gerichteten Oberflächen bekommen dieselbe Behandlung: `bundle.zip` wird von einem zweiten, bewusst langsamen Zip-Leser zurückgelesen, der in der Testdatei steht und nicht von dem Schreiber, der es erzeugt hat; `/llms.txt` wird gegen die Form von llmstxt.org geparst und Zeile für Zeile mit den echten Block-, Theme- und Quellentabellen abgeglichen; und der MCP-Server wird durch den Handshake, durch jedes Werkzeug, das er ankündigt, und durch eine Reihe kaputt eintreffender Nachrichten geführt.
Ebenfalls abgedeckt: der Vertrag des Katalogs, jetzt durchgesetzt statt bloß erklärt — ein Block, dessen Props das Schema nicht erfüllen, das `lib/catalog.ts` für ihn festlegt, hält seinen Platz als Skelett, statt bei einem Renderer zu landen, der nur castet; die Freigabe nach Datenlage (ein leeres Array zählt als fehlend, nicht als bereit), die Abschlussreihenfolge von `asSettled` und die Kanten des JSX-Serialisierers — Custom Properties brauchen `as CSSProperties`, Semikolons in einem Farbverlauf sind keine Trennzeichen, Text mit geschweiften Klammern muss eingefasst werden, und weder die Animationsklasse `blk-in` noch der `<style>`-Block dürfen in den Export gelangen. Die an Agenten gerichteten Oberflächen bekommen dieselbe Behandlung: `bundle.zip` wird von einem zweiten, bewusst langsamen Zip-Leser zurückgelesen, der in der Testdatei steht und nicht von dem Schreiber, der es erzeugt hat; `/llms.txt` wird gegen die Form von llmstxt.org geparst und Zeile für Zeile mit den echten Block-, Theme- und Quellentabellen abgeglichen; und der MCP-Server wird durch den Handshake, durch jedes Werkzeug, das er ankündigt, und durch eine Reihe kaputt eintreffender Nachrichten geführt.

Dazu strukturelle Invarianten: Jeder von einem Archetyp referenzierte Slot muss existieren, Pflicht und Optional dürfen sich nicht überschneiden, `SLOT_ORDER` muss jeden Slot genau einmal abdecken, und jede Variante muss die Felder deklarieren, die sie braucht.

Expand Down
4 changes: 2 additions & 2 deletions docs/README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,12 +332,12 @@ La auditoría necesita el servidor en marcha y `npx playwright install chromium`


```bash
npm test # 347, unos 1,5 s, sin red
npm test # 362, unos 1,5 s, sin red
```

Cubren **las tres reglas recuperadas del modelo**: el número de argumentos decide rejilla o lista, el número de planes decide tarjeta única o comparativa, y la existencia de una captura de interfaz decide la composición de la portada. Si alguna se desvía, la decisión vuelve en silencio a un modelo que no puede tomarla, así que son las que no deben moverse.

También se cubren: el control de disponibilidad (un array vacío cuenta como ausente, no como listo), el orden de finalización de `asSettled`, y las aristas del serializador de JSX —las propiedades personalizadas necesitan `as CSSProperties`, los punto y coma dentro de un degradado no son separadores, el texto con llaves debe envolverse, y ni la clase de animación `blk-in` ni el bloque `<style>` pueden llegar a la exportación—. Las superficies dirigidas a agentes reciben el mismo trato: `bundle.zip` se vuelve a leer con un segundo lector de zip, deliberadamente lento, escrito en el propio archivo de pruebas y no con el escritor que lo produjo; `/llms.txt` se analiza contra la forma de llmstxt.org y se coteja línea a línea con las tablas reales de bloques, temas y fuentes; y al servidor MCP se le hace pasar por el saludo, por todas las herramientas que anuncia y por una serie de mensajes que llegan rotos.
También se cubren: el contrato del catálogo, ahora exigido y no solo declarado — un bloque cuyas props no satisfacen el esquema que `lib/catalog.ts` le fija guarda su sitio como esqueleto en lugar de llegar a un renderizador que se limita a castear; el control de disponibilidad (un array vacío cuenta como ausente, no como listo), el orden de finalización de `asSettled`, y las aristas del serializador de JSX —las propiedades personalizadas necesitan `as CSSProperties`, los punto y coma dentro de un degradado no son separadores, el texto con llaves debe envolverse, y ni la clase de animación `blk-in` ni el bloque `<style>` pueden llegar a la exportación—. Las superficies dirigidas a agentes reciben el mismo trato: `bundle.zip` se vuelve a leer con un segundo lector de zip, deliberadamente lento, escrito en el propio archivo de pruebas y no con el escritor que lo produjo; `/llms.txt` se analiza contra la forma de llmstxt.org y se coteja línea a línea con las tablas reales de bloques, temas y fuentes; y al servidor MCP se le hace pasar por el saludo, por todas las herramientas que anuncia y por una serie de mensajes que llegan rotos.

Además, invariantes estructurales: cada slot que referencia un arquetipo debe existir, obligatorios y opcionales no pueden solaparse, `SLOT_ORDER` debe cubrir cada slot exactamente una vez, y cada variante debe declarar los campos que necesita.

Expand Down
4 changes: 2 additions & 2 deletions docs/README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,12 @@ L'audit a besoin d'un serveur lancé et de `npx playwright install chromium`, il


```bash
npm test # 347, environ 1,5 s, sans réseau
npm test # 362, environ 1,5 s, sans réseau
```

Ils couvrent **les trois règles reprises au modèle** : le nombre d'arguments décide grille ou liste, le nombre de paliers décide carte unique ou comparatif, et la présence d'une capture d'interface décide la mise en page de l'en-tête. Si l'une dérive, la décision retourne en silence à un modèle incapable de la prendre : ce sont celles qui ne doivent pas bouger.

Sont également couverts : le contrôle de disponibilité (un tableau vide compte comme absent, pas comme prêt), l'ordre d'achèvement de `asSettled`, et les pièges du sérialiseur JSX — les propriétés personnalisées ont besoin de `as CSSProperties`, les points-virgules d'un dégradé ne sont pas des séparateurs, un texte contenant des accolades doit être encadré, et ni la classe d'animation `blk-in` ni le bloc `<style>` ne doivent atteindre l'export. Les surfaces destinées aux agents reçoivent le même traitement : `bundle.zip` est relu par un second lecteur de zip, délibérément lent, écrit dans le fichier de test et non par l'écrivain qui l'a produit ; `/llms.txt` est analysé contre la forme de llmstxt.org et confronté ligne à ligne aux vraies tables de blocs, de thèmes et de sources ; et le serveur MCP est mené à travers la poignée de main, chacun des outils qu'il annonce, et une série de messages qui arrivent cassés.
Sont également couverts : le contrat du catalogue, désormais appliqué et non plus seulement déclaré — un bloc dont les props ne satisfont pas le schéma que `lib/catalog.ts` lui fixe garde sa place en squelette plutôt que d'atteindre un moteur de rendu qui se contente d'un cast ; le contrôle de disponibilité (un tableau vide compte comme absent, pas comme prêt), l'ordre d'achèvement de `asSettled`, et les pièges du sérialiseur JSX — les propriétés personnalisées ont besoin de `as CSSProperties`, les points-virgules d'un dégradé ne sont pas des séparateurs, un texte contenant des accolades doit être encadré, et ni la classe d'animation `blk-in` ni le bloc `<style>` ne doivent atteindre l'export. Les surfaces destinées aux agents reçoivent le même traitement : `bundle.zip` est relu par un second lecteur de zip, délibérément lent, écrit dans le fichier de test et non par l'écrivain qui l'a produit ; `/llms.txt` est analysé contre la forme de llmstxt.org et confronté ligne à ligne aux vraies tables de blocs, de thèmes et de sources ; et le serveur MCP est mené à travers la poignée de main, chacun des outils qu'il annonce, et une série de messages qui arrivent cassés.

Plus des invariants structurels : tout slot référencé par un archétype doit exister, obligatoires et facultatifs ne doivent pas se recouper, `SLOT_ORDER` doit couvrir chaque slot exactement une fois, et chaque variante doit déclarer les champs dont elle a besoin.

Expand Down
4 changes: 2 additions & 2 deletions docs/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,12 @@ npm run audit:a11y # 生成結果に axe-core、6 テーマすべて


```bash
npm test # 347 件、約 1.5 秒、ネットワーク不使用
npm test # 362 件、約 1.5 秒、ネットワーク不使用
```

中心は**モデルから取り戻した三つの規則**です——売りの個数がグリッドかリストかを決め、料金段階数が単一か比較かを決め、画面写真の有無がファーストビューの版を決める。ここが戻ると、判断は答えられないモデルに黙って返されてしまうので、最も動いてはいけない部分です。

ほかに準備完了の門(空配列は準備済みではなく欠落として扱う)、`asSettled` の完了順、そして JSX 直列化の落とし穴——カスタムプロパティには `as CSSProperties` が要る、グラデーション内のセミコロンは区切りではない、波括弧を含む文字列は包む必要がある、`blk-in` アニメーションクラスと `<style>` は書き出しに入れてはいけない。エージェント向けの面も同じ扱いです。`bundle.zip` は、それを書いた側ではなくテストファイルに別途書いた、わざと素朴な zip リーダーで読み戻します。`/llms.txt` は llmstxt.org の形として解析し、実際のブロック・テーマ・供給元の表と一行ずつ突き合わせます。MCP サーバーはハンドシェイク、公開しているすべてのツール、そして壊れた形で届く一連のメッセージを通して動かします。
ほかにカタログの契約——宣言されるだけでなく実際に検査されるようになりました。`lib/catalog.ts` が定めたスキーマを props が満たさないブロックは、型を鵜呑みにするレンダラーへ渡らずスケルトンのまま場所を保ちます。加えて準備完了の門(空配列は準備済みではなく欠落として扱う)、`asSettled` の完了順、そして JSX 直列化の落とし穴——カスタムプロパティには `as CSSProperties` が要る、グラデーション内のセミコロンは区切りではない、波括弧を含む文字列は包む必要がある、`blk-in` アニメーションクラスと `<style>` は書き出しに入れてはいけない。エージェント向けの面も同じ扱いです。`bundle.zip` は、それを書いた側ではなくテストファイルに別途書いた、わざと素朴な zip リーダーで読み戻します。`/llms.txt` は llmstxt.org の形として解析し、実際のブロック・テーマ・供給元の表と一行ずつ突き合わせます。MCP サーバーはハンドシェイク、公開しているすべてのツール、そして壊れた形で届く一連のメッセージを通して動かします。

構造の不変条件も:原型が参照するスロットは必ず存在すること、必須と任意が重ならないこと、`SLOT_ORDER` が全スロットを過不足なく覆うこと、各バリアントが必要なフィールドを宣言していること。

Expand Down
4 changes: 2 additions & 2 deletions docs/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,12 @@ npm run audit:a11y # 생성 결과에 axe-core, 여섯 테마 전부


```bash
npm test # 347개, 약 1.5초, 네트워크 미사용
npm test # 362개, 약 1.5초, 네트워크 미사용
```

핵심은 **모델에게서 되찾아온 세 규칙**입니다 — 강점 개수가 그리드냐 리스트냐를, 요금 단계 수가 단일이냐 비교냐를, 화면 스크린샷 유무가 첫 화면 판을 정합니다. 여기가 되돌아가면 결정이 답할 수 없는 모델에게 조용히 넘어가므로, 가장 흔들리면 안 되는 부분입니다.

그 밖에 준비 완료 게이트(빈 배열은 준비됨이 아니라 누락으로 취급), `asSettled` 의 완료 순서, 그리고 JSX 직렬화의 함정 — 사용자 정의 속성에는 `as CSSProperties` 가 필요하고, 그라디언트 안의 세미콜론은 구분자가 아니며, 중괄호를 포함한 텍스트는 감싸야 하고, `blk-in` 애니메이션 클래스와 `<style>` 블록은 내보내기에 들어가면 안 됩니다. 에이전트를 향한 면들도 같은 대우를 받습니다. `bundle.zip` 은 그것을 만든 쓰기 쪽이 아니라 테스트 파일에 따로 쓴, 일부러 느리게 만든 zip 리더로 다시 읽습니다. `/llms.txt` 는 llmstxt.org 형태로 파싱해 실제 블록·테마·출처 표와 한 줄씩 대조합니다. MCP 서버는 핸드셰이크와, 스스로 광고하는 모든 도구와, 망가진 채로 도착하는 일련의 메시지를 통해 돌려 봅니다.
그 밖에 카탈로그 계약 — 이제 선언에 그치지 않고 실제로 검사됩니다. `lib/catalog.ts` 가 정한 스키마를 props 가 만족하지 못하는 블록은 타입을 그대로 믿는 렌더러에 닿지 않고 스켈레톤으로 자리를 지킵니다. 그리고 준비 완료 게이트(빈 배열은 준비됨이 아니라 누락으로 취급), `asSettled` 의 완료 순서, 그리고 JSX 직렬화의 함정 — 사용자 정의 속성에는 `as CSSProperties` 가 필요하고, 그라디언트 안의 세미콜론은 구분자가 아니며, 중괄호를 포함한 텍스트는 감싸야 하고, `blk-in` 애니메이션 클래스와 `<style>` 블록은 내보내기에 들어가면 안 됩니다. 에이전트를 향한 면들도 같은 대우를 받습니다. `bundle.zip` 은 그것을 만든 쓰기 쪽이 아니라 테스트 파일에 따로 쓴, 일부러 느리게 만든 zip 리더로 다시 읽습니다. `/llms.txt` 는 llmstxt.org 형태로 파싱해 실제 블록·테마·출처 표와 한 줄씩 대조합니다. MCP 서버는 핸드셰이크와, 스스로 광고하는 모든 도구와, 망가진 채로 도착하는 일련의 메시지를 통해 돌려 봅니다.

구조 불변식도 있습니다: 원형이 참조하는 슬롯은 반드시 존재할 것, 필수와 선택이 겹치지 않을 것, `SLOT_ORDER` 가 모든 슬롯을 빠짐없이 한 번씩 덮을 것, 각 변형이 필요한 필드를 선언할 것.

Expand Down
Loading
Loading