Pass 3 first slice: mod update action, error boundaries, a11y, palette
- New POST /api/mods/update SSE route: per-file Modrinth lookup → snapshot → download latest → swap old jar → restart + verify (if server-side) → rebuild modpack, with automatic rollback on any failure. - ModManager: "Update" button next to each mod with an available update, plus "Update all (N)" in the installed list header. Reuses the existing install timeline UI (same event shape). SSE reader extracted as consumeSSE helper. - Error boundaries: app/error.tsx (scoped), app/admin/error.tsx (admin subtree retry), app/not-found.tsx, app/global-error.tsx (hard-fail fallback with inline styles, no app shell dependency). - A11y sweep: aria-pressed + aria-label on LogViewer level chips and ModManager side filter; aria-label on admin TabsList; skip-to-content link in Navbar targeting <main id="main"> on public + admin pages; role/aria-live on install/update timeline; global Esc in ModManager clears open confirm prompts and exits search/review wizard steps. - Command palette (cmdk): global Ctrl/⌘+K dialog mounted in Providers. Navigate admin tabs, toggle theme, start/stop/restart server, create backup, re-check mod updates, jump to any cached mod/player/snapshot/ backup. Auth-aware — public users see only Home / Log in / Theme. - AdminTabs listens to hashchange so palette navigation updates the active tab live. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f9ae1afac1
commit
a011423017
16 changed files with 1124 additions and 22 deletions
|
|
@ -34,6 +34,15 @@ export function AdminTabs() {
|
|||
if (saved && TABS.some((t) => t.value === saved)) setValue(saved);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onHash = () => {
|
||||
const h = window.location.hash.replace("#", "");
|
||||
if (h && TABS.some((t) => t.value === h)) setValue(h);
|
||||
};
|
||||
window.addEventListener("hashchange", onHash);
|
||||
return () => window.removeEventListener("hashchange", onHash);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted) return;
|
||||
localStorage.setItem("admin-tab", value);
|
||||
|
|
@ -48,7 +57,10 @@ export function AdminTabs() {
|
|||
onValueChange={(v) => setValue(v as string)}
|
||||
className="w-full"
|
||||
>
|
||||
<TabsList className="flex w-full flex-wrap h-auto sm:h-9 gap-1 p-1 overflow-x-auto justify-start sm:justify-center">
|
||||
<TabsList
|
||||
aria-label="Admin sections"
|
||||
className="flex w-full flex-wrap h-auto sm:h-9 gap-1 p-1 overflow-x-auto justify-start sm:justify-center"
|
||||
>
|
||||
{TABS.map((t) => (
|
||||
<TabsTrigger key={t.value} value={t.value} className="text-xs sm:text-sm">
|
||||
{t.label}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue