Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
185 commits
Select commit Hold shift + click to select a range
758f421
feat(forms): Added form and form fields model and alembic migration
brandonalo Aug 12, 2026
e461333
fix(models): fix form relation with user
brandonalo Aug 12, 2026
1497e67
feat(forms): add form question presets
brandonalo Aug 12, 2026
38735ed
feat(forms): add form response and answer models
brandonalo Aug 13, 2026
ab6dd7d
feat(forms): helper functions and tests for form logic
brandonalo Aug 14, 2026
2d37a07
feat(forms): form/field CRUD and tests
brandonalo Aug 16, 2026
e3a24fa
fix(migrations): rechain forms migration after tournament-events rewr…
ethnjs Aug 17, 2026
f480b34
refactor(forms): rework Form ownership to many-to-many tournament/cha…
ethnjs Aug 18, 2026
2e165eb
refactor(migrations): squash forms migrations into one matching the r…
ethnjs Aug 18, 2026
1789223
refactor(forms): rewrite form schemas to match the reworked model and…
ethnjs Aug 18, 2026
de1bfc9
fix(forms): rebuild form/field routes with working create, CRUD, and …
ethnjs Aug 18, 2026
ef91177
feat(forms): add form response submission and listing routes
ethnjs Aug 18, 2026
4e5695a
refactor(forms): revert Form ownership to single tournament-or-chapte…
ethnjs Aug 18, 2026
8a3dbb4
refactor(forms): revert form schemas to single-owner shape and add cr…
ethnjs Aug 18, 2026
d32e8f2
refactor(forms): revert form routes to single-owner permission checks…
ethnjs Aug 18, 2026
5e89630
feat(forms): add creates_membership_on_submit with per-owner-type mem…
ethnjs Aug 18, 2026
015c9cc
fix(forms): make field_key required and client-supplied with tourname…
ethnjs Aug 18, 2026
a83e412
test(forms): add core and route test coverage, fix view access to all…
ethnjs Aug 18, 2026
4717d9b
docs(forms): forms question types reference
ethnjs Aug 18, 2026
2c5bfd0
feat(forms): validate field config shape and shift_select options per…
ethnjs Aug 18, 2026
2cd7152
fix(forms): key availability write-through validation off field_key, …
ethnjs Aug 18, 2026
a69815a
feat(forms): enforce reserved field_key question_type pairing, drop d…
ethnjs Aug 18, 2026
6a2c281
feat(forms): add branching validation for single/multi-select option …
ethnjs Aug 18, 2026
b6a09fa
refactor(forms): back config validation with per-question_type pydant…
ethnjs Aug 18, 2026
6afdc5e
feat(forms): validate whole form on publish/republish, reject empty f…
ethnjs Aug 18, 2026
3d490bf
feat(forms): replay branching graph at submission to enforce required…
ethnjs Aug 18, 2026
8757830
test(forms): add config/reserved-key/branching/reachability coverage,…
ethnjs Aug 19, 2026
c929d8c
refactor(forms): remove creates_membership_on_submit and its config t…
ethnjs Aug 19, 2026
f1d9088
feat(forms): add lunch/availability write-through models and migration
ethnjs Aug 19, 2026
7710857
feat(forms): validate lunch_{date}_{category} field_key requires sing…
ethnjs Aug 19, 2026
98356ed
feat(forms): add availability/lunch diff-sync write-through functions
ethnjs Aug 19, 2026
f6b3e40
feat(forms): wire availability/lunch write-through into response subm…
ethnjs Aug 19, 2026
6b2bb70
feat(forms): block TournamentShift deletion when referenced by member…
ethnjs Aug 19, 2026
f529498
feat(forms): lock answered fields against edit
ethnjs Aug 19, 2026
c83c122
test(forms): cover write-through sync, shift deletion guard, and fiel…
ethnjs Aug 19, 2026
c89f080
feat(forms): replace per-field routes with bulk PUT + option-id edit …
ethnjs Aug 20, 2026
3129a91
feat(forms): switch branching/write-through matching to option_id and…
ethnjs Aug 20, 2026
f7f7935
feat(forms): snapshot select answer value/label at submission time
ethnjs Aug 20, 2026
96e4a9d
feat(forms): allow value to be a shift-id list and validate both avai…
ethnjs Aug 20, 2026
c72e4c6
feat(forms): resolve availability options into combined shift time ra…
ethnjs Aug 20, 2026
36e11ee
feat(forms): resolve event_preference options into grouped tournament…
ethnjs Aug 20, 2026
ea85549
feat(forms): expand availability write-through from grouped option sh…
ethnjs Aug 20, 2026
b18e498
feat(tournament): block shift deletion when referenced by a live avai…
ethnjs Aug 20, 2026
9608a5c
docs(forms): rewrite question-types reference for edit lifecycle, opt…
ethnjs Aug 20, 2026
362d252
docs(forms): document Form.status gates and update option shapes for …
ethnjs Aug 20, 2026
927628e
feat(forms): require published status to accept responses; block reve…
ethnjs Aug 20, 2026
fa1d19d
feat(forms): resolve availability/event_preference option value into …
ethnjs Aug 20, 2026
d2a665f
feat(forms): make Form.id a public nanoid string; add title and respo…
ethnjs Aug 20, 2026
924b8ba
feat(forms): add GET list endpoints for tournament/chapter forms
ethnjs Aug 20, 2026
f45350d
feat(forms): add formsApi client
ethnjs Aug 20, 2026
32c1f2c
feat(forms): scaffold standalone /forms/{formId}/edit route
ethnjs Aug 20, 2026
d7bf91f
feat(forms): scaffold /forms/{formId}/preview route stub
ethnjs Aug 20, 2026
091decf
feat(forms): add tournament forms list page skeleton
ethnjs Aug 20, 2026
6ac0a47
fix(roles): add missing manage_forms permission to frontend catalog
ethnjs Aug 20, 2026
50fa883
feat(forms): render forms list table
ethnjs Aug 20, 2026
735a299
feat(forms): add New Form modal and wire it into the list page
ethnjs Aug 20, 2026
c447210
feat(forms): add response count column to forms list table
ethnjs Aug 20, 2026
32f6044
feat(forms): resolve creator on forms-list endpoints, drop fields/fie…
ethnjs Aug 20, 2026
db093b2
feat(forms): add Creator column to forms list, extract CreatorHoverCard
ethnjs Aug 20, 2026
546c589
refactor(ui): tokenize Button/SplitButton colors, rebuild SplitButton…
ethnjs Aug 20, 2026
9255886
feat(ui): rework SplitButton into a controlled status selector
ethnjs Aug 20, 2026
8c6edfd
chore(forms): remove SplitButton preview stub from forms list page
ethnjs Aug 20, 2026
d45ab56
feat(ui): add IconUpload to Icons.tsx
ethnjs Aug 20, 2026
1de40b8
feat(ui): support disabled+tooltip rows in Popover list mode, add Spl…
ethnjs Aug 20, 2026
e036ec6
feat(forms): wire real publish/status sub-header on the builder edit …
ethnjs Aug 20, 2026
c7de4f6
fix(forms): fix editable-name width bug, extract EditableText, put su…
ethnjs Aug 20, 2026
24aac5b
fix(ui): Input no longer clobbers caller onFocus/onBlur; Textarea get…
ethnjs Aug 20, 2026
b0346db
feat(forms): build title/description card using standard Input/Textarea
ethnjs Aug 20, 2026
28982ba
feat(forms): add QuestionRenderer for short_text/long_text/acknowledg…
ethnjs Aug 20, 2026
6892467
feat(forms): add display_style config for single_select_radio and mul…
ethnjs Aug 20, 2026
42063bb
feat(forms): branch QuestionRenderer on display_style, add FormFieldC…
ethnjs Aug 20, 2026
676bedb
feat(ui): add RadioCircle, RadioList, CheckboxList, RankedList compon…
ethnjs Aug 20, 2026
99a99e2
feat(forms): render field list via QuestionRenderer in collapsed prev…
ethnjs Aug 20, 2026
8e20c12
feat(forms): add field card expand/collapse shell with question_type …
ethnjs Aug 20, 2026
346ddc0
feat(forms): add floating add-field toolbar and description toggle to…
ethnjs Aug 20, 2026
ade45fe
feat(forms): add field_key Combobox with reserved presets, polish fie…
ethnjs Aug 20, 2026
efb0e53
feat(forms): field_key Combobox improvements — presets, type locking,…
ethnjs Aug 20, 2026
3a4dc6d
feat(forms): animate field card expand/collapse, collapse on click-ou…
ethnjs Aug 20, 2026
65d4360
feat(forms): rebuild field card expand/collapse as a real height anim…
ethnjs Aug 20, 2026
7d0dba1
feat(forms): add drag-and-drop options editor for select/ranked field…
ethnjs Aug 20, 2026
e15b985
style(forms): polish OptionsEditor row — hover-only grip, disabled ra…
ethnjs Aug 20, 2026
2db3653
fix(ui): make RadioCircle ring and dot share one color, match Checkbo…
ethnjs Aug 20, 2026
cd7cfc4
feat(forms): add entity-backed options editor for Availability/Event …
ethnjs Aug 20, 2026
ee13674
feat(forms): add lunch field body with date/category key derivation
ethnjs Aug 20, 2026
53322cd
style(forms): rework Checkbox/RadioCircle styling, scale up question …
ethnjs Aug 20, 2026
4c8e71c
style(forms): scale option text to 14px, match edit-mode sizing to pr…
ethnjs Aug 20, 2026
6531055
feat(forms): add endpoint listing a tournament's used field_keys
ethnjs Aug 20, 2026
6734078
feat(forms): show already-used field_keys as disabled Combobox rows
ethnjs Aug 20, 2026
da640db
feat(forms): add per-option branching and card footer to the field bu…
ethnjs Aug 20, 2026
981599f
feat(forms): add drag-and-drop reordering to the field builder
ethnjs Aug 20, 2026
87ae592
feat(forms): share one sticky toolbar across field cards and defer de…
ethnjs Aug 21, 2026
17c031e
feat(forms): expose display_style toggle in the field builder
ethnjs Aug 21, 2026
cdc913c
feat(forms): wire client-side validation and real Save into the field…
ethnjs Aug 21, 2026
2581722
fix(forms): remove card expand, drag, and scroll animations from the …
ethnjs Aug 21, 2026
625195c
fix(forms): keep FloatingSaveBar mounted in the empty state so its sl…
ethnjs Aug 21, 2026
70c6c41
fix(ui): fix checkmark proportions and checked-state background clobb…
ethnjs Aug 21, 2026
fdbf18e
refactor(forms): split the edit page into components, add QuestionRen…
ethnjs Aug 21, 2026
ef8e49f
style(forms): center the option-row grip and match the branch dropdow…
ethnjs Aug 21, 2026
5947aa2
style(forms): move Required to the card footer's left side and group …
ethnjs Aug 21, 2026
8ebe0c8
feat(forms): keep one field card always expanded and stop closing it …
ethnjs Aug 21, 2026
1350368
feat(forms): seed a starter option on type switch and keep at least o…
ethnjs Aug 21, 2026
1cfa19f
style(forms): match Add option's bullet to radio/checkbox questions a…
ethnjs Aug 21, 2026
7a84639
feat(forms): add an option and focus it on Enter, fix disabled cursor…
ethnjs Aug 21, 2026
d19d16d
fix(db): enable pool_pre_ping and pool_recycle to survive Railway's i…
ethnjs Aug 21, 2026
81eb0f8
fix(forms): strip config keys that don't belong to the new question_t…
ethnjs Aug 21, 2026
b7118bf
fix(proxy): add missing PUT handler to api proxy route
ethnjs Aug 21, 2026
eb4e86a
feat(forms): swap the display_style toggle icon and show a live chip …
ethnjs Aug 21, 2026
431e443
feat(forms): number dropdown option rows and preview the next number …
ethnjs Aug 21, 2026
cc5b575
fix(forms): keep the option delete button square when the row is crowded
ethnjs Aug 21, 2026
2b71b20
feat(forms): match availability_{date}/event_preference_{suffix} rese…
ethnjs Aug 21, 2026
74b2c00
fix(forms): reserve dynamic space for the floating save bar and make …
ethnjs Aug 21, 2026
90ec5c3
fix(forms): aggregate shift ids across multiple availability fields b…
ethnjs Aug 21, 2026
c17ddc1
fix(forms): stop drag-and-drop squishing field/option rows by droppin…
ethnjs Aug 21, 2026
0f96628
docs(forms): resolve event_preference multi-instance question, no new…
ethnjs Aug 21, 2026
aea3030
feat(forms): show a compact drag preview for field cards and auto-exp…
ethnjs Aug 21, 2026
03299f8
feat(nav): drop Sheets and Assignments from the tournament sidebar
ethnjs Aug 21, 2026
9c3fca7
fix(forms): match availability_*/event_preference_* patterns in answe…
ethnjs Aug 21, 2026
2df371e
refactor(memberships): drop deprecated role_preference/event_preferen…
ethnjs Aug 21, 2026
e3f6858
chore(forms): remove dead availability handling in self-service membe…
ethnjs Aug 21, 2026
e22fca0
feat(forms): split field key and reserved presets into their own tool…
ethnjs Aug 21, 2026
d6891e4
fix(forms): use suffixed availability key in chapter-form write-throu…
ethnjs Aug 21, 2026
1ec3d5e
fix(forms): rework the field key/preset popovers — right-side flyout,…
ethnjs Aug 21, 2026
0ad6bf6
fix(forms): make the key/preset popovers mutually exclusive, fix posi…
ethnjs Aug 21, 2026
d50e36f
fix(forms): stop clamping the field key/preset popover to the viewpor…
ethnjs Aug 21, 2026
08c3a17
fix(forms): stop requiring a field key when the question label is emp…
ethnjs Aug 21, 2026
7c0d13c
fix(forms): use the Button component's danger variant for field key/p…
ethnjs Aug 21, 2026
f56fdef
fix(forms): surface preset-incomplete errors on the specific missing …
ethnjs Aug 21, 2026
368dbd6
refactor(tournament): extract TournamentDayPicker from PresetPopover …
ethnjs Aug 21, 2026
acca08d
refactor(tournament): keep TournamentDayPicker as a single locked/unl…
ethnjs Aug 21, 2026
f485aac
fix(forms): let entity-backed options (availability/event_preference)…
ethnjs Aug 21, 2026
527938c
fix(forms): render Ranks/Allow-duplicate-ranks controls for event_pre…
ethnjs Aug 21, 2026
09d1875
fix(forms): align the entity picker badges/button under each availabi…
ethnjs Aug 21, 2026
194a644
fix(forms): add a raw=true builder mode to GET /forms/{id}/ so the ed…
ethnjs Aug 21, 2026
9abba32
feat(forms): add a Preview button and a Copy JSON menu action to the …
ethnjs Aug 21, 2026
b2d3f87
refactor(forms): switch FormField/FormResponse/FormAnswer ids to alph…
ethnjs Aug 21, 2026
cd846d2
fix(forms): wrap question/description inputs into auto-growing textar…
ethnjs Aug 21, 2026
44acaaf
fix(forms): move form-builder validation errors onto each input's own…
ethnjs Aug 21, 2026
6931709
fix(forms): match Textarea's error/disabled background to Input, and …
ethnjs Aug 21, 2026
5dcfd62
fix(forms): clear label/confirm-text/ranks errors live as the field b…
ethnjs Aug 21, 2026
7bd7c1b
fix(forms): clear the field card's danger border live once its errors…
ethnjs Aug 21, 2026
94b7336
fix(ui): add resting-color fallbacks to Button's hover tokens so a mi…
ethnjs Aug 21, 2026
21eb422
style(ui): change Card's danger variant to a square gradient — dark r…
ethnjs Aug 21, 2026
7295311
feat(forms): move branching to the field toolbar and add a custom-val…
ethnjs Aug 22, 2026
06baab5
fix(forms): reset branch targets and custom values to their defaults …
ethnjs Aug 22, 2026
193a795
refactor(forms): swap EntityOptionsEditor's badge-list picker for the…
ethnjs Aug 22, 2026
779f583
feat(tournament): add is_multi_day and timezone to TournamentRead, so…
ethnjs Aug 22, 2026
5c6a5e2
feat(forms): pass the full tournament through to EntityOptionsEditor …
ethnjs Aug 22, 2026
d4b8eaa
feat(forms): add a bulk event-options picker modal for event_preferen…
ethnjs Aug 22, 2026
11a5e22
fix(forms): keep event preference suffix as local state so multi-word…
ethnjs Aug 22, 2026
9f67ab8
fix(forms): give the button-style option bullet an explicit display s…
ethnjs Aug 22, 2026
303c9f6
refactor(forms): remove OptionsEditor's unused placeholder prop
ethnjs Aug 22, 2026
fb54621
feat(forms): add select-all to the event options picker and drop star…
ethnjs Aug 22, 2026
5324d17
feat(forms): show shift time ranges on availability options, includin…
ethnjs Aug 22, 2026
92b8bbf
fix(forms): rebuild RankedList as add-combobox plus drag-reorder, and…
ethnjs Aug 22, 2026
e50a0f2
feat(forms): build the respondent-facing form preview page
ethnjs Aug 22, 2026
caea993
fix(forms): center RadioCircle's dot and use a darker gray for both s…
ethnjs Aug 22, 2026
7173468
fix(forms): give Checkbox the same darker, no-accent coloring as Radi…
ethnjs Aug 22, 2026
091415e
style(forms): rework RankedList's picked-option rows and add-combobox
ethnjs Aug 22, 2026
b6d03d8
fix(forms): show the full option pool in RankedList's add-combobox
ethnjs Aug 22, 2026
abb162d
fix(forms): stop event-preference options from matching the shift tim…
ethnjs Aug 22, 2026
d6819bc
style(forms): drop hover background from radio/checkbox/ranked rows a…
ethnjs Aug 22, 2026
057b1f5
fix(forms): validate ranked_choice completeness and clean up preview …
ethnjs Aug 22, 2026
471eed2
feat(forms): auto-scroll the preview page on Continue and Submit
ethnjs Aug 22, 2026
15484cd
fix(forms): bump Form.updated_at when only its fields change
ethnjs Aug 22, 2026
41fbfe6
refactor(forms): extract FormFillFlow so every form viewer shares one…
ethnjs Aug 22, 2026
8028805
refactor(forms): drop the dead per-field CRUD helpers superseded by b…
ethnjs Aug 22, 2026
c198257
fix(forms): require a fresh Submit click after fixing a validation error
ethnjs Aug 22, 2026
1785da9
feat(forms): add a floating submit bar with error summary and leave-w…
ethnjs Aug 22, 2026
6d289d9
fix(forms): route RankedList's drag through DragOverlay so it stops g…
ethnjs Aug 22, 2026
387745d
fix(forms): drop the empty placeholder option when bulk-adding events…
ethnjs Aug 22, 2026
8723648
fix(forms): keep the edited question clear of the floating save bar
ethnjs Aug 22, 2026
88a26a6
fix(forms): keep the edited question in view as the save bar appears …
ethnjs Aug 22, 2026
890c1b2
fix(forms): give number bullets a fixed width so option inputs align …
ethnjs Aug 22, 2026
9e4e111
fix(forms): stop the save-bar scroll from yanking away from edits in …
ethnjs Aug 22, 2026
30a403b
fix(forms): fully clear viewport-sized questions from the save bar again
ethnjs Aug 22, 2026
5180068
fix(forms): center the focused control in view when the save bar cove…
ethnjs Aug 22, 2026
c1627db
feat(forms): scroll a question into frame when you expand its card
ethnjs Aug 22, 2026
758538b
fix(forms): collapse the drag placeholder to match the drag preview
ethnjs Aug 22, 2026
ac1af46
feat(forms): open a field card focused on whatever part of the previe…
ethnjs Aug 22, 2026
a136cc4
feat(forms): support button-group options in click-to-focus, behind a…
ethnjs Aug 22, 2026
87b8897
feat(forms): animate field card expand and collapse
ethnjs Aug 23, 2026
267dfe7
fix(forms): guard branching validation against non-string next_field_…
ethnjs Aug 25, 2026
5a98caa
docs: update readme
ethnjs Aug 25, 2026
b246ecf
docs: add CONTRIBUTING.md and issue templates, update backend .env.ex…
ethnjs Aug 25, 2026
2101327
fix(forms): sanitize archived field_key so a hyphen in the nanoid'd f…
ethnjs Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
name: Bug report
about: Something is broken or behaving incorrectly
title: "fix: "
labels: bug
---

