/);
+ expect(html).toContain('
');
+ });
+
+ it('renders a top-level row as Container>Grid>Column and a NESTED row as bare Grid (recursion)', () => {
+ const body = tree([{
+ id: 'r1', type: 'row', ratio: '33-67', container: { width: 'full', gap: 'compact', verticalAlign: 'center' },
+ children: [
+ { id: 'c1', type: 'column', children: [paragraph('p2', 'left')] },
+ { id: 'c2', type: 'column', container: { verticalAlign: 'bottom', gap: 'spacious' }, children: [{
+ id: 'r2', type: 'row', ratio: '50-50', children: [
+ { id: 'c3', type: 'column', children: [paragraph('p3', 'deep')] },
+ { id: 'c4', type: 'column', children: [] },
+ ],
+ }] },
+ ],
+ }]);
+ const html = renderToStaticMarkup(createElement(TreeRenderer, { body, site: site() }));
+ expect(html).toContain('data-max-width="full"'); // width applied top-level
+ expect((html.match(/data-press-layout="grid"/g) ?? []).length).toBe(2); // outer + nested grid
+ expect((html.match(/data-press-layout="container"/g) ?? []).length).toBe(1); // nested row gets NO Container
+ expect(html).toContain('data-align-items="center"');
+ expect(html).toContain('data-cell-align="end"');
+ expect(html).toContain('--press-cell-gap:var(--press-space-7)');
+ expect(html).toContain('deep');
+ });
+
+ it('resolves inherit slots against pageDefaults and hydrates the navbar there', () => {
+ const navbar: Node = { id: 'n', type: 'block', component: 'preset-organism.navbar', data: { items: [{ label: 'Home', url: '/' }] } };
+ const html = renderToStaticMarkup(
+ createElement(TreeRenderer, {
+ body: tree([], { header: { mode: 'inherit' } }),
+ site: site({ header: [navbar] }),
+ }),
+ );
+ expect(html).toContain('data-block="preset-organism.navbar"');
+ expect(html).toContain('Press'); // hydrated brand
+ });
+
+ it('applies the root gap as a main stack and omits it when undeclared', () => {
+ const withGap = tree([paragraph('p', 'x')], { container: { gap: 'compact' } });
+ expect(renderToStaticMarkup(createElement(TreeRenderer, { body: withGap, site: site() })))
+ .toMatch(/
]*data-press-stack[^>]*style="--press-tree-gap:var\(--press-space-3\)"/);
+ expect(renderToStaticMarkup(createElement(TreeRenderer, { body: tree([]), site: site() })))
+ .not.toContain('data-press-stack');
+ });
+
+ it('fails an invalid body to empty but KEEPS the inherited chrome (Spec §7)', () => {
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
+ const navbar: Node = { id: 'n', type: 'block', component: 'preset-organism.navbar', data: {} };
+ const html = renderToStaticMarkup(
+ createElement(TreeRenderer, { body: { version: 99 }, site: site({ header: [navbar] }) }),
+ );
+ expect(html).toContain('data-block="preset-organism.navbar"');
+ expect(html).toMatch(/]*><\/main>/);
+ expect(warn).toHaveBeenCalled();
+ warn.mockRestore();
+ });
+
+ it('skips unknown components with a dev warning and honors adopter overrides', () => {
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
+ const body = tree([
+ { id: 'u', type: 'block', component: 'custom-organism.mystery', data: {} },
+ { id: 'h', type: 'block', component: 'preset-organism.hero', data: { title: 'T' } },
+ ]);
+ const MyHero = () => createElement('div', { 'data-my-hero': '' }, 'override');
+ const html = renderToStaticMarkup(
+ createElement(TreeRenderer, { body, site: site(), components: { 'preset-organism.hero': MyHero } }),
+ );
+ expect(html).toContain('data-my-hero');
+ expect(html).not.toContain('mystery');
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('custom-organism.mystery'));
+ warn.mockRestore();
+ });
+});
+```
+
+- [ ] **Step 2: Run (FAIL), implement**
+
+Create `packages/web/src/tree/tree-renderer.tsx`:
+
+```tsx
+/**
+ * TreeRenderer — renders a PressTree as //