Release: test infrastructure, rules overhaul, and design refresh #30

Merged
TheFurya merged 43 commits from develop into main 2026-02-21 16:58:18 +01:00
18 changed files with 94 additions and 82 deletions
Showing only changes of commit 8cfa074ea6 - Show all commits

View File

@@ -1,11 +1,11 @@
---
# nuzlocke-tracker-l12w
title: Add condition badges for boss Pokemon mechanics
status: in-progress
status: completed
type: feature
priority: normal
created_at: 2026-02-16T20:11:02Z
updated_at: 2026-02-16T20:11:52Z
updated_at: 2026-02-16T20:17:40Z
---
Add visible badges on boss Pokemon that have mechanic-related conditions (Mega Evolution, Gigantamax, Dynamax, Terastallize). Create a ConditionBadge component following the EncounterMethodBadge pattern and integrate it into BossDefeatModal and BossTeamPreview.

View File

@@ -1,19 +1,18 @@
---
# nuzlocke-tracker-rb0p
title: Implement pre-commit hooks for linting
status: todo
status: in-progress
type: task
priority: high
created_at: 2026-02-10T12:05:39Z
updated_at: 2026-02-10T12:05:39Z
updated_at: 2026-02-17T17:15:05Z
---
Set up pre-commit hooks to automatically run linting before every commit, catching issues before they reach the pipeline.
## Checklist
- [ ] Install and configure a pre-commit framework (e.g. `pre-commit` for Python, `husky` + `lint-staged` for the frontend, or a unified approach)
- [ ] Add backend hook: `ruff check` + `ruff format --check` on staged Python files
- [ ] Add frontend hook: `eslint` + `tsc` on staged TS/TSX files
- [ ] Update CI workflow to replace lint checks with test runs once the test suite is in place
- [ ] Document the pre-commit setup in CLAUDE.md or DEPLOYMENT.md
- [x] Install and configure a pre-commit framework — migrated from `pre-commit` to `prek` (Rust)
- [x] Add backend hook: `ruff check --fix` + `ruff format` on staged Python files
- [x] Add frontend hook: `oxlint`, `oxfmt --check`, and `tsc -b` on staged TS/TSX files
- [x] Document the pre-commit setup in CLAUDE.md

View File

@@ -1,34 +0,0 @@
repos:
# Backend (Python) — ruff linting + formatting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.0
hooks:
- id: ruff
args: [--fix]
files: ^backend/
- id: ruff-format
files: ^backend/
# Frontend (TypeScript/React) — local hooks using project node_modules
- repo: local
hooks:
- id: oxlint
name: oxlint
entry: npx oxlint -c frontend/.oxlintrc.json
language: system
files: ^frontend/src/.*\.(ts|tsx)$
pass_filenames: true
- id: oxfmt
name: oxfmt
entry: npx oxfmt --check --config frontend/.oxfmtrc.json
language: system
files: ^frontend/src/.*\.(ts|tsx)$
pass_filenames: true
- id: tsc
name: tsc
entry: bash -c 'cd frontend && npx tsc -b'
language: system
files: ^frontend/src/.*\.(ts|tsx)$
pass_filenames: false

View File

