You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/architecture/blocks.md

114 lines
7.0 KiB

---
title: Blocks — reusable page-function compositions
type: reference
audience: human + agent
authority: E1 architecture — the blocks tier: what it is, the canon-vs-block admission rule, the B contract, the promotion path
status: current
---
# Blocks
A **block** is a named composition of canon components performing a **page
function** — a sticky site header, a hero, a pricing section, an app shell.
It sits ABOVE the UIX layers and OUTSIDE the component canon, at
`src/uix/blocks/` (alias `$blocks`). It consumes the framework; the framework
never references it. The live inventory is the `src/uix/blocks/` tree — never
a list in a doc.
Blocks exist so that **assembly knowledge does not dilute the canon**: the
catalog's value is contract density (events, ARIA, keyboard, tokens other
layers consume); a block's value is *correct assembly* — layout, landmarks,
heading hierarchy, responsive behavior, content slots. A block brings no new
primitives; it brings the right way to put existing ones together.
The tier map, for orientation:
| Tier | Value | Contract | Enters through |
| --- | --- | --- | --- |
| **Canon** (`src/uix/`) | contract density others consume | morfo + acceptance matrix | the 9-phase route ([`building-a-component.md`](../building-a-component.md)) |
| **Packs** (`src/packs/`) | parameterized leaf decoration | P contract ([`packs.md`](./packs.md)) | `packs:check` |
| **Blocks** (`src/uix/blocks/`) | page-function composition | B contract (below) | `blocks:check` |
## The admission rule (canon vs block)
> **Canon** = anything with contract surface — a real event, a state machine,
> a `data-*` attribute CSS needs to select, a widget a11y obligation. It is
> built FIRST as a canonical component through the 9-phase route; the block
> then composes it.
>
> **Block** = composition only. If building a block reveals a missing
> behavior, that is a **gap to flag and promote** — never a privilege to grow
> inside the block.
What a block DOES own semantically: **landmarks and document structure**
(`header/nav/main/aside/footer`, heading hierarchy, skip links, region
labels). Components cannot know page context; blocks can. That is their only
a11y surface of their own.
A block MAY hold local view-state (the active tab, a monthly/yearly toggle)
through the public props/events of the components it composes. What it may
NOT do is materialize that state with DOM/CSS of its own that would need
contract — that is the admission rule firing.
## Hard boundaries
1. **Dependency direction is one-way**: `src/uix/blocks/*` may import `$uix`,
`$adom` and the public arts; nothing in the canon
(`src/uix/{morfo,soma,sema,eidos,active-uix,langs}`) nor in
`src/{arts,libs,packs}` may import from `src/uix/blocks/`. Deleting the
tier must leave `npm run check` green — blocks is a tier under `src/uix/`,
not a fifth layer.
2. **No morfo, no matrix**: a block declares no morfo, ships no sema pack,
and takes no row in `component:audit`. Its quality floor is the B contract
below, guarded by `blocks:check`.
3. **Layout-components-first**: layout is composed from the canon layout
components (`Container / Section / Stack / Flex / Grid / AutoGrid / Wrap /
Group / Separator / AspectRatio / Surface`) and their props. A block ships
no `.css` file; a scoped `<style>` is the justified exception, never the
pattern.
4. **Blocks are removable by construction** — an app that never imports a
block pays zero bytes for it.
## The B contract (the block quality floor)
Guarded mechanically by `npm run blocks:check` (self-testing: the guard
asserts its own detectors against inline fixtures on every run).
| B | Obligation |
| --- | --- |
| B-1 | No morfo, no sema pack, no audit row. Behavior with contract surface is promoted to canon BEFORE the block composes it. |
| B-2 | Every interactive element is a canon eidos component (`Button`, `Link`, `Field`, …). Raw interactive natives (`button/input/select/textarea/a`) are an error. (Single exception: the already-rendered HTML that `Prose` receives — that content belongs to the app.) |
| B-3 | Composed components are consumed AS-IS through their public props (`variant/size/color/…`). Re-styling their internals from the block (CSS or `style=`) is forbidden. Colors are always roles/tokens via props. |
| B-4 | One-way imports (hard boundary 1). Deleting the tier leaves `check` green. Blocks do not import each other (see B-10). |
| B-5 | Content enters by composition (children/snippets) — never `root={tree}` data-tree props (`items={...}` only where the composed canon component is already data-driven). |
| B-6 | Responsive via the framework's mechanisms (responsive props of the layout components, canonical breakpoints). No `matchMedia`/listeners of its own — needing to observe something is the admission rule firing. |
| B-7 | A block owns NO visible string: all text arrives from the app as children/props. An unavoidable string is contract surface → the underlying canon component owns it (`texts:` + langs). |
| B-8 | Correct landmarks: sectioning element + `aria-label`/`aria-labelledby` where landmarks repeat; heading hierarchy coherent and documented in the block README (which level it emits, how to adjust). |
| B-9 | Every block ships `README.md` (**Function · Composition map** — which canon components, which props — **· Decisions · Gaps-with-disposition**) and a live demo page under `web/routes/blocks/{kebab}/`. |
| B-10 | A block does not import another block. Shared structure is either a canon component or a conscious duplication (recorded in Gaps). Declared exception: the shells (`app-shell`, `docs-shell`) compose F1 pieces and blocks by design — allow-listed in `blocks:check`. |
| B-11 | Motion only through the composed components' `motion` props/presets or `Cascade` for entrance choreography. No `@keyframes`/transitions of its own. |
## Conventions
- **API**: compound component with nested parts (`<SiteHeader>` /
`<SiteHeader.Nav>` / `<SiteHeader.Actions>`); content always via children.
- **Demos**: `web/routes/blocks/{kebab}/+page.svelte`, indexed by the gallery
at `web/routes/blocks/`.
- **Services**: blocks do not consume `uix.prefs` / langs / eidos directly
(v1) — wiring (theme, language, submits, transport) arrives as
handlers/props from the app. Revisable when ≥2 blocks demonstrate a real
need (same bar as the 2-of-3 rule).
## The promotion path (lived)
The tier was bootstrapped the way the admission rule prescribes: the eight
behaviors the first blocks needed — `sticky` (stuck-state affix),
`anchor-nav` (scrollspy TOC), `empty-state`, `result`, `callout`, `prose`,
`nav-tree` (data-driven navigation tree), `sidebar` — were identified as
contract surface and routed to the **canon first**, each through the full
9-phase route; the blocks compose them. Any
future block-shaped need with contract surface follows the same path. The
execution record (phases, per-item fiches, signed D-BLK decisions) lives in
[`process/PLAN-blocks.md`](../process/PLAN-blocks.md) — process, not
doctrine; this document is the standing truth.

Powered by TurnKey Linux.