## What's broken

<!-- What happened, and what did you expect instead? -->

## Steps to reproduce

<!-- Exact steps, in order, starting from a known state (e.g. "as admin@nexus.dev", "on the tournament events page"). Someone else should be able to follow these and hit the same bug. -->

1.
2.
3.

## Scope

<!-- Which area does this touch? backend route, frontend page/component, migration, etc. -->
26 changes: 26 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: Feature / task
about: New functionality, a rebuild, or a scoped chunk of work
title: "feat: "
labels: enhancement
---

## Background

<!-- What exists today (or doesn't), and why it's a problem or limitation worth addressing now. -->

## Motivation

<!-- What this unblocks or improves — especially if other planned work depends on it. -->

## Proposed changes

<!-- The actual design: models/fields/tables, routes, UI — organized by area. -->

## What will not change

<!-- Explicitly out of scope for this issue, including things that might look related. -->

## Completion criteria

<!-- Specific, testable conditions for "this is done" — a numbered list works well. -->
113 changes: 113 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Contributing to NEXUS

## Getting started

**Requirements:** Python 3.13, Node/pnpm, [Docker Desktop](https://www.docker.com/products/docker-desktop/), a Google service account credentials file.

### 1. Start the database

NEXUS runs Postgres locally via Docker instead of a native install. Install Docker Desktop, then from `backend/`:

```bash
cd backend
docker-compose up -d
```

This starts a single `postgres:16` container (service `db` in `docker-compose.yaml`) on port `5432`, with a `nexus` database/user/password all set to `nexus`. It also auto-creates a second `nexus_test` database on first boot (used by the test suite) — no manual step needed, including after `docker-compose down -v`.

To actually look at what's in the database — tables, rows, whether a migration did what you think it did — [TablePlus](https://tableplus.com/) is a good GUI client for this; the free tier is more than enough for local dev. Connect to `localhost:5432`, db `nexus`, user/password `nexus`.

### 2. Backend

```bash
cd backend
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # fill in GOOGLE_SERVICE_ACCOUNT_FILE at minimum
alembic upgrade head
uvicorn app.main:app --reload --port 8001
```

Swagger UI: [http://localhost:8001/docs](http://localhost:8001/docs)

API reference (Scalar): [http://localhost:8001/reference](http://localhost:8001/reference)

Dev seed accounts (created automatically on startup):
- `admin@nexus.dev` / `admin1234` — admin
- `user1@nexus.dev` .. `user15@nexus.dev` / `user1234` — regular users, no tournament seeded

### 3. Frontend

```bash
cd frontend
pnpm install
```

Create `frontend/.env.local`:
```
NEXT_PUBLIC_API_URL=http://localhost:8001
```

```bash
pnpm dev
```

### Running tests

```bash
cd backend
pytest
```

Runs against the `nexus_test` Postgres database from step 1. Each test rolls back its own transaction, so the test DB never accumulates data. The Google Sheets API is mocked — no external services required.

### Database migrations

Any model change needs a migration:
```bash
cd backend
alembic revision --autogenerate -m "description"
alembic upgrade head
```

---

## Workflow

1. **Open an issue first**, using the Bug report or Feature/task template. This is where scope/approach gets discussed before code exists.
2. **Branch off `main`**, named to match what the issue covers (see naming below).
3. **Link the branch to the issue** via the issue's "Development" sidebar (or "Create a branch" from the issue itself) — with that link in place, merging the PR closes the issue automatically.
4. **Open a PR back into `main`.** No direct commits to `main`.
5. **Get a review and approval from the other contributor before merging.** This applies every time, not just for large changes.

### Branch naming

Match the existing convention:
- `feat/short-description` — new functionality
- `fix/short-description` — bug fix
- `refactor/short-description` — no behavior change
- `docs/short-description` — docs only

### Commit messages

`type(scope): summary` — one line, no bullet body. `type` is `feat`/`fix`/`refactor`/`docs`/`style`/`test`/`chore`; `scope` is the area touched (`forms`, `tournament`, `ui`, `db`, etc).

```
feat(forms): add branching validation for single/multi-select
fix(db): enable pool_pre_ping to survive Railway's idle connection drops
```

### Before opening a PR

- `pytest` passes locally.
- If you touched any SQLAlchemy models, a matching Alembic migration is included.
- If you touched frontend UI, you've actually clicked through the change in a browser — type checking isn't a substitute for looking at it.

### PR description

Cover, briefly:
- **Summary** — what this does and why.
- **What changed** — grouped by area (backend/frontend, or by subsystem) if it's more than a couple files.
- **Out of scope** — anything the linked issue implied but this PR deliberately doesn't do.
- **Test plan** — what you ran, what you checked by hand.
Loading
Loading