Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Wire the voicing lane: register the provider, feed it contracts, calibrate before promoting

オープン
#188 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
20/100
issue の種類
機能追加
明瞭さ
明確に書かれている
活発さ
停滞
技術スタック
github, playwright, typescript

調査の方向性

Start with src/deps/build.ts and src/voicing/virtual-sr-provider.ts to trace provider registration, then read src/contracts/index.ts and the existing tests under test/voicing/. The issue sequences config validation, settle-signal integration, Chromium determinism testing, and shared screen-reader calibration before contracts and promotion. Treat #96 item 3 as the canonical calibration reference; done means every listed phase and the stated trust and disclosure requirements are met.

索引モデルが issue の本文から書いたものです。

説明

area: engine enhancement

Relationship to #96

This is the detailed implementation issue behind one bullet of the v0.3.0 tracking issue #96 ("Plus:
wire the voicing lane, which is built, exported, and currently unwired in v0.2.0"). It also shares the
calibration work with item 3 of #96 ("Published Orca calibration"): the calibration harness here (see
Trust question and Phase 4) is the same mechanism, and the two must not fork. Treat #96 item 3 as the
canonical home for the published-calibration attestation and staleness rule; this issue covers the
lane wiring and the engineering the calibration depends on. Held for v0.3.0, no work starts until it
opens.

What the voicing lane is

The lane checks what a screen reader actually says during an interaction, not just whether the page
structure is correct. Axe and the rulepack prove structure: a button has a name, an alert has the
right role, a live region exists. They cannot prove what a blind user experiences during a task: you
delete a cluster, and in the window right after that action the app should announce "Cluster deleted."
That is a claim about a transcript over time, tied to a specific step in a specific interaction. The
voicing lane models that claim and measures it.

An operator declares an InteractionContract for a surface: a task, a list of steps, and a list of
SpeechObligations ("after step N, the announcement must contain these words"). The engine walks the
interaction, records an announcement transcript per stop, and checks the obligations.

What is already built (verified against main at d4afc8f)

The deterministic transcript oracle exists and runs against real Chromium. The gap is input plumbing,
calibration, and authored contracts, not the oracle.

  • Frozen contracts in src/contracts/index.ts: AnnouncementToken (kind, text, fromTree,
    source: 'ax-tree' | 'attribute'), TranscriptStop, Step, StepRunner, SpeechObligation,
    InteractionContract, and promotedObligations?: string[] on UsablConfig (empty by default, the
    engine never writes it).
  • The oracle in src/deps/real.ts: a live-region MutationObserver over
    [aria-live],[role="status"],[role="alert"],[role="log"] with armAnnouncementCapture and
    drainAnnouncements, plus activeNode reading the accessibility tree over CDP
    Accessibility.getPartialAXTree and activePath. Deterministic given the pinned Chromium build,
    cross-platform, no application cooperation.
  • The concrete step runner in src/providers/keyboard-walk/steps.ts: executes tab, press-escape,
    activate, click (throws on unknown kinds), and per step builds AnnouncementTokens from the active
    node and drained live regions with provenance. Already passed into the check runner at
    src/deps/build.ts:258.
  • Structural tier (src/voicing/structural.ts): deterministic drafts for unreachable focus,
    missing name, missing role, missing live announcement, over-long tab path.
  • Voicing tier (src/voicing/voicing.ts): word-level voicing/missing-announcement, preview and
    unverified by default, deterministic fail only for promoted obligation classes. Matcher in
    src/voicing/normalize.ts.
  • The provider (src/voicing/virtual-sr-provider.ts): makeVirtualSrProvider(contracts, stepRunner), runs both tiers per matching surface, reads promotedObligations, never self-promotes.
  • Tests: test/voicing/{matching,normalize,promotion-invariants,structural,virtual-sr-provider,voicing}.test.ts.

