diff --git a/.Jules/palette.md b/.Jules/palette.md index 3265d33..1a6c0b8 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -1,3 +1,6 @@ ## 2026-05-16 - Add ARIA live region for dynamic form validation **Learning:** For multi-step wizard forms or dynamic client-side validation, screen readers need explicit context when errors appear. Using a hidden error div without `role="alert"` and `aria-live="polite"` prevents the error from being announced. **Action:** Always link the input field to its error container using `aria-describedby` and dynamically toggle `aria-invalid="true"` on the input when it fails validation. +## 2024-05-22 - Actionable Empty States +**Learning:** Empty states that simply state a lack of data (e.g., "No scan history yet.") are unhelpful. Providing actionable guidance or a direct call-to-action (e.g., "Trigger a scan from the domain overview.") significantly improves the user experience by guiding them on what to do next. +**Action:** Always include actionable guidance or clear calls-to-action in empty states across the application. diff --git a/src/views/dashboard.ts b/src/views/dashboard.ts index 69fa91a..7791149 100644 --- a/src/views/dashboard.ts +++ b/src/views/dashboard.ts @@ -1670,7 +1670,7 @@ export function renderDomainPanel({ // add-domain CTA out of the way when the user is just narrowing a list. tableBody = isFiltered ? `
-

No domains match these filters.

+

No domains match these filters. Try adjusting your search or clearing filters.

Clear filters
` : `
@@ -2290,7 +2290,7 @@ export function renderDomainDetailPage({ const historySection = scanHistory.length > 0 ? `` - : `

No scan history yet.

`; + : `

No scan history yet. Click "Scan Now" above to trigger your first scan.

`; const body = `
${esc(grade)} @@ -2358,7 +2358,7 @@ const GRADE_RANK_FOR_SPARKLINE: Record = { function renderSparkline(entries: HistoryScanEntry[]): string { if (entries.length === 0) { - return `

No scans yet to chart.

`; + return `

No scans yet to chart. Trigger a scan from the domain overview.

`; } const width = 600; const height = 80; @@ -2408,7 +2408,7 @@ function renderDriftCell( function renderDriftTable(entries: HistoryScanEntry[]): string { if (entries.length === 0) { - return `

No scans yet.

`; + return `

No scans yet. Trigger a scan from the domain overview.

`; } // Rows are newest-first. To highlight "changed vs the prior (older) scan", // we compare each row to the NEXT row in the list (which is chronologically @@ -2626,7 +2626,7 @@ ${error ? `
${esc(error)}
` : ""}`;

${ results.results.length === 0 - ? `

No domains parsed from the submission.

` + ? `

No domains parsed from the submission. Please check your input format and try again.

` : ` @@ -2940,7 +2940,7 @@ export function renderApiKeysPage({ let table: string; if (keys.length === 0) { - table = `

No API keys yet.

`; + table = `

No API keys yet. Generate your first one above.

`; } else { const rows = keys .map((k) => { diff --git a/test/mcp.test.ts b/test/mcp.test.ts index 25f6ad7..7f21296 100644 --- a/test/mcp.test.ts +++ b/test/mcp.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { handleMcpRequest, MCP_PROTOCOL_VERSION,