diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..20aadf6 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,13 @@ +#!/bin/sh +# Pre-commit gate: formatting, linting, tests must all pass. +# Enabled via `core.hooksPath` (set by the `prepare` npm script). +set -e + +echo "pre-commit › prettier --check" +npm run format:check + +echo "pre-commit › eslint" +npm run lint + +echo "pre-commit › vitest" +npm test diff --git a/.opencode/skills/textile-design/components.md b/.opencode/skills/textile-design/components.md index 6772baf..737d3af 100644 --- a/.opencode/skills/textile-design/components.md +++ b/.opencode/skills/textile-design/components.md @@ -77,10 +77,12 @@ Use the **native `title` attribute**. No custom CSS, no custom markup. ``` The browser renders the OS-native tooltip: + - macOS: dark translucent bubble below the element - Windows/Linux: follows OS theme **Rules:** + - Every interactive control that needs extra context gets a `title` - Icon-only buttons **must** also have `aria-label` (screen readers don't read `title` reliably) - Never build a custom tooltip with CSS/JS when `title` is sufficient diff --git a/package.json b/package.json index 0147c4a..9d9c1e1 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "format": "prettier --write .", "format:check": "prettier --check .", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "prepare": "git config core.hooksPath .githooks || true" }, "keywords": [], "author": "ethan", diff --git a/src/actions/CommandPalette.tsx b/src/actions/CommandPalette.tsx index 74e345a..2a9c19c 100644 --- a/src/actions/CommandPalette.tsx +++ b/src/actions/CommandPalette.tsx @@ -79,7 +79,9 @@ export function CommandPalette({ // Scroll selected item into view useEffect(() => { if (listRef.current && filteredActions.length > 0) { - const selectedEl = listRef.current.querySelector('[aria-selected="true"]'); + const selectedEl = listRef.current.querySelector( + '[aria-selected="true"]', + ); if (selectedEl && typeof selectedEl.scrollIntoView === 'function') { selectedEl.scrollIntoView({ block: 'nearest' }); } @@ -91,16 +93,14 @@ export function CommandPalette({ switch (e.key) { case 'ArrowDown': e.preventDefault(); - setSelectedIndex((prev) => - (prev + 1) % Math.max(filteredActions.length, 1), + setSelectedIndex( + (prev) => (prev + 1) % Math.max(filteredActions.length, 1), ); break; case 'ArrowUp': e.preventDefault(); setSelectedIndex((prev) => - prev <= 0 - ? Math.max(filteredActions.length - 1, 0) - : prev - 1, + prev <= 0 ? Math.max(filteredActions.length - 1, 0) : prev - 1, ); break; case 'Enter': @@ -151,7 +151,7 @@ export function CommandPalette({ setQuery(e.target.value)} @@ -165,10 +165,7 @@ export function CommandPalette({ {/* Results */} -
{provider.idleHint} @@ -155,27 +168,32 @@ function ProviderSection({
) : ( <> -No files. @@ -222,15 +240,15 @@ function ProviderSelector({ {open && ( -