Tkd-Drawsheet-App: The bracket engine that speaks Taekwondo

A close look at the rules logic, local-first state, and tournament-specific workflows behind an open-source draw sheet system built for real competition mats.

8 min read View on GitHub More from Maheshlee007

A wide editorial illustration of a Taekwondo bracket rendered like a mechanical rules machine. Seeding lanes, byes, and a final championship path are built into gears and channels, while a referee hand drops a token that starts the flow. It explains that this app is not just drawing brackets, it is encoding competition logic.
The core idea is not a prettier bracket. It is a bracket that knows the rules of the sport.

A Taekwondo drawsheet application for creating and managing Taekwondo drawsheets for tournaments.

Mahesh Udayashankar, Project Creator and Maintainer · Maheshlee007/Tkd-Drawsheet-App: A Taekwondo drawsheet application
Key Takeaways

Why a bracket is harder than it looks. Most bracket tools draw a tree. This one has to decide how a Taekwondo competition should behave when the number of entrants is awkward, the seeding matters, and a match ends by PTG, PUN, RSC, or DSQ. That shifts the project from a UI problem to a rules problem.

A WSJ-style hedcut portrait of Mahesh Udayashankar based on his GitHub avatar. The portrait is black ink on a pure white background and serves as attribution for the project creator, not as decoration.

The rules engine inside bracketUtils.ts

The technical center of gravity is the bracket generator. Instead of treating every event as the same elimination tree, it handles the annoying edge cases that make real tournaments messy: odd entrant counts, seeded pairings, and small brackets that need special treatment to stay fair.

The engine is not only creating pairings. It is tracking how a result unlocks the next bout and how that state survives real use.

// Conceptual shape of the bracket logic
function generateBrackets(players: Player[], seedMode: 'random' | 'ordered' | 'as-entered') {
  const pairs = buildSeededPairs(players, seedMode)
  return distributeByesRecursively(pairs)
}

function resolveMatch(match: MatchResult) {
  switch (match.winMethod) {
    case 'PTG': return applyPointGap(match)
    case 'PUN': return applyPunitiveDeclaration(match)
    case 'RSC': return applyRefereeStopContest(match)
    case 'DSQ': return applyDisqualification(match)
  }
}

// Each match knows where the winner goes next.
// That is the part most generic bracket tools gloss over.

Taekwondo logic, not generic bracket logic

CapabilityTkd-Drawsheet-AppGeneric bracket tools
Taekwondo win methodsPTG, PUN, RSC, DSQ are first-classUsually reduced to winner and loser
Bye handlingBuilt around competition edge casesOften generic and manual
Live tournament flowTracks dependent matches and resultsFocuses on the bracket display
Offline resilienceState can persist locally during venue useDepends on the platform and connection
Domain fitNarrow, sport-specific, operationalBroad, but less exact

That specificity is the point. A general bracket generator can tell you who plays next. It usually cannot speak the language of a Taekwondo table judge, a referee’s stoppage, or a disqualification that has to be reflected cleanly in the draw sheet.

A tournament that survives bad Wi-Fi

The other interesting layer is resilience. The store persists state locally, which means a live tournament can keep moving even if the network is unreliable or the page is refreshed. In a gym or event hall, that is not a convenience. It is the difference between keeping the bout flow intact and rebuilding it by hand.

A close-up illustration of a tournament control desk with two browser tabs, a bracket board, and a local state ledger. One organizer changes a match result and the update ripples to the other tab without breaking the flow. It explains why persistence and tab synchronization matter in live sports operations.
Local-first state turns a browser into a usable tournament desk instead of a fragile form.
// Why the store matters
// - Persist tournament state in localStorage
// - Keep multiple tabs in sync
// - Preserve match progress during refreshes
// - Tie results to specific match records

const useTournamentStore = create(
  persist(
    (set, get) => ({ /* tournament state */ }),
    { name: 'tkd-drawsheet-state' }
  )
)

Why the data model matters

The schema is doing editorial work. A BracketMatch is not just a database row. Its nextMatchId encodes the flow of the tournament itself, which is why result updates can propagate cleanly instead of being stitched together after the fact.

What this replaces in the market

OptionStrengthTrade-off
Tkd-Drawsheet-AppTaekwondo-specific logic and lightweight controlSmaller scope than full tournament platforms
Proprietary tournament suitesRegistration, scoring, reporting, and live operationsHigher cost and more vendor lock-in
Generic bracket servicesFast setup for basic elimination treesWeaker fit for Taekwondo rules and live workflow

This is not trying to outspend a commercial tournament suite. It is trying to out-specialize it. For organizers who need a focused draw sheet system, especially one that understands the sport instead of treating it as generic bracket math, that narrowness is a real advantage.

The narrowness is the point

The best open-source tools usually win by being annoyingly specific. Tkd-Drawsheet-App does not want to be everything for every sport. It wants to be the thing that gets Taekwondo brackets, results, and tournament flow right when the venue is live and the pressure is real.