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

@@ -3,21 +3,12 @@ import { useParams, Link } from 'react-router-dom'
import { useRun, useUpdateRun, useNamingCategories } from '../hooks/useRuns'
import { useGameRoutes } from '../hooks/useGames'
import { useCreateEncounter, useUpdateEncounter } from '../hooks/useEncounters'
import {
StatCard,
PokemonCard,
RuleBadges,
StatusChangeModal,
EndRunModal,
} from '../components'
import { StatCard, PokemonCard, RuleBadges, StatusChangeModal, EndRunModal } from '../components'
import type { RunStatus, EncounterDetail } from '../types'
type TeamSortKey = 'route' | 'level' | 'species' | 'dex'
function sortEncounters(
encounters: EncounterDetail[],
key: TeamSortKey
): EncounterDetail[] {
function sortEncounters(encounters: EncounterDetail[], key: TeamSortKey): EncounterDetail[] {
return [...encounters].sort((a, b) => {
switch (key) {
case 'route':
@@ -31,8 +22,7 @@ function sortEncounters(
}
case 'dex':
return (
(a.currentPokemon ?? a.pokemon).nationalDex -
(b.currentPokemon ?? b.pokemon).nationalDex
(a.currentPokemon ?? a.pokemon).nationalDex - (b.currentPokemon ?? b.pokemon).nationalDex
)
default:
return 0
@@ -41,8 +31,7 @@ function sortEncounters(
}
const statusStyles: Record<RunStatus, string> = {
active:
'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300',
active: 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300',
completed: 'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300',
failed: 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300',
}
@@ -64,8 +53,7 @@ export function RunDashboard() {
const updateEncounter = useUpdateEncounter(runIdNum)
const updateRun = useUpdateRun(runIdNum)
const { data: namingCategories } = useNamingCategories()
const [selectedEncounter, setSelectedEncounter] =
useState<EncounterDetail | null>(null)
const [selectedEncounter, setSelectedEncounter] = useState<EncounterDetail | null>(null)
const [showEndRun, setShowEndRun] = useState(false)
const [teamSort, setTeamSort] = useState<TeamSortKey>('route')
@@ -73,9 +61,7 @@ export function RunDashboard() {
const alive = useMemo(
() =>
sortEncounters(
encounters.filter(
(e) => e.status === 'caught' && e.faintLevel === null
),
encounters.filter((e) => e.status === 'caught' && e.faintLevel === null),
teamSort
),
[encounters, teamSort]
@@ -83,9 +69,7 @@ export function RunDashboard() {
const dead = useMemo(
() =>
sortEncounters(
encounters.filter(
(e) => e.status === 'caught' && e.faintLevel !== null
),
encounters.filter((e) => e.status === 'caught' && e.faintLevel !== null),
teamSort
),
[encounters, teamSort]
@@ -105,10 +89,7 @@ export function RunDashboard() {
<div className="rounded-lg bg-red-50 dark:bg-red-900/20 p-4 text-red-700 dark:text-red-400">
Failed to load run. It may not exist.
</div>
<Link
to="/runs"
className="inline-block mt-4 text-blue-600 hover:underline"
>
<Link to="/runs" className="inline-block mt-4 text-blue-600 hover:underline">
Back to runs
</Link>
</div>
@@ -131,14 +112,10 @@ export function RunDashboard() {
</Link>
<div className="flex items-start justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">
{run.name}
</h1>
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">{run.name}</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
{run.game.name} &middot;{' '}
{run.game.region.charAt(0).toUpperCase() +
run.game.region.slice(1)}{' '}
&middot; Started{' '}
{run.game.region.charAt(0).toUpperCase() + run.game.region.slice(1)} &middot; Started{' '}
{new Date(run.startedAt).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
@@ -204,26 +181,15 @@ export function RunDashboard() {
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
<StatCard
label="Encounters"
value={run.encounters.length}
color="blue"
/>
<StatCard label="Encounters" value={run.encounters.length} color="blue" />
<StatCard label="Alive" value={alive.length} color="green" />
<StatCard label="Deaths" value={dead.length} color="red" />
<StatCard
label="Routes Visited"
value={visitedRoutes}
total={totalRoutes}
color="purple"
/>
<StatCard label="Routes Visited" value={visitedRoutes} total={totalRoutes} color="purple" />
</div>
{/* Rules */}
<div className="mb-6">
<h2 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
Active Rules
</h2>
<h2 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">Active Rules</h2>
<RuleBadges rules={run.rules} />
</div>
@@ -236,9 +202,7 @@ export function RunDashboard() {
{isActive ? (
<select
value={run.namingScheme ?? ''}
onChange={(e) =>
updateRun.mutate({ namingScheme: e.target.value || null })
}
onChange={(e) => updateRun.mutate({ namingScheme: e.target.value || null })}
className="text-sm border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-1.5 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
>
<option value="">None</option>
@@ -251,8 +215,7 @@ export function RunDashboard() {
) : (
<span className="text-sm text-gray-900 dark:text-gray-100">
{run.namingScheme
? run.namingScheme.charAt(0).toUpperCase() +
run.namingScheme.slice(1)
? run.namingScheme.charAt(0).toUpperCase() + run.namingScheme.slice(1)
: 'None'}
</span>
)}
@@ -280,8 +243,7 @@ export function RunDashboard() {
</div>
{alive.length === 0 ? (
<p className="text-gray-500 dark:text-gray-400 text-sm">
No pokemon caught yet head to encounters to start building your
team!
No pokemon caught yet head to encounters to start building your team!
</p>
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
@@ -299,9 +261,7 @@ export function RunDashboard() {
{/* Graveyard */}
{dead.length > 0 && (
<div className="mb-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">
Graveyard
</h2>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">Graveyard</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
{dead.map((enc) => (
<PokemonCard
@@ -357,10 +317,7 @@ export function RunDashboard() {
{showEndRun && (
<EndRunModal
onConfirm={(status) => {
updateRun.mutate(
{ status },
{ onSuccess: () => setShowEndRun(false) }
)
updateRun.mutate({ status }, { onSuccess: () => setShowEndRun(false) })
}}
onClose={() => setShowEndRun(false)}
isPending={updateRun.isPending}