Align repo config with global development standards
Some checks failed
CI / backend-lint (push) Failing after 1m4s
CI / actions-lint (push) Failing after 6s
CI / frontend-lint (push) Successful in 59s

- Add missing tsconfig strictness flags (noUncheckedIndexedAccess,
  exactOptionalPropertyTypes, noImplicitOverride,
  noPropertyAccessFromIndexSignature) and fix all resulting type errors
- Replace ESLint/Prettier with oxlint 1.48.0 and oxfmt 0.33.0
- Pin all frontend and backend dependencies to exact versions
- Pin GitHub Actions to SHA hashes with persist-credentials: false
- Fix CI Python version mismatch (3.12 -> 3.14) and ruff target-version
- Add vitest 4.0.18 with jsdom environment for frontend testing
- Add ty 0.0.17 for Python type checking (non-blocking in CI)
- Add actionlint and zizmor CI job for workflow linting and security audit
- Add Dependabot config for npm, pip, and github-actions
- Update CLAUDE.md and pre-commit hooks to reflect new tooling
- Ignore Claude Code sandbox artifacts in gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 20:39:41 +01:00
parent e4814250db
commit 3a64661760
91 changed files with 2073 additions and 3215 deletions

View File

@@ -34,47 +34,27 @@ function pct(value: number | null): string {
return `${(value * 100).toFixed(1)}%`
}
function PokemonList({
title,
pokemon,
}: {
title: string
pokemon: PokemonRanking[]
}) {
function PokemonList({ title, pokemon }: { title: string; pokemon: PokemonRanking[] }) {
const [expanded, setExpanded] = useState(false)
const visible = expanded ? pokemon : pokemon.slice(0, 5)
return (
<div>
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
{title}
</h3>
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">{title}</h3>
{pokemon.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400">No data</p>
) : (
<>
<div className="space-y-1.5">
{visible.map((p, i) => (
<div
key={p.pokemonId}
className="flex items-center gap-2 text-sm"
>
<span className="text-gray-400 dark:text-gray-500 w-5 text-right">
{i + 1}.
</span>
<div key={p.pokemonId} className="flex items-center gap-2 text-sm">
<span className="text-gray-400 dark:text-gray-500 w-5 text-right">{i + 1}.</span>
{p.spriteUrl ? (
<img
src={p.spriteUrl}
alt={p.name}
className="w-6 h-6"
loading="lazy"
/>
<img src={p.spriteUrl} alt={p.name} className="w-6 h-6" loading="lazy" />
) : (
<div className="w-6 h-6 bg-gray-200 dark:bg-gray-700 rounded" />
)}
<span className="capitalize text-gray-800 dark:text-gray-200">
{p.name}
</span>
<span className="capitalize text-gray-800 dark:text-gray-200">{p.name}</span>
<span className="ml-auto text-gray-500 dark:text-gray-400 font-medium">
{p.count}
</span>
@@ -130,14 +110,10 @@ function HorizontalBar({
/>
<span
className={`absolute inset-0 flex items-center px-3 text-xs font-medium capitalize truncate ${
isLight
? 'text-gray-900 dark:text-gray-900'
: 'text-gray-700 dark:text-gray-200'
isLight ? 'text-gray-900 dark:text-gray-900' : 'text-gray-700 dark:text-gray-200'
}`}
style={{
textShadow: isLight
? '0 0 4px rgba(255,255,255,0.8)'
: '0 0 4px rgba(0,0,0,0.3)',
textShadow: isLight ? '0 0 4px rgba(255,255,255,0.8)' : '0 0 4px rgba(0,0,0,0.3)',
}}
>
{label}
@@ -150,18 +126,10 @@ function HorizontalBar({
)
}
function Section({
title,
children,
}: {
title: string
children: React.ReactNode
}) {
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h2 className="text-lg font-bold text-gray-900 dark:text-gray-100 mb-4">
{title}
</h2>
<h2 className="text-lg font-bold text-gray-900 dark:text-gray-100 mb-4">{title}</h2>
{children}
</section>
)
@@ -178,19 +146,13 @@ function StatsContent({ stats }: { stats: StatsResponse }) {
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4">
<StatCard label="Total Runs" value={stats.totalRuns} color="blue" />
<StatCard label="Active" value={stats.activeRuns} color="green" />
<StatCard
label="Completed"
value={stats.completedRuns}
color="blue"
/>
<StatCard label="Completed" value={stats.completedRuns} color="blue" />
<StatCard label="Failed" value={stats.failedRuns} color="red" />
</div>
<div className="flex flex-wrap gap-x-6 gap-y-1 text-sm text-gray-600 dark:text-gray-400">
<span>
Win Rate:{' '}
<strong className="text-gray-800 dark:text-gray-200">
{pct(stats.winRate)}
</strong>
<strong className="text-gray-800 dark:text-gray-200">{pct(stats.winRate)}</strong>
</span>
<span>
Avg Duration:{' '}
@@ -211,8 +173,7 @@ function StatsContent({ stats }: { stats: StatsResponse }) {
label={g.gameName}
value={g.count}
max={gameMax}
colorHex={g.gameColor ?? undefined}
color={g.gameColor ? undefined : 'bg-blue-500'}
{...(g.gameColor ? { colorHex: g.gameColor } : { color: 'bg-blue-500' })}
/>
))}
</div>
@@ -244,9 +205,7 @@ function StatsContent({ stats }: { stats: StatsResponse }) {
<div className="flex flex-wrap gap-x-6 gap-y-1 text-sm text-gray-600 dark:text-gray-400">
<span>
Catch Rate:{' '}
<strong className="text-gray-800 dark:text-gray-200">
{pct(stats.catchRate)}
</strong>
<strong className="text-gray-800 dark:text-gray-200">{pct(stats.catchRate)}</strong>
</span>
<span>
Avg per Run:{' '}
@@ -261,44 +220,31 @@ function StatsContent({ stats }: { stats: StatsResponse }) {
<Section title="Pokemon Rankings">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
<PokemonList title="Most Caught" pokemon={stats.topCaughtPokemon} />
<PokemonList
title="Most Encountered"
pokemon={stats.topEncounteredPokemon}
/>
<PokemonList title="Most Encountered" pokemon={stats.topEncounteredPokemon} />
</div>
</Section>
{/* Team & Deaths */}
<Section title="Team & Deaths">
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4">
<StatCard
label="Total Deaths"
value={stats.totalDeaths}
color="red"
/>
<StatCard label="Total Deaths" value={stats.totalDeaths} color="red" />
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border-l-4 border-amber-500">
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{pct(stats.mortalityRate)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Mortality Rate
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Mortality Rate</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border-l-4 border-blue-500">
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{fmt(stats.avgCatchLevel)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Avg Catch Lv.
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Avg Catch Lv.</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border-l-4 border-purple-500">
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{fmt(stats.avgFaintLevel)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Avg Faint Lv.
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Avg Faint Lv.</div>
</div>
</div>
@@ -310,12 +256,8 @@ function StatsContent({ stats }: { stats: StatsResponse }) {
<div className="space-y-1.5">
{stats.topDeathCauses.map((d, i) => (
<div key={d.cause} className="flex items-center gap-2 text-sm">
<span className="text-gray-400 dark:text-gray-500 w-5 text-right">
{i + 1}.
</span>
<span className="text-gray-800 dark:text-gray-200">
{d.cause}
</span>
<span className="text-gray-400 dark:text-gray-500 w-5 text-right">{i + 1}.</span>
<span className="text-gray-800 dark:text-gray-200">{d.cause}</span>
<span className="ml-auto text-gray-500 dark:text-gray-400 font-medium">
{d.count}
</span>
@@ -351,9 +293,7 @@ export function Stats() {
return (
<div className="max-w-4xl mx-auto p-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-6">
Stats
</h1>
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-6">Stats</h1>
{isLoading && (
<div className="flex justify-center py-12">
@@ -370,9 +310,7 @@ export function Stats() {
{stats && stats.totalRuns === 0 && (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<p className="text-lg mb-2">No data yet</p>
<p className="text-sm">
Start a Nuzlocke run to see your stats here.
</p>
<p className="text-sm">Start a Nuzlocke run to see your stats here.</p>
</div>
)}