@@ -10,9 +10,9 @@
# Pre-commit Hooks
This project uses [pre-commit](https://pre-commit.com/) to run linting and formatting checks before each commit.
This project uses [prek](https://prek.j178.dev/) (Rust-based pre-commit framework) to run linting and formatting checks before each commit.
**Setup:** `pip install pre-commit && pre-commit install`
**Setup:** `prek install`
**Hooks configured:**
- **Backend:** `ruff check --fix` and `ruff format` on Python files under `backend/`

View File

@@ -19,7 +19,7 @@ dependencies = [
dev = [
"ruff==0.15.0",
"ty==0.0.17",
"pre-commit==4.5.1",
"pytest==9.0.2",
"pytest-asyncio==1.3.0",
"httpx==0.28.1",

View File

@@ -37,10 +37,10 @@ class BossBattle(Base):
ForeignKey("games.id"), index=True, default=None
)
version_group: Mapped["VersionGroup"] = relationship(back_populates="boss_battles")
after_route: Mapped["Route | None"] = relationship()
game: Mapped["Game | None"] = relationship()
pokemon: Mapped[list["BossPokemon"]] = relationship(
version_group: Mapped[VersionGroup] = relationship(back_populates="boss_battles")
after_route: Mapped[Route | None] = relationship()
game: Mapped[Game | None] = relationship()
pokemon: Mapped[list[BossPokemon]] = relationship(
back_populates="boss_battle", cascade="all, delete-orphan"
)

View File

@@ -16,8 +16,8 @@ class BossPokemon(Base):
order: Mapped[int] = mapped_column(SmallInteger)
condition_label: Mapped[str | None] = mapped_column(String(100))
boss_battle: Mapped["BossBattle"] = relationship(back_populates="pokemon")
pokemon: Mapped["Pokemon"] = relationship()
boss_battle: Mapped[BossBattle] = relationship(back_populates="pokemon")
pokemon: Mapped[Pokemon] = relationship()
def __repr__(self) -> str:
return f"<BossPokemon(id={self.id}, boss_battle_id={self.boss_battle_id}, pokemon_id={self.pokemon_id})>"

View File

@@ -23,8 +23,8 @@ class BossResult(Base):
attempts: Mapped[int] = mapped_column(SmallInteger, default=1)
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
run: Mapped["NuzlockeRun"] = relationship(back_populates="boss_results")
boss_battle: Mapped["BossBattle"] = relationship()
run: Mapped[NuzlockeRun] = relationship(back_populates="boss_results")
boss_battle: Mapped[BossBattle] = relationship()
def __repr__(self) -> str:
return f"<BossResult(id={self.id}, run_id={self.run_id}, boss_battle_id={self.boss_battle_id}, result='{self.result}')>"

View File

@@ -28,12 +28,12 @@ class Encounter(Base):
DateTime(timezone=True), server_default=func.now()
)
run: Mapped["NuzlockeRun"] = relationship(back_populates="encounters")
route: Mapped["Route"] = relationship(back_populates="encounters")
pokemon: Mapped["Pokemon"] = relationship(
run: Mapped[NuzlockeRun] = relationship(back_populates="encounters")
route: Mapped[Route] = relationship(back_populates="encounters")
pokemon: Mapped[Pokemon] = relationship(
foreign_keys=[pokemon_id], back_populates="encounters"
)
current_pokemon: Mapped["Pokemon | None"] = relationship(
current_pokemon: Mapped[Pokemon | None] = relationship(
foreign_keys=[current_pokemon_id]
)

View File

@@ -19,8 +19,8 @@ class Evolution(Base):
) # catch-all for other conditions
region: Mapped[str | None] = mapped_column(String(30))
from_pokemon: Mapped["Pokemon"] = relationship(foreign_keys=[from_pokemon_id])
to_pokemon: Mapped["Pokemon"] = relationship(foreign_keys=[to_pokemon_id])
from_pokemon: Mapped[Pokemon] = relationship(foreign_keys=[from_pokemon_id])
to_pokemon: Mapped[Pokemon] = relationship(foreign_keys=[to_pokemon_id])
def __repr__(self) -> str:
return f"<Evolution(id={self.id}, from={self.from_pokemon_id}, to={self.to_pokemon_id}, trigger='{self.trigger}')>"

View File

@@ -22,8 +22,8 @@ class Game(Base):
ForeignKey("version_groups.id"), index=True
)
version_group: Mapped["VersionGroup | None"] = relationship(back_populates="games")
runs: Mapped[list["NuzlockeRun"]] = relationship(back_populates="game")
version_group: Mapped[VersionGroup | None] = relationship(back_populates="games")
runs: Mapped[list[NuzlockeRun]] = relationship(back_populates="game")
def __repr__(self) -> str:
return f"<Game(id={self.id}, name='{self.name}')>"

View File

@@ -23,7 +23,7 @@ class Genlocke(Base):
DateTime(timezone=True), server_default=func.now()
)
legs: Mapped[list["GenlockeLeg"]] = relationship(
legs: Mapped[list[GenlockeLeg]] = relationship(
back_populates="genlocke", order_by="GenlockeLeg.leg_order"
)
@@ -45,6 +45,6 @@ class GenlockeLeg(Base):
leg_order: Mapped[int] = mapped_column(SmallInteger)
retired_pokemon_ids: Mapped[list[int] | None] = mapped_column(JSONB, default=None)
genlocke: Mapped["Genlocke"] = relationship(back_populates="legs")
game: Mapped["Game"] = relationship()
run: Mapped["NuzlockeRun | None"] = relationship()
genlocke: Mapped[Genlocke] = relationship(back_populates="legs")
game: Mapped[Game] = relationship()
run: Mapped[NuzlockeRun | None] = relationship()

View File

@@ -24,9 +24,9 @@ class NuzlockeRun(Base):
hof_encounter_ids: Mapped[list[int] | None] = mapped_column(JSONB, default=None)
naming_scheme: Mapped[str | None] = mapped_column(String(50), nullable=True)
game: Mapped["Game"] = relationship(back_populates="runs")
encounters: Mapped[list["Encounter"]] = relationship(back_populates="run")
boss_results: Mapped[list["BossResult"]] = relationship(back_populates="run")
game: Mapped[Game] = relationship(back_populates="runs")
encounters: Mapped[list[Encounter]] = relationship(back_populates="run")
boss_results: Mapped[list[BossResult]] = relationship(back_populates="run")
def __repr__(self) -> str:
return (

View File

@@ -15,10 +15,10 @@ class Pokemon(Base):
types: Mapped[list[str]] = mapped_column(ARRAY(String(20)))
sprite_url: Mapped[str | None] = mapped_column(String(500))
route_encounters: Mapped[list["RouteEncounter"]] = relationship(
route_encounters: Mapped[list[RouteEncounter]] = relationship(
back_populates="pokemon"
)
encounters: Mapped[list["Encounter"]] = relationship(
encounters: Mapped[list[Encounter]] = relationship(
foreign_keys="[Encounter.pokemon_id]", back_populates="pokemon"
)

View File

@@ -23,17 +23,17 @@ class Route(Base):
)
pinwheel_zone: Mapped[int | None] = mapped_column(SmallInteger, default=None)
version_group: Mapped["VersionGroup"] = relationship(back_populates="routes")
route_encounters: Mapped[list["RouteEncounter"]] = relationship(
version_group: Mapped[VersionGroup] = relationship(back_populates="routes")
route_encounters: Mapped[list[RouteEncounter]] = relationship(
back_populates="route", cascade="all, delete-orphan"
)
encounters: Mapped[list["Encounter"]] = relationship(back_populates="route")
encounters: Mapped[list[Encounter]] = relationship(back_populates="route")
# Self-referential relationships for route grouping
parent: Mapped["Route | None"] = relationship(
parent: Mapped[Route | None] = relationship(
back_populates="children", remote_side=[id]
)
children: Mapped[list["Route"]] = relationship(
children: Mapped[list[Route]] = relationship(
back_populates="parent", cascade="all, delete-orphan"
)

View File

@@ -25,9 +25,9 @@ class RouteEncounter(Base):
min_level: Mapped[int] = mapped_column(SmallInteger)
max_level: Mapped[int] = mapped_column(SmallInteger)
route: Mapped["Route"] = relationship(back_populates="route_encounters")
pokemon: Mapped["Pokemon"] = relationship(back_populates="route_encounters")
game: Mapped["Game"] = relationship()
route: Mapped[Route] = relationship(back_populates="route_encounters")
pokemon: Mapped[Pokemon] = relationship(back_populates="route_encounters")
game: Mapped[Game] = relationship()
def __repr__(self) -> str:
return f"<RouteEncounter(route_id={self.route_id}, pokemon_id={self.pokemon_id}, method='{self.encounter_method}')>"

View File

@@ -11,9 +11,9 @@ class VersionGroup(Base):
name: Mapped[str] = mapped_column(String(100))
slug: Mapped[str] = mapped_column(String(100), unique=True)
games: Mapped[list["Game"]] = relationship(back_populates="version_group")
routes: Mapped[list["Route"]] = relationship(back_populates="version_group")
boss_battles: Mapped[list["BossBattle"]] = relationship(
games: Mapped[list[Game]] = relationship(back_populates="version_group")
routes: Mapped[list[Route]] = relationship(back_populates="version_group")
boss_battles: Mapped[list[BossBattle]] = relationship(
back_populates="version_group"
)

47
prek.toml Normal file
View File

@@ -0,0 +1,47 @@
# Configuration file for `prek`, a git hook framework written in Rust.
# See https://prek.j178.dev for more information.
#:schema https://www.schemastore.org/prek.json
[[repos]]
repo = "https://github.com/astral-sh/ruff-pre-commit"
rev = "v0.15.0"
hooks = [
{
id = "ruff",
args = ["--fix"],
files = "^backend/"
},
{
id = "ruff-format",
files = "^backend/"
}
]
[[repos]]
repo = "local"
hooks = [
{
id = "oxlint",
name = "oxlint",
entry = "npx oxlint -c frontend/.oxlintrc.json",
language = "system",
files = '^frontend/src/.*\.(ts|tsx)$',
pass_filenames = true
},
{
id = "oxfmt",
name = "oxfmt",
entry = "npx oxfmt --check --config frontend/.oxfmtrc.json",
language = "system",
files = '^frontend/src/.*\.(ts|tsx)$',
pass_filenames = true
},
{
id = "tsc",
name = "tsc",
entry = "bash -c 'cd frontend && npx tsc -b'",
language = "system",
files = '^frontend/src/.*\.(ts|tsx)$',
pass_filenames = false
}
]