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
@@ -0,0 +1,4 @@
## 2024-05-18 - Filter Pills Accessibility and Keyboard Support

**Learning:** Elements functioning as standalone toggle buttons, such as custom filter pills, need `aria-pressed` to indicate their toggled state to screen readers. In addition, dark theme interactive elements must implement clear and distinct keyboard focus styles (e.g., `focus-visible:ring-white/50 focus-visible:ring-offset-neutral-900`) to ensure visibility during keyboard navigation. Also, when providing "Clear" or "Close" actions using a decorative icon like `X` alongside text, the icon should be marked with `aria-hidden="true"` to prevent redundant screen reader announcements.
**Action:** Always verify custom interactive toggles for `aria-pressed` or `aria-expanded` attributes. Apply appropriate dark/light theme focus ring classes (`focus-visible`) specifically to support keyboard-only users. Ensure decorative icons paired with text have `aria-hidden="true"`.
1 change: 1 addition & 0 deletions .github/workflows/model-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
REAL_DATA_MIN_DAYS: 7
LEARNED_KFOLDS: 5
LEARNED_MAX_VAL_LOSS: 0.2
FORECAST_BACKTEST_STALE_DAYS: 7
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
6 changes: 4 additions & 2 deletions src/components/dashboard/filter-pills.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ export function FilterPills({ options, selected, onToggle, onClear, className }:
<motion.button
key={option.id}
onClick={() => onToggle(option.id)}
aria-pressed={isSelected}
transition={{ duration: 0.15 }}
className={cn(
'inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-medium transition-all',
'focus:outline-none focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-900',
isSelected
? 'bg-[#4C8BF5] text-white shadow-sm'
: 'bg-neutral-800 text-neutral-300 hover:bg-neutral-700'
Expand All @@ -60,9 +62,9 @@ export function FilterPills({ options, selected, onToggle, onClear, className }:
{hasSelection && onClear && (
<button
onClick={onClear}
className="inline-flex items-center gap-1 rounded-full px-3 py-1.5 text-xs font-medium text-neutral-400 hover:text-neutral-300 transition-colors"
className="inline-flex items-center gap-1 rounded-full px-3 py-1.5 text-xs font-medium text-neutral-400 hover:text-neutral-300 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-900"
>
<X className="size-3" />
<X className="size-3" aria-hidden="true" />
Clear
</button>
)}
Expand Down
12 changes: 7 additions & 5 deletions src/lib/forecastModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,13 @@ function arimaSurrogateForecast(
const meanDiff = diffs.reduce((s, v) => s + v, 0) / Math.max(diffs.length, 1)

// Simple AR(1) on diffs
const phi =
diffs.length > 1
? diffs.slice(1).reduce((s, v, idx) => s + v * diffs[idx], 0) /
diffs.slice(0, -1).reduce((s, v) => s + v * v, 0)
: 0
let phi = 0
if (diffs.length > 1) {
const denominator = diffs.slice(0, -1).reduce((s, v) => s + v * v, 0)
if (denominator !== 0) {
phi = diffs.slice(1).reduce((s, v, idx) => s + v * diffs[idx], 0) / denominator
}
}

const residuals = diffs.slice(1).map((v, idx) => v - phi * diffs[idx])
const residualStd = standardDeviation(residuals) || 1
Expand Down
Loading