import type { FieldRow } from '../db/schema.js'; /** * The content accessibility checker. * * **What it is not.** This audits *published content* — what a visitor receives. The WCAG * compliance of the admin itself is a different job, checked by `npm run a11y`, and the two must * not be confused: an editor can write an inaccessible page in a perfectly accessible editor. * * **Advisory, never blocking.** Nothing here runs in a write path and nothing here refuses a save * or a publish. That is deliberate rather than a first step: an author who cannot publish because a * checker disagrees with them routes around the CMS, and a false positive in a rule would become an * outage. `validateItemData` is where a rule that must hold goes; this is where a rule that should * usually hold goes, and mixing the two is how "advisory" quietly stops being advisory. * * **Pure, with no database handle**, for the same reason `resolveSeo` is: the editor's live panel * and the site-wide report must not drift, and one of those two callers is a React island running * on every keystroke. Everything it needs to resolve — alt text, block type schemas, reusable block * content — arrives in `A11yContext`, looked up by whoever has the connection. */ export type A11yRule = 'image-alt' | 'heading-order' | 'link-name' | 'link-text' | 'embed-name' | 'embed-title'; export type A11ySeverity = 'error' | 'warning'; export interface A11yIssue { rule: A11yRule; severity: A11ySeverity; /** Written for the person who has to fix it, so it says what to do rather than what is wrong. */ message: string; /** * The **top-level** field this came from, however deeply nested the value was. * * That is what the editor can actually scroll to: a block three levels down has no control of its * own on the page, but the block field holding it does. `location` carries the rest of the trail. */ fieldApiId: string; /** Human trail to the value, e.g. `Body → Block 2 (Hero) → Heading`. */ location: string; /** * Set when the issue belongs to a reusable block rather than to this item. * * A referenced block carries no content of its own — the library row owns it — so the page's * author cannot fix this here, and telling them to would be sending them to the wrong screen. */ inheritedFrom?: { id: string; name: string; }; } /** One media asset, as much of it as a rule needs. */ export interface A11yMediaInfo { filename: string; mimeType: string; /** * `null` means nobody has said. `''` means somebody said it is decorative. * * The distinction is the whole reason `image-alt` is usable: without it every divider, icon, and * background flourish is a permanent complaint, and a panel that is always red is a panel nobody * reads. */ altText: string | null; } /** * Whether an asset should be reported as undescribed. * * One function because four places ask: this checker, the media library's banner and cards, the * picker, and the media field. They used to ask it as `!altText`, which is also true of `''` — so * the moment `''` came to mean "decorative", every one of them would have gone on calling a * deliberately undescribed image a mistake. */ export declare function needsAltText(asset: { mimeType: string; altText: string | null; }): boolean; export interface A11yContext { /** * Alt text for the media this item references, keyed by id. * * An id **absent** from the map is not reported. The map is built by querying for exactly the ids * in the item's data, so a missing one is an asset whose row is gone — a broken reference, which * is a different problem, and "this image has no alt text" would send somebody to fix a screen * that no longer exists. Omitting the map entirely means no image can be checked, which is the * honest answer for a caller that did not resolve any. */ altById?: Map; /** Block type schemas keyed by `api_id`, as `blockTypeRegistry` returns them, plus a name. */ blockTypes?: Map; /** Library entries keyed by id, for blocks placed by reference. */ reusableBlocks?: Map; }>; } export interface A11yRuleMeta { rule: A11yRule; label: string; /** One line, in an editor's vocabulary — it is a filter's explanation, not a spec. */ description: string; } /** * The rules, in the order a report should list them. * * Here rather than in the admin for the same reason `FIELD_TYPE_META` is: the report's filter names * them and so does the handbook, and a copy in the admin is a copy free to go on describing a rule * that no longer works that way. */ export declare const A11Y_RULE_META: Record; export declare const A11Y_RULES: A11yRule[]; export declare function isA11yRule(value: string): value is A11yRule; /** * Every accessibility issue in one content item's field values. * * The walk mirrors `validateItemData`'s exactly — top-level fields, blocks through the registry * bounded by `MAX_BLOCK_DEPTH`, repeater rows through `repeaterRowFields` — because a value that * validation reaches and this does not is a value nobody is checking. */ export declare function checkItemAccessibility(fields: FieldRow[], data: Record, context?: A11yContext): A11yIssue[]; type RichTextFinding = Pick; /** * Heading order and link quality within **one** rich text value. * * The scope is the interesting part rather than a shortcut. Taproot ships no templates and has no * idea what order a site renders a content type's fields in — or whether it renders all of them — * so the document outline a visitor actually receives is not knowable here. Within one value it is * knowable exactly, so that is what is checked, and "why didn't it catch the h2 after my block's * h3" has an answer rather than a bug. */ export declare function checkRichText(html: string): RichTextFinding[]; /** * The media ids a content item's data references, for building `A11yContext.altById`. * * Both callers need this and neither should walk the field tree twice, so it is one function using * the same walk as the check itself: a media field the checker reads and this misses would report * every one of its images as undescribed. */ export declare function referencedMediaIds(fields: FieldRow[], data: Record, context?: Pick): string[]; /** How many of each severity, for a panel heading or a report row. */ export declare function countBySeverity(issues: A11yIssue[]): { errors: number; warnings: number; }; export {};