feat(ui): render ending lines distinctly and lock input on end-state

Ending-kind lines get a separator and italic styling. Once endedWith is
set, the terminal disables the input and rejects all commands except
restart and undo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 14:57:52 -05:00
parent 19d1efc586
commit e167979fa7
2 changed files with 31 additions and 0 deletions
+14
View File
@@ -169,3 +169,17 @@
@media (pointer: coarse) {
.mystery-chips { display: flex; }
}
.mystery-transcript .ending {
margin-top: 2em;
margin-bottom: 1em;
padding-top: 1em;
border-top: 1px solid currentColor;
font-style: italic;
white-space: pre-wrap;
}
[data-mystery-input]:disabled {
opacity: 0.4;
cursor: not-allowed;
}
+17
View File
@@ -33,6 +33,10 @@ if (!transcriptEl || !inputEl) {
})
}
const syncEndedUI = (): void => {
inputEl!.disabled = state.endedWith !== null
}
const buildParserContext = (s: GameState): ParserContext => {
const room = world.rooms[s.location]
const visibleNouns: { id: string; aliases: string[] }[] = []
@@ -81,6 +85,7 @@ if (!transcriptEl || !inputEl) {
renderAll(state.transcript)
refreshChips()
syncEndedUI()
inputEl.focus()
inputEl.addEventListener('keydown', (e) => {
@@ -91,6 +96,15 @@ if (!transcriptEl || !inputEl) {
if (!raw.trim()) return
appendLines([{ kind: 'player', text: raw }])
// Once the game has ended, only restart and undo are allowed.
if (state.endedWith !== null) {
const lower = raw.trim().toLowerCase()
if (lower !== 'restart' && lower !== 'undo') {
appendLines([{ kind: 'system', text: 'The story has ended. Type `restart` or `undo`.' }])
return
}
}
// Engine-level meta-commands handled here so the engine stays pure.
const trimmed = raw.trim().toLowerCase()
if (trimmed === 'restart') {
@@ -105,6 +119,7 @@ if (!transcriptEl || !inputEl) {
renderAll(state.transcript)
saveState(state)
refreshChips()
syncEndedUI()
return
}
if (trimmed === 'undo') {
@@ -114,6 +129,7 @@ if (!transcriptEl || !inputEl) {
appendLines([{ kind: 'system', text: '(undone)' }])
saveState(state)
refreshChips()
syncEndedUI()
} else {
appendLines([{ kind: 'system', text: 'There is no further back.' }])
}
@@ -139,6 +155,7 @@ if (!transcriptEl || !inputEl) {
document.dispatchEvent(new CustomEvent('halfstreet-toggle-theme'))
}
refreshChips()
syncEndedUI()
} catch (err) {
console.error('[halfstreet] dispatch error', err)
appendLines([{ kind: 'system', text: '[ The terminal hums and resets. ]' }])