Context
Several of this app's own panels correctly associate a <label> with its control by nesting the
control inside the label (e.g. commands-panel.tsx lines 91-102, maintainer-settings.tsx's
FieldGroup/select/number fields at lines 437-474 — <label className="block"><span>…</span><input/></label>).
That pattern gives the input an accessible name implicitly, with no htmlFor/id needed, and is fine.
Four places in the four files below do not follow that pattern, or have no <label> at all:
-
owner-panel.tsx lines 85-94 — the "Repository" <label> is a sibling of the <Input>, not a
wrapper, and neither carries htmlFor/id:
<label className="font-mono text-token-2xs uppercase tracking-wider text-muted-foreground">
Repository
</label>
<Input value={repo} onChange={(e) => setRepo(e.target.value)} ... placeholder="owner/repo" />
A screen reader gets no accessible name for this input at all (placeholder text is not a reliable
substitute for a programmatic label — WCAG 3.3.2/4.1.2).
-
playground-panel.tsx lines 216-249 — the same sibling-label pattern, three times over: the "Tool"
<select>, the "Repo" <input>, and the "Branch" <input> all have a <label> immediately preceding
them with no htmlFor/id and no nesting.
-
maintainer-settings.tsx's FocusManifestEditor — the JSON focus-manifest <textarea> (an
editable, functionally significant control — it edits real repo config) has no label at all, only a
preceding <h3>/<p> that is not programmatically linked via aria-labelledby, htmlFor, or aria-label.
-
digest-panel.tsx's SubscribeForm — the email <Input type="email"> has only a placeholder and
nearby (sibling, unassociated) descriptive text ("Store-only digest" / "Persist a subscription record…"),
no <label>/aria-label.
Confirmed the shared Input primitive (packages/loopover-ui-kit/src/components/input.tsx) is a thin
wrapper around a native <input> with no automatic aria-label/placeholder-as-name behavior, so these four
controls genuinely have no accessible name today — this is code-verifiable, not a subjective design call.
None of the four files has any accessibility-focused test today (maintainer-settings.tsx,
owner-panel.tsx, playground-panel.tsx, and digest-panel.tsx have no .test.tsx file at all).
Requirements
owner-panel.tsx's Repository input: either nest the <Input> inside the <label> (matching the
pattern already used in commands-panel.tsx/maintainer-settings.tsx), or give the label htmlFor and
the input a matching id.
playground-panel.tsx's Tool/Repo/Branch controls: same fix, applied to all three.
maintainer-settings.tsx's focus-manifest <textarea>: add either a visually-hidden <label htmlFor=…>
tied to an id on the textarea, or an aria-label/aria-labelledby pointing at the existing <h3>
heading — any one is acceptable, the requirement is that the textarea has a non-empty accessible name.
digest-panel.tsx's email input: add a <label> (nested or htmlFor/id-linked) or an aria-label
(e.g. aria-label="Digest notification email") — a bare placeholder does not satisfy this.
- Do not change any visual layout, spacing, or styling — this is a markup-association fix only, not a
redesign. (Per this repo's UI rule, any change that alters visible output requires a screenshot table and
manual review regardless; if the fix can be done with zero visual diff — e.g. htmlFor/id addition, or
a visually-hidden label — prefer that so the change stays in the "no visual change" lane and follows the
normal gate instead of forced manual review.)
Deliverables
Test Coverage Requirements
apps/loopover-ui/** is outside the Codecov coverage.include set (UI code, not src/**/workers/**), so
the 99% patch gate does not apply, but each of these four files currently has zero tests — add at least one
test per file asserting the fixed control is reachable via its accessible name/role, so the fix is
regression-proof and not just visually-inspected.
Expected Outcome
All four controls are reachable by screen readers and testing-library queries via their accessible name;
getByLabelText/getByRole(..., { name }) finds each control without falling back to placeholder text.
Links & Resources
apps/loopover-ui/src/components/site/app-panels/owner-panel.tsx lines 85-94
apps/loopover-ui/src/components/site/app-panels/playground-panel.tsx lines 213-250
apps/loopover-ui/src/components/site/app-panels/maintainer-settings.tsx (FocusManifestEditor,
<textarea> around lines 575-582)
apps/loopover-ui/src/components/site/app-panels/digest-panel.tsx (SubscribeForm, <Input> around lines
216-224)
- Correct existing pattern to mirror:
apps/loopover-ui/src/components/site/app-panels/commands-panel.tsx
lines 91-102, maintainer-settings.tsx's own FieldGroup select/number fields (lines 437-474)
- Shared primitive:
packages/loopover-ui-kit/src/components/input.tsx (confirms no automatic
accessible-name behavior)
Context
Several of this app's own panels correctly associate a
<label>with its control by nesting thecontrol inside the label (e.g.
commands-panel.tsxlines 91-102,maintainer-settings.tsx'sFieldGroup/select/number fields at lines 437-474 —<label className="block"><span>…</span><input/></label>).That pattern gives the input an accessible name implicitly, with no
htmlFor/idneeded, and is fine.Four places in the four files below do not follow that pattern, or have no
<label>at all:owner-panel.tsxlines 85-94 — the "Repository"<label>is a sibling of the<Input>, not awrapper, and neither carries
htmlFor/id:A screen reader gets no accessible name for this input at all (placeholder text is not a reliable
substitute for a programmatic label — WCAG 3.3.2/4.1.2).
playground-panel.tsxlines 216-249 — the same sibling-label pattern, three times over: the "Tool"<select>, the "Repo"<input>, and the "Branch"<input>all have a<label>immediately precedingthem with no
htmlFor/idand no nesting.maintainer-settings.tsx'sFocusManifestEditor— the JSON focus-manifest<textarea>(aneditable, functionally significant control — it edits real repo config) has no label at all, only a
preceding
<h3>/<p>that is not programmatically linked viaaria-labelledby,htmlFor, oraria-label.digest-panel.tsx'sSubscribeForm— the email<Input type="email">has only aplaceholderandnearby (sibling, unassociated) descriptive text ("Store-only digest" / "Persist a subscription record…"),
no
<label>/aria-label.Confirmed the shared
Inputprimitive (packages/loopover-ui-kit/src/components/input.tsx) is a thinwrapper around a native
<input>with no automaticaria-label/placeholder-as-name behavior, so these fourcontrols genuinely have no accessible name today — this is code-verifiable, not a subjective design call.
None of the four files has any accessibility-focused test today (
maintainer-settings.tsx,owner-panel.tsx,playground-panel.tsx, anddigest-panel.tsxhave no.test.tsxfile at all).Requirements
owner-panel.tsx's Repository input: either nest the<Input>inside the<label>(matching thepattern already used in
commands-panel.tsx/maintainer-settings.tsx), or give the labelhtmlForandthe input a matching
id.playground-panel.tsx's Tool/Repo/Branch controls: same fix, applied to all three.maintainer-settings.tsx's focus-manifest<textarea>: add either a visually-hidden<label htmlFor=…>tied to an
idon the textarea, or anaria-label/aria-labelledbypointing at the existing<h3>heading — any one is acceptable, the requirement is that the textarea has a non-empty accessible name.
digest-panel.tsx's email input: add a<label>(nested orhtmlFor/id-linked) or anaria-label(e.g.
aria-label="Digest notification email") — a bareplaceholderdoes not satisfy this.redesign. (Per this repo's UI rule, any change that alters visible output requires a screenshot table and
manual review regardless; if the fix can be done with zero visual diff — e.g.
htmlFor/idaddition, ora visually-hidden label — prefer that so the change stays in the "no visual change" lane and follows the
normal gate instead of forced manual review.)
Deliverables
owner-panel.tsx— Repository input has a programmatically associated label.playground-panel.tsx— Tool/Repo/Branch controls each have a programmatically associated label.maintainer-settings.tsx— the focus-manifest<textarea>has a non-empty accessible name.digest-panel.tsx— the subscribe-email input has a non-empty accessible name.name/role (e.g. Testing Library's
getByRole("textbox", { name: /.../ })/getByLabelText) for allfour fixed controls.
Test Coverage Requirements
apps/loopover-ui/**is outside the Codecovcoverage.includeset (UI code, notsrc/**/workers/**), sothe 99% patch gate does not apply, but each of these four files currently has zero tests — add at least one
test per file asserting the fixed control is reachable via its accessible name/role, so the fix is
regression-proof and not just visually-inspected.
Expected Outcome
All four controls are reachable by screen readers and testing-library queries via their accessible name;
getByLabelText/getByRole(..., { name })finds each control without falling back to placeholder text.Links & Resources
apps/loopover-ui/src/components/site/app-panels/owner-panel.tsxlines 85-94apps/loopover-ui/src/components/site/app-panels/playground-panel.tsxlines 213-250apps/loopover-ui/src/components/site/app-panels/maintainer-settings.tsx(FocusManifestEditor,<textarea>around lines 575-582)apps/loopover-ui/src/components/site/app-panels/digest-panel.tsx(SubscribeForm,<Input>around lines216-224)
apps/loopover-ui/src/components/site/app-panels/commands-panel.tsxlines 91-102,
maintainer-settings.tsx's ownFieldGroupselect/number fields (lines 437-474)packages/loopover-ui-kit/src/components/input.tsx(confirms no automaticaccessible-name behavior)