Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9bffaa9
Keep a confirm open through the bounce of the tap that opened it
gitrgoliveira Oct 5, 2026
610c190
Floor the operator's small score-editor and lineup controls at 44px
gitrgoliveira Oct 5, 2026
aa06cf8
Show a team sheet lineup refusal inside the row it came from
gitrgoliveira Oct 5, 2026
e13d99d
Pin the team sheet's header, result band and actions on screen
gitrgoliveira Oct 5, 2026
3ed26ee
Floor the lineup name input itself at 44px under a coarse pointer
gitrgoliveira Oct 5, 2026
f910aab
Satisfy js/lint in the new tests
gitrgoliveira Oct 5, 2026
fd67f1d
Recapture the team editor screenshots for the pinned header
gitrgoliveira Oct 5, 2026
740c228
Keep the name list and the dock under the topbar on the team sheet
gitrgoliveira Oct 5, 2026
ab01589
Guard every tap-opened layer through one tap_guard helper
gitrgoliveira Oct 5, 2026
061e6f6
Show the inherited lineup in the at-court panel and never save an unt…
gitrgoliveira Oct 5, 2026
a138e46
A team carries its previous match's lineup unless one is entered
gitrgoliveira Oct 5, 2026
bb1d1fa
Set a starting lineup or any match's lineup on the Lineups page
gitrgoliveira Oct 5, 2026
a287aa9
Keep unsaved lineup edits as a draft in the browser tab
gitrgoliveira Oct 5, 2026
3c7ce5a
Recapture the Lineups page screenshot with the "Lineup for" choice
gitrgoliveira Oct 5, 2026
79573d2
Guard the participant Edit, public match and sign-in layers too
gitrgoliveira Oct 5, 2026
803ce8a
Match a lineup by team id only, and clear match lineups with the draw
gitrgoliveira Oct 5, 2026
fb7c087
Lineup editors save only what they read, and their confirm shows on top
gitrgoliveira Oct 5, 2026
2a55ea8
Team sheet writes a lineup on the one it read, never on an empty one
gitrgoliveira Oct 5, 2026
34b8604
Compose a sheet write on a queued lineup; say plainly when a read fails
gitrgoliveira Oct 5, 2026
66b860d
Read an unread side when a name is picked; editors follow lineup changes
gitrgoliveira Oct 5, 2026
933cc9f
Lineup editors save only what the operator changed
gitrgoliveira Oct 5, 2026
9623848
Recapture the Lineups page screenshot: Save is off until a change
gitrgoliveira Oct 5, 2026
4d4e3cb
Lineup editors name the position already held and show the conflict
gitrgoliveira Oct 6, 2026
5b1962c
Show a team of six's bouts in the Scores overlay; close lineup editor…
gitrgoliveira Oct 6, 2026
87ce880
Give a re-seated side a fresh lineup editor in the at-court panel
gitrgoliveira Oct 6, 2026
314c3ea
Lineup editors refuse a name typed twice before writing and keep a me…
gitrgoliveira Oct 6, 2026
dfdf052
Move the lineups earlier releases saved for a round onto matches
gitrgoliveira Oct 6, 2026
d8ca910
Team score sheet: a member name it wrote stands over an older list
gitrgoliveira Oct 6, 2026
280eaf8
Team score sheet: call onRenamed without a guard
gitrgoliveira Oct 6, 2026
0849ad0
Merge branch 'main' into worktree-bridge-cse_018uNoSEX98xtx8fduygp2Qe
gitrgoliveira Oct 6, 2026
329000e
Ending a reopened match from the editor that reopened it names its re…
gitrgoliveira Oct 6, 2026
51e32fb
Lineup refusals name a position one way everywhere and go once it cha…
gitrgoliveira Oct 6, 2026
10386bf
Migrated lineups match v2.1.1 at every match; member lists wait and f…
gitrgoliveira Oct 6, 2026
63ee0db
Pinned bars never hide a control scrolled into view; Escape closes on…
gitrgoliveira Oct 6, 2026
bea7769
Court console confirms take focus, close on Escape and give focus back
gitrgoliveira Oct 6, 2026
40b1af5
Court picker takes no focus as it mounts
gitrgoliveira Oct 6, 2026
ec42292
Close the code review's findings that need no decision
gitrgoliveira Oct 7, 2026
2f50e84
Lineup saves name their changed positions; members carry a stamp
gitrgoliveira Oct 7, 2026
2db4bb2
Lineup panel controls work at once; name an unnamed member's rename box
gitrgoliveira Oct 7, 2026
5000ebc
A lineup save held offline says so, with a pending icon
gitrgoliveira Oct 7, 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
24 changes: 15 additions & 9 deletions CLAUDE.md

