Say who the game is waiting on, and move the Fedora; replay in words
Three items folded into the unshipped v0.7.9. WAITING ON (reported by Jesse from play). The status line said "nobody — the Division is running itself" while the game was stopped on the Superintendent. Frame.actor carried clock.currentActor, which is null for the whole Mainline Phase, so all three interruptions — §8.1's clearance ruling, Gitea#5's Yard Office offer, Gitea#19's Red Flag prompt — reported that nobody was holding it up. actingPlayer had the answer since the Gitea#5 refactor; the Frame threw it away. It carries actingPlayer now, plus a new `awaiting` field naming the question and the train: "waiting on Bob · a clearance ruling · Train 4". Naming the person alone is not enough when three different things can be pending. THE FEDORA (TODO #29) rides at the right-hand end of the phase row instead of a line of its own, and wraps under rather than squeezing the chips. THE DEVELOPER REPLAY (TODO #34) printed "loss — revenueFloor", the same defect Gitea#16 was filed about, still alive because nothing player-facing pointed at it. panels.ts's reasonSentence is exported and shared rather than reimplemented, fed the last recorded frame and stripped of markup. The drift test maps win/loss to won/lost so it still checks the two AGREE rather than that they are spelled alike. Also carries the previous, unsigned commit's work: the two setup screens worded the same section by section. 877 tests pass, ten new. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdG46Ja2PEDBkpqiDazMoX
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
131538dc7c
commit
a70b7f88f3
+18
-25
@@ -256,25 +256,21 @@ export function runLobby(handlers: LobbyHandlers, resume?: { token: string; game
|
||||
else if (type === 'custom') type = base;
|
||||
for (const r of typeRadios()) r.checked = r.value === type;
|
||||
|
||||
const scoring = preset(base).scoring;
|
||||
const note = $('lb-type-note');
|
||||
if (type === 'custom') {
|
||||
note.textContent =
|
||||
`${gameTypeLabel('custom', scoring)} · ${differing.length} ` +
|
||||
`${differing.length === 1 ? 'setting differs' : 'settings differ'} from ${preset(base).label}.`;
|
||||
note.className = 'ng-note changed-note';
|
||||
// A Custom game is nobody's default: open the block that says how it differs.
|
||||
$<HTMLDetailsElement>('lb-settings').open = true;
|
||||
} else {
|
||||
note.textContent = preset(type as PresetName).blurb;
|
||||
note.className = 'ng-note';
|
||||
}
|
||||
/**
|
||||
* NO SENTENCE UNDER THE RADIOS since 2026-08-30 — it restated the type just chosen to the person
|
||||
* who had just chosen it, and the row is already labelled and already carries its own
|
||||
* description (Jesse: "There's no need to repeat it below"). `form.mark` still puts a hint on
|
||||
* each row that actually differs, which is where a Custom game's differences can be acted on.
|
||||
*/
|
||||
// A Custom game is nobody's default: open the block that says how it differs.
|
||||
if (type === 'custom') $<HTMLDetailsElement>('lb-settings').open = true;
|
||||
}
|
||||
|
||||
for (const r of typeRadios()) {
|
||||
// Solitaire is on this screen so the two screens read as one list, but there is nothing here to
|
||||
// deal it with — the New Game dialog is where a solitaire game comes from.
|
||||
if (r.value === 'solitaire') markUnavailable(r, 'dealt with the New game button, not here');
|
||||
// deal it with. Dimmed and left to speak for itself: the heading says "Game type
|
||||
// (multi-player)", which is the explanation (Jesse, 2026-08-30).
|
||||
if (r.value === 'solitaire') markUnavailable(r);
|
||||
r.onchange = () => {
|
||||
if (!r.checked) return;
|
||||
if (r.value === 'custom') {
|
||||
@@ -615,18 +611,15 @@ export function prefillCode(code: string): void {
|
||||
*
|
||||
* Reported by Jesse 2026-08-23: "solitaire is disabled, but really hard to tell." A bare `disabled`
|
||||
* on a radio leaves the whole row at full strength — the dot simply refuses the click, which reads
|
||||
* as a broken control rather than an unavailable one. Dims the row and says why, once.
|
||||
* as a broken control rather than an unavailable one.
|
||||
*
|
||||
* The dimming is the whole signal now. It used to append a reason to the row as well, and dropped
|
||||
* that in 2026-08-30 along with the same text on the solitaire screen: one heading naming which
|
||||
* game the screen deals says it once, where three dimmed rows each said it again.
|
||||
*/
|
||||
function markUnavailable(radio: HTMLInputElement, why: string): void {
|
||||
function markUnavailable(radio: HTMLInputElement): void {
|
||||
radio.disabled = true;
|
||||
const row = radio.closest('label');
|
||||
if (!row) return;
|
||||
row.classList.add('disabled');
|
||||
if (row.querySelector('.lb-why')) return;
|
||||
const note = document.createElement('span');
|
||||
note.className = 'lb-why';
|
||||
note.textContent = ` — ${why}`;
|
||||
row.querySelector('span')?.appendChild(note);
|
||||
radio.closest('label')?.classList.add('disabled');
|
||||
}
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
|
||||
Reference in New Issue
Block a user