diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b74fbf..adc1fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,5 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Vault creation no longer lands inside the app's working tree (`apps/desktop/src-tauri/…`), which caused `tauri dev` rebuilds to restart the window and wipe the open vault/note state - External-change reloader no longer unsubscribes itself under React StrictMode (idempotent `start()` / `dispose()` lifecycle) +- Doctor panel layout: "Rebuild index" button and its result no longer render inside the header row (was breaking the `flex` header layout) +- Doctor panel modal a11y: focus trap (Tab wraps close ⇄ rebuild), `Escape` to close, auto-focus on open, accessible name via `aria-labelledby` +- `Home.tsx`: index rebuild no longer uses the `indexer!` non-null assertion ### Removed diff --git a/apps/desktop/src/features/doctor/DoctorPanel.tsx b/apps/desktop/src/features/doctor/DoctorPanel.tsx index 7aa062d..85ad5fe 100644 --- a/apps/desktop/src/features/doctor/DoctorPanel.tsx +++ b/apps/desktop/src/features/doctor/DoctorPanel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from "react"; import type { DoctorReport } from "@trachyte/core"; import { runDoctor } from "../../ipc/doctor"; @@ -14,6 +14,8 @@ export default function DoctorPanel({ path, onClose, onRebuild }: DoctorPanelPro const [loading, setLoading] = useState(true); const [rebuilding, setRebuilding] = useState(false); const [rebuildResult, setRebuildResult] = useState(null); + const closeRef = useRef(null); + const rebuildRef = useRef(null); async function handleRebuild() { setRebuilding(true); @@ -28,6 +30,20 @@ export default function DoctorPanel({ path, onClose, onRebuild }: DoctorPanelPro } } + function handleCloseKeyDown(e: ReactKeyboardEvent) { + if (e.shiftKey && e.key === "Tab") { + e.preventDefault(); + rebuildRef.current?.focus(); + } + } + + function handleRebuildKeyDown(e: ReactKeyboardEvent) { + if (e.key === "Tab" && !e.shiftKey) { + e.preventDefault(); + closeRef.current?.focus(); + } + } + useEffect(() => { let cancelled = false; void runDoctor(path) @@ -45,36 +61,60 @@ export default function DoctorPanel({ path, onClose, onRebuild }: DoctorPanelPro }; }, [path]); + useEffect(() => { + closeRef.current?.focus(); + }, []); + + useEffect(() => { + function onKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") onClose(); + } + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [onClose]); + return (
-

Trachyte Doctor

- +

+ Trachyte Doctor +

- {rebuildResult !== null && ( -

- {rebuildResult} -

- )}
+ + {rebuildResult !== null && ( +

+ {rebuildResult} +

+ )} + {loading &&

Checking vault health…

} {!loading && error !== null &&

Error: {error}

} diff --git a/apps/desktop/src/routes/Home.tsx b/apps/desktop/src/routes/Home.tsx index 82c4640..6e0c0be 100644 --- a/apps/desktop/src/routes/Home.tsx +++ b/apps/desktop/src/routes/Home.tsx @@ -187,7 +187,9 @@ export default function Home() { key={openedVault} path={openedVault} onClose={() => setDoctorOpen(false)} - onRebuild={() => indexer!.rebuild()} + onRebuild={async () => { + if (indexer !== null) await indexer.rebuild(); + }} /> )} {paletteOpen && openedVault !== null && (