Large diffs are not rendered by default.

55 changes: 54 additions & 1 deletion docs/architecture/data-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ classDiagram
+string ID
+int Index
+string Name
+int64 ModifiedAt
}

class Overrides {
Expand Down Expand Up @@ -156,6 +157,12 @@ id, so a bout already fought names the same person whatever the name is changed
member stays available at any time, including after the start, because a team fields
replacements mid tournament.

Each member also records when it was last added, named, renamed or cleared (`ModifiedAt`,
server time, 0 until the first such write). One member's stamps only grow, so a page that
holds two copies of a member, one from a list it read and one from a write it made, keeps the
newer one whatever order they arrived in, and a list read before a rename can never put the
old name back.

The label an organiser reads is the team's competitor number followed by the member index,
for example `T10.1`. That label is composed when it is shown and never stored, because a
competitor number can change and the identity underneath it cannot. It is derived the same
Expand All @@ -169,6 +176,52 @@ it may hold more members than the competition's team size: the extra entries are
replacements an organiser can field, and the team size only fixes how many positions a
round has.

A lineup is stored for a match or as a team's starting lineup (its round-0 entry), and the
lineup a team fields in a match is not stored: it is worked out when read, because a team
carries the lineup of its previous match unless one is entered for the match. The lineup in
force at a match is the match's own, else the latest one the team had before it: one saved for
an earlier match of the team, or its starting lineup, which comes before every match.
Matches are ordered by pool-match number first, then by knockout round and position, with the
3rd-place match last. A lineup saved for a match counts as an earlier lineup only while that
match is in the current draw and the team is seated in it by participant id. A team is its
participant id and nothing else: a lineup is the team's only when it is stored under that id,
a lineup stored under a team name is not the team's, and a side with no id has none.
Discarding a draw removes the lineups saved for its matches, because a draw generated again
reuses the match ids, and keeps the starting lineups. One rule in the engine owns this, and
the kachinuki roster, the Kachinuki Detail export and the public `lineup-in-force` read all
ask it.

A lineup save names the positions it changed, and the server writes only those, under the
competition's lock, onto the lineup stored for that match or that starting lineup, or, for a
match with no lineup of its own yet, onto the lineup in force there. Two devices that change
different positions of one lineup both keep their change, whichever save arrives first, a
save sent later from a device that was offline included. Two changes to the same position
keep the later arrival.

Releases up to v2.1.1 also saved a lineup for a round, and read the lineup a team fielded at
a match as the match's own, else the round lineup with the highest round at or below the
match's round, else the highest round, so a lineup saved for one match applied to that match
alone. Such lineups are converted into this form rather than read under that rule. A lineup
stored under a team's name is stored under the team's id when exactly one team has that name.
A legacy team is one with a lineup for round 1 or later, or one that, when the app first
settles the competition, holds a lineup saved for a match of the current draw that seats it
by participant id. `config.md` records that list at the first settlement
(`round_lineups_legacy`), so a lineup saved for a match afterwards never makes a team legacy.
A legacy team is given, at every match it is seated in by participant id, a lineup of its own
equal to the one v2.1.1 showed there, or an empty one where v2.1.1 showed none (no round
lineup and no lineup for that match), since carrying would show an earlier match's instead. A
match it is seated in later is given its lineup by the write that seats it: inside that
write's transaction when it runs in one, otherwise straight after it. `config.md` lists, under
each team's id, the matches already given one (`round_lineups_given`), so a lineup removed
from one of them is not given again. Discarding the draw, or generating a new one, clears that
list, since the next draw reuses the match ids, and drops from the legacy list every team with
no round lineup, whose converted match lineups went with the draw. A legacy team with round
lineups and no starting lineup gets the one v2.1.1 showed before its first saved round, its
highest round's. The round lineups stay, never read, until the competition is completed; then
the next write of the draw, or the next start of the app, removes them and `config.md` records
`round_lineups_converted`, which is set as soon as no legacy team waits. A new competition
starts with it set.

## 3. The match and result model

This is the detailed part of the model, because the rules it encodes are detailed. A match
Expand Down Expand Up @@ -416,7 +469,7 @@ classDiagram
}
class lineups_yaml["lineups.yaml"] {
<<YAML>>
TeamLineup by round or match
TeamLineup per match, or the starting one
position to name and member id
a position may hold an id and an empty name
}
Expand Down
Binary file modified docs/screenshots/kachinuki-correct-bout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/kachinuki-knockout-tie-encho.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/kachinuki-reopen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/kachinuki-scoring-buttons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/team-lineup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/user-guide/court-operators/recording-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ If both competitors due to meet are already barred, neither can fight. In a pool

