Release: test infrastructure, rules overhaul, and design refresh #30
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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})>"
|
||||
|
||||
@@ -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}')>"
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
|
||||
@@ -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}')>"
|
||||
|
||||
@@ -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}')>"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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}')>"
|
||||
|
||||
@@ -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
47
prek.toml
Normal 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
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user