Harden stats APIs and fix Electron Yomitan debug runtime

- Validate stats session IDs/limits and add AnkiConnect request timeouts
- Stabilize stats window/runtime lifecycle and tighten window security defaults
- Fix Electron CLI debug startup by unsetting `ELECTRON_RUN_AS_NODE` and wiring Yomitan session state
- Expand regression coverage for tracker queries/events ordering and session aggregates
- Update docs for stats dashboard usage and Yomitan lookup troubleshooting
This commit is contained in:
2026-03-15 12:26:29 -07:00
parent 93811ebfde
commit 46fbea902a
16 changed files with 401 additions and 90 deletions

View File

@@ -41,53 +41,38 @@ export function App() {
<TabBar activeTab={activeTab} onTabChange={handleTabChange} />
</header>
<main className="flex-1 overflow-y-auto p-4">
<section
id="panel-overview"
role="tabpanel"
aria-labelledby="tab-overview"
hidden={activeTab !== 'overview'}
>
<OverviewTab />
</section>
<section
id="panel-anime"
role="tabpanel"
aria-labelledby="tab-anime"
hidden={activeTab !== 'anime'}
>
<AnimeTab
initialAnimeId={selectedAnimeId}
onClearInitialAnime={() => setSelectedAnimeId(null)}
onNavigateToWord={openWordDetail}
/>
</section>
<section
id="panel-trends"
role="tabpanel"
aria-labelledby="tab-trends"
hidden={activeTab !== 'trends'}
>
<TrendsTab />
</section>
<section
id="panel-vocabulary"
role="tabpanel"
aria-labelledby="tab-vocabulary"
hidden={activeTab !== 'vocabulary'}
>
<VocabularyTab
onNavigateToAnime={navigateToAnime}
onOpenWordDetail={openWordDetail}
/>
</section>
<section
id="panel-sessions"
role="tabpanel"
aria-labelledby="tab-sessions"
hidden={activeTab !== 'sessions'}
>
<SessionsTab />
</section>
{activeTab === 'overview' ? (
<section id="panel-overview" role="tabpanel" aria-labelledby="tab-overview">
<OverviewTab />
</section>
) : null}
{activeTab === 'anime' ? (
<section id="panel-anime" role="tabpanel" aria-labelledby="tab-anime">
<AnimeTab
initialAnimeId={selectedAnimeId}
onClearInitialAnime={() => setSelectedAnimeId(null)}
onNavigateToWord={openWordDetail}
/>
</section>
) : null}
{activeTab === 'trends' ? (
<section id="panel-trends" role="tabpanel" aria-labelledby="tab-trends">
<TrendsTab />
</section>
) : null}
{activeTab === 'vocabulary' ? (
<section id="panel-vocabulary" role="tabpanel" aria-labelledby="tab-vocabulary">
<VocabularyTab
onNavigateToAnime={navigateToAnime}
onOpenWordDetail={openWordDetail}
/>
</section>
) : null}
{activeTab === 'sessions' ? (
<section id="panel-sessions" role="tabpanel" aria-labelledby="tab-sessions">
<SessionsTab />
</section>
) : null}
</main>
<WordDetailPanel
wordId={globalWordId}

View File

@@ -19,8 +19,20 @@ export function VocabularyTab({ onNavigateToAnime, onOpenWordDetail }: Vocabular
const [selectedKanjiId, setSelectedKanjiId] = useState<number | null>(null);
const [search, setSearch] = useState('');
if (loading) return <div className="text-ctp-overlay2 p-4">Loading...</div>;
if (error) return <div className="text-ctp-red p-4">Error: {error}</div>;
if (loading) {
return (
<div className="text-ctp-overlay2 p-4" role="status" aria-live="polite">
Loading...
</div>
);
}
if (error) {
return (
<div className="text-ctp-red p-4" role="alert" aria-live="assertive">
Error: {error}
</div>
);
}
const summary = buildVocabularySummary(words, kanji);