fix(uix): C4 — APG anchors for 29 interactive components (20 patterns + 5 documented absences)

Every interactive morfo now either anchors its W3C APG pattern or
documents that none exists. Real patterns (20): the picker family +
command + search-field anchor combobox (field/input + popup, the
pattern's own popup taxonomy); date-field mirrors time-field's
spinbutton (per-segment contract); rating-group -> radio;
navigation-menu -> disclosure (the APG's own site-nav guidance);
splitter -> windowsplitter; tag-group/tags-input/virtual-grid -> grid;
clipboard/file-upload -> button (the trigger IS the widget);
announce -> alert (live region); form -> the form landmark;
image-adjustments -> slider; table -> table.

The A-1.4 valve (documented exception) is wired like the R-x rules,
and five components whose widget has NO APG pattern say so in their
README instead of faking an anchor: cropper (2-D manipulation),
pagination (nav + composed Buttons), stepper, timeline (display list),
image-picker (media picker). Remaining warns are the four expected:
card/drag-drop/virtual-list host their note when C8 writes their
READMEs, media-player is foreign WIP.

Catalog: 83 PASS / 47 NEEDS-WORK / 0 BROKEN (was 75/50/5 at the
audit's start). npm run check at the 61-error baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
menubar-v4-safe
dev 3 months ago
parent 3b08711d77
commit 85e2356d49

@ -40,7 +40,7 @@ Por dimensión:
| **C1** | **BROKEN mecánicos → 0 BROKEN**: válvula `R-x.y exception:` en audit (R-1.1/R-1.2/R-1.5/E-2.2 + filas checklist); cascade/motion/gradient-builder `texts.label`+langs; menu-dial default export; READMEs contrato (cascade·motion·menu-dial·qr-code: Baseline/Comparativa/Decisiones/Gaps/Passive+excepciones). cascade·motion·menu-dial → PASS; gradient-builder·qr-code → NEEDS-WORK (resto = D-* fase demos + C8) | **HECHA 2026-07-02** |
| **C2** | **Estados error-severity**. **(a) R-1.5 HECHA 2026-07-02**: el audit ahora reconoce las 4 evidencias sancionadas (`:focus-visible` · `:focus-within` · `:has(:focus)` · `[data-focused]` canónico) — añadir rings por recipe habría sido el anti-patrón "double border" (archetypes.css:164 ya da ring universal por arquetipo); 6 excepciones citadas (css-field/number-field → spin-field compartido; date-picker/dialog → foundation+content-exclusion; metrics/timeline → sin parte enfocable); clipboard+announce esperan su README (C8). **(b) R-1.2/1.3/1.4 HECHAS 2026-07-03**: válvula cableada también en R-1.3/R-1.4; fixes REALES verificados en navegador (switch/toggle/rating-group invalid=threat-element como checkbox·pin-input; toggle+pin-input readonly; spin-field readonly NUEVO en la capa compartida; ntp disabled); excepciones citadas (spin-field para css/number-field; dialog provider virtual; color-picker→color-field; date-range-field overlay de attrs; time-picker→time-field). ntp conserva R-1.3/R-1.4 warn para su pasada C8. media-player NO tocado (foráneo) | **HECHA** |
| **C3** | **R-2.7 tipografía literal HECHA 2026-07-03** — 16 recipes: 8 font-weights → `var(--font-weight-*)` (verificado resolve=500 en navegador); ~13 proporcionales/identidad anotados `/* literal: */` con razón (avatar ∝ diámetro; `calc(1em − (md−sm))` = label one-step-down con delta token-derived; `1em` identidad de contexto; captions ×0.85; optical −1px) | **HECHA** |
| **C4** | **A-1.4 APG** (33 morfos) — URL del patrón W3C correspondiente; los sin patrón APG real → nota explícita | pendiente |
| **C4** | **A-1.4 APG HECHA 2026-07-03** — 20 morfos con patrón real (pickers/command/search→combobox · date-field→spinbutton espejo de time-field · rating→radio · nav-menu→disclosure · splitter→windowsplitter · tag-group/tags-input/virtual-grid→grid · file-upload/clipboard→button · announce→alert · form→landmark · image-adjustments→slider · table→table); válvula A-1.4 + notas en 5 READMEs (cropper·pagination·stepper·timeline·image-picker — sin patrón APG real). Restan 4 esperados: card/drag-drop/virtual-list (nota aterriza con su README en C8) + media-player (foráneo) | **HECHA** |
| **C5** | **Sema `expression:`** (~41 morfos con eventos sin declarar) — clasificar pack / family-default / delegated / none por componente (criterios D.4 de book-deviations); `persistence`/`a11ySemantic` donde el evento lo pida (tabla D.9 como referencia) | pendiente |
| **C6** | **Paleta universal** — `paletteScaleDecls` pasa de helper manual (7 usos) a paso del generador para toda recipe con tokens `palette-*` + regen + guard ("data-color sin cascade de paleta = error") | pendiente |
| **C7** | **Size-bundle sweep** (~90 recipes; patrón theming/reference §5, pilot toggle) + guard "recipe que re-declara size→font = warn" | pendiente |

@ -274,10 +274,14 @@ function checkMorfo(kebab: string, src: string, info: ComponentReport): CheckRes
}
}
// A-1.4: apg URL for interactive components
// A-1.4: apg URL for interactive components. Components with no real APG
// pattern document that instead (`A-1.4 exception:` in the README) — an
// invented anchor would be worse than an explicit absence.
const hasApg = /\bapg:\s*['"]https?:/.test(src);
if (info.interactive) {
const apgExc = readmeException(kebab, 'A-1.4');
if (hasApg) out.push(pass('A-1.4', 'warn'));
else if (apgExc) out.push(pass('A-1.4', 'warn', `exception documented: ${apgExc}`));
else out.push(fail('A-1.4', 'warn', 'No apg URL declared for interactive component'));
}

@ -97,3 +97,7 @@ wrapper (code-split) — intentionally NOT in `eidos/index.css`.
| Aspect exactly at a boundary | **diferir** | Edge clamping may relax the locked ratio at the very edge; drag inward restores it. |
| External-CORS images taint the canvas | **diferir** | Blob URLs (the ImagePicker flow) are fine; cross-origin `<img>` needs CORS headers to export. |
| Baking ImagePicker filter/rotation into the crop | **diferir** | v1 crops the source `src`; applying the upstream filter/rotation in the canvas is a pipeline follow-up. |
## Audit exceptions
- `A-1.4 exception:` no APG pattern exists for a 2-D crop/manipulation widget; the closest primitives (slider for the zoom, button for the actions) are composed and carry their own contracts.

@ -103,3 +103,7 @@ from the wrapper (code-split chunk) — intentionally NOT in `eidos/index.css`.
| `highlights` / `shadows` tone curves | **diferir** | Need canvas / SVG `feComponentTransfer`; tracked in ImageAdjustments. |
| Multiple images / gallery | **descartar** | Single image by design (`multiple={false}`); use `file-upload` directly for multi. |
| WAI-ARIA APG pattern | **diferir** | No single APG pattern covers a file-picker composite; the dropzone is a button, the root a labelled `group`. |
## Audit exceptions
- `A-1.4 exception:` no APG pattern covers a media picker (gallery + upload); its trigger/actions are composed `<Button>`s and the overlay follows the composed Popover/Dialog contract.

@ -115,3 +115,7 @@ page-size pertenece a Field/Select + app state, no al recipe de Pagination.
| Cobertura adicional de variantes visuales | **diferir** | El recipe cubre sm/md/lg + solid/outline/ghost. Más variantes requieren caso concreto. |
| Documentación per-prop exhaustiva | **implementar** | Cuando se cierre el ciclo de remediación de cada componente. |
| Tests browser-level del flujo completo (Playwright) | **implementar** | Cobertura visual + interacciones. Se hace en una pasada conjunta de tests. |
## Audit exceptions
- `A-1.4 exception:` the APG defines no pagination pattern; the component is a `nav` landmark whose page controls are composed `<Button>`s (button contract).

@ -78,3 +78,7 @@ paso.
| Cobertura adicional de variantes visuales | **diferir** | El recipe cubre sm/md/lg + solid/outline/ghost. Más variantes requieren caso concreto. |
| Documentación per-prop exhaustiva | **implementar** | Cuando se cierre el ciclo de remediación de cada componente. |
| Tests browser-level del flujo completo (Playwright) | **implementar** | Cobertura visual + interacciones. Se hace en una pasada conjunta de tests. |
## Audit exceptions
- `A-1.4 exception:` the APG defines no stepper/wizard pattern; steps are a labelled list and the navigation controls are composed `<Button>`s.

@ -91,3 +91,4 @@ Tokens: `src/uix/eidos/lib/recipes/base.ts` (`timeline`). Selectors:
## Audit exceptions
- `R-1.5 exception:` no focusable part of its own — items are display (`item` / `title` / `description`); anything focusable inside is consumer content that brings its own treatment.
- `A-1.4 exception:` the APG defines no timeline pattern; the component is a display list — anything interactive inside is consumer content with its own contract.

@ -5,6 +5,7 @@ export const announceMorfo = {
name: 'Announce',
kebab: 'announce',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/alert/',
// Announce IS sema's `signal.announce/alert` literal. Pack exists in
// `sema/components/announce.ts` when needed; for now family base is fine.
expression: 'family-default',

@ -5,6 +5,7 @@ export const clipboardMorfo = {
name: 'Clipboard',
kebab: 'clipboard',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/button/',
// Copy + fail events benefit from a pack to differentiate the save chime
// from the error tone. To be tuned in a follow-up; family base for now.
expression: 'family-default',

@ -5,6 +5,7 @@ export const colorPickerMorfo = {
name: 'ColorPicker',
kebab: 'color-picker',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
events: [
{
name: 'commit-reset',

@ -5,6 +5,7 @@ export const commandMorfo = {
name: 'Command',
kebab: 'command',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
expression: "family-default",
texts: {
label: '#?components.command.label|Command palette',

@ -5,6 +5,7 @@ export const dateFieldMorfo = {
name: 'DateField',
kebab: 'date-field',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/',
texts: {
label: '#?components.date-field.label|Date Field',
input: '#?components.date-field.input|Date field'

@ -11,6 +11,7 @@ export const datePickerMorfo = {
name: 'DatePicker',
kebab: 'date-picker',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
// Identity wrapper over a composed Popover (§2): the Popover IS the dialog.
// This morfo declares no dialog semantics (no apg:dialog-modal, no role:dialog,
// no focus trap, no open/close events) — they belong to the composed Popover.

@ -11,6 +11,7 @@ export const dateRangePickerMorfo = {
name: 'DateRangePicker',
kebab: 'date-range-picker',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
expression: "delegated",
texts: {
label: '#?components.date-range-picker.label|Date range picker',

@ -5,6 +5,7 @@ export const fileUploadMorfo = {
name: 'FileUpload',
kebab: 'file-upload',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/button/',
texts: {
label: '#?components.file-upload.label|File Upload',
dropzone: '#?components.file-upload.dropzone|Upload files',

@ -5,6 +5,7 @@ export const formMorfo = {
name: 'Form',
kebab: 'form',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/form.html',
texts: {
label: '#?components.form.label|Form',
'error-summary-title': '#?components.form.error-summary-title|{{count}} form errors need your attention',

@ -16,6 +16,7 @@ export const imageAdjustmentsMorfo = {
name: 'ImageAdjustments',
kebab: 'image-adjustments',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/slider/',
texts: {
label: '#?components.image-adjustments.label|Image adjustments',
reset: '#?components.image-adjustments.reset|Reset'

@ -5,6 +5,7 @@ export const navigationMenuMorfo = {
name: 'NavigationMenu',
kebab: 'navigation-menu',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/',
// expression resolved via the sema pack at
// `src/uix/sema/components/navigation-menu.ts` — subtle commit + tap
// haptic for high-frequency nav link selection.

@ -5,6 +5,7 @@ export const ratingGroupMorfo = {
name: 'RatingGroup',
kebab: 'rating-group',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/radio/',
texts: {
label: '#?components.rating-group.label|Rating'
},

@ -5,6 +5,7 @@ export const searchFieldMorfo = {
name: 'SearchField',
kebab: 'search-field',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
texts: {
label: '#?components.search-field.label|Search',
clear: '#?components.search-field.clear|Clear search'

@ -14,6 +14,7 @@ export const splitterMorfo = {
// since the components/splitter eidos wrappers reference the morfo
// in their CSS recipe (`data-event='commit-set'` pulse).
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/',
texts: {
label: '#?components.splitter.label|Splitter',
resize: '#?components.splitter.resize|Resize panels'

@ -5,6 +5,7 @@ export const tableMorfo = {
name: 'Table',
kebab: 'table',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/table/',
expression: "family-default",
texts: {
label: '#?components.table.label|Data table',

@ -5,6 +5,7 @@ export const tagGroupMorfo = {
name: 'TagGroup',
kebab: 'tag-group',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/grid/',
texts: {
label: '#?components.tag-group.label|Tags',
remove: '#?components.tag-group.remove|Remove {{tag}}'

@ -5,6 +5,7 @@ export const tagsInputMorfo = {
name: 'TagsInput',
kebab: 'tags-input',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/grid/',
texts: {
label: '#?components.tags-input.label|Tags Input',
remove: '#?components.tags-input.remove|Remove tag',

@ -11,6 +11,7 @@ export const timePickerMorfo = {
name: 'TimePicker',
kebab: 'time-picker',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
expression: "delegated",
texts: {
label: '#?components.time-picker.label|Time Picker',

@ -11,6 +11,7 @@ export const timeRangePickerMorfo = {
name: 'TimeRangePicker',
kebab: 'time-range-picker',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
// Identity wrapper over a composed Popover (§2): the Popover IS the dialog.
// This morfo declares no dialog semantics (no apg:dialog-modal, no role:dialog,
// no focus trap, no open/close events) — they belong to the composed Popover.

@ -5,6 +5,7 @@ export const virtualGridMorfo = {
name: 'VirtualGrid',
kebab: 'virtual-grid',
scope: ['soma', 'sema', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/grid/',
// User scroll events use handle base; programmatic scroll events use
// shift.navigate base. Family defaults are appropriate.
expression: 'family-default',

Loading…
Cancel
Save

Powered by TurnKey Linux.