import type { Kysely } from 'kysely'; import type { ContentTypeRow, Database } from '../db/schema.js'; import { type A11yIssue, type A11yRule } from './accessibility.js'; import { type ContentItem } from './items.js'; /** * The site-wide accessibility report: the same rules, run over many items at once. * * Split from `accessibility.ts` on purpose. The rules there are a pure function of a value, which * is what lets the editor's panel run them on every keystroke and what makes them testable without * a database. This file is the other half — finding the content and resolving what the rules need * to look at — and it is the only half that needs a connection. * * **Never cached, always recomputed.** Same reasoning as `releasePreflight`: a stored list of * reasons still accuses somebody an hour after they fixed it, and there is no invalidation that * covers "somebody edited the reusable block this page references". The cost is real and stated on * the screen rather than hidden — this is a scan, not an indexed query. */ export interface AccessibilityReportOptions { contentTypeId?: string; /** * Restrict to what the public can actually see, through the same `visibleToPublic` rule every * other reader uses. The default, because a draft nobody has finished is not yet a problem * anybody has. */ visibleOnly?: boolean; /** Show only items with an issue of this rule. Applied after the scan — see `AuditedItem`. */ rule?: A11yRule; search?: string; limit?: number; offset?: number; } export interface AuditedItem { item: ContentItem; contentType: ContentTypeRow; issues: A11yIssue[]; } export interface AccessibilityReport { /** Items on this page **that have issues**, in path order. */ items: AuditedItem[]; /** How many items the filters matched, of which this page scanned `scanned`. */ totalItems: number; scanned: number; errors: number; warnings: number; } /** * Audit one page of content items. * * Paginated because it has to be: every item's `data` is read and walked, so the honest bound is * "a page at a time, and the screen says how far it got". A site-wide issue *total* is deliberately * not offered — it would mean reading every row of the table to render a number, and a total that * is quietly capped is worse than no total at all. * * Queries are per page, not per item: one for the items, one for every content type's fields, one * for the block registry, one for the library, and one for the media those items reference. */ export declare function auditContentItems(db: Kysely, options?: AccessibilityReportOptions): Promise; export interface UndescribedImage { id: string; filename: string; storage_key: string; } /** * Images in the library that nobody has described and nobody has marked decorative. * * A real query rather than a scan, which is what lets it carry an honest total while the item audit * above cannot. It is also the only part of the report that finds a problem *before* it reaches a * page: an image uploaded and not yet placed appears in no item's data, so the walk cannot see it — * and it will be undescribed on whatever page it eventually lands on. * * `alt_text is null` rather than `= ''` or falsy: `''` is a deliberate "this is decorative", and * the two are what `needsAltText` exists to keep apart. Expressed in SQL here because the whole * point is not loading the table to answer it. */ export declare function undescribedImages(db: Kysely, options?: { limit?: number; }): Promise<{ images: UndescribedImage[]; total: number; }>;