Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions ui-web/src/features/rail/source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ async function refreshHarness({
reconciled = null as { rows: Row[]; currentMissing: boolean } | null,
}) {
const log: unknown[][] = []
const replaced: Row[][] = []
await loadPart(() => import('../../state/session/registry'), {
fakes: {
'src/features/rail/store': {
Expand All @@ -218,7 +219,7 @@ async function refreshHarness({
},
'src/state/session/rows': {
sess: (id: string) => rows.find((r) => r.id === id),
replace: (next: Row[]) => log.push(['sessionReplace', next.map((r) => r.id)]),
replace: (next: Row[]) => { replaced.push(next); log.push(['sessionReplace', next.map((r) => r.id)]) },
rows: () => rows,
},
'src/i18n/t': {
Expand All @@ -238,10 +239,11 @@ async function refreshHarness({
},
})
const registry = await import('../../state/session/registry')
const rail = await import('./source')
await fakeGateway(async () => answer)
const { setSources } = await import('../../state/sources')
setSources({ composer: {}, rail: {}, transcript: {} } as unknown as Partial<Sources>)
return { registry, log }
return { registry, rail, log, replaced }
}

describe('re-reading the session list', () => {
Expand Down Expand Up @@ -275,6 +277,42 @@ describe('re-reading the session list', () => {
expect(h.log.filter((c) => c[0] === 'sessionDraw')).toEqual([])
})

it('a plain re-read draws the rows it just replaced, in that order', async () => {
/* Replacing the rows is not showing them. The archive page's restore
reaches the rail only through this function, so without the draw the
restored conversation came back on the server and on disk and stayed off
the screen until the page was reloaded. Asserted as a sequence: a draw
before the replace paints the list the reader already had. */
const h = await refreshHarness({
rows: [{ id: 'a' }],
answer: { sessions: [{ id: 'a', started_at: 1 }, { id: 'b', started_at: 2 }] },
})

await h.rail.loadSessions()

expect(h.log.filter((c) => c[0] !== 'reconcile'))
.toEqual([['sessionReplace', ['b', 'a']], ['sessionDraw']])
})

it('a plain re-read hands the list to the reconcile and replaces with what it gets', async () => {
/* The answer does not carry which conversation has a turn running, nor the
current one while it is too new to be listed; the reconcile is what
carries both across. Replacing with the raw list dropped them, and the
draw above is what makes that visible -- a running badge wiped by
restoring something from the archive page. */
const h = await refreshHarness({
rows: [{ id: 'a' }],
cur: 'a',
answer: { sessions: [{ id: 'b', started_at: 2 }] },
reconciled: { rows: [{ id: 'a' }, { id: 'b' }], currentMissing: false },
})

await h.rail.loadSessions()

expect(h.log).toContainEqual(['reconcile', ['a'], ['b'], 'a'])
expect(h.replaced[0]!.map((r) => r.id)).toEqual(['a', 'b'])
})

it('keeps the stale list when the read fails', async () => {
const h = await refreshHarness({ rows: [{ id: 'a' }] })
const { setGateway } = await import('../../rpc/gateway')
Expand Down
16 changes: 14 additions & 2 deletions ui-web/src/features/rail/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { switchTo } from '../../state/session/registry'
import { replace as sessionReplace, rows as sessionRows, sess } from '../../state/session/rows'
import { show as toast } from '../../state/toast'
import { busy } from '../composer/turn'
import { draw as sessionDraw } from './store'
import { draw as sessionDraw, reconcileRows } from './store'
import { plainTitle } from './title'

import type { ResultOf } from '../../rpc/generated'
Expand Down Expand Up @@ -123,7 +123,19 @@ export const SESS_CHANNELS = ['tui', 'cron']
export async function loadSessions(): Promise<void> {
await loadCronNames()
const r = await gateway().call('session.list', { channels: SESS_CHANNELS })
sessionReplace((r.sessions || []).map(rowFrom).sort((a, b) => (b.at || 0) - (a.at || 0)))
const listed = (r.sessions || []).map(rowFrom).sort((a, b) => (b.at || 0) - (a.at || 0))
/* Through the reconcile, because the answer does not carry what only this
page knows: which conversation has a turn running, and the current one
while it is too new to be listed. Replacing without it dropped both, which
was invisible while nothing redrew and is a wiped running badge now that
something does. `currentMissing` is left to `refreshList`, whose job the
conversation having been deleted under the reader is. */
sessionReplace(reconcileRows(sessionRows(), listed, sessionCurrent()).rows)
/* And replacing the rows is not showing them: wire.ts and the registry both
draw after theirs, and the archive page's restore reaches the rail only
through here. At boot the rail is held, so this draw is inert until
`releaseRail` paints. */
sessionDraw()
}

/* ── the two writes a row makes for itself ───────────────────────────────── */
Expand Down
Loading