Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@
## 2026-06-09 - Empty State Feedback
**Learning:** When a list or process finishes with zero results, an empty summary container gives no feedback, leaving the user wondering if it actually ran or failed silently.
**Action:** Always provide a helpful empty state message explaining why no results might have occurred, using styles consistent with other hints to offer guidance without showing as an error.
## 2026-06-15 - Use Semantic Fieldsets for Form Grouping
**Learning:** Generic `div` containers and pseudo-labels using `role="group"`, `role="radiogroup"`, and `aria-labelledby` can be entirely avoided by using native `<fieldset>` and `<legend>` elements. This inherently structures grouped form controls (like radio buttons or segmented buttons) for assistive technologies. We can then easily reset browser default `fieldset` styles (`border`, `padding`, `margin`) in CSS.
**Action:** Next time I encounter `div`s with `role="radiogroup"` or `role="group"`, replace them with native `<fieldset>` and style `<legend>` directly instead of managing `aria-labelledby` attributes.
9 changes: 8 additions & 1 deletion popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ section {
border-radius: 6px;
}

label {
label,
legend {
display: block;
font-size: 12px;
color: #94a3b8;
Expand Down Expand Up @@ -260,3 +261,9 @@ button.secondary:hover {
.summary .skipped {
color: #fbbf24;
}

fieldset {
border: none;
padding: 0;
margin: 0;
}
24 changes: 12 additions & 12 deletions popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,34 @@ <h1>Jules Task Archiver</h1>

<section class="options">
<h2>Options</h2>
<div class="setting-row">
<label id="opModeLabel">Operation</label>
<div class="segmented" id="opMode" role="group" aria-labelledby="opModeLabel">
<fieldset class="setting-row">
<legend id="opModeLabel">Operation</legend>
<div class="segmented" id="opMode">
<button type="button" data-value="archive" class="active" aria-pressed="true">Archive Tasks</button>
<button type="button" data-value="suggestions" aria-pressed="false">Start Suggestions</button>
</div>
</div>
<div class="setting-row">
<label id="execModeLabel">Execution Mode</label>
<div class="radio-group" role="radiogroup" aria-labelledby="execModeLabel">
</fieldset>
<fieldset class="setting-row">
<legend id="execModeLabel">Execution Mode</legend>
<div class="radio-group">
<label><input type="radio" name="mode" value="dry" checked /> Dry Run</label>
<label><input type="radio" name="mode" value="run" /> Run</label>
</div>
</div>
</fieldset>
<div class="setting-row">
<label class="checkbox">
<input type="checkbox" id="force" aria-describedby="forceHint" />
Force
</label>
<span class="hint" id="forceHint" style="display: block; margin-left: 20px; margin-top: 2px;">Archive every task, ignoring state and matching open Pull Requests</span>
</div>
<div class="setting-row">
<label id="scopeLabel">Scope</label>
<div class="radio-group" role="radiogroup" aria-labelledby="scopeLabel">
<fieldset class="setting-row">
<legend id="scopeLabel">Scope</legend>
<div class="radio-group">
<label><input type="radio" name="scope" value="current" /> Current tab</label>
<label><input type="radio" name="scope" value="all" checked /> All Jules tabs</label>
</div>
</div>
</fieldset>
</section>

<section class="settings">
Expand Down
9 changes: 4 additions & 5 deletions tests/popup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,11 +435,10 @@ describe('popup.html accessibility', () => {
)
})

it('should use explicit visible labels for radio groups via aria-labelledby', () => {
assert.ok(popupHtml.includes('id="execModeLabel"'), 'execModeLabel should exist')
assert.ok(popupHtml.includes('aria-labelledby="execModeLabel"'), 'mode radiogroup should use aria-labelledby')
assert.ok(popupHtml.includes('id="scopeLabel"'), 'scopeLabel should exist')
assert.ok(popupHtml.includes('aria-labelledby="scopeLabel"'), 'scope radiogroup should use aria-labelledby')
it('should use explicit visible labels for radio groups via fieldset and legend', () => {
assert.ok(popupHtml.includes('<legend id="execModeLabel">'), 'execModeLabel legend should exist')
assert.ok(popupHtml.includes('<legend id="scopeLabel">'), 'scopeLabel legend should exist')
assert.ok(popupHtml.includes('<fieldset'), 'fieldset tags should be used for grouping')
})
})

Expand Down