fix(client): lock game input during countdown overlay (#110) #112

Merged
bosun merged 2 commits from i/110-countdown-input-lock into main 2026-06-22 21:53:18 +02:00
Owner

What + why

During the "Lockdown… BREAK!" countdown before play, pieces were already controllable (move/rotate/drop) — the reported head-start advantage in versus (#110).

Probe finding (source-verified both sides of the waterline): the countdown is a client-local cosmetic overlay, not a real phase. net.ts goes matched → playing directly; the server never emits a 'countdown' phase (grep server/ = zero; proto.ts has no countdown message). The 'countdown' value in the Phase type + the scattered || phase === 'countdown' guards are vestigial scaffolding. The overlay is driven by the client countdownStart timer, painted on top of an already-'playing' game. Meanwhile the server runs an authoritative 20Hz gravity tick that starts immediately at matchStart (solo.go:68; versus ready-ups then ticks at once) — so during the 3s overlay both server gravity and client input were live.

The fix

Lock the client side during the overlay window. A single COUNTDOWN_MS constant + inCountdown() predicate gate all four input/tick sites, and the same constant bounds the overlay draw — so input unlocks at exactly the frame the "3/2/1" clears, by construction rather than coincidence:

Site Gate
keyboard game-input block && !inCountdown()
touch dispatchGameAction || inCountdown() early-return
softDropTick (loop-driven repeat) || inCountdown(t) early-return
mock gravity tick (loop) && !inCountdown(t)

The lock is mode-blind — both players are equally frozen, so it's fair + deterministic.

Scope boundary (Option-1, per Bosun ratify)

This closes the reported harm (the input head-start, #110's stated severity = an input-timing axis) and is 100% client-lane.

For server-backed play (solo #38 + versus), server gravity is authoritative and still advances ~2-3 rows during the 3s window — the client cannot pause the server tick. The server-side countdown gravity-hold that fully completes AC2(b) ("game-tick does not advance") is relocated to a separate Engineer issue, #111. In ?mock-solo there is no server, so the mock-tick gate above makes it a complete freeze.

This is a deliberate decision tree, not a partial fix: a client-only input-lock fully addresses the unfair-advantage harm; the gravity-hold is engine-room polish that belongs below the waterline (#111). Had AC2(b)'s tick-freeze been the primary harm rather than input-timing, the correct path would have been the cross-substrate arc (Engineer server-hold first, then client gates on a real 'countdown' phase) — surfaced as Option 2 and not chosen.

AC mapping (#110)

  • AC1 probe — phase model + guards identified (above)
  • AC2(a) not controllable — all four input/tick sites gated
  • AC2(b) tick does not advance for ?mock-solo; server-backed play relocated to #111
  • AC3 control active on countdown→playing — lock lifts at COUNTDOWN_MS, verified by the positive-control test half
  • AC4 no regression on playing — 35/35 harness green
  • AC5 harness countdown-locked-input row, mutation-provable — see below

Verification

  • npx tsc --noEmit clean (vite build skips tsc).
  • 35/35 nav harness green (34 prior + the new #110 row), completeness meta-check included.
  • Mutation-proven (closed loop): dropping the keydown !inCountdown() guard → ArrowLeft fires during the lock → __inputSent true → the locked-input assertion reds. Restored precisely (re-edit, not checkout).
  • The new test discriminates the lock specifically: a negative half (ArrowLeft swallowed while locked) + a positive control (the same key fires once releaseCountdown expires the window), with countdownLock exposed to guard the precondition so a timing slip can't pass for the wrong reason.

Uncertainty / reviewer notes

  • The mock-tick gate (?mock-dev fidelity only; real play is server-backed) shares the proven inCountdown(t) predicate but isn't independently asserted in the harness — AC5 scopes the row to input, and observing mock gravity would need new board-position surface. Flagging in case you want a dedicated mock-freeze row.
  • Vestigial 'countdown' scaffolding (the type value + || phase==='countdown' guards) is left intact#111 may wire it Option-A-style (server-driven real phase) later; ripping it out now would churn code #111 will want.
  • countdownLock is a new NavState field → the navState serial-merge seam (expected; siblings rebase onto this since #110 leads the sequence).

Closes #110

🤖 Generated with Claude Code

## What + why During the "Lockdown… BREAK!" countdown before play, pieces were already controllable (move/rotate/drop) — the reported head-start advantage in versus (#110). **Probe finding (source-verified both sides of the waterline):** the countdown is a **client-local cosmetic overlay**, not a real phase. `net.ts` goes `matched → playing` directly; the server never emits a `'countdown'` phase (grep `server/` = zero; `proto.ts` has no countdown message). The `'countdown'` value in the `Phase` type + the scattered `|| phase === 'countdown'` guards are **vestigial scaffolding**. The overlay is driven by the client `countdownStart` timer, painted *on top of* an already-`'playing'` game. Meanwhile the server runs an authoritative 20Hz gravity tick that **starts immediately at matchStart** (`solo.go:68`; versus ready-ups then ticks at once) — so during the 3s overlay **both** server gravity **and** client input were live. ## The fix Lock the **client** side during the overlay window. A single `COUNTDOWN_MS` constant + `inCountdown()` predicate gate all four input/tick sites, and the same constant bounds the overlay draw — so **input unlocks at exactly the frame the "3/2/1" clears**, by construction rather than coincidence: | Site | Gate | |---|---| | keyboard game-input block | `&& !inCountdown()` | | touch `dispatchGameAction` | `\|\| inCountdown()` early-return | | `softDropTick` (loop-driven repeat) | `\|\| inCountdown(t)` early-return | | mock gravity tick (`loop`) | `&& !inCountdown(t)` | The lock is **mode-blind** — both players are equally frozen, so it's fair + deterministic. ## Scope boundary (Option-1, per Bosun ratify) This closes the **reported harm** (the input head-start, #110's stated severity = an input-timing axis) and is 100% client-lane. For **server-backed play** (solo #38 + versus), server gravity is authoritative and still advances ~2-3 rows during the 3s window — the client cannot pause the server tick. The server-side countdown gravity-hold that fully completes **AC2(b)** ("game-tick does not advance") is relocated to a separate Engineer issue, **#111**. In `?mock`-solo there is no server, so the mock-tick gate above makes it a **complete freeze**. This is a deliberate decision tree, not a partial fix: a client-only input-lock fully addresses the unfair-advantage harm; the gravity-hold is engine-room polish that belongs below the waterline (#111). Had AC2(b)'s tick-freeze been the *primary* harm rather than input-timing, the correct path would have been the cross-substrate arc (Engineer server-hold first, then client gates on a real `'countdown'` phase) — surfaced as Option 2 and not chosen. ## AC mapping (#110) - **AC1 probe** ✅ — phase model + guards identified (above) - **AC2(a) not controllable** ✅ — all four input/tick sites gated - **AC2(b) tick does not advance** — ✅ for `?mock`-solo; server-backed play relocated to **#111** - **AC3 control active on countdown→playing** ✅ — lock lifts at `COUNTDOWN_MS`, verified by the positive-control test half - **AC4 no regression on playing** ✅ — 35/35 harness green - **AC5 harness countdown-locked-input row, mutation-provable** ✅ — see below ## Verification - `npx tsc --noEmit` clean (vite build skips tsc). - **35/35** nav harness green (34 prior + the new `#110` row), completeness meta-check included. - **Mutation-proven (closed loop):** dropping the keydown `!inCountdown()` guard → `ArrowLeft` fires during the lock → `__inputSent` true → the locked-input assertion reds. Restored precisely (re-edit, not checkout). - The new test discriminates the lock specifically: a **negative** half (ArrowLeft swallowed while locked) + a **positive control** (the *same* key fires once `releaseCountdown` expires the window), with `countdownLock` exposed to guard the precondition so a timing slip can't pass for the wrong reason. ## Uncertainty / reviewer notes - The mock-tick gate (`?mock`-dev fidelity only; real play is server-backed) shares the proven `inCountdown(t)` predicate but isn't independently asserted in the harness — AC5 scopes the row to **input**, and observing mock gravity would need new board-position surface. Flagging in case you want a dedicated mock-freeze row. - Vestigial `'countdown'` scaffolding (the type value + `|| phase==='countdown'` guards) is **left intact** — #111 may wire it Option-A-style (server-driven real phase) later; ripping it out now would churn code #111 will want. - `countdownLock` is a new `NavState` field → the navState serial-merge seam (expected; siblings rebase onto this since #110 leads the sequence). Closes #110 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(client): lock game input during countdown overlay (#110)
All checks were successful
test / server (pull_request) Successful in 17s
test / client (pull_request) Successful in 28s
test / client-nav (pull_request) Successful in 36s
f981e62463
The "Lockdown… BREAK!" countdown is a client-local cosmetic overlay
(countdownStart timer) painted over an already-'playing' game — the
server never emits a 'countdown' phase (net.ts goes matched→playing;
the 'countdown' Phase value + scattered guards are vestigial). So
during the 3s overlay both server gravity AND client input were live:
a player could move/rotate/drop before BREAK!, the reported head-start
advantage in versus.

Lock the client side during the overlay window: a shared COUNTDOWN_MS
constant + inCountdown() predicate gate all four input/tick sites —
keyboard game-input, touch dispatchGameAction, softDropTick, and the
mock gravity tick — and the same constant bounds the overlay draw, so
input unlocks at exactly the frame the "3/2/1" clears. The lock is
mode-blind (both players equally frozen → fair + deterministic).

Scope boundary (Option-1 per Bosun ratify): server gravity is
authoritative and still advances ~2-3 rows during the 3s window for
real play (solo #38 + versus). The server-side gravity-hold that fully
closes AC2(b) is tracked separately in cellblock#111 (Engineer). In
?mock-solo there is no server, so the mock-tick gate makes it a
complete freeze.

Harness: enterCountdownLockedPlaying + releaseCountdown seams + a
countdownLock navState observable drive a mutation-proven test —
ArrowLeft is swallowed while locked (negative) and fires once released
(positive control); countdownLock guards the precondition. 35/35 green,
tsc clean. Mutation-verified: drop the keydown !inCountdown() guard →
the locked-input assertion reds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor approved these changes 2026-06-22 21:45:19 +02:00
Dismissed
surveyor left a comment

APPROVED — #110 countdown input-lock

Reviewed at head f981e62 (on current main, merge_base == base == 8ab2f0c, mergeable). Verified premise-at-source + harness + mutation.

Premise verified at source (the load-bearing claim)

The fix rests on "the countdown is a client-local cosmetic overlay painted over an already-'playing' game; the server never emits a 'countdown' phase." Confirmed both sides of the waterline:

  • grep -rn countdown server/zero matches (the Go server has no countdown concept).
  • All countdown references live in client/src/ only (state.ts, main.ts, copy.ts, render.ts); proto.ts (the wire protocol) has none.

So the lock-the-client approach is correct: there's no real phase to gate on, the overlay is countdownStart-timer-driven on top of 'playing', and the vestigial 'countdown' type value / || phase==='countdown' guards are dead-but-harmless (correctly left intact for #111 to wire — ripping them now would churn code #111 wants).

What I verified

  • Single inCountdown() predicate gates all four sites (countdownStart !== null && now - countdownStart < COUNTDOWN_MS): keyboard game-input (main.ts:928), touch dispatchGameAction (:736), softDropTick (:1133), mock gravity (:1164). One constant (COUNTDOWN_MS=3000) drives both the lock and the overlay-draw bound — input unlocks at exactly the frame the digits clear, by construction.
  • The overlay-draw change is a behavior-preserving frozen→recomputed refactor. 3 - Math.floor(elapsed/1000)Math.ceil((COUNTDOWN_MS - elapsed)/1000). I checked the digit sequence over the full [0,3000) window: identical at every ms (3→2→1, same boundaries; never 0; elapsed >= MS → no draw). The ceil form just shares COUNTDOWN_MS instead of hard-coding 3 — math-equality is the by-construction no-regression proof for this refactor class.
  • npx tsc --noEmit clean (exit 0); 35/35 nav harness green (CI=1 fresh), new #110 row included.
  • Keydown-guard mutation reproduced precisely: dropping && !inCountdown() from the keyboard gate (line 928, line-anchored) → ArrowLeft fires during the lock → __inputSent true → the negative half reds ("movement input during countdown must be swallowed"). Reverted precisely (line-anchored), tree clean.
  • Test design is solid: precondition guard (countdownLock true before the keypress — rules out a timing slip passing part 1 for the wrong reason) + negative half + positive control (releaseCountdown → the same key reaches the input path → rules out a silent no-binding pass). The positive control also self-validates the ArrowLeft→net.send wiring.

Honest ceiling — correctly named, deferral chain verified

AC2(b) ("game-tick does not advance") is fully met only for ?mock-solo (no server). For server-backed play, the authoritative 20Hz gravity still advances ~2-3 rows in the 3s window — the client can't pause the server tick. The PR relocates the server-side gravity-hold to #111, which I confirmed exists and is well-formed (MED, open, own AC set, cross-refs #110/#92). This is a clean Option-1 decision-tree, not a silent partial fix: the reported harm — the input head-start — is fully closed, and the engine-room polish is tracked below the waterline. Deferral discipline holds (un-ticked AC → filed follow-up + inline ref).

Should-consider (non-blocking)

Per-site mutation independence. Only the keyboard gate is mutation-proven. The other three sites share the same proven inCountdown() predicate (single source of truth — low risk of a silently-uncovered path), but aren't independently asserted. The touch dispatchGameAction gate is load-bearing on mobile — touch buttons are the input surface there, and #110's head-start harm is cross-platform — so a dedicated touch row would be the highest-value addition: a enterCountdownLockedTouchPlaying-style seam + mutation (drop inCountdown() || from dispatchGameAction → red) proves the touch path independently rather than by-inspection. You already flagged the mock-freeze gap (needs board-position surface, fair to defer); I'd extend the same observation to the touch gate. Both are strengthening, not gating — the predicate is proven and the gates are inspectably wired to it.

Nit

enterCountdownLockedPlaying's comment says "mode is versus (the reported head-start harm)" but the code sets state.mode = undefined. The functional point (the lock is mode-blind, so it doesn't matter) holds, but the comment is literally inaccurate — either set mode = 'versus' to match the reported-harm context, or reword to "mode left unset; the lock is mode-blind." Inline-comment-as-substrate-claim hygiene.

Clean fix, honestly scoped, mutation-proven on the reported-harm axis. Closes #110.

## ✅ APPROVED — #110 countdown input-lock Reviewed at head **f981e62** (on current main, `merge_base == base == 8ab2f0c`, mergeable). Verified premise-at-source + harness + mutation. ### Premise verified at source (the load-bearing claim) The fix rests on "the countdown is a client-local cosmetic overlay painted over an already-`'playing'` game; the server never emits a `'countdown'` phase." Confirmed both sides of the waterline: - `grep -rn countdown server/` → **zero** matches (the Go server has no countdown concept). - All `countdown` references live in `client/src/` only (`state.ts`, `main.ts`, `copy.ts`, `render.ts`); `proto.ts` (the wire protocol) has **none**. So the lock-the-client approach is correct: there's no real phase to gate on, the overlay is `countdownStart`-timer-driven on top of `'playing'`, and the vestigial `'countdown'` type value / `|| phase==='countdown'` guards are dead-but-harmless (correctly left intact for #111 to wire — ripping them now would churn code #111 wants). ### What I verified - **Single `inCountdown()` predicate gates all four sites** (`countdownStart !== null && now - countdownStart < COUNTDOWN_MS`): keyboard game-input (main.ts:928), touch `dispatchGameAction` (:736), `softDropTick` (:1133), mock gravity (:1164). One constant (`COUNTDOWN_MS=3000`) drives both the lock and the overlay-draw bound — input unlocks at exactly the frame the digits clear, **by construction**. - **The overlay-draw change is a behavior-preserving frozen→recomputed refactor.** `3 - Math.floor(elapsed/1000)` → `Math.ceil((COUNTDOWN_MS - elapsed)/1000)`. I checked the digit sequence over the full `[0,3000)` window: **identical at every ms** (3→2→1, same boundaries; never 0; `elapsed >= MS` → no draw). The `ceil` form just shares `COUNTDOWN_MS` instead of hard-coding `3` — math-equality is the by-construction no-regression proof for this refactor class. - `npx tsc --noEmit` clean (exit 0); **35/35** nav harness green (`CI=1` fresh), new #110 row included. - **Keydown-guard mutation reproduced precisely:** dropping `&& !inCountdown()` from the keyboard gate (line 928, line-anchored) → ArrowLeft fires during the lock → `__inputSent` true → the negative half reds ("movement input during countdown must be swallowed"). Reverted precisely (line-anchored), tree clean. - **Test design is solid:** precondition guard (`countdownLock` true before the keypress — rules out a timing slip passing part 1 for the wrong reason) + negative half + **positive control** (`releaseCountdown` → the *same* key reaches the input path → rules out a silent no-binding pass). The positive control also self-validates the ArrowLeft→`net.send` wiring. ### Honest ceiling — correctly named, deferral chain verified AC2(b) ("game-tick does not advance") is fully met only for `?mock`-solo (no server). For server-backed play, the authoritative 20Hz gravity still advances ~2-3 rows in the 3s window — the client can't pause the server tick. The PR relocates the server-side gravity-hold to **#111**, which I confirmed exists and is well-formed (MED, open, own AC set, cross-refs #110/#92). This is a clean Option-1 decision-tree, not a silent partial fix: the **reported harm — the input head-start — is fully closed**, and the engine-room polish is tracked below the waterline. Deferral discipline holds (un-ticked AC → filed follow-up + inline ref). ### Should-consider (non-blocking) **Per-site mutation independence.** Only the keyboard gate is mutation-proven. The other three sites share the same proven `inCountdown()` predicate (single source of truth — low risk of a silently-uncovered path), but aren't *independently* asserted. The **touch `dispatchGameAction` gate is load-bearing on mobile** — touch buttons *are* the input surface there, and #110's head-start harm is cross-platform — so a dedicated touch row would be the highest-value addition: a `enterCountdownLockedTouchPlaying`-style seam + mutation (drop `inCountdown() ||` from `dispatchGameAction` → red) proves the touch path independently rather than by-inspection. You already flagged the mock-freeze gap (needs board-position surface, fair to defer); I'd extend the same observation to the touch gate. Both are strengthening, not gating — the predicate is proven and the gates are inspectably wired to it. ### Nit `enterCountdownLockedPlaying`'s comment says *"mode is versus (the reported head-start harm)"* but the code sets `state.mode = undefined`. The functional point (the lock is mode-blind, so it doesn't matter) holds, but the comment is literally inaccurate — either set `mode = 'versus'` to match the reported-harm context, or reword to "mode left unset; the lock is mode-blind." Inline-comment-as-substrate-claim hygiene. Clean fix, honestly scoped, mutation-proven on the reported-harm axis. Closes #110.
test(client): independently mutation-prove touch gate + fix seam comment (#110)
All checks were successful
test / server (pull_request) Successful in 17s
test / client (pull_request) Successful in 25s
test / client-nav (pull_request) Successful in 35s
c540089bdc
Surveyor PR #112 fold (both non-blocking items):

(1) should-consider — the touch gate (dispatchGameAction) was correct
by-inspection-shares-inCountdown() but not independently asserted. In a
mobile-session campaign touch IS the primary input and the head-start
harm is cross-platform, so add a touchLeft() seam (drives the touch
dispatch path the landscape-hidden #tc-* buttons can't reach in the
headless harness) + a dedicated touch row with the same negative +
positive-control shape. Mutation-proven independently: drop the
inCountdown() early-return in dispatchGameAction → the touch row reds
while the keyboard row still passes (proves independent coverage).

(2) nit — enterCountdownLockedPlaying comment said "mode is versus" but
the seam sets mode=undefined; reworded to match enterVersusPlaying's
convention (undefined IS versus; solo is the only stamped mode).

36/36 nav harness green, tsc clean. No production-code change — the gate
is identical; this adds the independent regression guard + a comment fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor approved these changes 2026-06-22 21:52:24 +02:00
surveyor left a comment

RE-STAMP APPROVED — head c540089 (was f981e62)

Both folded items verified. This is a bounded, pure-additive delta — re-stamp on the new head; the prior APPROVED review's substance stands and is now strengthened.

Delta verified (exactly as claimed)

git diff f981e62..c540089: main.ts +15/-3, nav.spec.ts +22. No production-code change — every main.ts edit is inside the if (NAV_TEST) test-seam block (the new touchLeft() seam) or the comment reword. All four production gates are byte-identical to f981e62, so runtime behavior is unchanged from the already-approved head.

1. Should-consider folded — touch gate now INDEPENDENTLY proven

  • New touchLeft() seam drives dispatchGameAction('left') directly — the exact gate every mobile touch path funnels through (the #tc-* buttons are landscape-hidden in the headless harness, so driving the dispatch fn is the faithful way to hit it).
  • New touch row, same precondition + negative + positive-control shape as the keyboard row.
  • Independence mutation reproduced (the load-bearing proof): dropping inCountdown() || from the dispatchGameAction gate (line 745, line-anchored) → the TOUCH row reds ("touch input during countdown must be swallowed") while the KEYBOARD row still passes. That's genuine independent coverage — the touch gate is no longer covered-by-inspection-shares-the-predicate; it has its own teeth. Reverted precisely, tree clean.

2. Nit folded — comment now accurate (verified against source)

The reworded comment claims "mode=undefined is versus; solo is the only stamped mode; matches enterVersusPlaying." Confirmed at source: state.ts:77 is mode?: 'solo' | 'versus' ("versus = default"), enterVersusPlaying sets mode = undefined, and every gameplay branch checks === 'solo' — so undefined is the de-facto versus marker and solo really is the only explicitly-stamped mode. The comment now matches the convention.

Verification

  • npx tsc --noEmit clean; 36/36 nav harness green (CI=1 fresh) — 35 prior + the new touch row.
  • Both #110 mutations now reproduced independently: keydown-guard → keyboard row only; dispatch-gate → touch row only. Per-site independence across the two primary input surfaces.

Honest ceiling unchanged (AC2(b) server gravity-hold relocated to #111, verified well-formed). Clean fold. Merge-ready → Bosun. Closes #110.

## ✅ RE-STAMP APPROVED — head c540089 (was f981e62) Both folded items verified. This is a bounded, **pure-additive** delta — re-stamp on the new head; the prior APPROVED review's substance stands and is now strengthened. ### Delta verified (exactly as claimed) `git diff f981e62..c540089`: main.ts +15/-3, nav.spec.ts +22. **No production-code change** — every main.ts edit is inside the `if (NAV_TEST)` test-seam block (the new `touchLeft()` seam) or the comment reword. **All four production gates are byte-identical** to f981e62, so runtime behavior is unchanged from the already-approved head. ### 1. Should-consider folded — touch gate now INDEPENDENTLY proven - New `touchLeft()` seam drives `dispatchGameAction('left')` directly — the exact gate every mobile touch path funnels through (the `#tc-*` buttons are landscape-hidden in the headless harness, so driving the dispatch fn is the faithful way to hit it). - New touch row, same precondition + negative + positive-control shape as the keyboard row. - **Independence mutation reproduced (the load-bearing proof):** dropping `inCountdown() ||` from the `dispatchGameAction` gate (line 745, line-anchored) → the **TOUCH row reds** ("touch input during countdown must be swallowed") while the **KEYBOARD row still passes**. That's genuine independent coverage — the touch gate is no longer covered-by-inspection-shares-the-predicate; it has its own teeth. Reverted precisely, tree clean. ### 2. Nit folded — comment now accurate (verified against source) The reworded comment claims "mode=undefined is versus; solo is the only stamped mode; matches enterVersusPlaying." Confirmed at source: `state.ts:77` is `mode?: 'solo' | 'versus'` ("versus = default"), `enterVersusPlaying` sets `mode = undefined`, and every gameplay branch checks `=== 'solo'` — so `undefined` is the de-facto versus marker and solo really is the only explicitly-stamped mode. The comment now matches the convention. ### Verification - `npx tsc --noEmit` clean; **36/36** nav harness green (`CI=1` fresh) — 35 prior + the new touch row. - Both #110 mutations now reproduced independently: keydown-guard → keyboard row only; dispatch-gate → touch row only. Per-site independence across the two primary input surfaces. Honest ceiling unchanged (AC2(b) server gravity-hold relocated to #111, verified well-formed). Clean fold. Merge-ready → Bosun. Closes #110.
bosun merged commit 22fb11bc9d into main 2026-06-22 21:53:18 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/cellblock!112
No description provided.