Просмотр исходного кода

remove key/parentKey from flat specs (#77)

Chris Tate 5 месяцев назад
Родитель
Сommit
711e79b069

+ 0 - 16
apps/web/app/(main)/docs/openapi/page.tsx

@@ -275,11 +275,9 @@ export const openapiCatalog = createCatalog({
 }
 }
 
 
 interface SpecElement {
 interface SpecElement {
-  key: string;
   type: string;
   type: string;
   props: Record<string, unknown>;
   props: Record<string, unknown>;
   children: string[];
   children: string[];
-  parentKey: string;
 }
 }
 
 
 function schemaToSpec(
 function schemaToSpec(
@@ -295,7 +293,6 @@ function schemaToSpec(
 
 
   if (schema.enum) {
   if (schema.enum) {
     elements.set(key, {
     elements.set(key, {
-      key,
       type: 'EnumField',
       type: 'EnumField',
       props: {
       props: {
         name,
         name,
@@ -306,11 +303,9 @@ function schemaToSpec(
         defaultValue: schema.default as string,
         defaultValue: schema.default as string,
       },
       },
       children: [],
       children: [],
-      parentKey,
     });
     });
   } else if (schema.type === 'string') {
   } else if (schema.type === 'string') {
     elements.set(key, {
     elements.set(key, {
-      key,
       type: 'StringField',
       type: 'StringField',
       props: {
       props: {
         name,
         name,
@@ -323,11 +318,9 @@ function schemaToSpec(
         defaultValue: schema.default as string,
         defaultValue: schema.default as string,
       },
       },
       children: [],
       children: [],
-      parentKey,
     });
     });
   } else if (schema.type === 'integer' || schema.type === 'number') {
   } else if (schema.type === 'integer' || schema.type === 'number') {
     elements.set(key, {
     elements.set(key, {
-      key,
       type: 'NumberField',
       type: 'NumberField',
       props: {
       props: {
         name,
         name,
@@ -340,11 +333,9 @@ function schemaToSpec(
         defaultValue: schema.default as number,
         defaultValue: schema.default as number,
       },
       },
       children: [],
       children: [],
-      parentKey,
     });
     });
   } else if (schema.type === 'boolean') {
   } else if (schema.type === 'boolean') {
     elements.set(key, {
     elements.set(key, {
-      key,
       type: 'BooleanField',
       type: 'BooleanField',
       props: {
       props: {
         name,
         name,
@@ -353,7 +344,6 @@ function schemaToSpec(
         defaultValue: schema.default as boolean,
         defaultValue: schema.default as boolean,
       },
       },
       children: [],
       children: [],
-      parentKey,
     });
     });
   } else if (schema.type === 'array' && schema.items) {
   } else if (schema.type === 'array' && schema.items) {
     const childKeys: string[] = [];
     const childKeys: string[] = [];
@@ -361,7 +351,6 @@ function schemaToSpec(
     childKeys.push(itemKey);
     childKeys.push(itemKey);
 
 
     elements.set(key, {
     elements.set(key, {
-      key,
       type: 'ArrayField',
       type: 'ArrayField',
       props: {
       props: {
         name,
         name,
@@ -369,7 +358,6 @@ function schemaToSpec(
         description: schema.description,
         description: schema.description,
       },
       },
       children: childKeys,
       children: childKeys,
-      parentKey,
     });
     });
   } else if (schema.type === 'object' && schema.properties) {
   } else if (schema.type === 'object' && schema.properties) {
     const childKeys: string[] = [];
     const childKeys: string[] = [];
@@ -386,7 +374,6 @@ function schemaToSpec(
     }
     }
 
 
     elements.set(key, {
     elements.set(key, {
-      key,
       type: 'ObjectField',
       type: 'ObjectField',
       props: {
       props: {
         name,
         name,
@@ -394,7 +381,6 @@ function schemaToSpec(
         description: schema.description,
         description: schema.description,
       },
       },
       children: childKeys,
       children: childKeys,
-      parentKey,
     });
     });
   }
   }
 
 
@@ -428,7 +414,6 @@ export function operationToSpec(
   }
   }
 
 
   elements.set(rootKey, {
   elements.set(rootKey, {
-    key: rootKey,
     type: 'Form',
     type: 'Form',
     props: {
     props: {
       operationId,
       operationId,
@@ -438,7 +423,6 @@ export function operationToSpec(
       description,
       description,
     },
     },
     children: childKeys,
     children: childKeys,
-    parentKey: '',
   });
   });
 
 
   return {
   return {

+ 4 - 14
apps/web/app/(main)/docs/schemas/page.tsx

@@ -98,25 +98,19 @@ export default function SchemasPage() {
   "root": "card-1",
   "root": "card-1",
   "elements": {
   "elements": {
     "card-1": {
     "card-1": {
-      "key": "card-1",
       "type": "Card",
       "type": "Card",
       "props": { "title": "Dashboard" },
       "props": { "title": "Dashboard" },
-      "children": ["text-1", "button-1"],
-      "parentKey": ""
+      "children": ["text-1", "button-1"]
     },
     },
     "text-1": {
     "text-1": {
-      "key": "text-1",
       "type": "Text",
       "type": "Text",
       "props": { "content": "Welcome, $data.user.name" },
       "props": { "content": "Welcome, $data.user.name" },
-      "children": [],
-      "parentKey": "card-1"
+      "children": []
     },
     },
     "button-1": {
     "button-1": {
-      "key": "button-1",
       "type": "Button",
       "type": "Button",
       "props": { "label": "Click me" },
       "props": { "label": "Click me" },
-      "children": [],
-      "parentKey": "card-1"
+      "children": []
     }
     }
   }
   }
 }`}</Code>
 }`}</Code>
@@ -129,11 +123,9 @@ export default function SchemasPage() {
         structure:
         structure:
       </p>
       </p>
       <Code lang="typescript">{`interface Element {
       <Code lang="typescript">{`interface Element {
-  key: string;                 // Unique identifier
   type: string;                // Component type from catalog
   type: string;                // Component type from catalog
   props: Record<string, any>;  // Component properties
   props: Record<string, any>;  // Component properties
   children: string[];          // Array of child element keys
   children: string[];          // Array of child element keys
-  parentKey: string;           // Parent element key (empty for root)
   visible?: VisibilityRule;    // Conditional display
   visible?: VisibilityRule;    // Conditional display
 }`}</Code>
 }`}</Code>
 
 
@@ -143,14 +135,12 @@ export default function SchemasPage() {
         <code className="text-foreground">$data</code> prefix in props:
         <code className="text-foreground">$data</code> prefix in props:
       </p>
       </p>
       <Code lang="json">{`{
       <Code lang="json">{`{
-  "key": "greeting",
   "type": "Text",
   "type": "Text",
   "props": {
   "props": {
     "content": "$data.user.name",
     "content": "$data.user.name",
     "count": "$data.items.length"
     "count": "$data.items.length"
   },
   },
-  "children": [],
-  "parentKey": "card-1"
+  "children": []
 }`}</Code>
 }`}</Code>
 
 
       <h3 className="text-lg font-medium mt-8 mb-3">Action Format</h3>
       <h3 className="text-lg font-medium mt-8 mb-3">Action Format</h3>

+ 20 - 70
apps/web/app/(main)/docs/specs/page.tsx

@@ -50,18 +50,14 @@ export default function SpecsPage() {
   "root": "card-1",
   "root": "card-1",
   "elements": {
   "elements": {
     "card-1": {
     "card-1": {
-      "key": "card-1",
       "type": "Card",
       "type": "Card",
       "props": { "title": "Welcome" },
       "props": { "title": "Welcome" },
-      "children": ["text-1"],
-      "parentKey": ""
+      "children": ["text-1"]
     },
     },
     "text-1": {
     "text-1": {
-      "key": "text-1",
       "type": "Text",
       "type": "Text",
       "props": { "content": "Hello, $data.user.name!" },
       "props": { "content": "Hello, $data.user.name!" },
-      "children": [],
-      "parentKey": "card-1"
+      "children": []
     }
     }
   }
   }
 }`}</Code>
 }`}</Code>
@@ -74,53 +70,39 @@ export default function SpecsPage() {
   "root": "card-1",
   "root": "card-1",
   "elements": {
   "elements": {
     "card-1": {
     "card-1": {
-      "key": "card-1",
       "type": "Card",
       "type": "Card",
       "props": { "title": "User Profile", "padding": "md" },
       "props": { "title": "User Profile", "padding": "md" },
-      "children": ["row-1", "button-1"],
-      "parentKey": ""
+      "children": ["row-1", "button-1"]
     },
     },
     "row-1": {
     "row-1": {
-      "key": "row-1",
       "type": "Row",
       "type": "Row",
       "props": { "gap": "md" },
       "props": { "gap": "md" },
-      "children": ["avatar-1", "stack-1"],
-      "parentKey": "card-1"
+      "children": ["avatar-1", "stack-1"]
     },
     },
     "avatar-1": {
     "avatar-1": {
-      "key": "avatar-1",
       "type": "Avatar",
       "type": "Avatar",
       "props": { "src": "$data.user.avatar", "alt": "$data.user.name" },
       "props": { "src": "$data.user.avatar", "alt": "$data.user.name" },
-      "children": [],
-      "parentKey": "row-1"
+      "children": []
     },
     },
     "stack-1": {
     "stack-1": {
-      "key": "stack-1",
       "type": "Stack",
       "type": "Stack",
       "props": { "gap": "sm" },
       "props": { "gap": "sm" },
-      "children": ["name-text", "email-text"],
-      "parentKey": "row-1"
+      "children": ["name-text", "email-text"]
     },
     },
     "name-text": {
     "name-text": {
-      "key": "name-text",
       "type": "Text",
       "type": "Text",
       "props": { "content": "$data.user.name", "variant": "heading" },
       "props": { "content": "$data.user.name", "variant": "heading" },
-      "children": [],
-      "parentKey": "stack-1"
+      "children": []
     },
     },
     "email-text": {
     "email-text": {
-      "key": "email-text",
       "type": "Text",
       "type": "Text",
       "props": { "content": "$data.user.email", "variant": "caption" },
       "props": { "content": "$data.user.email", "variant": "caption" },
-      "children": [],
-      "parentKey": "stack-1"
+      "children": []
     },
     },
     "button-1": {
     "button-1": {
-      "key": "button-1",
       "type": "Button",
       "type": "Button",
       "props": { "label": "Edit Profile" },
       "props": { "label": "Edit Profile" },
-      "children": [],
-      "parentKey": "card-1"
+      "children": []
     }
     }
   }
   }
 }`}</Code>
 }`}</Code>
@@ -133,21 +115,16 @@ export default function SpecsPage() {
   "root": "page",
   "root": "page",
   "elements": {
   "elements": {
     "page": {
     "page": {
-      "key": "page",
       "type": "Page",
       "type": "Page",
       "props": {},
       "props": {},
-      "children": ["header", "hero", "features", "footer"],
-      "parentKey": ""
+      "children": ["header", "hero", "features", "footer"]
     },
     },
     "header": {
     "header": {
-      "key": "header",
       "type": "Header",
       "type": "Header",
       "props": { "logo": "/logo.svg", "navItems": ["Products", "Pricing", "Docs"] },
       "props": { "logo": "/logo.svg", "navItems": ["Products", "Pricing", "Docs"] },
-      "children": [],
-      "parentKey": "page"
+      "children": []
     },
     },
     "hero": {
     "hero": {
-      "key": "hero",
       "type": "Hero",
       "type": "Hero",
       "props": {
       "props": {
         "title": "Build UIs with JSON",
         "title": "Build UIs with JSON",
@@ -155,43 +132,32 @@ export default function SpecsPage() {
         "ctaLabel": "Get Started",
         "ctaLabel": "Get Started",
         "ctaHref": "/docs"
         "ctaHref": "/docs"
       },
       },
-      "children": [],
-      "parentKey": "page"
+      "children": []
     },
     },
     "features": {
     "features": {
-      "key": "features",
       "type": "Features",
       "type": "Features",
       "props": { "columns": 3 },
       "props": { "columns": 3 },
-      "children": ["feature-1", "feature-2", "feature-3"],
-      "parentKey": "page"
+      "children": ["feature-1", "feature-2", "feature-3"]
     },
     },
     "feature-1": {
     "feature-1": {
-      "key": "feature-1",
       "type": "Feature",
       "type": "Feature",
       "props": { "icon": "zap", "title": "Fast", "description": "Render UIs in milliseconds" },
       "props": { "icon": "zap", "title": "Fast", "description": "Render UIs in milliseconds" },
-      "children": [],
-      "parentKey": "features"
+      "children": []
     },
     },
     "feature-2": {
     "feature-2": {
-      "key": "feature-2",
       "type": "Feature",
       "type": "Feature",
       "props": { "icon": "shield", "title": "Secure", "description": "Validate all specs against your catalog" },
       "props": { "icon": "shield", "title": "Secure", "description": "Validate all specs against your catalog" },
-      "children": [],
-      "parentKey": "features"
+      "children": []
     },
     },
     "feature-3": {
     "feature-3": {
-      "key": "feature-3",
       "type": "Feature",
       "type": "Feature",
       "props": { "icon": "sparkles", "title": "AI-Ready", "description": "Generate prompts from your catalog" },
       "props": { "icon": "sparkles", "title": "AI-Ready", "description": "Generate prompts from your catalog" },
-      "children": [],
-      "parentKey": "features"
+      "children": []
     },
     },
     "footer": {
     "footer": {
-      "key": "footer",
       "type": "Footer",
       "type": "Footer",
       "props": { "copyright": "2025 Acme Inc", "links": ["Privacy", "Terms", "Contact"] },
       "props": { "copyright": "2025 Acme Inc", "links": ["Privacy", "Terms", "Contact"] },
-      "children": [],
-      "parentKey": "page"
+      "children": []
     }
     }
   }
   }
 }`}</Code>
 }`}</Code>
@@ -209,11 +175,9 @@ export default function SpecsPage() {
   "root": "card-1",
   "root": "card-1",
   "elements": {
   "elements": {
     "card-1": {
     "card-1": {
-      "key": "card-1",
       "type": "Card",
       "type": "Card",
       "props": { "title": "My Card" },
       "props": { "title": "My Card" },
-      "children": ["text-1"],
-      "parentKey": ""
+      "children": ["text-1"]
     },
     },
     "text-1": { ... }
     "text-1": { ... }
   }
   }
@@ -224,17 +188,11 @@ export default function SpecsPage() {
         Each element in the map has a consistent shape:
         Each element in the map has a consistent shape:
       </p>
       </p>
       <Code lang="json">{`{
       <Code lang="json">{`{
-  "key": "unique-id",
   "type": "ComponentName",
   "type": "ComponentName",
   "props": { "label": "Hello" },
   "props": { "label": "Hello" },
-  "children": ["child-1", "child-2"],
-  "parentKey": "parent-id"
+  "children": ["child-1", "child-2"]
 }`}</Code>
 }`}</Code>
       <ul className="list-disc list-inside text-sm text-muted-foreground space-y-1 mt-3 mb-4">
       <ul className="list-disc list-inside text-sm text-muted-foreground space-y-1 mt-3 mb-4">
-        <li>
-          <code className="text-foreground">key</code> — Unique identifier for
-          this element
-        </li>
         <li>
         <li>
           <code className="text-foreground">type</code> — Component type from
           <code className="text-foreground">type</code> — Component type from
           your catalog
           your catalog
@@ -246,10 +204,6 @@ export default function SpecsPage() {
           <code className="text-foreground">children</code> — Array of child
           <code className="text-foreground">children</code> — Array of child
           element keys
           element keys
         </li>
         </li>
-        <li>
-          <code className="text-foreground">parentKey</code> — Key of parent
-          element (empty string for root)
-        </li>
       </ul>
       </ul>
 
 
       <h3 className="text-lg font-medium mt-8 mb-3">Dynamic Data</h3>
       <h3 className="text-lg font-medium mt-8 mb-3">Dynamic Data</h3>
@@ -258,15 +212,13 @@ export default function SpecsPage() {
         <code className="text-foreground">$data</code> paths:
         <code className="text-foreground">$data</code> paths:
       </p>
       </p>
       <Code lang="json">{`{
       <Code lang="json">{`{
-  "key": "metric-1",
   "type": "Metric",
   "type": "Metric",
   "props": {
   "props": {
     "label": "Total Revenue",
     "label": "Total Revenue",
     "value": "$data.metrics.revenue",
     "value": "$data.metrics.revenue",
     "change": "$data.metrics.revenueChange"
     "change": "$data.metrics.revenueChange"
   },
   },
-  "children": [],
-  "parentKey": "dashboard"
+  "children": []
 }`}</Code>
 }`}</Code>
 
 
       <h3 className="text-lg font-medium mt-8 mb-3">Conditional Visibility</h3>
       <h3 className="text-lg font-medium mt-8 mb-3">Conditional Visibility</h3>
@@ -275,13 +227,11 @@ export default function SpecsPage() {
         <code className="text-foreground">visible</code> property:
         <code className="text-foreground">visible</code> property:
       </p>
       </p>
       <Code lang="json">{`{
       <Code lang="json">{`{
-  "key": "alert-1",
   "type": "Alert",
   "type": "Alert",
   "props": {
   "props": {
     "message": "You have unsaved changes"
     "message": "You have unsaved changes"
   },
   },
   "children": [],
   "children": [],
-  "parentKey": "form",
   "visible": {
   "visible": {
     "path": "$data.form.isDirty",
     "path": "$data.form.isDirty",
     "operator": "eq",
     "operator": "eq",

+ 4 - 3
apps/web/app/(main)/docs/streaming/page.tsx

@@ -18,9 +18,10 @@ export default function StreamingPage() {
         format where each line is a JSON patch operation that progressively
         format where each line is a JSON patch operation that progressively
         builds your spec:
         builds your spec:
       </p>
       </p>
-      <Code lang="json">{`{"op":"set","path":"/root","value":{"key":"root","type":"Card","props":{"title":"Dashboard"}}}
-{"op":"set","path":"/root/children/0","value":{"key":"metric-1","type":"Metric","props":{"label":"Revenue"}}}
-{"op":"set","path":"/root/children/1","value":{"key":"metric-2","type":"Metric","props":{"label":"Users"}}}`}</Code>
+      <Code lang="json">{`{"op":"set","path":"/root","value":"root"}
+{"op":"set","path":"/elements/root","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","metric-2"]}}
+{"op":"set","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue"}}}
+{"op":"set","path":"/elements/metric-2","value":{"type":"Metric","props":{"label":"Users"}}}`}</Code>
 
 
       <h2 className="text-xl font-semibold mt-12 mb-4">useUIStream Hook</h2>
       <h2 className="text-xl font-semibold mt-12 mb-4">useUIStream Hook</h2>
       <p className="text-sm text-muted-foreground mb-4">
       <p className="text-sm text-muted-foreground mb-4">

+ 11 - 13
apps/web/app/(main)/page.tsx

@@ -127,15 +127,16 @@ export const catalog = createCatalog({
                 Constrained output that your components render natively.
                 Constrained output that your components render natively.
               </p>
               </p>
               <Code lang="json">{`{
               <Code lang="json">{`{
-  "key": "dashboard",
-  "type": "Card",
-  "props": {
-    "title": "Revenue Dashboard",
-    "description": null
-  },
-  "children": [
-    {
-      "key": "revenue",
+  "root": "dashboard",
+  "elements": {
+    "dashboard": {
+      "type": "Card",
+      "props": {
+        "title": "Revenue Dashboard"
+      },
+      "children": ["revenue"]
+    },
+    "revenue": {
       "type": "Metric",
       "type": "Metric",
       "props": {
       "props": {
         "label": "Total Revenue",
         "label": "Total Revenue",
@@ -143,7 +144,7 @@ export const catalog = createCatalog({
         "format": "currency"
         "format": "currency"
       }
       }
     }
     }
-  ]
+  }
 }`}</Code>
 }`}</Code>
             </div>
             </div>
           </div>
           </div>
@@ -170,13 +171,11 @@ export const catalog = createCatalog({
   "root": "card",
   "root": "card",
   "elements": {
   "elements": {
     "card": {
     "card": {
-      "key": "card",
       "type": "Card",
       "type": "Card",
       "props": { "title": "Revenue" },
       "props": { "title": "Revenue" },
       "children": ["metric", "chart"]
       "children": ["metric", "chart"]
     },
     },
     "metric": {
     "metric": {
-      "key": "metric",
       "type": "Metric",
       "type": "Metric",
       "props": {
       "props": {
         "label": "Total Revenue",
         "label": "Total Revenue",
@@ -185,7 +184,6 @@ export const catalog = createCatalog({
       }
       }
     },
     },
     "chart": {
     "chart": {
-      "key": "chart",
       "type": "Chart",
       "type": "Chart",
       "props": {
       "props": {
         "dataPath": "analytics/salesByRegion"
         "dataPath": "analytics/salesByRegion"

+ 4 - 19
apps/web/components/demo.tsx

@@ -29,7 +29,6 @@ const SIMULATION_STAGES: SimulationStage[] = [
       root: "card",
       root: "card",
       elements: {
       elements: {
         card: {
         card: {
-          key: "card",
           type: "Card",
           type: "Card",
           props: { title: "Contact Us", maxWidth: "md" },
           props: { title: "Contact Us", maxWidth: "md" },
           children: [],
           children: [],
@@ -43,110 +42,96 @@ const SIMULATION_STAGES: SimulationStage[] = [
       root: "card",
       root: "card",
       elements: {
       elements: {
         card: {
         card: {
-          key: "card",
           type: "Card",
           type: "Card",
           props: { title: "Contact Us", maxWidth: "md" },
           props: { title: "Contact Us", maxWidth: "md" },
           children: ["name"],
           children: ["name"],
         },
         },
         name: {
         name: {
-          key: "name",
           type: "Input",
           type: "Input",
           props: { label: "Name", name: "name" },
           props: { label: "Name", name: "name" },
         },
         },
       },
       },
     },
     },
     stream:
     stream:
-      '{"op":"add","path":"/elements/card","value":{"key":"card","type":"Card","props":{"title":"Contact Us","maxWidth":"md"},"children":["name"]}}',
+      '{"op":"add","path":"/elements/card","value":{"type":"Card","props":{"title":"Contact Us","maxWidth":"md"},"children":["name"]}}',
   },
   },
   {
   {
     tree: {
     tree: {
       root: "card",
       root: "card",
       elements: {
       elements: {
         card: {
         card: {
-          key: "card",
           type: "Card",
           type: "Card",
           props: { title: "Contact Us", maxWidth: "md" },
           props: { title: "Contact Us", maxWidth: "md" },
           children: ["name", "email"],
           children: ["name", "email"],
         },
         },
         name: {
         name: {
-          key: "name",
           type: "Input",
           type: "Input",
           props: { label: "Name", name: "name" },
           props: { label: "Name", name: "name" },
         },
         },
         email: {
         email: {
-          key: "email",
           type: "Input",
           type: "Input",
           props: { label: "Email", name: "email" },
           props: { label: "Email", name: "email" },
         },
         },
       },
       },
     },
     },
     stream:
     stream:
-      '{"op":"add","path":"/elements/email","value":{"key":"email","type":"Input","props":{"label":"Email","name":"email"}}}',
+      '{"op":"add","path":"/elements/email","value":{"type":"Input","props":{"label":"Email","name":"email"}}}',
   },
   },
   {
   {
     tree: {
     tree: {
       root: "card",
       root: "card",
       elements: {
       elements: {
         card: {
         card: {
-          key: "card",
           type: "Card",
           type: "Card",
           props: { title: "Contact Us", maxWidth: "md" },
           props: { title: "Contact Us", maxWidth: "md" },
           children: ["name", "email", "message"],
           children: ["name", "email", "message"],
         },
         },
         name: {
         name: {
-          key: "name",
           type: "Input",
           type: "Input",
           props: { label: "Name", name: "name" },
           props: { label: "Name", name: "name" },
         },
         },
         email: {
         email: {
-          key: "email",
           type: "Input",
           type: "Input",
           props: { label: "Email", name: "email" },
           props: { label: "Email", name: "email" },
         },
         },
         message: {
         message: {
-          key: "message",
           type: "Textarea",
           type: "Textarea",
           props: { label: "Message", name: "message" },
           props: { label: "Message", name: "message" },
         },
         },
       },
       },
     },
     },
     stream:
     stream:
-      '{"op":"add","path":"/elements/message","value":{"key":"message","type":"Textarea","props":{"label":"Message","name":"message"}}}',
+      '{"op":"add","path":"/elements/message","value":{"type":"Textarea","props":{"label":"Message","name":"message"}}}',
   },
   },
   {
   {
     tree: {
     tree: {
       root: "card",
       root: "card",
       elements: {
       elements: {
         card: {
         card: {
-          key: "card",
           type: "Card",
           type: "Card",
           props: { title: "Contact Us", maxWidth: "md" },
           props: { title: "Contact Us", maxWidth: "md" },
           children: ["name", "email", "message", "submit"],
           children: ["name", "email", "message", "submit"],
         },
         },
         name: {
         name: {
-          key: "name",
           type: "Input",
           type: "Input",
           props: { label: "Name", name: "name" },
           props: { label: "Name", name: "name" },
         },
         },
         email: {
         email: {
-          key: "email",
           type: "Input",
           type: "Input",
           props: { label: "Email", name: "email" },
           props: { label: "Email", name: "email" },
         },
         },
         message: {
         message: {
-          key: "message",
           type: "Textarea",
           type: "Textarea",
           props: { label: "Message", name: "message" },
           props: { label: "Message", name: "message" },
         },
         },
         submit: {
         submit: {
-          key: "submit",
           type: "Button",
           type: "Button",
           props: { label: "Send Message", variant: "primary" },
           props: { label: "Send Message", variant: "primary" },
         },
         },
       },
       },
     },
     },
     stream:
     stream:
-      '{"op":"add","path":"/elements/submit","value":{"key":"submit","type":"Button","props":{"label":"Send Message","variant":"primary"}}}',
+      '{"op":"add","path":"/elements/submit","value":{"type":"Button","props":{"label":"Send Message","variant":"primary"}}}',
   },
   },
 ];
 ];
 
 

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
examples/remotion/tsconfig.tsbuildinfo


+ 0 - 6
examples/stripe-app/README.md

@@ -70,25 +70,19 @@ const spec: Spec = {
   root: "container",
   root: "container",
   elements: {
   elements: {
     container: {
     container: {
-      key: "container",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "medium" },
       props: { direction: "vertical", gap: "medium" },
       children: ["heading", "metric"],
       children: ["heading", "metric"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Revenue", size: "large" },
       props: { text: "Revenue", size: "large" },
       children: [],
       children: [],
-      parentKey: "container",
     },
     },
     metric: {
     metric: {
-      key: "metric",
       type: "Metric",
       type: "Metric",
       props: { label: "Total", value: "$12,345", format: "currency" },
       props: { label: "Total", value: "$12,345", format: "currency" },
       children: [],
       children: [],
-      parentKey: "container",
     },
     },
   },
   },
 };
 };

+ 0 - 50
examples/stripe-app/src/views/CustomerDetails.tsx

@@ -84,14 +84,11 @@ function createCustomerDetailSpec(
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["header", "details", "sections"],
       children: ["header", "details", "sections"],
-      parentKey: null,
     },
     },
     header: {
     header: {
-      key: "header",
       type: "Stack",
       type: "Stack",
       props: {
       props: {
         direction: "horizontal",
         direction: "horizontal",
@@ -99,38 +96,28 @@ function createCustomerDetailSpec(
         distribute: "space-between",
         distribute: "space-between",
       },
       },
       children: ["customerInfo", "actions"],
       children: ["customerInfo", "actions"],
-      parentKey: "root",
     },
     },
     customerInfo: {
     customerInfo: {
-      key: "customerInfo",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "xsmall" },
       props: { direction: "vertical", gap: "xsmall" },
       children: ["customerName", "customerEmail"],
       children: ["customerName", "customerEmail"],
-      parentKey: "header",
     },
     },
     customerName: {
     customerName: {
-      key: "customerName",
       type: "Heading",
       type: "Heading",
       props: { text: customer?.name ?? "Unknown Customer", size: "xlarge" },
       props: { text: customer?.name ?? "Unknown Customer", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "customerInfo",
     },
     },
     customerEmail: {
     customerEmail: {
-      key: "customerEmail",
       type: "Text",
       type: "Text",
       props: { content: customer?.email ?? "", color: "secondary" },
       props: { content: customer?.email ?? "", color: "secondary" },
       children: [],
       children: [],
-      parentKey: "customerInfo",
     },
     },
     actions: {
     actions: {
-      key: "actions",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: ["editBtn", "portalBtn"],
       children: ["editBtn", "portalBtn"],
-      parentKey: "header",
     },
     },
     editBtn: {
     editBtn: {
-      key: "editBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Edit",
         label: "Edit",
@@ -139,10 +126,8 @@ function createCustomerDetailSpec(
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "actions",
     },
     },
     portalBtn: {
     portalBtn: {
-      key: "portalBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Billing Portal",
         label: "Billing Portal",
@@ -151,31 +136,23 @@ function createCustomerDetailSpec(
         type: "primary",
         type: "primary",
       },
       },
       children: [],
       children: [],
-      parentKey: "actions",
     },
     },
     details: {
     details: {
-      key: "details",
       type: "PropertyList",
       type: "PropertyList",
       props: { orientation: "horizontal" },
       props: { orientation: "horizontal" },
       children: ["detailStatus", "detailCreated", "detailBalance"],
       children: ["detailStatus", "detailCreated", "detailBalance"],
-      parentKey: "root",
     },
     },
     detailStatus: {
     detailStatus: {
-      key: "detailStatus",
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Status", value: customer?.status ?? "Unknown" },
       props: { label: "Status", value: customer?.status ?? "Unknown" },
       children: [],
       children: [],
-      parentKey: "details",
     },
     },
     detailCreated: {
     detailCreated: {
-      key: "detailCreated",
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Created", value: customer?.created ?? "Unknown" },
       props: { label: "Created", value: customer?.created ?? "Unknown" },
       children: [],
       children: [],
-      parentKey: "details",
     },
     },
     detailBalance: {
     detailBalance: {
-      key: "detailBalance",
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: {
       props: {
         label: "Balance",
         label: "Balance",
@@ -184,42 +161,32 @@ function createCustomerDetailSpec(
           : "$0.00",
           : "$0.00",
       },
       },
       children: [],
       children: [],
-      parentKey: "details",
     },
     },
     sections: {
     sections: {
-      key: "sections",
       type: "Accordion",
       type: "Accordion",
       props: {},
       props: {},
       children: ["paymentsSection", "subscriptionsSection", "invoicesSection"],
       children: ["paymentsSection", "subscriptionsSection", "invoicesSection"],
-      parentKey: "root",
     },
     },
 
 
     // Payments Section
     // Payments Section
     paymentsSection: {
     paymentsSection: {
-      key: "paymentsSection",
       type: "AccordionItem",
       type: "AccordionItem",
       props: { title: `Payments (${payments?.total ?? 0})`, defaultOpen: true },
       props: { title: `Payments (${payments?.total ?? 0})`, defaultOpen: true },
       children: paymentsList.length > 0 ? ["paymentsList"] : ["noPayments"],
       children: paymentsList.length > 0 ? ["paymentsList"] : ["noPayments"],
-      parentKey: "sections",
     },
     },
     paymentsList: {
     paymentsList: {
-      key: "paymentsList",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: paymentsList.slice(0, 5).map((_, i) => `payment${i}`),
       children: paymentsList.slice(0, 5).map((_, i) => `payment${i}`),
-      parentKey: "paymentsSection",
     },
     },
     noPayments: {
     noPayments: {
-      key: "noPayments",
       type: "Text",
       type: "Text",
       props: { content: "No payments found", color: "secondary" },
       props: { content: "No payments found", color: "secondary" },
       children: [],
       children: [],
-      parentKey: "paymentsSection",
     },
     },
 
 
     // Subscriptions Section
     // Subscriptions Section
     subscriptionsSection: {
     subscriptionsSection: {
-      key: "subscriptionsSection",
       type: "AccordionItem",
       type: "AccordionItem",
       props: {
       props: {
         title: `Subscriptions (${subscriptions?.total ?? 0})`,
         title: `Subscriptions (${subscriptions?.total ?? 0})`,
@@ -229,54 +196,42 @@ function createCustomerDetailSpec(
         subscriptionsList.length > 0
         subscriptionsList.length > 0
           ? ["subscriptionsList"]
           ? ["subscriptionsList"]
           : ["noSubscriptions"],
           : ["noSubscriptions"],
-      parentKey: "sections",
     },
     },
     subscriptionsList: {
     subscriptionsList: {
-      key: "subscriptionsList",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: subscriptionsList.slice(0, 5).map((_, i) => `subscription${i}`),
       children: subscriptionsList.slice(0, 5).map((_, i) => `subscription${i}`),
-      parentKey: "subscriptionsSection",
     },
     },
     noSubscriptions: {
     noSubscriptions: {
-      key: "noSubscriptions",
       type: "Text",
       type: "Text",
       props: { content: "No subscriptions found", color: "secondary" },
       props: { content: "No subscriptions found", color: "secondary" },
       children: [],
       children: [],
-      parentKey: "subscriptionsSection",
     },
     },
 
 
     // Invoices Section
     // Invoices Section
     invoicesSection: {
     invoicesSection: {
-      key: "invoicesSection",
       type: "AccordionItem",
       type: "AccordionItem",
       props: {
       props: {
         title: `Invoices (${invoices?.total ?? 0})`,
         title: `Invoices (${invoices?.total ?? 0})`,
         defaultOpen: false,
         defaultOpen: false,
       },
       },
       children: invoicesList.length > 0 ? ["invoicesList"] : ["noInvoices"],
       children: invoicesList.length > 0 ? ["invoicesList"] : ["noInvoices"],
-      parentKey: "sections",
     },
     },
     invoicesList: {
     invoicesList: {
-      key: "invoicesList",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: invoicesList.slice(0, 5).map((_, i) => `invoice${i}`),
       children: invoicesList.slice(0, 5).map((_, i) => `invoice${i}`),
-      parentKey: "invoicesSection",
     },
     },
     noInvoices: {
     noInvoices: {
-      key: "noInvoices",
       type: "Text",
       type: "Text",
       props: { content: "No invoices found", color: "secondary" },
       props: { content: "No invoices found", color: "secondary" },
       children: [],
       children: [],
-      parentKey: "invoicesSection",
     },
     },
   };
   };
 
 
   // Add payment cards
   // Add payment cards
   paymentsList.slice(0, 5).forEach((p, i) => {
   paymentsList.slice(0, 5).forEach((p, i) => {
     elements[`payment${i}`] = {
     elements[`payment${i}`] = {
-      key: `payment${i}`,
       type: "PaymentCard",
       type: "PaymentCard",
       props: {
       props: {
         amount: 0,
         amount: 0,
@@ -286,14 +241,12 @@ function createCustomerDetailSpec(
         paymentId: p.id,
         paymentId: p.id,
       },
       },
       children: [],
       children: [],
-      parentKey: "paymentsList",
     };
     };
   });
   });
 
 
   // Add subscription cards
   // Add subscription cards
   subscriptionsList.slice(0, 5).forEach((s, i) => {
   subscriptionsList.slice(0, 5).forEach((s, i) => {
     elements[`subscription${i}`] = {
     elements[`subscription${i}`] = {
-      key: `subscription${i}`,
       type: "SubscriptionCard",
       type: "SubscriptionCard",
       props: {
       props: {
         planName: s.planName,
         planName: s.planName,
@@ -304,14 +257,12 @@ function createCustomerDetailSpec(
         currentPeriodEnd: s.currentPeriodEnd,
         currentPeriodEnd: s.currentPeriodEnd,
       },
       },
       children: [],
       children: [],
-      parentKey: "subscriptionsList",
     };
     };
   });
   });
 
 
   // Add invoice cards
   // Add invoice cards
   invoicesList.slice(0, 5).forEach((inv, i) => {
   invoicesList.slice(0, 5).forEach((inv, i) => {
     elements[`invoice${i}`] = {
     elements[`invoice${i}`] = {
-      key: `invoice${i}`,
       type: "InvoiceCard",
       type: "InvoiceCard",
       props: {
       props: {
         invoiceNumber: inv.invoiceNumber,
         invoiceNumber: inv.invoiceNumber,
@@ -321,7 +272,6 @@ function createCustomerDetailSpec(
         dueDate: inv.dueDate,
         dueDate: inv.dueDate,
       },
       },
       children: [],
       children: [],
-      parentKey: "invoicesList",
     };
     };
   });
   });
 
 

+ 0 - 22
examples/stripe-app/src/views/Customers.tsx

@@ -36,28 +36,21 @@ function createCustomersListSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["heading", "stats", "actions", "list", "pagination"],
       children: ["heading", "stats", "actions", "list", "pagination"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Customer Directory", size: "xlarge" },
       props: { text: "Customer Directory", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     stats: {
     stats: {
-      key: "stats",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["totalMetric"],
       children: ["totalMetric"],
-      parentKey: "root",
     },
     },
     totalMetric: {
     totalMetric: {
-      key: "totalMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Total Customers",
         label: "Total Customers",
@@ -65,17 +58,13 @@ function createCustomersListSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "stats",
     },
     },
     actions: {
     actions: {
-      key: "actions",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: ["refreshBtn", "exportBtn"],
       children: ["refreshBtn", "exportBtn"],
-      parentKey: "root",
     },
     },
     refreshBtn: {
     refreshBtn: {
-      key: "refreshBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Refresh",
         label: "Refresh",
@@ -83,10 +72,8 @@ function createCustomersListSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "actions",
     },
     },
     exportBtn: {
     exportBtn: {
-      key: "exportBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Export CSV",
         label: "Export CSV",
@@ -95,24 +82,18 @@ function createCustomersListSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "actions",
     },
     },
     list: {
     list: {
-      key: "list",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: customerList.slice(0, 10).map((_, i) => `customer${i}`),
       children: customerList.slice(0, 10).map((_, i) => `customer${i}`),
-      parentKey: "root",
     },
     },
     pagination: {
     pagination: {
-      key: "pagination",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: customers?.hasMore ? ["loadMore"] : [],
       children: customers?.hasMore ? ["loadMore"] : [],
-      parentKey: "root",
     },
     },
     loadMore: {
     loadMore: {
-      key: "loadMore",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Load More",
         label: "Load More",
@@ -124,13 +105,11 @@ function createCustomersListSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "pagination",
     },
     },
   };
   };
 
 
   customerList.slice(0, 10).forEach((c, i) => {
   customerList.slice(0, 10).forEach((c, i) => {
     elements[`customer${i}`] = {
     elements[`customer${i}`] = {
-      key: `customer${i}`,
       type: "CustomerCard",
       type: "CustomerCard",
       props: {
       props: {
         name: c.name,
         name: c.name,
@@ -139,7 +118,6 @@ function createCustomersListSpec(data: Record<string, unknown>): Spec {
         customerId: c.id,
         customerId: c.id,
       },
       },
       children: [],
       children: [],
-      parentKey: "list",
     };
     };
   });
   });
 
 

+ 0 - 82
examples/stripe-app/src/views/Home.tsx

@@ -38,28 +38,21 @@ function createRevenueSpec(data: Record<string, unknown>): Spec {
     root: "root",
     root: "root",
     elements: {
     elements: {
       root: {
       root: {
-        key: "root",
         type: "Stack",
         type: "Stack",
         props: { direction: "vertical", gap: "large" },
         props: { direction: "vertical", gap: "large" },
         children: ["heading", "metrics", "refresh"],
         children: ["heading", "metrics", "refresh"],
-        parentKey: null,
       },
       },
       heading: {
       heading: {
-        key: "heading",
         type: "Heading",
         type: "Heading",
         props: { text: "Revenue Dashboard", size: "xlarge" },
         props: { text: "Revenue Dashboard", size: "xlarge" },
         children: [],
         children: [],
-        parentKey: "root",
       },
       },
       metrics: {
       metrics: {
-        key: "metrics",
         type: "Stack",
         type: "Stack",
         props: { direction: "horizontal", gap: "medium" },
         props: { direction: "horizontal", gap: "medium" },
         children: ["m1", "m2", "m3"],
         children: ["m1", "m2", "m3"],
-        parentKey: "root",
       },
       },
       m1: {
       m1: {
-        key: "m1",
         type: "Metric",
         type: "Metric",
         props: {
         props: {
           label: "Payment Volume",
           label: "Payment Volume",
@@ -70,10 +63,8 @@ function createRevenueSpec(data: Record<string, unknown>): Spec {
           changeType: "positive",
           changeType: "positive",
         },
         },
         children: [],
         children: [],
-        parentKey: "metrics",
       },
       },
       m2: {
       m2: {
-        key: "m2",
         type: "Metric",
         type: "Metric",
         props: {
         props: {
           label: "Active Subscriptions",
           label: "Active Subscriptions",
@@ -84,10 +75,8 @@ function createRevenueSpec(data: Record<string, unknown>): Spec {
           changeType: "positive",
           changeType: "positive",
         },
         },
         children: [],
         children: [],
-        parentKey: "metrics",
       },
       },
       m3: {
       m3: {
-        key: "m3",
         type: "Metric",
         type: "Metric",
         props: {
         props: {
           label: "Total Customers",
           label: "Total Customers",
@@ -95,10 +84,8 @@ function createRevenueSpec(data: Record<string, unknown>): Spec {
           changeType: "neutral",
           changeType: "neutral",
         },
         },
         children: [],
         children: [],
-        parentKey: "metrics",
       },
       },
       refresh: {
       refresh: {
-        key: "refresh",
         type: "Button",
         type: "Button",
         props: {
         props: {
           label: "Refresh Data",
           label: "Refresh Data",
@@ -106,7 +93,6 @@ function createRevenueSpec(data: Record<string, unknown>): Spec {
           type: "secondary",
           type: "secondary",
         },
         },
         children: [],
         children: [],
-        parentKey: "root",
       },
       },
     },
     },
   };
   };
@@ -129,28 +115,21 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["heading", "stats", "payments", "refresh"],
       children: ["heading", "stats", "payments", "refresh"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Recent Payments", size: "xlarge" },
       props: { text: "Recent Payments", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     stats: {
     stats: {
-      key: "stats",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["s1", "s2"],
       children: ["s1", "s2"],
-      parentKey: "root",
     },
     },
     s1: {
     s1: {
-      key: "s1",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Total Volume",
         label: "Total Volume",
@@ -158,10 +137,8 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "stats",
     },
     },
     s2: {
     s2: {
-      key: "s2",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Success Rate",
         label: "Success Rate",
@@ -169,17 +146,13 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "stats",
     },
     },
     payments: {
     payments: {
-      key: "payments",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: paymentsList.slice(0, 5).map((_, i) => `p${i}`),
       children: paymentsList.slice(0, 5).map((_, i) => `p${i}`),
-      parentKey: "root",
     },
     },
     refresh: {
     refresh: {
-      key: "refresh",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Refresh Payments",
         label: "Refresh Payments",
@@ -187,13 +160,11 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
   };
   };
 
 
   paymentsList.slice(0, 5).forEach((p, i) => {
   paymentsList.slice(0, 5).forEach((p, i) => {
     elements[`p${i}`] = {
     elements[`p${i}`] = {
-      key: `p${i}`,
       type: "PaymentCard",
       type: "PaymentCard",
       props: {
       props: {
         amount: 0, // We'll use formattedAmount in description
         amount: 0, // We'll use formattedAmount in description
@@ -203,7 +174,6 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         paymentId: p.id,
         paymentId: p.id,
       },
       },
       children: [],
       children: [],
-      parentKey: "payments",
     };
     };
   });
   });
 
 
@@ -229,28 +199,21 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["heading", "metrics", "alert", "subs", "refresh"],
       children: ["heading", "metrics", "alert", "subs", "refresh"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Subscriptions Overview", size: "xlarge" },
       props: { text: "Subscriptions Overview", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     metrics: {
     metrics: {
-      key: "metrics",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["m1", "m2", "m3"],
       children: ["m1", "m2", "m3"],
-      parentKey: "root",
     },
     },
     m1: {
     m1: {
-      key: "m1",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Active",
         label: "Active",
@@ -258,10 +221,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     m2: {
     m2: {
-      key: "m2",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Trialing",
         label: "Trialing",
@@ -269,10 +230,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     m3: {
     m3: {
-      key: "m3",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Past Due",
         label: "Past Due",
@@ -280,27 +239,21 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         changeType: subscriptions?.pastDue ? "negative" : "neutral",
         changeType: subscriptions?.pastDue ? "negative" : "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     alert: {
     alert: {
-      key: "alert",
       type: "Banner",
       type: "Banner",
       props: {
       props: {
         title: `${subscriptions?.trialing ?? 0} subscriptions currently trialing`,
         title: `${subscriptions?.trialing ?? 0} subscriptions currently trialing`,
         type: "default",
         type: "default",
       },
       },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     subs: {
     subs: {
-      key: "subs",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: subsList.slice(0, 5).map((_, i) => `sub${i}`),
       children: subsList.slice(0, 5).map((_, i) => `sub${i}`),
-      parentKey: "root",
     },
     },
     refresh: {
     refresh: {
-      key: "refresh",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Refresh Subscriptions",
         label: "Refresh Subscriptions",
@@ -308,13 +261,11 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
   };
   };
 
 
   subsList.slice(0, 5).forEach((s, i) => {
   subsList.slice(0, 5).forEach((s, i) => {
     elements[`sub${i}`] = {
     elements[`sub${i}`] = {
-      key: `sub${i}`,
       type: "SubscriptionCard",
       type: "SubscriptionCard",
       props: {
       props: {
         planName: s.planName,
         planName: s.planName,
@@ -323,7 +274,6 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         interval: (s.interval as "month" | "year") ?? "month",
         interval: (s.interval as "month" | "year") ?? "month",
       },
       },
       children: [],
       children: [],
-      parentKey: "subs",
     };
     };
   });
   });
 
 
@@ -346,28 +296,21 @@ function createCustomersSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["heading", "stats", "customers", "refresh"],
       children: ["heading", "stats", "customers", "refresh"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Customer Directory", size: "xlarge" },
       props: { text: "Customer Directory", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     stats: {
     stats: {
-      key: "stats",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["s1"],
       children: ["s1"],
-      parentKey: "root",
     },
     },
     s1: {
     s1: {
-      key: "s1",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Total Customers",
         label: "Total Customers",
@@ -375,17 +318,13 @@ function createCustomersSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "stats",
     },
     },
     customers: {
     customers: {
-      key: "customers",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: customersList.slice(0, 5).map((_, i) => `c${i}`),
       children: customersList.slice(0, 5).map((_, i) => `c${i}`),
-      parentKey: "root",
     },
     },
     refresh: {
     refresh: {
-      key: "refresh",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Refresh Customers",
         label: "Refresh Customers",
@@ -393,13 +332,11 @@ function createCustomersSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
   };
   };
 
 
   customersList.slice(0, 5).forEach((c, i) => {
   customersList.slice(0, 5).forEach((c, i) => {
     elements[`c${i}`] = {
     elements[`c${i}`] = {
-      key: `c${i}`,
       type: "CustomerCard",
       type: "CustomerCard",
       props: {
       props: {
         name: c.name,
         name: c.name,
@@ -408,7 +345,6 @@ function createCustomersSpec(data: Record<string, unknown>): Spec {
         customerId: c.id,
         customerId: c.id,
       },
       },
       children: [],
       children: [],
-      parentKey: "customers",
     };
     };
   });
   });
 
 
@@ -434,28 +370,21 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["heading", "stats", "invoices", "refresh"],
       children: ["heading", "stats", "invoices", "refresh"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Invoice Management", size: "xlarge" },
       props: { text: "Invoice Management", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     stats: {
     stats: {
-      key: "stats",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["s1", "s2", "s3"],
       children: ["s1", "s2", "s3"],
-      parentKey: "root",
     },
     },
     s1: {
     s1: {
-      key: "s1",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Outstanding",
         label: "Outstanding",
@@ -463,10 +392,8 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "stats",
     },
     },
     s2: {
     s2: {
-      key: "s2",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Paid",
         label: "Paid",
@@ -474,10 +401,8 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "stats",
     },
     },
     s3: {
     s3: {
-      key: "s3",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Overdue",
         label: "Overdue",
@@ -485,17 +410,13 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         changeType: invoices?.overdue !== "$0.00" ? "negative" : "neutral",
         changeType: invoices?.overdue !== "$0.00" ? "negative" : "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "stats",
     },
     },
     invoices: {
     invoices: {
-      key: "invoices",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: invoicesList.slice(0, 5).map((_, i) => `inv${i}`),
       children: invoicesList.slice(0, 5).map((_, i) => `inv${i}`),
-      parentKey: "root",
     },
     },
     refresh: {
     refresh: {
-      key: "refresh",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Refresh Invoices",
         label: "Refresh Invoices",
@@ -503,13 +424,11 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
   };
   };
 
 
   invoicesList.slice(0, 5).forEach((inv, i) => {
   invoicesList.slice(0, 5).forEach((inv, i) => {
     elements[`inv${i}`] = {
     elements[`inv${i}`] = {
-      key: `inv${i}`,
       type: "InvoiceCard",
       type: "InvoiceCard",
       props: {
       props: {
         invoiceNumber: inv.invoiceNumber,
         invoiceNumber: inv.invoiceNumber,
@@ -518,7 +437,6 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         dueDate: inv.dueDate,
         dueDate: inv.dueDate,
       },
       },
       children: [],
       children: [],
-      parentKey: "invoices",
     };
     };
   });
   });
 
 

+ 0 - 30
examples/stripe-app/src/views/Invoices.tsx

@@ -41,7 +41,6 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: [
       children: [
@@ -52,24 +51,18 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         "list",
         "list",
         "pagination",
         "pagination",
       ],
       ],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Invoices", size: "xlarge" },
       props: { text: "Invoices", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     metrics: {
     metrics: {
-      key: "metrics",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["outstandingMetric", "paidMetric", "overdueMetric"],
       children: ["outstandingMetric", "paidMetric", "overdueMetric"],
-      parentKey: "root",
     },
     },
     outstandingMetric: {
     outstandingMetric: {
-      key: "outstandingMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Outstanding",
         label: "Outstanding",
@@ -77,10 +70,8 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     paidMetric: {
     paidMetric: {
-      key: "paidMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Paid",
         label: "Paid",
@@ -88,10 +79,8 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     overdueMetric: {
     overdueMetric: {
-      key: "overdueMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Overdue",
         label: "Overdue",
@@ -99,10 +88,8 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         changeType: invoices?.overdue !== "$0.00" ? "negative" : "neutral",
         changeType: invoices?.overdue !== "$0.00" ? "negative" : "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     alert: {
     alert: {
-      key: "alert",
       type: "Banner",
       type: "Banner",
       props: {
       props: {
         title:
         title:
@@ -112,24 +99,18 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         type: invoices?.overdue !== "$0.00" ? "caution" : "default",
         type: invoices?.overdue !== "$0.00" ? "caution" : "default",
       },
       },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     filters: {
     filters: {
-      key: "filters",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: ["refreshBtn", "createBtn", "exportBtn"],
       children: ["refreshBtn", "createBtn", "exportBtn"],
-      parentKey: "root",
     },
     },
     refreshBtn: {
     refreshBtn: {
-      key: "refreshBtn",
       type: "Button",
       type: "Button",
       props: { label: "Refresh", action: "refreshInvoices", type: "secondary" },
       props: { label: "Refresh", action: "refreshInvoices", type: "secondary" },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     createBtn: {
     createBtn: {
-      key: "createBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Create Invoice",
         label: "Create Invoice",
@@ -138,10 +119,8 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         type: "primary",
         type: "primary",
       },
       },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     exportBtn: {
     exportBtn: {
-      key: "exportBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Export CSV",
         label: "Export CSV",
@@ -150,24 +129,18 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     list: {
     list: {
-      key: "list",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: invoicesList.slice(0, 10).map((_, i) => `invoice${i}`),
       children: invoicesList.slice(0, 10).map((_, i) => `invoice${i}`),
-      parentKey: "root",
     },
     },
     pagination: {
     pagination: {
-      key: "pagination",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: invoices?.hasMore ? ["loadMore"] : [],
       children: invoices?.hasMore ? ["loadMore"] : [],
-      parentKey: "root",
     },
     },
     loadMore: {
     loadMore: {
-      key: "loadMore",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Load More",
         label: "Load More",
@@ -176,13 +149,11 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "pagination",
     },
     },
   };
   };
 
 
   invoicesList.slice(0, 10).forEach((inv, i) => {
   invoicesList.slice(0, 10).forEach((inv, i) => {
     elements[`invoice${i}`] = {
     elements[`invoice${i}`] = {
-      key: `invoice${i}`,
       type: "InvoiceCard",
       type: "InvoiceCard",
       props: {
       props: {
         invoiceNumber: inv.invoiceNumber,
         invoiceNumber: inv.invoiceNumber,
@@ -198,7 +169,6 @@ function createInvoicesSpec(data: Record<string, unknown>): Spec {
         customerEmail: inv.customerEmail,
         customerEmail: inv.customerEmail,
       },
       },
       children: [],
       children: [],
-      parentKey: "list",
     };
     };
   });
   });
 
 

+ 0 - 38
examples/stripe-app/src/views/PaymentDetails.tsx

@@ -56,14 +56,11 @@ function createPaymentDetailSpec(
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["header", "details", "actions", "refundsSection"],
       children: ["header", "details", "actions", "refundsSection"],
-      parentKey: null,
     },
     },
     header: {
     header: {
-      key: "header",
       type: "Stack",
       type: "Stack",
       props: {
       props: {
         direction: "horizontal",
         direction: "horizontal",
@@ -71,31 +68,23 @@ function createPaymentDetailSpec(
         distribute: "space-between",
         distribute: "space-between",
       },
       },
       children: ["paymentInfo", "statusBadge"],
       children: ["paymentInfo", "statusBadge"],
-      parentKey: "root",
     },
     },
     paymentInfo: {
     paymentInfo: {
-      key: "paymentInfo",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "xsmall" },
       props: { direction: "vertical", gap: "xsmall" },
       children: ["paymentAmount", "paymentId"],
       children: ["paymentAmount", "paymentId"],
-      parentKey: "header",
     },
     },
     paymentAmount: {
     paymentAmount: {
-      key: "paymentAmount",
       type: "Heading",
       type: "Heading",
       props: { text: payment?.formattedAmount ?? "$0.00", size: "xlarge" },
       props: { text: payment?.formattedAmount ?? "$0.00", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "paymentInfo",
     },
     },
     paymentId: {
     paymentId: {
-      key: "paymentId",
       type: "Text",
       type: "Text",
       props: { content: paymentId, color: "secondary", size: "small" },
       props: { content: paymentId, color: "secondary", size: "small" },
       children: [],
       children: [],
-      parentKey: "paymentInfo",
     },
     },
     statusBadge: {
     statusBadge: {
-      key: "statusBadge",
       type: "Badge",
       type: "Badge",
       props: {
       props: {
         label: payment?.status ?? "unknown",
         label: payment?.status ?? "unknown",
@@ -107,10 +96,8 @@ function createPaymentDetailSpec(
               : "negative",
               : "negative",
       },
       },
       children: [],
       children: [],
-      parentKey: "header",
     },
     },
     details: {
     details: {
-      key: "details",
       type: "PropertyList",
       type: "PropertyList",
       props: { orientation: "vertical" },
       props: { orientation: "vertical" },
       children: [
       children: [
@@ -119,54 +106,42 @@ function createPaymentDetailSpec(
         "detailCurrency",
         "detailCurrency",
         "detailCustomer",
         "detailCustomer",
       ],
       ],
-      parentKey: "root",
     },
     },
     detailDescription: {
     detailDescription: {
-      key: "detailDescription",
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: {
       props: {
         label: "Description",
         label: "Description",
         value: payment?.description ?? "No description",
         value: payment?.description ?? "No description",
       },
       },
       children: [],
       children: [],
-      parentKey: "details",
     },
     },
     detailCreated: {
     detailCreated: {
-      key: "detailCreated",
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Created", value: payment?.created ?? "Unknown" },
       props: { label: "Created", value: payment?.created ?? "Unknown" },
       children: [],
       children: [],
-      parentKey: "details",
     },
     },
     detailCurrency: {
     detailCurrency: {
-      key: "detailCurrency",
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: {
       props: {
         label: "Currency",
         label: "Currency",
         value: (payment?.currency ?? "usd").toUpperCase(),
         value: (payment?.currency ?? "usd").toUpperCase(),
       },
       },
       children: [],
       children: [],
-      parentKey: "details",
     },
     },
     detailCustomer: {
     detailCustomer: {
-      key: "detailCustomer",
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Customer", value: payment?.customerId ?? "Guest" },
       props: { label: "Customer", value: payment?.customerId ?? "Guest" },
       children: [],
       children: [],
-      parentKey: "details",
     },
     },
     actions: {
     actions: {
-      key: "actions",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children:
       children:
         payment?.status === "succeeded"
         payment?.status === "succeeded"
           ? ["refundBtn", "viewBtn"]
           ? ["refundBtn", "viewBtn"]
           : ["viewBtn"],
           : ["viewBtn"],
-      parentKey: "root",
     },
     },
     refundBtn: {
     refundBtn: {
-      key: "refundBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Refund Payment",
         label: "Refund Payment",
@@ -175,10 +150,8 @@ function createPaymentDetailSpec(
         type: "destructive",
         type: "destructive",
       },
       },
       children: [],
       children: [],
-      parentKey: "actions",
     },
     },
     viewBtn: {
     viewBtn: {
-      key: "viewBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "View in Dashboard",
         label: "View in Dashboard",
@@ -187,44 +160,34 @@ function createPaymentDetailSpec(
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "actions",
     },
     },
     refundsSection: {
     refundsSection: {
-      key: "refundsSection",
       type: "Accordion",
       type: "Accordion",
       props: {},
       props: {},
       children: ["refundsAccordion"],
       children: ["refundsAccordion"],
-      parentKey: "root",
     },
     },
     refundsAccordion: {
     refundsAccordion: {
-      key: "refundsAccordion",
       type: "AccordionItem",
       type: "AccordionItem",
       props: {
       props: {
         title: `Refunds (${refunds?.total ?? 0})`,
         title: `Refunds (${refunds?.total ?? 0})`,
         defaultOpen: refundsList.length > 0,
         defaultOpen: refundsList.length > 0,
       },
       },
       children: refundsList.length > 0 ? ["refundsList"] : ["noRefunds"],
       children: refundsList.length > 0 ? ["refundsList"] : ["noRefunds"],
-      parentKey: "refundsSection",
     },
     },
     refundsList: {
     refundsList: {
-      key: "refundsList",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: refundsList.slice(0, 5).map((_, i) => `refund${i}`),
       children: refundsList.slice(0, 5).map((_, i) => `refund${i}`),
-      parentKey: "refundsAccordion",
     },
     },
     noRefunds: {
     noRefunds: {
-      key: "noRefunds",
       type: "Text",
       type: "Text",
       props: { content: "No refunds", color: "secondary" },
       props: { content: "No refunds", color: "secondary" },
       children: [],
       children: [],
-      parentKey: "refundsAccordion",
     },
     },
   };
   };
 
 
   refundsList.slice(0, 5).forEach((r, i) => {
   refundsList.slice(0, 5).forEach((r, i) => {
     elements[`refund${i}`] = {
     elements[`refund${i}`] = {
-      key: `refund${i}`,
       type: "RefundCard",
       type: "RefundCard",
       props: {
       props: {
         amount: r.amount,
         amount: r.amount,
@@ -233,7 +196,6 @@ function createPaymentDetailSpec(
         reason: r.reason,
         reason: r.reason,
       },
       },
       children: [],
       children: [],
-      parentKey: "refundsList",
     };
     };
   });
   });
 
 

+ 0 - 26
examples/stripe-app/src/views/Payments.tsx

@@ -38,28 +38,21 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["heading", "metrics", "filters", "list", "pagination"],
       children: ["heading", "metrics", "filters", "list", "pagination"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Payments", size: "xlarge" },
       props: { text: "Payments", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     metrics: {
     metrics: {
-      key: "metrics",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["volumeMetric", "rateMetric", "countMetric"],
       children: ["volumeMetric", "rateMetric", "countMetric"],
-      parentKey: "root",
     },
     },
     volumeMetric: {
     volumeMetric: {
-      key: "volumeMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Total Volume",
         label: "Total Volume",
@@ -67,10 +60,8 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     rateMetric: {
     rateMetric: {
-      key: "rateMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Success Rate",
         label: "Success Rate",
@@ -78,10 +69,8 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     countMetric: {
     countMetric: {
-      key: "countMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Total Payments",
         label: "Total Payments",
@@ -89,24 +78,18 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     filters: {
     filters: {
-      key: "filters",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: ["refreshBtn", "exportBtn"],
       children: ["refreshBtn", "exportBtn"],
-      parentKey: "root",
     },
     },
     refreshBtn: {
     refreshBtn: {
-      key: "refreshBtn",
       type: "Button",
       type: "Button",
       props: { label: "Refresh", action: "refreshPayments", type: "secondary" },
       props: { label: "Refresh", action: "refreshPayments", type: "secondary" },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     exportBtn: {
     exportBtn: {
-      key: "exportBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Export CSV",
         label: "Export CSV",
@@ -115,24 +98,18 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     list: {
     list: {
-      key: "list",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: paymentsList.slice(0, 10).map((_, i) => `payment${i}`),
       children: paymentsList.slice(0, 10).map((_, i) => `payment${i}`),
-      parentKey: "root",
     },
     },
     pagination: {
     pagination: {
-      key: "pagination",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: payments?.hasMore ? ["loadMore"] : [],
       children: payments?.hasMore ? ["loadMore"] : [],
-      parentKey: "root",
     },
     },
     loadMore: {
     loadMore: {
-      key: "loadMore",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Load More",
         label: "Load More",
@@ -144,13 +121,11 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "pagination",
     },
     },
   };
   };
 
 
   paymentsList.slice(0, 10).forEach((p, i) => {
   paymentsList.slice(0, 10).forEach((p, i) => {
     elements[`payment${i}`] = {
     elements[`payment${i}`] = {
-      key: `payment${i}`,
       type: "PaymentCard",
       type: "PaymentCard",
       props: {
       props: {
         amount: 0,
         amount: 0,
@@ -160,7 +135,6 @@ function createPaymentsSpec(data: Record<string, unknown>): Spec {
         paymentId: p.id,
         paymentId: p.id,
       },
       },
       children: [],
       children: [],
-      parentKey: "list",
     };
     };
   });
   });
 
 

+ 0 - 36
examples/stripe-app/src/views/Products.tsx

@@ -49,28 +49,21 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: ["heading", "metrics", "filters", "list", "pagination"],
       children: ["heading", "metrics", "filters", "list", "pagination"],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Products & Prices", size: "xlarge" },
       props: { text: "Products & Prices", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     metrics: {
     metrics: {
-      key: "metrics",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: ["productsMetric", "pricesMetric", "activeMetric"],
       children: ["productsMetric", "pricesMetric", "activeMetric"],
-      parentKey: "root",
     },
     },
     productsMetric: {
     productsMetric: {
-      key: "productsMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Total Products",
         label: "Total Products",
@@ -78,10 +71,8 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     pricesMetric: {
     pricesMetric: {
-      key: "pricesMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Total Prices",
         label: "Total Prices",
@@ -89,10 +80,8 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     activeMetric: {
     activeMetric: {
-      key: "activeMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Active Products",
         label: "Active Products",
@@ -100,24 +89,18 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     filters: {
     filters: {
-      key: "filters",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: ["refreshBtn", "createBtn"],
       children: ["refreshBtn", "createBtn"],
-      parentKey: "root",
     },
     },
     refreshBtn: {
     refreshBtn: {
-      key: "refreshBtn",
       type: "Button",
       type: "Button",
       props: { label: "Refresh", action: "fetchProducts", type: "secondary" },
       props: { label: "Refresh", action: "fetchProducts", type: "secondary" },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     createBtn: {
     createBtn: {
-      key: "createBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Create Product",
         label: "Create Product",
@@ -126,24 +109,18 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
         type: "primary",
         type: "primary",
       },
       },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     list: {
     list: {
-      key: "list",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: productsList.slice(0, 10).map((_, i) => `product${i}`),
       children: productsList.slice(0, 10).map((_, i) => `product${i}`),
-      parentKey: "root",
     },
     },
     pagination: {
     pagination: {
-      key: "pagination",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: products?.hasMore ? ["loadMore"] : [],
       children: products?.hasMore ? ["loadMore"] : [],
-      parentKey: "root",
     },
     },
     loadMore: {
     loadMore: {
-      key: "loadMore",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Load More",
         label: "Load More",
@@ -152,7 +129,6 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "pagination",
     },
     },
   };
   };
 
 
@@ -169,15 +145,12 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
         : "No prices";
         : "No prices";
 
 
     elements[`product${i}`] = {
     elements[`product${i}`] = {
-      key: `product${i}`,
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: [`productCard${i}`],
       children: [`productCard${i}`],
-      parentKey: "list",
     };
     };
 
 
     elements[`productCard${i}`] = {
     elements[`productCard${i}`] = {
-      key: `productCard${i}`,
       type: "PropertyList",
       type: "PropertyList",
       props: { orientation: "vertical" },
       props: { orientation: "vertical" },
       children: [
       children: [
@@ -186,39 +159,30 @@ function createProductsSpec(data: Record<string, unknown>): Spec {
         `productPrice${i}`,
         `productPrice${i}`,
         `productStatus${i}`,
         `productStatus${i}`,
       ],
       ],
-      parentKey: `product${i}`,
     };
     };
 
 
     elements[`productName${i}`] = {
     elements[`productName${i}`] = {
-      key: `productName${i}`,
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Name", value: p.name },
       props: { label: "Name", value: p.name },
       children: [],
       children: [],
-      parentKey: `productCard${i}`,
     };
     };
 
 
     elements[`productDesc${i}`] = {
     elements[`productDesc${i}`] = {
-      key: `productDesc${i}`,
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Description", value: p.description || "No description" },
       props: { label: "Description", value: p.description || "No description" },
       children: [],
       children: [],
-      parentKey: `productCard${i}`,
     };
     };
 
 
     elements[`productPrice${i}`] = {
     elements[`productPrice${i}`] = {
-      key: `productPrice${i}`,
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Prices", value: priceDisplay },
       props: { label: "Prices", value: priceDisplay },
       children: [],
       children: [],
-      parentKey: `productCard${i}`,
     };
     };
 
 
     elements[`productStatus${i}`] = {
     elements[`productStatus${i}`] = {
-      key: `productStatus${i}`,
       type: "PropertyListItem",
       type: "PropertyListItem",
       props: { label: "Status", value: p.active ? "Active" : "Archived" },
       props: { label: "Status", value: p.active ? "Active" : "Archived" },
       children: [],
       children: [],
-      parentKey: `productCard${i}`,
     };
     };
   });
   });
 
 

+ 0 - 30
examples/stripe-app/src/views/Subscriptions.tsx

@@ -42,7 +42,6 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
 
 
   const elements: Spec["elements"] = {
   const elements: Spec["elements"] = {
     root: {
     root: {
-      key: "root",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "large" },
       props: { direction: "vertical", gap: "large" },
       children: [
       children: [
@@ -53,17 +52,13 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         "list",
         "list",
         "pagination",
         "pagination",
       ],
       ],
-      parentKey: null,
     },
     },
     heading: {
     heading: {
-      key: "heading",
       type: "Heading",
       type: "Heading",
       props: { text: "Subscriptions", size: "xlarge" },
       props: { text: "Subscriptions", size: "xlarge" },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     metrics: {
     metrics: {
-      key: "metrics",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "medium" },
       props: { direction: "horizontal", gap: "medium" },
       children: [
       children: [
@@ -72,10 +67,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         "pastDueMetric",
         "pastDueMetric",
         "canceledMetric",
         "canceledMetric",
       ],
       ],
-      parentKey: "root",
     },
     },
     activeMetric: {
     activeMetric: {
-      key: "activeMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Active",
         label: "Active",
@@ -83,10 +76,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     trialingMetric: {
     trialingMetric: {
-      key: "trialingMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Trialing",
         label: "Trialing",
@@ -94,10 +85,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         changeType: "positive",
         changeType: "positive",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     pastDueMetric: {
     pastDueMetric: {
-      key: "pastDueMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Past Due",
         label: "Past Due",
@@ -105,10 +94,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         changeType: subscriptions?.pastDue ? "negative" : "neutral",
         changeType: subscriptions?.pastDue ? "negative" : "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     canceledMetric: {
     canceledMetric: {
-      key: "canceledMetric",
       type: "Metric",
       type: "Metric",
       props: {
       props: {
         label: "Canceled",
         label: "Canceled",
@@ -116,10 +103,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         changeType: "neutral",
         changeType: "neutral",
       },
       },
       children: [],
       children: [],
-      parentKey: "metrics",
     },
     },
     alert: {
     alert: {
-      key: "alert",
       type: "Banner",
       type: "Banner",
       props: {
       props: {
         title: subscriptions?.pastDue
         title: subscriptions?.pastDue
@@ -128,17 +113,13 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         type: subscriptions?.pastDue ? "caution" : "default",
         type: subscriptions?.pastDue ? "caution" : "default",
       },
       },
       children: [],
       children: [],
-      parentKey: "root",
     },
     },
     filters: {
     filters: {
-      key: "filters",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: ["refreshBtn", "exportBtn"],
       children: ["refreshBtn", "exportBtn"],
-      parentKey: "root",
     },
     },
     refreshBtn: {
     refreshBtn: {
-      key: "refreshBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Refresh",
         label: "Refresh",
@@ -146,10 +127,8 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     exportBtn: {
     exportBtn: {
-      key: "exportBtn",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Export CSV",
         label: "Export CSV",
@@ -158,26 +137,20 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "filters",
     },
     },
     list: {
     list: {
-      key: "list",
       type: "Stack",
       type: "Stack",
       props: { direction: "vertical", gap: "small" },
       props: { direction: "vertical", gap: "small" },
       children: subscriptionsList
       children: subscriptionsList
         .slice(0, 10)
         .slice(0, 10)
         .map((_, i) => `subscription${i}`),
         .map((_, i) => `subscription${i}`),
-      parentKey: "root",
     },
     },
     pagination: {
     pagination: {
-      key: "pagination",
       type: "Stack",
       type: "Stack",
       props: { direction: "horizontal", gap: "small" },
       props: { direction: "horizontal", gap: "small" },
       children: subscriptions?.hasMore ? ["loadMore"] : [],
       children: subscriptions?.hasMore ? ["loadMore"] : [],
-      parentKey: "root",
     },
     },
     loadMore: {
     loadMore: {
-      key: "loadMore",
       type: "Button",
       type: "Button",
       props: {
       props: {
         label: "Load More",
         label: "Load More",
@@ -186,13 +159,11 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         type: "secondary",
         type: "secondary",
       },
       },
       children: [],
       children: [],
-      parentKey: "pagination",
     },
     },
   };
   };
 
 
   subscriptionsList.slice(0, 10).forEach((s, i) => {
   subscriptionsList.slice(0, 10).forEach((s, i) => {
     elements[`subscription${i}`] = {
     elements[`subscription${i}`] = {
-      key: `subscription${i}`,
       type: "SubscriptionCard",
       type: "SubscriptionCard",
       props: {
       props: {
         planName: s.planName,
         planName: s.planName,
@@ -208,7 +179,6 @@ function createSubscriptionsSpec(data: Record<string, unknown>): Spec {
         currentPeriodEnd: s.currentPeriodEnd,
         currentPeriodEnd: s.currentPeriodEnd,
       },
       },
       children: [],
       children: [],
-      parentKey: "list",
     };
     };
   });
   });
 
 

+ 4 - 13
packages/codegen/src/traverse.test.ts

@@ -13,18 +13,15 @@ describe("traverseSpec", () => {
       root: "root",
       root: "root",
       elements: {
       elements: {
         root: {
         root: {
-          key: "root",
           type: "Card",
           type: "Card",
           props: {},
           props: {},
           children: ["child1", "child2"],
           children: ["child1", "child2"],
         },
         },
         child1: {
         child1: {
-          key: "child1",
           type: "Text",
           type: "Text",
           props: {},
           props: {},
         },
         },
         child2: {
         child2: {
-          key: "child2",
           type: "Button",
           type: "Button",
           props: {},
           props: {},
         },
         },
@@ -32,8 +29,8 @@ describe("traverseSpec", () => {
     };
     };
 
 
     const visited: string[] = [];
     const visited: string[] = [];
-    traverseSpec(spec, (element) => {
-      visited.push(element.key);
+    traverseSpec(spec, (_element, key) => {
+      visited.push(key);
     });
     });
 
 
     expect(visited).toEqual(["root", "child1", "child2"]);
     expect(visited).toEqual(["root", "child1", "child2"]);
@@ -41,8 +38,8 @@ describe("traverseSpec", () => {
 
 
   it("handles empty spec", () => {
   it("handles empty spec", () => {
     const visited: string[] = [];
     const visited: string[] = [];
-    traverseSpec(null as unknown as Spec, (element) => {
-      visited.push(element.key);
+    traverseSpec(null as unknown as Spec, (_element, key) => {
+      visited.push(key);
     });
     });
     expect(visited).toEqual([]);
     expect(visited).toEqual([]);
   });
   });
@@ -54,18 +51,15 @@ describe("collectUsedComponents", () => {
       root: "root",
       root: "root",
       elements: {
       elements: {
         root: {
         root: {
-          key: "root",
           type: "Card",
           type: "Card",
           props: {},
           props: {},
           children: ["child1", "child2"],
           children: ["child1", "child2"],
         },
         },
         child1: {
         child1: {
-          key: "child1",
           type: "Text",
           type: "Text",
           props: {},
           props: {},
         },
         },
         child2: {
         child2: {
-          key: "child2",
           type: "Text",
           type: "Text",
           props: {},
           props: {},
         },
         },
@@ -83,7 +77,6 @@ describe("collectDataPaths", () => {
       root: "root",
       root: "root",
       elements: {
       elements: {
         root: {
         root: {
-          key: "root",
           type: "Metric",
           type: "Metric",
           props: { valuePath: "analytics/revenue" },
           props: { valuePath: "analytics/revenue" },
         },
         },
@@ -99,7 +92,6 @@ describe("collectDataPaths", () => {
       root: "root",
       root: "root",
       elements: {
       elements: {
         root: {
         root: {
-          key: "root",
           type: "Text",
           type: "Text",
           props: { content: { path: "user/name" } },
           props: { content: { path: "user/name" } },
         },
         },
@@ -117,7 +109,6 @@ describe("collectActions", () => {
       root: "root",
       root: "root",
       elements: {
       elements: {
         root: {
         root: {
-          key: "root",
           type: "Button",
           type: "Button",
           props: { action: "submit_form" },
           props: { action: "submit_form" },
         },
         },

+ 10 - 5
packages/codegen/src/traverse.ts

@@ -4,7 +4,12 @@ import type { Spec, UIElement } from "@json-render/core";
  * Visitor function for spec traversal
  * Visitor function for spec traversal
  */
  */
 export interface TreeVisitor {
 export interface TreeVisitor {
-  (element: UIElement, depth: number, parent: UIElement | null): void;
+  (
+    element: UIElement,
+    key: string,
+    depth: number,
+    parent: UIElement | null,
+  ): void;
 }
 }
 
 
 /**
 /**
@@ -25,7 +30,7 @@ export function traverseSpec(
     const element = spec.elements[key];
     const element = spec.elements[key];
     if (!element) return;
     if (!element) return;
 
 
-    visitor(element, depth, parent);
+    visitor(element, key, depth, parent);
 
 
     if (element.children) {
     if (element.children) {
       for (const childKey of element.children) {
       for (const childKey of element.children) {
@@ -43,7 +48,7 @@ export function traverseSpec(
 export function collectUsedComponents(spec: Spec): Set<string> {
 export function collectUsedComponents(spec: Spec): Set<string> {
   const components = new Set<string>();
   const components = new Set<string>();
 
 
-  traverseSpec(spec, (element) => {
+  traverseSpec(spec, (element, _key) => {
     components.add(element.type);
     components.add(element.type);
   });
   });
 
 
@@ -56,7 +61,7 @@ export function collectUsedComponents(spec: Spec): Set<string> {
 export function collectDataPaths(spec: Spec): Set<string> {
 export function collectDataPaths(spec: Spec): Set<string> {
   const paths = new Set<string>();
   const paths = new Set<string>();
 
 
-  traverseSpec(spec, (element) => {
+  traverseSpec(spec, (element, _key) => {
     // Check props for data paths
     // Check props for data paths
     for (const [propName, propValue] of Object.entries(element.props)) {
     for (const [propName, propValue] of Object.entries(element.props)) {
       // Check for path props (e.g., valuePath, dataPath, bindPath)
       // Check for path props (e.g., valuePath, dataPath, bindPath)
@@ -141,7 +146,7 @@ function collectPathsFromCondition(
 export function collectActions(spec: Spec): Set<string> {
 export function collectActions(spec: Spec): Set<string> {
   const actions = new Set<string>();
   const actions = new Set<string>();
 
 
-  traverseSpec(spec, (element) => {
+  traverseSpec(spec, (element, _key) => {
     for (const propValue of Object.values(element.props)) {
     for (const propValue of Object.values(element.props)) {
       // Check for action prop (string action name)
       // Check for action prop (string action name)
       if (typeof propValue === "string" && propValue.startsWith("action:")) {
       if (typeof propValue === "string" && propValue.startsWith("action:")) {

+ 2 - 10
packages/core/src/catalog.test.ts

@@ -72,12 +72,10 @@ describe("createCatalog", () => {
     });
     });
 
 
     const validElement = {
     const validElement = {
-      key: "1",
       type: "text",
       type: "text",
       props: { content: "Hello" },
       props: { content: "Hello" },
     };
     };
     const invalidElement = {
     const invalidElement = {
-      key: "1",
       type: "text",
       type: "text",
       props: { content: 123 },
       props: { content: 123 },
     };
     };
@@ -96,7 +94,7 @@ describe("createCatalog", () => {
     const validSpec = {
     const validSpec = {
       root: "1",
       root: "1",
       elements: {
       elements: {
-        "1": { key: "1", type: "text", props: { content: "Hello" } },
+        "1": { type: "text", props: { content: "Hello" } },
       },
       },
     };
     };
 
 
@@ -120,16 +118,13 @@ describe("createCatalog", () => {
       root: "card-1",
       root: "card-1",
       elements: {
       elements: {
         "card-1": {
         "card-1": {
-          key: "card-1",
           type: "card",
           type: "card",
           props: { title: "Hello" },
           props: { title: "Hello" },
           children: ["text-1"],
           children: ["text-1"],
         },
         },
         "text-1": {
         "text-1": {
-          key: "text-1",
           type: "text",
           type: "text",
           props: { content: "World" },
           props: { content: "World" },
-          parentKey: "card-1",
         },
         },
       },
       },
     };
     };
@@ -147,7 +142,7 @@ describe("createCatalog", () => {
     const invalidSpec = {
     const invalidSpec = {
       root: "1",
       root: "1",
       elements: {
       elements: {
-        "1": { key: "1", type: "nonexistent", props: { content: "Hello" } },
+        "1": { type: "nonexistent", props: { content: "Hello" } },
       },
       },
     };
     };
 
 
@@ -398,7 +393,6 @@ describe("defineCatalog (new schema API)", () => {
       root: s.string(),
       root: s.string(),
       elements: s.record(
       elements: s.record(
         s.object({
         s.object({
-          key: s.string(),
           type: s.ref("catalog.components"),
           type: s.ref("catalog.components"),
           props: s.any(),
           props: s.any(),
           children: s.array(s.string()),
           children: s.array(s.string()),
@@ -489,7 +483,6 @@ describe("defineCatalog (new schema API)", () => {
       root: "text-1",
       root: "text-1",
       elements: {
       elements: {
         "text-1": {
         "text-1": {
-          key: "text-1",
           type: "Text",
           type: "Text",
           props: { content: "Hello" },
           props: { content: "Hello" },
           children: [],
           children: [],
@@ -558,7 +551,6 @@ describe("defineCatalog (new schema API)", () => {
       root: "text-1",
       root: "text-1",
       elements: {
       elements: {
         "text-1": {
         "text-1": {
-          key: "text-1",
           type: "Text",
           type: "Text",
           props: { content: "Hello" },
           props: { content: "Hello" },
           children: [],
           children: [],

+ 2 - 6
packages/core/src/catalog.ts

@@ -143,11 +143,9 @@ export function createCatalog<
     const def = components[componentName]!;
     const def = components[componentName]!;
 
 
     return z.object({
     return z.object({
-      key: z.string(),
       type: z.literal(componentName as string),
       type: z.literal(componentName as string),
       props: def.props,
       props: def.props,
       children: z.array(z.string()).optional(),
       children: z.array(z.string()).optional(),
-      parentKey: z.string().nullable().optional(),
       visible: VisibilityConditionSchema.optional(),
       visible: VisibilityConditionSchema.optional(),
     });
     });
   });
   });
@@ -157,11 +155,9 @@ export function createCatalog<
 
 
   if (componentSchemas.length === 0) {
   if (componentSchemas.length === 0) {
     elementSchema = z.object({
     elementSchema = z.object({
-      key: z.string(),
       type: z.string(),
       type: z.string(),
       props: z.record(z.string(), z.unknown()),
       props: z.record(z.string(), z.unknown()),
       children: z.array(z.string()).optional(),
       children: z.array(z.string()).optional(),
-      parentKey: z.string().nullable().optional(),
       visible: VisibilityConditionSchema.optional(),
       visible: VisibilityConditionSchema.optional(),
     }) as unknown as z.ZodType<UIElement>;
     }) as unknown as z.ZodType<UIElement>;
   } else if (componentSchemas.length === 1) {
   } else if (componentSchemas.length === 1) {
@@ -500,7 +496,7 @@ export function generateSystemPrompt<
   lines.push("OUTPUT FORMAT (JSONL):");
   lines.push("OUTPUT FORMAT (JSONL):");
   lines.push('{"op":"set","path":"/root","value":"element-key"}');
   lines.push('{"op":"set","path":"/root","value":"element-key"}');
   lines.push(
   lines.push(
-    '{"op":"add","path":"/elements/key","value":{"key":"...","type":"...","props":{...},"children":[...]}}',
+    '{"op":"add","path":"/elements/key","value":{"type":"...","props":{...},"children":[...]}}',
   );
   );
   lines.push('{"op":"remove","path":"/elements/key"}');
   lines.push('{"op":"remove","path":"/elements/key"}');
   lines.push("");
   lines.push("");
@@ -513,7 +509,7 @@ export function generateSystemPrompt<
     "Remove elements with op:remove - also update the parent's children array to exclude the removed key",
     "Remove elements with op:remove - also update the parent's children array to exclude the removed key",
     "Children array contains string keys, not objects",
     "Children array contains string keys, not objects",
     "Parent first, then children",
     "Parent first, then children",
-    "Each element needs: key, type, props",
+    "Each element needs: type, props",
     "ONLY use props listed above - never invent new props",
     "ONLY use props listed above - never invent new props",
   ];
   ];
   const allRules = [...baseRules, ...customRules];
   const allRules = [...baseRules, ...customRules];

+ 1 - 0
packages/core/src/index.ts

@@ -5,6 +5,7 @@ export type {
   DynamicNumber,
   DynamicNumber,
   DynamicBoolean,
   DynamicBoolean,
   UIElement,
   UIElement,
+  FlatElement,
   Spec,
   Spec,
   VisibilityCondition,
   VisibilityCondition,
   LogicExpression,
   LogicExpression,

+ 5 - 6
packages/core/src/schema.ts

@@ -546,9 +546,9 @@ function generatePrompt<TDef extends SchemaDefinition, TCatalog>(
   lines.push("Example output (each line is a separate JSON object):");
   lines.push("Example output (each line is a separate JSON object):");
   lines.push("");
   lines.push("");
   lines.push(`{"op":"set","path":"/root","value":"card-1"}
   lines.push(`{"op":"set","path":"/root","value":"card-1"}
-{"op":"set","path":"/elements/card-1","value":{"key":"card-1","type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"],"parentKey":""}}
-{"op":"set","path":"/elements/metric-1","value":{"key":"metric-1","type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[],"parentKey":"card-1"}}
-{"op":"set","path":"/elements/chart-1","value":{"key":"chart-1","type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[],"parentKey":"card-1"}}`);
+{"op":"set","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"]}}
+{"op":"set","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[]}}
+{"op":"set","path":"/elements/chart-1","value":{"type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[]}}`);
   lines.push("");
   lines.push("");
 
 
   // Components section
   // Components section
@@ -594,9 +594,8 @@ function generatePrompt<TDef extends SchemaDefinition, TCatalog>(
     'First line sets root: {"op":"set","path":"/root","value":"<root-key>"}',
     'First line sets root: {"op":"set","path":"/root","value":"<root-key>"}',
     'Then add each element: {"op":"set","path":"/elements/<key>","value":{...}}',
     'Then add each element: {"op":"set","path":"/elements/<key>","value":{...}}',
     "ONLY use components listed above",
     "ONLY use components listed above",
-    "Each element value needs: key, type, props, children (array of child keys), parentKey",
-    "Use unique keys (e.g., 'header', 'metric-1', 'chart-revenue')",
-    "Root element's parentKey is empty string, children reference their parent's key",
+    "Each element value needs: type, props, children (array of child keys)",
+    "Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')",
   ];
   ];
   const allRules = [...baseRules, ...customRules];
   const allRules = [...baseRules, ...customRules];
   allRules.forEach((rule, i) => {
   allRules.forEach((rule, i) => {

+ 15 - 4
packages/core/src/types.ts

@@ -53,20 +53,31 @@ export interface UIElement<
   T extends string = string,
   T extends string = string,
   P = Record<string, unknown>,
   P = Record<string, unknown>,
 > {
 > {
-  /** Unique key for reconciliation */
-  key: string;
   /** Component type from the catalog */
   /** Component type from the catalog */
   type: T;
   type: T;
   /** Component props */
   /** Component props */
   props: P;
   props: P;
   /** Child element keys (flat structure) */
   /** Child element keys (flat structure) */
   children?: string[];
   children?: string[];
-  /** Parent element key (null for root) */
-  parentKey?: string | null;
   /** Visibility condition */
   /** Visibility condition */
   visible?: VisibilityCondition;
   visible?: VisibilityCondition;
 }
 }
 
 
+/**
+ * Element with key and parentKey for use with flatToTree.
+ * When elements are in an array (not a keyed map), key and parentKey
+ * are needed to establish identity and parent-child relationships.
+ */
+export interface FlatElement<
+  T extends string = string,
+  P = Record<string, unknown>,
+> extends UIElement<T, P> {
+  /** Unique key identifying this element */
+  key: string;
+  /** Parent element key (null for root) */
+  parentKey?: string | null;
+}
+
 /**
 /**
  * Visibility condition types
  * Visibility condition types
  */
  */

+ 11 - 6
packages/react/src/hooks.ts

@@ -1,7 +1,12 @@
 "use client";
 "use client";
 
 
 import { useState, useCallback, useRef, useEffect } from "react";
 import { useState, useCallback, useRef, useEffect } from "react";
-import type { Spec, UIElement, JsonPatch } from "@json-render/core";
+import type {
+  Spec,
+  UIElement,
+  FlatElement,
+  JsonPatch,
+} from "@json-render/core";
 import { setByPath } from "@json-render/core";
 import { setByPath } from "@json-render/core";
 
 
 /**
 /**
@@ -236,18 +241,18 @@ export function useUIStream({
 }
 }
 
 
 /**
 /**
- * Convert a flat element list to a Spec
+ * Convert a flat element list to a Spec.
+ * Input elements use key/parentKey to establish identity and relationships.
+ * Output spec uses the map-based format where key is the map entry key
+ * and parent-child relationships are expressed through children arrays.
  */
  */
-export function flatToTree(
-  elements: Array<UIElement & { parentKey?: string | null }>,
-): Spec {
+export function flatToTree(elements: FlatElement[]): Spec {
   const elementMap: Record<string, UIElement> = {};
   const elementMap: Record<string, UIElement> = {};
   let root = "";
   let root = "";
 
 
   // First pass: add all elements to map
   // First pass: add all elements to map
   for (const element of elements) {
   for (const element of elements) {
     elementMap[element.key] = {
     elementMap[element.key] = {
-      key: element.key,
       type: element.type,
       type: element.type,
       props: element.props,
       props: element.props,
       children: [],
       children: [],

+ 0 - 4
packages/react/src/schema.ts

@@ -15,16 +15,12 @@ export const schema = defineSchema((s) => ({
     /** Flat map of elements by key */
     /** Flat map of elements by key */
     elements: s.record(
     elements: s.record(
       s.object({
       s.object({
-        /** Unique key for this element */
-        key: s.string(),
         /** Component type from catalog */
         /** Component type from catalog */
         type: s.ref("catalog.components"),
         type: s.ref("catalog.components"),
         /** Component props */
         /** Component props */
         props: s.propsOf("catalog.components"),
         props: s.propsOf("catalog.components"),
         /** Child element keys (flat reference) */
         /** Child element keys (flat reference) */
         children: s.array(s.string()),
         children: s.array(s.string()),
-        /** Parent element key (null for root) */
-        parentKey: s.string(),
         /** Visibility condition */
         /** Visibility condition */
         visible: s.any(),
         visible: s.any(),
       }),
       }),

Некоторые файлы не были показаны из-за большого количества измененных файлов