Section titles are stacked single-cell rows: the "TipiLAN 2026 CS2" title
sits above notice rows ("If a team withdraws..."), and parse_team_sections
kept only the LAST one, so the notice clobbered the title and resolve_divider
saw no game/year keywords -> None -> teams never positioned.
- parse_team_sections now accumulates all single-cell rows above a header, so
the game/year title survives alongside the notices.
- _refresh_teams_sync resolves the divider from "<tab name> <section title>",
so the game is taken reliably from the CS2/LoL worksheet name while the year
still comes from the title, keeping year-scoped TEAM_DIVIDER_*_2026 vars.
- regression test: title survives notice rows and still resolves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPRsW4tazVtYi2jNzVQkre
Divider placement matched the divider role by its exact Discord name, so
renaming the role in Discord silently broke positioning. Switch the
TEAM_DIVIDER_<SUFFIX> config to hold a role ID; resolve the ID to the
role's current name in apply_team_role_positions and keep the existing
name-based ordering maths downstream unchanged.
- config._parse_team_dividers now parses values as ints (rejects non-ints)
- resolve_divider / _team_divider cache / get_team_dividers return IDs
- apply_team_role_positions resolves each ID via guild.get_role once
- .env.example documents IDs and ships the CS2/LoL divider role IDs
- resolve_divider tests updated to assert IDs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPRsW4tazVtYi2jNzVQkre
Match members by Discord username against the lineup nicknames in the
separate registration spreadsheet (TEAM_SHEET_ID) and give each their
team's role. One team per person: switching teams removes the old team
role, and a team with no Discord role yet is auto-created. Only role
names present in the sheet are ever touched, so organisation/field/base
roles are never at risk; the feature is a no-op when TEAM_SHEET_ID is
unset.
The sheet is not a single table (merged rows, stacked CS2/LoL sections
with different layouts), so it is parsed via raw-row scanning rather than
get_all_records. Citizenship markers like "(EST)" are used only as
player delimiters and discarded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>