Skip to content

Commit 2a0fef2

Browse files
committed
fix(desktop): read run records saved before runs had a state as the state they meant
1 parent 54db79e commit 2a0fef2

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

‎apps/desktop/src/main/terminal/run-ledger.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,34 @@ describe('the tmux run ledger', () => {
114114
expect(createRunLedger(dir).list()).toEqual([{ ...RUN, state: 'stop' }])
115115
})
116116

117+
it('reads a record saved before runs had a state as the state it meant', () => {
118+
const dir = scratch()
119+
mkdirSync(dir, { recursive: true })
120+
const { state: _state, ...saved } = RUN
121+
const legacy = {
122+
'run-started': { delivered: false },
123+
'run-delivered': { delivered: true },
124+
'run-stop': { delivered: false, mustStop: true },
125+
// A stop wins over an acknowledgement.
126+
'run-stop-delivered': { delivered: true, mustStop: true },
127+
}
128+
for (const [runId, fields] of Object.entries(legacy)) {
129+
writeFileSync(join(dir, `${runId}.json`), JSON.stringify({ ...saved, runId, ...fields }))
130+
}
131+
132+
const states = Object.fromEntries(
133+
createRunLedger(dir)
134+
.list()
135+
.map((record) => [record.runId, record.state])
136+
)
137+
expect(states).toEqual({
138+
'run-started': 'started',
139+
'run-delivered': 'delivered',
140+
'run-stop': 'stop',
141+
'run-stop-delivered': 'stop',
142+
})
143+
})
144+
117145
it('finds the run a call started, a previous process recorded it or this one', () => {
118146
const dir = scratch()
119147
// Recovery may hand the model the result of a previous process's call.

‎apps/desktop/src/main/terminal/run-ledger.ts‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,31 @@ export interface RunLedger {
5252
/** Run tags are generated ids; anything else in the directory is not a record. */
5353
const RUN_ID = /^[A-Za-z0-9_-]{1,128}$/
5454

55+
/** A record as saved: the current shape, or the earlier one with `delivered` and `mustStop`. */
56+
type SavedRecord = Partial<RunRecord> & { delivered?: unknown; mustStop?: unknown }
57+
58+
/**
59+
* The record's state. A record from before `state` existed (a released build wrote them) is read
60+
* as the state it meant: `mustStop` as `stop`, which wins, then `delivered` as `delivered`.
61+
*/
62+
function stateOf(saved: SavedRecord): RunState | null {
63+
if (RUN_STATES.includes(saved.state as RunState)) return saved.state as RunState
64+
if (typeof saved.delivered !== 'boolean') return null
65+
if (saved.mustStop === true) return 'stop'
66+
return saved.delivered ? 'delivered' : 'started'
67+
}
68+
5569
function parseRecord(text: string): RunRecord | null {
5670
try {
57-
const parsed = JSON.parse(text) as Partial<RunRecord>
71+
const parsed = JSON.parse(text) as SavedRecord
72+
const state = stateOf(parsed)
5873
if (
5974
typeof parsed.runId === 'string' &&
6075
RUN_ID.test(parsed.runId) &&
6176
typeof parsed.pane === 'string' &&
6277
/^%\d+$/.test(parsed.pane) &&
6378
typeof parsed.callId === 'string' &&
64-
RUN_STATES.includes(parsed.state as RunState) &&
79+
state &&
6580
typeof parsed.socket === 'string' &&
6681
parsed.socket.startsWith('/')
6782
) {
@@ -70,7 +85,7 @@ function parseRecord(text: string): RunRecord | null {
7085
pane: parsed.pane,
7186
socket: parsed.socket,
7287
callId: parsed.callId,
73-
state: parsed.state as RunState,
88+
state,
7489
}
7590
}
7691
} catch {

0 commit comments

Comments
 (0)