mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
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:
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user