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
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2025-02-14 - Visual Grid Layouts Hiding Context from Screen Readers
**Learning:** In a visual grid layout (like a workout log) where column headers (Weight, Reps, RPE) exist separately from the rows, screen readers lose context. Inputs appear unlabelled, making it difficult for users to know what data they are entering.
**Action:** Add explicit `aria-label` attributes to each input that combine the column meaning with the row context (e.g., "Weight for set 1").

## 2025-05-03 - Contextual ARIA labels on Exercise Action Buttons
**Learning:** Icon-only action buttons within repeated list items (like the "History" or "Plates" buttons inside an exercise card) can be confusing for screen reader users if they lack context. Simply adding `aria-label="History"` is insufficient because the user won't know *which* exercise history they are accessing.
**Action:** When adding ARIA labels to actions inside a list or grid, always interpolate the relevant item context (e.g., `aria-label="History for ${escapeHTML(ex.exerciseName)}"`).
2 changes: 1 addition & 1 deletion src/components/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function openModal(contentHTML, { title = '', onClose = null } = {}) {
<div class="modal-handle"></div>
<div style="display:flex;align-items:center;justify-content:space-between;gap:var(--sp-2)">
${title ? `<h2 class="modal-title" style="margin:0;flex:1">${escapeHTML(String(title))}</h2>` : '<div style="flex:1"></div>'}
<button class="btn btn-ghost btn-icon modal-close-btn" style="width:32px;height:32px;flex-shrink:0" title="Close"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
<button class="btn btn-ghost btn-icon modal-close-btn" aria-label="Close modal" style="width:32px;height:32px;flex-shrink:0" title="Close"><svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
<div class="modal-body">${contentHTML}</div>
`;
Expand Down
12 changes: 7 additions & 5 deletions src/pages/workout.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,20 +280,22 @@ function renderExerciseCard(ex, ei, unit) {
const prevText = ex.previousPerformance ? `Last: ${ex.previousPerformance[0]?.weight || 0}${unit} Γ— ${ex.previousPerformance[0]?.reps || 0}` : 'First time';
const reasonBadge = ex.suggestionReason === 'increment' ? '<span class="badge badge-success">↑ Up</span>' : ex.suggestionReason === 'deload' ? '<span class="badge badge-danger">↓ Deload</span>' : '';

const safeExName = escapeHTML(ex.exerciseName);

return `<div class="card" data-ei="${ei}" style="${ex.supersetGroup ? 'border:none;box-shadow:none;background:transparent;padding:0' : ''}">
<div class="card-header" style="cursor:pointer" data-toggle="${ei}">
<div><div class="card-title">${ex.exerciseName}</div><div class="flex gap-2" style="margin-top:2px">${reasonBadge}<span class="prev-hint">${prevText}</span></div></div>
<div class="flex items-center gap-1">
<button class="btn btn-ghost btn-icon" data-show-history="${ei}" title="History" style="width:32px;height:32px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></button>
<button class="btn btn-ghost btn-icon" data-swap-ex="${ei}" title="Swap exercise" style="width:32px;height:32px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 16V4m0 0L3 8m4-4l4 4"/><path d="M17 8v12m0 0l4-4m-4 4l-4-4"/></svg></button>
<button class="btn btn-ghost btn-icon" data-show-rm="${ei}" title="1RM Calculator" style="width:32px;height:32px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v4m0 12v4M2 12h4m12 0h4"/><circle cx="12" cy="12" r="3"/></svg></button>
<button class="btn btn-ghost btn-icon" data-show-plates="${ei}" title="Plates" style="width:32px;height:32px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="6" width="4" height="12" rx="1"/><rect x="18" y="6" width="4" height="12" rx="1"/><line x1="6" y1="12" x2="18" y2="12"/></svg></button>
<button class="btn btn-ghost btn-icon" aria-label="History for ${safeExName}" data-show-history="${ei}" title="History" style="width:32px;height:32px"><svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></button>
<button class="btn btn-ghost btn-icon" aria-label="Swap exercise ${safeExName}" data-swap-ex="${ei}" title="Swap exercise" style="width:32px;height:32px"><svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 16V4m0 0L3 8m4-4l4 4"/><path d="M17 8v12m0 0l4-4m-4 4l-4-4"/></svg></button>
<button class="btn btn-ghost btn-icon" aria-label="1RM Calculator for ${safeExName}" data-show-rm="${ei}" title="1RM Calculator" style="width:32px;height:32px"><svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v4m0 12v4M2 12h4m12 0h4"/><circle cx="12" cy="12" r="3"/></svg></button>
<button class="btn btn-ghost btn-icon" aria-label="Plates calculator for ${safeExName}" data-show-plates="${ei}" title="Plates" style="width:32px;height:32px"><svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="6" width="4" height="12" rx="1"/><rect x="18" y="6" width="4" height="12" rx="1"/><line x1="6" y1="12" x2="18" y2="12"/></svg></button>
</div>
</div>
<div class="exercise-body" style="${ex.collapsed ? 'display:none' : ''}">
<div style="display:grid;grid-template-columns:36px 1fr 1fr 56px 36px;gap:var(--sp-2);align-items:center;padding:var(--sp-1) 0;color:var(--text-muted);font-size:var(--text-xs);font-weight:500"><span style="text-align:center">SET</span><span style="text-align:center">${unit.toUpperCase()}</span><span style="text-align:center">REPS${ex.config?.repsMax && ex.config.repsMax !== ex.config.reps ? ` (${ex.config.reps}–${ex.config.repsMax})` : ''}</span><span style="text-align:center">RPE</span><span style="text-align:center">βœ“</span></div>
${ex.sets.map((set, si) => renderSetRow(set, si, ei)).join('')}
<div class="flex gap-2" style="margin-top:var(--sp-2)"><button class="btn btn-ghost text-sm" data-add-set="${ei}" style="flex:1">+ Set</button>${ex.sets.length > 1 ? `<button class="btn btn-ghost text-sm text-danger" data-remove-set="${ei}">βˆ’ Set</button>` : ''}<button class="btn btn-ghost text-sm" data-ex-note="${ei}" title="Note"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg></button></div>
<div class="flex gap-2" style="margin-top:var(--sp-2)"><button class="btn btn-ghost text-sm" data-add-set="${ei}" style="flex:1">+ Set</button>${ex.sets.length > 1 ? `<button class="btn btn-ghost text-sm text-danger" data-remove-set="${ei}">βˆ’ Set</button>` : ''}<button class="btn btn-ghost text-sm" aria-label="Note for ${safeExName}" data-ex-note="${ei}" title="Note"><svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg></button></div>
${ex.notes ? `<div class="text-xs text-muted" style="margin-top:var(--sp-1);padding:var(--sp-1) var(--sp-2);background:var(--bg-elevated);border-radius:var(--radius-sm);font-style:italic">${escapeHTML(ex.notes)}</div>` : ''}
</div></div>`;
}
Expand Down
Loading