Comprehensive UX audit fixes: navigation, feedback, affordances, and accessibility
CI / build-and-push (push) Successful in 28s

Address 18 issues across high/medium/low impact tiers identified in a full
interface review. Key changes: Models page decomposed into tabs, confirmation
dialogs for irreversible actions (deploy/open-source/acquire), chart Y-axes
made visible, hash router extended for Market tab persistence, collapsible
sidebar, keyboard navigation shortcuts (g+key chords), notification bulk
actions, achievement progress bars, and ARIA label improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 09:05:26 -04:00
parent 09a5cb69a7
commit 8d650fefae
17 changed files with 332 additions and 82 deletions
+25 -10
View File
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useGameStore, type ActivePage } from '@/store';
const VALID_PAGES = new Set<ActivePage>([
@@ -7,32 +7,47 @@ const VALID_PAGES = new Set<ActivePage>([
'finance', 'achievements', 'leaderboard', 'settings',
]);
function parseHash(): { page: ActivePage | null; subPath: string | null } {
const raw = window.location.hash.slice(1);
const [page, subPath] = raw.split('/') as [string, string | undefined];
return {
page: VALID_PAGES.has(page as ActivePage) ? (page as ActivePage) : null,
subPath: subPath || null,
};
}
export function useHashRouter() {
const activePage = useGameStore((s) => s.activePage);
const setActivePage = useGameStore((s) => s.setActivePage);
const [subPath, setSubPath] = useState<string | null>(() => parseHash().subPath);
useEffect(() => {
const hash = window.location.hash.slice(1) as ActivePage;
if (hash && VALID_PAGES.has(hash) && hash !== activePage) {
setActivePage(hash);
const { page, subPath: sub } = parseHash();
if (page && page !== activePage) {
setActivePage(page);
}
setSubPath(sub);
}, []);
useEffect(() => {
const current = window.location.hash.slice(1);
if (current !== activePage) {
window.history.pushState(null, '', `#${activePage}`);
const expected = subPath ? `${activePage}/${subPath}` : activePage;
if (current !== expected) {
window.history.pushState(null, '', `#${expected}`);
}
}, [activePage]);
}, [activePage, subPath]);
useEffect(() => {
const handler = () => {
const hash = window.location.hash.slice(1) as ActivePage;
if (hash && VALID_PAGES.has(hash)) {
setActivePage(hash);
const { page, subPath: sub } = parseHash();
if (page) {
setActivePage(page);
}
setSubPath(sub);
};
window.addEventListener('hashchange', handler);
return () => window.removeEventListener('hashchange', handler);
}, [setActivePage]);
return { subPath, setSubPath };
}