What is not wired (the precise gaps)

  1. The provider is never registered. makeVirtualSrProvider is exported from src/index.ts but
    not in the providers array at src/deps/build.ts:222, so it never runs during usabl check.
  2. No config input for contracts. UsablConfig has no field, loader, or validation for
    InteractionContract[]. The provider takes contracts but nothing supplies them. Main missing
    plumbing.
  3. No contracts are authored for any surface.
  4. No calibration (shared with #96 item 3). The oracle is deterministic but unproven against a
    real screen reader, so no obligation class can be promoted honestly yet.
  5. The capture window is not bound to a settle signal. drainAnnouncements runs right after each
    step, so a late live-region update can be missed and timing can vary run to run. Ties to the #146
    readiness work and the #186 partial-render residual.

The trust question: deterministic versus trustworthy

The oracle is deterministic. It is not yet proven trustworthy. Trustworthy can only mean "agrees with
a named reference screen reader within a measured bound," never "universally correct," because screen
readers genuinely disagree across versions, settings, and platforms.

You earn the bound with a calibration harness, run offline, never in the gate path: drive the same
interaction fixtures with a real screen reader (Orca on Linux per #96 item 3, and
@guidepup/guidepup for NVDA and VoiceOver), capture the spoken phrases, compare to the
reconstruction at the token level (not exact strings, which is what obligationSatisfied already
does), and produce a measured agreement number per obligation class. That number is the evidence that
lets a class be promoted. Where agreement is too low, the class stays preview and the engine abstains.

Plan

Sequenced. Each phase complete to the bar before the next. Nothing gates on a signal it cannot back.

  • Phase 0, spike the trust question. A handful of fixtures, reconstruction versus a real screen
    reader, measured token agreement. This decides whether the reconstruction is faithful enough to ever
    promote. If no, the lane ships as an advisory preview signal only and later phases scope down.
  • Phase 1, contract input path. Add interaction contracts to config, validated and trusted-ref
    overlaid the same way requirements are, so a pull request cannot rewrite its own obligations and
    pass itself. Validate steps non-empty, afterStep in range, requiredTokens non-empty, surfaceId
    matches a declared surface. Additive, contracts are already frozen.
  • Phase 2, register the provider. Add makeVirtualSrProvider(contracts, stepRunner) to the
    providers array in src/deps/build.ts, present only when contracts are configured. It declares
    capabilities: ['live'], so under static-only mode it records not-covered gaps, not a crash.
    Confirm drafts reach the gate as preview (non-gating) by default.
  • Phase 3, determinism hardening. Bind the announcement capture window to the #146 settle signal so
    drainAnnouncements is stable and late updates are not missed. Add a real-Chromium integration test
    that runs a known interaction twice and asserts an identical transcript.
  • Phase 4, calibration harness (shared with #96 item 3). Turn the Phase 0 spike into a repeatable
    tool: fixtures, Orca and guidepup capture, token-level comparison, a measured agreement report per
    obligation class, stored as the justification for any promotion.
  • Phase 5, author contracts and promote. Write InteractionContracts for the real surfaces.
    Promote only the classes the calibration report backs. Disclose the reference screen reader and the
    agreement bound on every promoted finding, as fix text is framed as untrusted today.
  • Phase 6, surfaces and docs. Render voicing findings on the stop hook, the dev overlay, and CI and
    pull request comments, advisory where preview and deterministic where promoted. Same disclosure
    discipline as the rest of the engine.

Risks and open decisions

  • Screen readers disagree. Trust is a measured bound against a named reference, never universal.
    Disclose it on the finding.
  • Live-region coalescing. The MutationObserver captures every mutation; a real screen reader
    drops some under load, so the reconstruction can over-report announcements a user would miss. An
    optimism risk on "was it announced," which calibration measures.
  • Browse mode versus focus mode. Interaction contracts are focus and action based, which maps to
    focus mode. Browse-mode reading is out of scope and should be stated.
  • Authoring cost (decision). Contracts are operator content. Accept hand-authored contracts, or
    auto-derive candidate contracts from discovery for the operator to confirm.
  • Where contracts live (decision). Inline in usabl.config.json, a referenced file, or folded
    into the requirements bundle.
  • guidepup in CI. Drives real screen readers on macOS and Windows runners and can be flaky.
    Acceptable because it is the offline calibration reference, never the runtime oracle.

Definition of done

  • The provider is registered and runs on live scans when contracts are configured, abstains (preview)
    by default, and records not-covered under static-only mode.
  • Config carries interaction contracts with validation and trusted-ref overlay.
  • The transcript is proven deterministic by a real-Chromium test that runs twice with identical output.
  • The calibration harness produces a measured agreement per obligation class, and only backed classes
    are promoted, with the reference screen reader and the bound disclosed (in step with #96 item 3).
  • Voicing findings render on all surfaces with the correct trust framing.
  • No false green: an unmeasured or uncalibrated obligation never gates.
主要言語
TypeScript
スター
0
フォーク
0
平均マージ
2時間 48分
マージ済み PR(30日)
8

環境構築

  • Dockerfile・Docker Compose ファイルなし
  • プルリクエストのテンプレートあり
  • コントリビューションガイドなし

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

usabl-dev/usabl のほかの issue

usabl-dev/usabl の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。