From 96393e43058a21aa19d1328867fe278a91dd0439 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 1 Aug 2026 01:57:35 +0200 Subject: [PATCH] docs(blocks): cada block declara su posicion bajo la doctrina de coordinacion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Y `content-section.Media` pasa a seguir al texto por defecto. DEFAULT: `.Media` era `width='wide'`, asi que una figura se salia de la columna SALVO que dijeras lo contrario. Al reves de lo que hacen todas las referencias (en `@tailwindcss/typography` una imagen dentro de `prose` queda capada a la medida y salirse exige el truco del `translate`; en `markdown-body` de GitHub, `max-width: 100%` de la columna). Ahora el default es `measure` — la figura sigue al texto y el escape se PIDE. Cambiado en los cuatro sitios que decian lo contrario: la parte, la ruta `preview`, el estado inicial del control de la demo (una demo que pisa el default en silencio ensena algo que el block no hace solo) y la doc. Verificado: 528 = ancho del texto exacto a 1011/1280/1920, y `wide` / `full` siguen funcionando cuando se piden. DOCUMENTACION: los 15 READMEs declaran ahora su posicion bajo §«Coordination», y el mapa real no es uniforme — - Coordinan: `contact` (maquina + esquema + palabras) y `pricing` (el periodo por contexto). Pero en pricing nada se puede BLOQUEAR, asi que no necesita maquina ni palabras: coordinar no es siempre una maquina. - Comparten CONFIGURACION, no estado: `team` (`align`) y `content-section` (la medida). - Costura bindable hacia el canon: `faq` (`value` → `Accordion`) y `site-header` (`mobileOpen` → `Drawer`). Reenviar no es poseer. - No poseen nada: banner · cta · feature-grid · feature-split · hero · site-footer · stats-band · testimonials. Decirlo explicitamente es el punto: inventarle estado a un block que no coordina nada es el error contrario. - `newsletter` queda como CANDIDATO ABIERTO: coloca un envio que puede quedar bloqueado sin que nada diga por que — el `disabled` lo monta el app en el punto de uso — que es exactamente el hueco que cerro `contact`. Es anterior a la doctrina (block del 30-07, doctrina del 31-07). Registrado, no asumido: se toca con decision del usuario. El README del tier apunta a la doctrina y el handoff recoge el mapa completo. Gates: blocks:check verde (15) · docs:check 0 · svelte-check sin errores en lo tocado · prettier limpio. Co-Authored-By: Claude Opus 5 --- docs/process/CONTINUE-blocks.md | 31 +++++++---- src/uix/blocks/README.md | 19 +++++-- src/uix/blocks/banner/README.md | 8 +++ src/uix/blocks/content-section/README.md | 18 ++++--- .../content-section-media.svelte | 8 ++- src/uix/blocks/content-section/types.ts | 11 ++-- src/uix/blocks/cta/README.md | 54 +++++++++++-------- src/uix/blocks/faq/README.md | 27 ++++++---- src/uix/blocks/feature-grid/README.md | 34 +++++++----- src/uix/blocks/feature-split/README.md | 38 +++++++------ src/uix/blocks/hero/README.md | 48 ++++++++++------- src/uix/blocks/newsletter/README.md | 15 ++++++ src/uix/blocks/pricing/README.md | 48 ++++++++++------- src/uix/blocks/site-footer/README.md | 8 +++ src/uix/blocks/site-header/README.md | 35 +++++++----- src/uix/blocks/stats-band/README.md | 28 ++++++---- src/uix/blocks/team/README.md | 9 ++++ src/uix/blocks/testimonials/README.md | 35 +++++++----- .../blocks/content-section/+page.svelte | 4 +- .../content-section/preview/+page.svelte | 2 +- 20 files changed, 318 insertions(+), 162 deletions(-) diff --git a/docs/process/CONTINUE-blocks.md b/docs/process/CONTINUE-blocks.md index 2ce15e234..e3f0999e0 100644 --- a/docs/process/CONTINUE-blocks.md +++ b/docs/process/CONTINUE-blocks.md @@ -1,4 +1,4 @@ -# CONTINUE — F2 blocks de sitio (handoff, act. 2026-07-31) +# CONTINUE — F2 blocks de sitio (handoff, act. 2026-08-01) **Estado: F1 CERRADA (8/8) · F2 CERRADA (15/15).** Hechos: site-header · hero · feature-grid · **feature-split** · pricing · @@ -6,10 +6,9 @@ testimonials · faq · stats-band · cta · newsletter · site-footer · banner **contact** (§F2.13, el primero que COORDINA) · **content-section** (§F2.14). **Siguiente**: F3 (blocks de aplicación, 10) — o antes la página compuesta de -prueba que el plan pide para cerrar F2 (header + hero + features + pricing + faq - -- cta + footer juntos, mirada en claro/oscuro/375/1280): es el test de - integración del tier y todavía no existe. +prueba que el plan pide para cerrar F2: header, hero, features, pricing, faq, cta +y footer juntos, mirada en claro/oscuro/375/1280. Es el test de integración del +tier y todavía no existe. > ⚠️ **CAMBIO DE DOCTRINA (decisión del usuario, 2026-07-31).** Lee la sección > «La doctrina cambió» antes de tocar nada: un block ya no es solo colocación. @@ -57,10 +56,24 @@ enmendado (posee las palabras de SUS estados, como idlangref con fallback inglés) y la convención de servicios se acota: **el traductor es el único servicio sancionado**. La sección dice también a quién NO aplica. -**Alcance acordado**: `contact` primero como prueba de la forma — **HECHO**; -queda revisar los que tienen estado real — `pricing` (periodo), `newsletter` -(alta), `site-footer` (idioma), `banner` (descarte), `team` (eje). El resto es -layout puro y ahí no hay coordinación que mover. +**Alcance acordado**: `contact` primero como prueba de la forma — **HECHO**. +**Revisión documental de los 15 HECHA** (2026-08-01): cada README declara ahora +su posición bajo la doctrina, y el mapa real salió así — + +- **Coordinan**: `contact` (máquina + esquema + palabras) · `pricing` (el periodo + por contexto, pero nada se puede BLOQUEAR ahí, así que no necesita máquina ni + palabras: coordinar no es siempre una máquina). +- **Comparten configuración, no estado**: `team` (`align`) · `content-section` + (la medida). +- **Costura bindable hacia el canon**: `faq` (`value` → `Accordion`) · + `site-header` (`mobileOpen` → `Drawer`). Reenviar no es poseer. +- **No poseen nada**: banner · cta · feature-grid · feature-split · hero · + site-footer · stats-band · testimonials. +- ⚠️ **`newsletter` es el candidato ABIERTO**: coloca un envío que puede quedar + bloqueado sin que nada diga por qué (el `disabled` lo monta el app en el punto + de uso) — exactamente el hueco que cerró `contact`. Es anterior a la doctrina + (block del 2026-07-30, doctrina del 2026-07-31). **Siguiente candidato a + tocar**, con decisión tuya antes de moverlo. ⚠️ Al revisarlos, el listón es el que dejó `contact`: **el estado se deriva de una fuente y las partes lo leen**; si un estado puede bloquear algo, tiene frase diff --git a/src/uix/blocks/README.md b/src/uix/blocks/README.md index ab089b028..6ae5e5e3c 100644 --- a/src/uix/blocks/README.md +++ b/src/uix/blocks/README.md @@ -4,6 +4,13 @@ Named compositions of canon components performing a **page function** (sticky site header, hero, app shell, docs shell, …). This tree IS the live inventory — one folder per block, no list in any doc. +Every block's README states its **position under the coordination doctrine** +(2026-07-31): whether it owns its section's state, the shape of its data and the +words for its states — or owns nothing because it is layout. Most are layout, and +saying so explicitly is the point: inventing state for a block that coordinates +nothing is the opposite mistake. Worked example: `contact`. Open candidate: +`newsletter`. + - **Doctrine** (what a block is, the admission rule, the B contract): [`docs/architecture/blocks.md`](../../../docs/architecture/blocks.md) - **Guard**: `npm run blocks:check` (B contract, self-testing) @@ -16,7 +23,7 @@ inventory — one folder per block, no list in any doc. ```text src/uix/blocks/{kebab}/ -├── README.md # Function · Composition map · Decisions · Gaps (B-9) +├── README.md # Function · Composition map · Coordination · Decisions · Gaps ├── index.ts # public export (compound component) └── {kebab}.svelte # composition — canon components only, layout via # layout components, no .css file (B contract) @@ -59,21 +66,25 @@ web/routes/blocks/{kebab}/ # {Name} ## Function + One paragraph: the page function this block performs. ## Composition map -| Part | Composes | Key props | -| --- | --- | --- | -| `.Nav` | `NavigationMenu` | … | + +| Part | Composes | Key props | +| ------ | ---------------- | --------- | +| `.Nav` | `NavigationMenu` | … | Landmark + heading hierarchy: which sectioning element / heading level the block emits and how the app adjusts it. ## Decisions + Dated, with the reference comparison (which block catalogs were studied, what was adopted/discarded). ## Gaps + Every deferred feature with a disposition (canon candidate · future variant · rejected-with-reason). ``` diff --git a/src/uix/blocks/banner/README.md b/src/uix/blocks/banner/README.md index 3c3176e90..44547a0a2 100644 --- a/src/uix/blocks/banner/README.md +++ b/src/uix/blocks/banner/README.md @@ -39,6 +39,14 @@ A banner's parts neither repeat nor coordinate, so there is no compound API — the tier's admission rule. _(The plan sketched `` + children; the `badge` and `action` slots are the two positions the references converge on.)_ +## Coordination + +_Position under the 2026-07-31 doctrine ([`architecture/blocks.md`](../../../../docs/architecture/blocks.md) §«Coordination»)._ + +**Owns nothing.** The dismissal is the app's — a `{#if}` around the block plus +`onDismiss` — and the canon `Banner` owns `dismissed` where it belongs. Nothing +here can be blocked, so there is no state to derive and no state to explain. + ## Decisions **2026-07-30 — reference floor** (dossier: Tailwind Plus «Banners» 13 · Untitled diff --git a/src/uix/blocks/content-section/README.md b/src/uix/blocks/content-section/README.md index 527a36dcb..9fbee40e7 100644 --- a/src/uix/blocks/content-section/README.md +++ b/src/uix/blocks/content-section/README.md @@ -48,12 +48,12 @@ edge. ## Composition map -| Slot | Composes | Notes | -| ------- | --------------------------------------- | --------------------------------------------------------------- | -| root | bare `
` + `Section` + `Grid` | owns the landmark `id`; the grid replaces the usual `Container` | -| header | `Motion` + `Stack` + `Heading` + `Text` | snippets: `eyebrow` · `title` · `lede` · `meta` | -| `Body` | `Box` + `Prose` (`measure={false}`) | a run of the article's copy | -| `Media` | `Box` + `Motion` + `
` | a figure outside the prose flow, with a real `
` | +| Slot | Composes | Notes | +| ------- | --------------------------------------- | ---------------------------------------------------------------- | +| root | bare `
` + `Section` + `Grid` | owns the landmark `id`; the grid replaces the usual `Container` | +| header | `Motion` + `Stack` + `Heading` + `Text` | snippets: `eyebrow` · `title` · `lede` · `meta` | +| `Body` | `Box` + `Prose` (`measure={false}`) | a run of the article's copy | +| `Media` | `Box` + `Motion` + `
` | a figure with a real `
`; follows the text by default | The root shares the measure by context (`context.ts`) — **configuration, not state**: the caption of a broken-out figure needs the section's reading column, @@ -82,6 +82,12 @@ entry; Tailwind Plus, GitHub `.markdown-body`, Starlight): - **Adopted — the breakout.** Every editorial reference lets media exceed the text measure; it is the single thing that distinguishes an article layout from a paragraph in a box. +- **Adopted — a figure follows the text by default.** In every reference an + image inside the reading column stays in it (`@tailwindcss/typography` caps it + at the measure; escaping takes the `translate` hack or a hand-rolled grid), so + `Media` defaults to `width='measure'` and breaking out is opt-in. A figure + that widens itself unasked is a surprise, and the block's own demo was the + proof: the first question it got was «why is the blue wider than the text?». - **Adopted — the caption stays narrow.** A `
` set to the width of a full-bleed image is unreadable, so the caption is capped at the reading measure even when its figure is not. Several references do not do this. diff --git a/src/uix/blocks/content-section/content-section-media.svelte b/src/uix/blocks/content-section/content-section-media.svelte index eb11eecc3..61d4b97b8 100644 --- a/src/uix/blocks/content-section/content-section-media.svelte +++ b/src/uix/blocks/content-section/content-section-media.svelte @@ -8,6 +8,10 @@ * interactive, so this is document structure — the one a11y surface a block * owns (B-8) — not the admission rule firing. * + * It follows the TEXT by default, and breaking out is opt-in — the way every + * reference behaves (an image inside `prose` is capped at the measure; escaping + * it takes a hack). A figure that widens itself unasked is a surprise. + * * The caption stays in the READING column even when the figure breaks out: a * caption set to the width of a full-bleed image is unreadable. */ @@ -19,14 +23,14 @@ import { getContentSectionContext, captionWidth } from './context'; import type { ContentSectionMediaProps } from './types'; - let { width = 'wide', caption, children, ...rest }: ContentSectionMediaProps = $props(); + let { width = 'measure', caption, children, ...rest }: ContentSectionMediaProps = $props(); const eidos = ActiveEidos.require(); const section = getContentSectionContext(); // Resolved the way every eidos primitive resolves a responsive prop, so // `{ base: 'full', md: 'wide' }` works here exactly as it does on `Box`. - const reach = $derived(eidos.resolve(width, 'wide')); + const reach = $derived(eidos.resolve(width, 'measure')); const capWidth = $derived(captionWidth(section?.measure ?? 'normal', reach)); diff --git a/src/uix/blocks/content-section/types.ts b/src/uix/blocks/content-section/types.ts index beb5a55a8..8d9705e46 100644 --- a/src/uix/blocks/content-section/types.ts +++ b/src/uix/blocks/content-section/types.ts @@ -81,10 +81,13 @@ export type ContentSectionBodyProps = Omit & { // so Box's own is not something a caller should be setting anyway. export type ContentSectionMediaProps = Omit & { /** - * How far it escapes the reading column. **Responsive** — the reason the prop - * exists at all: a photo that runs edge to edge on a phone and sits contained - * on a desktop is `{ base: 'full', md: 'wide' }`, which one value cannot say. - * @default 'wide' + * How far it escapes the reading column. **Responsive** — a photo that runs + * edge to edge on a phone and sits contained on a desktop is + * `{ base: 'full', md: 'wide' }`, which one value cannot say. + * + * @default 'measure' — the figure FOLLOWS THE TEXT unless asked otherwise, + * the way every reference behaves. Breaking out is the exception you request, + * not something a figure does to you. */ width?: ResponsiveProp; /** The caption — rendered as a `
` under the media. */ diff --git a/src/uix/blocks/cta/README.md b/src/uix/blocks/cta/README.md index 6c6a6dfa9..53e5dabfc 100644 --- a/src/uix/blocks/cta/README.md +++ b/src/uix/blocks/cta/README.md @@ -9,25 +9,33 @@ beat of a page (`center`) or a mid-page nudge that must not stop the reading ## Composition map -| Slot | Composes | Notes | -| --- | --- | --- | -| root | bare `
` | landmark; `id` names it from the title the block renders | -| padding · measure | `Section` + `Container` | `size` / `container` | -| entrance | `Motion` (`trigger="viewport"`, `scale-fade`) | the panel arrives WHOLE — it is one statement, not a list, so it does not stagger its parts | -| panel | `Surface` (always `solid` · `color` · `gradient` · `rounded`) | the look is the system's finish, not paint of the block's own | -| layout | `Stack` (center) · `Grid` 2-col (justified) | `justified` stacks on narrow, where one row would crush both halves | -| `eyebrow` | — (app: `Badge` / text) | above the title | -| `title` | `Heading` at `level` (default 2) | the block wraps the app's words and owns the landmark `id`; on-solid ink | -| `description` | `Text` `as="p"` (`60ch` measure) | on-solid ink. A `span` would ignore `align` — `text-align` is inert on an inline box | -| `actions` | `Flex` (column → row at `sm`) | the app drops `Button`s / `Link`s | +| Slot | Composes | Notes | +| ----------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| root | bare `
` | landmark; `id` names it from the title the block renders | +| padding · measure | `Section` + `Container` | `size` / `container` | +| entrance | `Motion` (`trigger="viewport"`, `scale-fade`) | the panel arrives WHOLE — it is one statement, not a list, so it does not stagger its parts | +| panel | `Surface` (always `solid` · `color` · `gradient` · `rounded`) | the look is the system's finish, not paint of the block's own | +| layout | `Stack` (center) · `Grid` 2-col (justified) | `justified` stacks on narrow, where one row would crush both halves | +| `eyebrow` | — (app: `Badge` / text) | above the title | +| `title` | `Heading` at `level` (default 2) | the block wraps the app's words and owns the landmark `id`; on-solid ink | +| `description` | `Text` `as="p"` (`60ch` measure) | on-solid ink. A `span` would ignore `align` — `text-align` is inert on an inline box | +| `actions` | `Flex` (column → row at `sm`) | the app drops `Button`s / `Link`s | ## Form: snippet slots, not sub-components The tier's rule is that a compound API is earned when parts **repeat** (`feature-grid.Item`) or **coordinate** (`pricing.Switch` ↔ `PlanPrice`). A CTA's parts do neither: they are fixed positional slots the root arranges. So it takes -the same shape as `hero`. *(The plan sketched `.Title`/`.Description`/`.Actions`; -this is the same registered deviation as `hero`.)* +the same shape as `hero`. _(The plan sketched `.Title`/`.Description`/`.Actions`; +this is the same registered deviation as `hero`.)_ + +## Coordination + +_Position under the 2026-07-31 doctrine ([`architecture/blocks.md`](../../../../docs/architecture/blocks.md) §«Coordination»)._ + +**Owns nothing.** A closing panel places copy and actions; there is no state, no +data shape and no word of its own. Layout blocks do not coordinate, and giving +one a machine would be the opposite mistake. ## Decisions @@ -37,7 +45,7 @@ PrimeBlocks «CTA 12» · Untitled UI): - **Adopted**: the centred panel (parity) **and the `justified` arrangement** — copy at the inline-start, actions at the end — which the dossier names as the gap to close. -- **The finish is the system's**: a CTA is distinguished by *treatment*, and +- **The finish is the system's**: a CTA is distinguished by _treatment_, and treatment is already framework vocabulary. `Surface` with `gradient` on by default (the one section that earns it), so a theme or palette swap carries the panel along. The block paints nothing of its own. @@ -67,14 +75,14 @@ layout, colour, finish and direction, and the device widths served from ## Gaps -| Gap | Disposition | -| --- | --- | -| **Split with media** (panel with a screenshot beside the copy) | **deferred** — recurs in the refs; it is a third arrangement, and `Mockup` already exists for the media. Enters when a demo asks | -| **Full-bleed panel** (edge to edge, no container) | **app-land** — the app sets `container="full"` | -| **Dismissible / sticky CTA** | **out** — that is the `banner` block's job (F2.11), not this one | -| **A quiet BOUNDED panel** | **canon** — `Surface soft` does not bound (0.002 L from the page in light) and has no border; `Card outline` bounds but takes no gradient finish. No primitive covers "quiet CTA panel with an edge" | -| **`contrast` slot is white on every solid step** | **canon** — so a mid-L solid canvas (`neutral` 3.32 · `secondary` 3.30 · `slate` 3.30 · `teal` 3.07 in light) puts body copy below AA. The pairing guarantee holds for the dark canvases only. Measured with a luminance probe, not eyeballed | -| **On-solid chip treatment** | **canon** — `Badge` has no on-solid variant; the demo uses `variant="soft"`, whose near-white track happens to read against a saturated canvas. A `Badge` that knows it sits on a solid panel would be the real answer | +| Gap | Disposition | +| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Split with media** (panel with a screenshot beside the copy) | **deferred** — recurs in the refs; it is a third arrangement, and `Mockup` already exists for the media. Enters when a demo asks | +| **Full-bleed panel** (edge to edge, no container) | **app-land** — the app sets `container="full"` | +| **Dismissible / sticky CTA** | **out** — that is the `banner` block's job (F2.11), not this one | +| **A quiet BOUNDED panel** | **canon** — `Surface soft` does not bound (0.002 L from the page in light) and has no border; `Card outline` bounds but takes no gradient finish. No primitive covers "quiet CTA panel with an edge" | +| **`contrast` slot is white on every solid step** | **canon** — so a mid-L solid canvas (`neutral` 3.32 · `secondary` 3.30 · `slate` 3.30 · `teal` 3.07 in light) puts body copy below AA. The pairing guarantee holds for the dark canvases only. Measured with a luminance probe, not eyeballed | +| **On-solid chip treatment** | **canon** — `Badge` has no on-solid variant; the demo uses `variant="soft"`, whose near-white track happens to read against a saturated canvas. A `Badge` that knows it sits on a solid panel would be the real answer | ## Found while composing @@ -85,6 +93,6 @@ layout, colour, finish and direction, and the device widths served from until the consumer changes the element. Canon candidate. - **`Surface soft` cannot bound a panel** — measured above; see Gaps. - **`Group` does not stack.** The actions row needed `Flex direction={{ base: - 'column', sm: 'row' }}`: in a `Group` at 420px the secondary action's label +'column', sm: 'row' }}`: in a `Group` at 420px the secondary action's label breaks mid-phrase against the primary button. `hero` composes its actions with `Group` too, so it has the same narrow behaviour — noted for its next pass. diff --git a/src/uix/blocks/faq/README.md b/src/uix/blocks/faq/README.md index 883088d43..3eb6fd3cd 100644 --- a/src/uix/blocks/faq/README.md +++ b/src/uix/blocks/faq/README.md @@ -7,12 +7,12 @@ narrow readable column. Usually near the end of a page, often after pricing. ## Composition map -| Part | Composes | Notes | -| --- | --- | --- | -| root | bare `
` + `Section` + `Container` (narrow) + `Stack` | places the header and the list | -| `.Header` | `Box` + `Stack` | the app's section `Heading` + `Text` | -| `.List` | `Motion` (`trigger="viewport"`) wrapping the `Accordion` | **is** the accordion — its whole API passes through | -| `.Item` | `Accordion.Item` + `.Header` + `.Trigger` + `.Content` | thin proxy: `question` snippet → trigger, children → content | +| Part | Composes | Notes | +| --------- | ------------------------------------------------------------- | ------------------------------------------------------------ | +| root | bare `
` + `Section` + `Container` (narrow) + `Stack` | places the header and the list | +| `.Header` | `Box` + `Stack` | the app's section `Heading` + `Text` | +| `.List` | `Motion` (`trigger="viewport"`) wrapping the `Accordion` | **is** the accordion — its whole API passes through | +| `.Item` | `Accordion.Item` + `.Header` + `.Trigger` + `.Content` | thin proxy: `question` snippet → trigger, children → content | ## Why compound (a thin proxy) @@ -23,6 +23,15 @@ API and passes it straight through** (`type`, `bind:value`, `collapsible`, one convenience: it spares the app the `Accordion.Item > Header > Trigger` / `Content` scaffolding and auto-generates the item `value` when omitted. +## Coordination + +_Position under the 2026-07-31 doctrine ([`architecture/blocks.md`](../../../../docs/architecture/blocks.md) §«Coordination»)._ + +**Owns nothing of its own.** `value` is a bindable SEAM forwarded straight to the +canon `Accordion`, which owns the disclosure state, its keyboard and its ARIA. A +seam is not ownership: the block never derives from it and never gates anything +on it. + ## Decisions **2026-07-24 — reference floor** (dossier §P1: Tailwind Plus «FAQs 7» · @@ -42,10 +51,10 @@ questions. Mini-page in `FaqSite.svelte`, shared by both surfaces. ## Gaps -| Gap | Disposition | -| --- | --- | +| Gap | Disposition | +| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Static 2/3-column list** (questions + answers laid out, no disclosure) | **scope-approval pending** — the dossier's FAQ gap: 6 of 7 in TW are NOT accordions. It is a different arrangement (a grid of Q&A, always open), so likely a `layout` prop or a sibling — a user decision, not a silent add | -| **"Still have questions?" tail** (a CTA to support below the list) | **app-land** — the app puts a `Text` + `Link`/`Button` after `.List`; a dedicated slot is only worth it if a demo asks | +| **"Still have questions?" tail** (a CTA to support below the list) | **app-land** — the app puts a `Text` + `Link`/`Button` after `.List`; a dedicated slot is only worth it if a demo asks | ## Found while composing diff --git a/src/uix/blocks/feature-grid/README.md b/src/uix/blocks/feature-grid/README.md index 81012882e..eca8721c9 100644 --- a/src/uix/blocks/feature-grid/README.md +++ b/src/uix/blocks/feature-grid/README.md @@ -8,15 +8,15 @@ after the hero. ## Composition map -| Part | Composes | Notes | -| --- | --- | --- | -| root | bare `
` + `Section` + `Container` + `Stack` | places the header and the grid | -| `.Header` | `Box` (measure-cap) + `Stack` | the app supplies the section `Heading` (h2) + `Text` | -| `.Items` | `AutoGrid` + `data-stagger` | fluid (`minChildWidth`, default `16rem`) or fixed `columns`; the stagger rhythm is structural | -| `.Item` | `Motion` (`trigger="viewport"`) wrapping a `Stack` | IS the reveal, so it must stay the grid's direct child to get its structural index; `align="start"` (default) or `center` | -| `.ItemIcon` | `Surface` (solid, accent) | the coloured chip — solid so it reads in light mode, where the soft tint is near-white; the app drops an `` inside | -| `.ItemTitle` | `Heading` (level 3) | the feature name | -| `.ItemText` | `Text` (muted) | the feature description | +| Part | Composes | Notes | +| ------------ | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| root | bare `
` + `Section` + `Container` + `Stack` | places the header and the grid | +| `.Header` | `Box` (measure-cap) + `Stack` | the app supplies the section `Heading` (h2) + `Text` | +| `.Items` | `AutoGrid` + `data-stagger` | fluid (`minChildWidth`, default `16rem`) or fixed `columns`; the stagger rhythm is structural | +| `.Item` | `Motion` (`trigger="viewport"`) wrapping a `Stack` | IS the reveal, so it must stay the grid's direct child to get its structural index; `align="start"` (default) or `center` | +| `.ItemIcon` | `Surface` (solid, accent) | the coloured chip — solid so it reads in light mode, where the soft tint is near-white; the app drops an `` inside | +| `.ItemTitle` | `Heading` (level 3) | the feature name | +| `.ItemText` | `Text` (muted) | the feature description | ## Why compound (and not snippet slots) @@ -31,6 +31,14 @@ parent's, the cells are the app's. `.Items` is an explicit grid wrapper (like a list's `List`): it lets the header sit outside the grid without a full-row `grid-column: 1 / -1` span hack. +## Coordination + +_Position under the 2026-07-31 doctrine ([`architecture/blocks.md`](../../../../docs/architecture/blocks.md) §«Coordination»)._ + +**Owns nothing.** The items repeat but do not talk to each other — no context, no +state, no words. That is exactly why they are a compound without a context, and +why the rule is applied part by part, not block by block. + ## Decisions **2026-07-23 — reference floor** (dossier §P1: Tailwind Plus «Features 15» · @@ -52,11 +60,11 @@ control of `columns`/`minChildWidth`/`align`/item-count, device widths from ## Gaps -| Gap | Disposition | -| --- | --- | +| Gap | Disposition | +| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Feature-split / alternating** (a text block beside a screenshot, sides alternating) | **scope-approval pending** — the dossier's **#1 F2 gap**: "lo shippean las 5 refs y es la sección nº1 tras el hero". It is a different arrangement (alternating 2-column rows, not an icon grid), so likely its own block (`feature-split`) or a sibling — a user decision, not a silent add here | -| **Card variant** (each item boxed) | **deferred** — one prop away (wrap the `Item` body in a `Card`); built when a demo asks | -| **Stat / numbered variants** | **deferred** — the number replaces the icon chip; app-composable today | +| **Card variant** (each item boxed) | **deferred** — one prop away (wrap the `Item` body in a `Card`); built when a demo asks | +| **Stat / numbered variants** | **deferred** — the number replaces the icon chip; app-composable today | ## Found while composing diff --git a/src/uix/blocks/feature-split/README.md b/src/uix/blocks/feature-split/README.md index a98b18ead..49ccc8845 100644 --- a/src/uix/blocks/feature-split/README.md +++ b/src/uix/blocks/feature-split/README.md @@ -9,16 +9,16 @@ beside a screenshot, repeated down the page. ## Composition map -| Part | Composes | Notes | -| --- | --- | --- | -| root | bare `
` + `Section` + `Container` + `Stack` | stacks the rows | -| `.Row` | `Grid` (2-col) + `Box` (grid placement) | `reversed` flips the sides via `grid-column`; DOM stays copy-first | -| `.Row` `media` slot | — (app: a `Mockup`, `Image`, `AspectRatio`) | the media column | -| `.Eyebrow` | `Text` (primary, semibold, sm) | the small label above the title | -| `.Title` | `Heading` (level 2) | the row's claim | -| `.Text` | `Text` (muted, lg) | the supporting sentence | -| `.Features` / `.Feature` | `Stack` / `Group` + `Icon.Check` + `Text` | the checklist; the check is decorative (aria-hidden) | -| `.Actions` | `Group` | the CTA cluster | +| Part | Composes | Notes | +| ------------------------ | ---------------------------------------------------- | ------------------------------------------------------------------ | +| root | bare `
` + `Section` + `Container` + `Stack` | stacks the rows | +| `.Row` | `Grid` (2-col) + `Box` (grid placement) | `reversed` flips the sides via `grid-column`; DOM stays copy-first | +| `.Row` `media` slot | — (app: a `Mockup`, `Image`, `AspectRatio`) | the media column | +| `.Eyebrow` | `Text` (primary, semibold, sm) | the small label above the title | +| `.Title` | `Heading` (level 2) | the row's claim | +| `.Text` | `Text` (muted, lg) | the supporting sentence | +| `.Features` / `.Feature` | `Stack` / `Group` + `Icon.Check` + `Text` | the checklist; the check is decorative (aria-hidden) | +| `.Actions` | `Group` | the CTA cluster | **Reversal keeps reading order**: the copy is always first in the DOM; `reversed` only sets `grid-column` so the media moves to the inline-start side. Screen @@ -30,11 +30,19 @@ Like `feature-grid`, the `Row` **repeats** — the app maps over N feature rows so it earns a compound API (the tier's admission rule). The media is composed, not baked: the app drops a **`Mockup`** (the canon media-frame primitive: browser chrome, phone, or plain) into the `media` slot. That is the point of -this block's fase-0 — the dossier said the real gap is *media treatment*, so we +this block's fase-0 — the dossier said the real gap is _media treatment_, so we closed it with a reusable primitive (`$uix/eidos/components/mockup`) that this block, `hero`, `testimonials`… all share, rather than faking a screenshot per demo. +## Coordination + +_Position under the 2026-07-31 doctrine ([`architecture/blocks.md`](../../../../docs/architecture/blocks.md) §«Coordination»)._ + +**Owns nothing.** The rows repeat and are independent; `reversed` is a per-row +prop, not shared state. Nothing here can be blocked, so there is nothing to +explain. + ## Decisions **2026-07-23 — reference floor** (dossier §P1: Tailwind Plus «Features 15» · @@ -56,10 +64,10 @@ Mini-page in `FeatureSplitSite.svelte`, shared by both surfaces. ## Gaps -| Gap | Disposition | -| --- | --- | -| Feature-split with a **background band** per row (tinted section behind alternating rows) | **deferred** — the app wraps a `Row` in a `Surface`; a prop is sugar | -| **Sticky media** (the screenshot pins while the copy scrolls) | **deferred** — behaviour, would compose `Sticky`; built when a demo asks | +| Gap | Disposition | +| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Feature-split with a **background band** per row (tinted section behind alternating rows) | **deferred** — the app wraps a `Row` in a `Surface`; a prop is sugar | +| **Sticky media** (the screenshot pins while the copy scrolls) | **deferred** — behaviour, would compose `Sticky`; built when a demo asks | ## Found while composing diff --git a/src/uix/blocks/hero/README.md b/src/uix/blocks/hero/README.md index 291290960..7ee03c24a 100644 --- a/src/uix/blocks/hero/README.md +++ b/src/uix/blocks/hero/README.md @@ -10,25 +10,33 @@ renders) while the app owns every word. ## Composition map -| Slot | Composes | Notes | -| --- | --- | --- | -| root | bare `
` | landmark; `id` names it from the title the block renders | -| padding | `Section` | `size` (block-axis padding, default `xl`) | -| measure | `Container` | `size` (`container` prop, default `lg`) | -| decoration | `Backdrop` | `decor` prop — `glow` (default) · `mesh` · `grid` · `dots` · `none`; skipped under `background` | -| layout | `Grid` (split) · `Stack` (center) · positioned `Box` layers (background) | `split` is two columns only when there is `media` | -| entrance | `Motion` (`trigger="viewport"`) + `data-stagger` on the copy stack | the copy unfolds by structural index; media enters with `scale-fade` after it | -| `eyebrow` | — (app: `Badge` / text) | placed above the title | -| `title` | `Display` (`as={h{level}}`) | the hero typography primitive; the block wraps the app's words and owns the `id`; one size step down in `split`; on-solid ink under `background` | -| `description` | `Text` (`60ch` measure) | muted normally; on-solid ink under `background` | -| `actions` | `Group` | the app drops `Button`s / `Link`s | -| `media` | — (app: `Image` / `AspectRatio` / `Surface`) | second column (split) or below (center) | -| `backdrop` | positioned `Box` + scrim + cover `