## Correcting a withdrawal recorded by mistake

A kiken or fusenpai entered in error can always be fixed. Open the match in the score editor. It shows what is recorded, for example "Recorded: Kiken – Voluntary, Kyoto withdrew", and marks the side that withdrew with **Kiken** or **Fus.** beside its name. In a team match the result below the bouts names the recorded winner and the decision, and the bouts after the withdrawal are credited to the other team, even though nobody actually fought them. Pick the fix that matches what happened:
A kiken or fusenpai entered in error can always be fixed. Open the match in the score editor. It shows what is recorded, for example "Recorded: Kiken – Voluntary, Kyoto withdrew", and marks the side that withdrew with **Kiken** or **Fus.** beside its name. In a team match the result under the team names, at the top of the score sheet, names the recorded winner and the decision, and the bouts after the withdrawal are credited to the other team, even though nobody actually fought them. Pick the fix that matches what happened:

- **The wrong competitor or team was marked.** Record the withdrawal again for the other side. In an individual match, use the **Kiken – Voluntary**, **Kiken – Injury** or **Fusenpai** button in the editor's **Decision** row. In a team match, open **Withdrawal or no-show** and use the same buttons there. The side you first marked is eligible again, and the other side becomes the one that withdrew.
- **Nobody withdrew, and the match was fought to a result.** Choose **Remove kiken**, **Remove fusenpai**, or **Remove fusensho**: the button names the decision that was recorded. The editor takes that decision off the score board: its circles go, the points the withdrawn competitor struck stay, and you enter the result as it was fought, or a draw in a pool or league. In a team match, every bout needs a result, as when you finish one. Then use **Save correction**, which asks for a short reason like any correction. The match stays finished the whole time, so the fix never needs the court and a match being fought there carries on undisturbed. **Undo** puts the recorded decision back before you save; once the correction is saved on the device and waiting to be sent, it can no longer be undone this way. In a knockout match whose bouts were tied, a representative bout decided it: use **Clear kiken and reopen** (or **Clear fusenpai and reopen**) instead, which puts the match back on the court to fight it.
Expand Down
29 changes: 24 additions & 5 deletions docs/user-guide/organisers/team-tournaments.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,30 @@ Team tournaments work with any of the four formats described in [Tournament form

## Team lineups

Before each team encounter, set the fighting order for each team across the five positions: Senpo, Jiho, Chuken, Fukusho, and Taisho. Smaller teams use fewer positions.
A team's lineup is its fighting order across the five positions: Senpo, Jiho, Chuken, Fukusho, and Taisho. Smaller teams use fewer positions.

![The Lineups tab: a team and round selector above a completed fighting order, with a competitor picked for each of the five positions (Senpo, Jiho, Chuken, Fukusho, Taisho) from a numbered list, and the Team members list beneath it with a Rename and a Clear name control on each named position.](../../screenshots/team-lineup.png)
A team keeps the lineup of its previous team match unless you enter a new one for a match. The **Lineups** page sets each team's **Starting lineup**, which its first match uses unless that match has a lineup of its own, and it can set the lineup of any one of the team's matches. Pick the team, then choose **Starting lineup** or one of its matches in **Lineup for**. A lineup you enter for a match is used for that match and for every later match of the team, until you enter another. It never changes an earlier match. Changing a match's lineup also changes every later match of the team that carries it, including matches already played.

Each team encounter has its own lineup. To carry over the same order from the previous encounter, use **Copy from previous match** at the top of the lineup panel.
Matches count in their order: pool matches by their number in the pool, then the knockout by round, with the 3rd-place match last. **Lineup for** lists a team's matches in that order. In that order, the lineup a team fields in a match is the first of these that exists:

1. The lineup entered for that match.
2. The latest lineup the team had before it: one entered for an earlier match of the team, or its **Starting lineup**.

The lineup panel for a match says where the lineup it shows comes from: **Lineup for this match**, **Same as** the earlier match it is carried from, or **Starting lineup**. The score sheet, the viewer, the court display, the streaming overlay and the Kachinuki Detail sheet of the export all use the same lineup.

![The Lineups tab: a team and lineup selector above a completed fighting order, with a competitor picked for each of the five positions (Senpo, Jiho, Chuken, Fukusho, Taisho) from a numbered list, and the Team members list beneath it with a Rename and a Clear name control on each named position.](../../screenshots/team-lineup.png)

To go back to the previous match's lineup, choose **Use the previous match's lineup**, on the **Lineups** page or at the top of the lineup panel. It appears while the lineup shown is the match's own, asks first, and removes that lineup, so the match carries the team's previous lineup again. Every later match that has no lineup of its own follows it.

Saving a lineup writes only the positions you changed. If someone saves a change to another position of the same lineup from a different device, both changes are kept, whichever save arrives first, even when yours is sent later because this device was offline. If you both changed the same position, the save that arrives last is kept. A save made while this device is offline says "Not sent yet: saved on this device, and sent when the connection returns." and is sent once the connection is back.

Changes you make to a lineup and have not saved are kept in that browser tab, so a reload, going back, or closing the lineup panel does not lose them. When you open the same lineup again, on the **Lineups** page or in the lineup panel, it shows **Unsaved lineup changes restored** with a **Discard** button that puts the saved lineup back. Nothing is saved until you press **Save lineup**. If the lineup changed in the meantime, for example saved from another device, the kept changes are not applied, and a notice lists the names that were not restored.

Lineups that an earlier version of the app saved on the **Lineups** page for a round are converted when the app starts, so every match shows the lineup that version showed for it. Each match the team is seated in gets that lineup as its own, and a match the team reaches later, such as its next knockout match, gets it as soon as the team is seated there. You can change or remove any of these like any other match's lineup, and a lineup you remove stays removed. A team with no **Starting lineup** gets the lineup of its latest round as its **Starting lineup**, which is what that version showed before the team's first saved round. The round lineups themselves are removed once the competition is completed.

That version also used a lineup entered for a match for that match only. A team that had one is converted the same way, so each of its matches shows what that version showed there: the lineup entered for that match, else the team's **Starting lineup**, or no lineup when the team had none. A lineup you enter after the upgrade is carried to later matches as usual.

Discarding a draw removes the lineups of its matches, including those converted from an earlier version, since a new draw makes new matches. The new draw's matches are given the lineups converted from round lineups again, and each team's **Starting lineup** stays. Lineups that version entered for a match went with the old draw, so those teams carry their lineups like any other.

### Team members

Expand Down Expand Up @@ -146,7 +165,7 @@ If your correction changes who won that bout, the bouts after it were fought on
When the last bout is tied, the editor offers every legitimate way forward and you choose, according to the [kachinuki mode](#kachinuki-modes) in force; the app never decides it from the stage:

- **Record bout** retires both fighters and brings the next pair up.
- **Encho** keeps the same pair fighting on that bout until one of them takes a point. It is offered on any tied bout: whether a pair fights on is your call, and the app records it. Use it whenever your rules say the pairing must have a result, in any stage. Tapped Encho by mistake, or twice? **Undo encho**, beside it, takes back one overtime period per tap while nothing has been scored in overtime; taking back the last one returns the bout to its tie.
- **Encho** keeps the same pair fighting on that bout until one of them takes a point. It is offered on any tied bout: whether a pair fights on is your call, and the app records it. Use it whenever your rules say the pairing must have a result, in any stage. Tapped Encho by mistake, or twice? **Undo encho**, beside it, takes back one overtime period per tap while nothing has been scored in overtime; taking back the last one returns the bout to its tie. Its place beside Encho is kept empty until there is something to undo, so no button moves under your finger when it appears.
- **End match** finishes the encounter on the tie. In pools and leagues this records a drawn encounter. In a knockout the bracket needs a winner, so End match is held back while the last bout is tied; continue with Record bout or Encho instead.

![The kachinuki score editor on a tied knockout bout, showing the notice that a knockout cannot end in a draw with an Encho button, and the End match button held back.](../../screenshots/kachinuki-knockout-tie-encho.png)
Expand All @@ -161,7 +180,7 @@ Reopening is refused while another match is already running on the same court, b

![The score editor for a completed kachinuki match, showing the recorded bouts and the Reopen match button, the correction control for a kachinuki match that did not end with a withdrawal.](../../screenshots/kachinuki-reopen.png)

The results workbook (**Export & print**, then **Download results (.xlsx)**) and the blank template (**Download blank template (.xlsx)**) include a **Kachinuki Detail** sheet with a section for every kachinuki encounter in the draw. Each section is titled with its match's name followed by (Kachinuki). A knockout section's name is the title of that match's block on the **Elimination Matches** sheet, so Round 2 - Match 3 there is Round 2 - Match 3 (Kachinuki) here, and a side that is not decided yet reads as the match it comes from, for example M 3. Each section is laid out like a match on the **Elimination Matches** sheet: White (Shiro) on the left and Red (Aka) on the right, matching the scoreboard, each over its own team's name. Each bout row shows the bout number with who fought it and their lineup position, each side's score, and a tie or overtime in the centre column. The result mark (Fus., Kiken) sits beside the fighter it names. An encounter with recorded bouts lists exactly those bouts. An encounter with none yet, such as every encounter in the blank template, gets empty numbered rows to fill in by hand: one for each bout the encounter can take, which is twice the team size less one (9 rows for teams of five).
The results workbook (**Export & print**, then **Download results (.xlsx)**) and the blank template (**Download blank template (.xlsx)**) include a **Kachinuki Detail** sheet with a section for every kachinuki encounter in the draw. Each section is titled with its match's name followed by (Kachinuki). A knockout section's name is the title of that match's block on the **Elimination Matches** sheet, so Round 2 - Match 3 there is Round 2 - Match 3 (Kachinuki) here, and a side that is not decided yet reads as the match it comes from, for example M 3. Each section is laid out like a match on the **Elimination Matches** sheet: White (Shiro) on the left and Red (Aka) on the right, matching the scoreboard, each over its own team's name. Each bout row shows the bout number with who fought it and their lineup position (the position in the lineup the team fielded in that encounter), each side's score, and a tie or overtime in the centre column. The result mark (Fus., Kiken) sits beside the fighter it names. An encounter with recorded bouts lists exactly those bouts. An encounter with none yet, such as every encounter in the blank template, gets empty numbered rows to fill in by hand: one for each bout the encounter can take, which is twice the team size less one (9 rows for teams of five).

The **Pool Matches** and **Elimination Matches** sheets give each kachinuki encounter the same number of bout rows, the 3rd-place match included. A result fills the bouts fought, in order, and leaves the rest empty. If an encounter fields reserves and runs to more bouts than that, those sheets show its first bouts and the Kachinuki Detail sheet lists them all.

Expand Down
Binary file modified docs/videos/kachinuki-demo.webm
Binary file not shown.
Loading
Loading