Skip to content

Native app: apply session_row frames instead of waiting for the poll - #341

Open
kentdebruin wants to merge 16 commits into
mainfrom
report-handle-live-session-row-frames
Open

kentdebruin wants to merge 16 commits into
mainfrom
report-handle-live-session-row-frames

Conversation

@kentdebruin

Copy link
Copy Markdown
Contributor

Brings the native Swift client up to the session-list wire behavior shipped in 7e7ff4d and 29bbe68. The server expects a sidebar socket to send sessions_subscribe and answers most metadata changes with session_row / session_row_removed; at HEAD the native app ignored both and waited for its 5s poll.

Before / after

Before After
Subscribe never sent sessions_subscribe with ?archived=exclude after every hello and on account switch
session_row .ignored decoded off the main actor, coalesced 120ms, merged in place
session_row_removed .ignored row dropped, archived index marked stale
sessions_invalidated .ignored list re-read
Archive elsewhere shows up in up to 5s under 1s (server coalesce 250ms + 120ms)

Changes

  • ServerEvent: .sessionRow(Session), .sessionRowRemoved(sessionId:), .sessionsInvalidated; malformed frames stay .ignored.
  • OS1Socket: subscribeSessions(query:); row frames take the detached decode path (type-prefix match).
  • PresenceStore: subscribes the active account's socket after hello and in start() (account switch / foreground), forwards that account's frames plus a subscribed event to observers.
  • SessionsListViewModel: applyingRowUpdates is a pure off-main merge that keeps the poll's filters, local archive/restore overlays, optimistic placeholders (retire on own row, otherwise stay in front), recency order and hide resurfacing. A publish landing mid-merge requeues the frames. Poll stays as fallback; OS1_SESSIONS_POLL_SECONDS lengthens it for verification.

Verification

  • tella-mac-node: xcodegen generate, OS1 (iOS Simulator) and OS1Mac build; ServerEventTests, SessionRowUpdateTests (new, 13), PresenceStoreTests, ArchivedSliceTests, SessionsListLensTests: 70 pass.
  • Live against os.tella.dev with OS1_SESSIONS_POLL_SECONDS=600: archiving a session over REST removed its row from the Mac app within 5s (1,028 -> 1,027), restoring it brought the row back (-> 1,028). Screenshots in the session.

Started by Kent de Bruin in this OS session

The server now expects a sidebar socket to send sessions_subscribe and
answers most metadata changes with session_row or session_row_removed
(7e7ff4d, 29bbe68). The native app decoded neither and sat on its 5s
poll for every archive, rename, model switch and run boundary.

ServerEvent decodes session_row (the list projection, same tolerant
Session model as the poll), session_row_removed (`id`) and
sessions_invalidated; malformed frames stay .ignored. OS1Socket sends
sessions_subscribe and decodes row frames off the main actor, matched
by a type prefix. PresenceStore subscribes the active account's socket
after every hello and on account switch, using the live list's query
(?archived=exclude), and forwards that account's frames plus a
`subscribed` event to observers.

SessionsListViewModel keys pending frames by session, flushes them
after 120ms, and merges off the main actor with applyingRowUpdates: the
poll's filters (no desk rows, a locally archived row stays hidden, a
locally restored one reads as live), pending creates retire when their
own row arrives and otherwise stay in front, recency order is kept, and
a blocked row resurfaces its hide. A publish that lands mid-merge
requeues the frames instead of overwriting it. sessions_invalidated and
a reconnect re-read the list. The poll remains as the fallback;
OS1_SESSIONS_POLL_SECONDS lengthens it for a build under test.

Verified on tella-mac-node: OS1 and OS1Mac build, 70 tests pass (13 new
list-update, 3 new decoder). Against os.tella.dev with the poll at 600s,
archiving a session over REST dropped its row from the Mac app within
5s and restoring it brought the row back.

Co-authored-by: Kent de Bruin <[email protected]>
@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
🕙 Outdated review — superseded by a newer review below

🤖 OS review · request changes · quality 3/5 · risk low

Safe once the P1 below is fixed. A concurrent fallback poll can undo the live update this PR is intended to preserve.

🟢 Risk low · recovery in minutes · secrets or config
Socket row updates are UI-only with a five-second poll fallback; the new poll-interval environment variable is runtime config.

1 inline comment below.

Reviewed 1ae9e70 · GPT-5.6 Sol · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

🔁 1 finding → owning session · fix round 1/6

@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
opensession Ready Ready Preview Sep 10, 2026 12:10pm UTC

@open-session-os-tella-dev open-session-os-tella-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS review · 1ae9e70

// The list moved under the merge (a poll landed, a local archive).
// That publish is newer for everything but these rows, so replay
// them onto it rather than publishing this stale base over it.
guard revision == sessionsRevision else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 P1 — A stale poll can overwrite a newer row frame

This revision check protects only the case where a poll publishes while the row merge is detached. The reverse ordering remains unsafe: a poll can receive an old response, then a session_row with a newer title can flush and call setSessions, and finally the poll's detached preparation/grouping can finish and unconditionally publish its old response in refresh(). The live change then disappears for up to the next 5-second poll, or much longer with the documented verification interval. Capture sessionsRevision when the poll starts and re-check it immediately before publishing the poll result; discard or retry the response if another list mutation landed meanwhile.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7f9660e. refresh() now captures sessionsRevision before its request goes out. Each row flush that publishes while a poll is in flight records its frames (newest per id, tagged with the revision that carries them); the poll replays the frames newer than its start revision over the response (polled, off the main actor), then re-checks the revision right before publishing. If a flush landed during the detached passes it reruns them over the moved list (at most twice), and a response is discarded once a later-started poll has already published. Tests: testAPollReplaysNewerFramesOverItsResponse, testAPollReplayKeepsTheLocalOverlays, testReplayPicksTheFramesAppliedAfterThePollStarted, testAPollWithNothingToReplayIsThePreparedList. OS1Mac test suite (1062) and the OS1 simulator build pass.

A poll's response was built before any row frame applied while the
request was out, but refresh() published it unconditionally, so a
rename or archive that a frame had already moved past reappeared until
the next poll. Capture the list revision when the request goes out,
record the frames each flush applies while a poll is in flight, replay
the newer ones over the response off the main actor, and re-check the
revision before publishing. A flush that lands during the detached
passes reruns them over the moved list; a response is discarded once a
later-started poll has published.

Co-authored-by: Kent de Bruin <[email protected]>
@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
🕙 Outdated review — superseded by a newer review below

🤖 OS review · request changes · quality 3/5 · risk low

Safe once the P1 below is fixed. The new replay logic protects loaded lists, but initial-load frames are still discarded.

🟢 Risk low · recovery in minutes · secrets or config, large diff
The 891-line native change adds socket list updates and a poll-interval environment override, without persistence or delivered output.

1 inline comment below.

Reviewed 7f9660e · GPT-5.6 Sol · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

🔁 1 finding → owning session · fix round 2/6

@open-session-os-tella-dev open-session-os-tella-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS review · 7f9660e


private func flushRowUpdates() async {
// A row that arrives before the first list would publish a one-row
// list; the poll's first answer carries it anyway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 P1 — The stale-poll fix still drops frames during initial loading

The new replay mechanism only sees updates that pass this guard. On launch, the initial sessions request can fetch session X, then another client archives X while workspace loading/grouping is still running. The resulting session_row_removed flushes while hasLoaded is false and is deleted here; the initial response then publishes X because neither sessionsRevision nor appliedRowUpdates records the newer removal. X remains incorrectly visible until the next poll. Preserve and replay frames received while the initial refresh is in flight instead of clearing them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fa627ae. Before the first list has loaded, flushRowUpdates no longer clears the frames. When a refresh is in flight it bumps sessionsRevision (nothing published, but the list's truth moved) and records them through the same recordAppliedRowUpdates the post-load path uses, so the first response replays them via polled, and a flush that lands mid-pass reruns the detached passes over the recorded frames. With no request out they are still dropped, since the first poll's answer carries them. The record/replay/prune path is the one exercised by the round-1 tests (testAPollReplaysNewerFramesOverItsResponse etc.); OS1Mac suite (1062) and the OS1 simulator build pass.

A frame that flushed before the first list had loaded was dropped, so
an archive or rename made elsewhere while the initial request was out
could not be replayed over that request's response, and the row it had
already moved past was published until the next poll. When a refresh
is in flight, record those frames for its replay instead; with no
request out, the first poll's answer carries them anyway.

Co-authored-by: Kent de Bruin <[email protected]>
@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
🕙 Outdated review — superseded by a newer review below

🤖 OS review · request changes · quality 3/5 · risk high

Safe once the P1 below is fixed. The initial-loading race fix still loses frames when the bounded replay attempts are exhausted.

🔴 Risk high · recovery in days · secrets or config, large diff
A 912-line native list rewrite adds an environment-controlled poll interval, while installed builds lack an immediate rollback path. Gate row-frame application with a server-controlled kill switch.

1 inline comment below.

Reviewed fa627ae · GPT-5.6 Sol · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

🔁 1 finding → owning session · fix round 3/6

@open-session-os-tella-dev open-session-os-tella-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS review · fa627ae

guard sequence > publishedRefreshSequence else { break }
if snapshotRevision != sessionsRevision {
if attempt < 2 { continue }
break

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 P1 — The retry cap still drops frames during initial loading

The new replay fixes ordinary initial-load races, but only while one of these three attempts reaches a stable revision. With the documented 5.5k-row list, three 120ms-coalesced frame batches can land across the detached filtering/grouping passes. The third revision mismatch takes this break; refresh() then sets hasLoaded = true, and its defer prunes the recorded frames even though no initial list was published. The UI remains empty, or is rebuilt one subsequent row frame at a time, until the next five-second poll. Do not complete the initial load after exhausting this retry budget: keep retrying until a stable initial publication, or retain the replay and immediately start another refresh while hasLoaded remains false.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a0c4717. The rerun budget now applies only once a list is on screen. While hasLoaded is false the first response reruns its detached passes over the moved list, with the recorded frames in the replay, until a pass reaches a stable revision and publishes; it no longer breaks out, so the initial load cannot complete with an empty screen or prune the frames it needed. After the first list the cap of two reruns stays, since a later poll covers a list that will not hold still. OS1Mac suite (1062) and the OS1 simulator build pass.

The poll reran its detached passes at most twice when a row flush
published under them, then gave up and left the rest to the next poll.
Before the first list that is the wrong trade: giving up marked the
load done with an empty screen and pruned the frames it needed, so a
5.5k-row list under a burst of frames came back one row at a time
until the next poll. Rerun until the first response lands; keep the
cap once a list is on screen.

Co-authored-by: Kent de Bruin <[email protected]>
@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

🤖 OS review · request changes · quality 3/5 · risk low

Safe once the P1 below is fixed. The first-load replay issue is addressed, but stale detached attempts can still persist an incorrect hide removal.

🟢 Risk low · recovery in minutes · secrets or config, large diff
Large client runtime diff adds socket-driven list merging and an optional poll-interval environment variable; polling remains a five-second fallback.

1 inline comment below.

Reviewed a0c4717 · GPT-5.6 Sol · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
🕙 Outdated review — superseded by a newer review below

🤖 OS review · request changes · quality 3/5 · risk low

Safe once the P1 below is fixed. The first-load replay issue is addressed, but stale detached attempts can still persist an incorrect hide removal.

🟢 Risk low · recovery in minutes · secrets or config, large diff
Large client runtime diff adds socket-driven list merging and an optional poll-interval environment variable; polling remains a five-second fallback.

1 inline comment below.

Reviewed a0c4717 · GPT-5.6 Sol · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

🔁 1 finding → owning session · fix round 4/6

@open-session-os-tella-dev open-session-os-tella-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS review · a0c4717

// question, and the entry is consumed when it does — so a hide can
// never swallow work that needs you. Consuming it here (not in the
// row filter) keeps the mutation out of view body evaluation.
HideStore.shared.clear(polled.resurfacedHideKeys)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 P1 — A stale poll attempt can permanently clear a sidebar hide

polled is computed from a snapshot, but its resurfaced hide keys are cleared before the revision checks at lines 1370-1375. For example, a poll sees hidden session X in needsInput; while the detached pass runs, a newer session_row reports that X no longer needs input and advances sessionsRevision. This line still removes X's hide and HideStore.clear persists that removal to the server, after which the stale list attempt is discarded. Defer this mutation until the attempt's sequence and revision have been accepted. For the no-publish path, validate the revision before clearing and breaking.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 48ee5d9: poll-pass side effects now run only after the connection, refresh sequence, and sessions revision are accepted. The no-publish path performs the same validation before clearing resurfaced hides, and the legacy archive index is deferred with that clear.

open-session-os-tella-dev Bot and others added 8 commits September 10, 2026 10:12
The server now expects a sidebar socket to send sessions_subscribe and
answers most metadata changes with session_row or session_row_removed
(7e7ff4d, 29bbe68). The native app decoded neither and sat on its 5s
poll for every archive, rename, model switch and run boundary.

ServerEvent decodes session_row (the list projection, same tolerant
Session model as the poll), session_row_removed (`id`) and
sessions_invalidated; malformed frames stay .ignored. OS1Socket sends
sessions_subscribe and decodes row frames off the main actor, matched
by a type prefix. PresenceStore subscribes the active account's socket
after every hello and on account switch, using the live list's query
(?archived=exclude), and forwards that account's frames plus a
`subscribed` event to observers.

SessionsListViewModel keys pending frames by session, flushes them
after 120ms, and merges off the main actor with applyingRowUpdates: the
poll's filters (no desk rows, a locally archived row stays hidden, a
locally restored one reads as live), pending creates retire when their
own row arrives and otherwise stay in front, recency order is kept, and
a blocked row resurfaces its hide. A publish that lands mid-merge
requeues the frames instead of overwriting it. sessions_invalidated and
a reconnect re-read the list. The poll remains as the fallback;
OS1_SESSIONS_POLL_SECONDS lengthens it for a build under test.

Verified on tella-mac-node: OS1 and OS1Mac build, 70 tests pass (13 new
list-update, 3 new decoder). Against os.tella.dev with the poll at 600s,
archiving a session over REST dropped its row from the Mac app within
5s and restoring it brought the row back.

Co-authored-by: Kent de Bruin <[email protected]>
A poll's response was built before any row frame applied while the
request was out, but refresh() published it unconditionally, so a
rename or archive that a frame had already moved past reappeared until
the next poll. Capture the list revision when the request goes out,
record the frames each flush applies while a poll is in flight, replay
the newer ones over the response off the main actor, and re-check the
revision before publishing. A flush that lands during the detached
passes reruns them over the moved list; a response is discarded once a
later-started poll has published.

Co-authored-by: Kent de Bruin <[email protected]>
A frame that flushed before the first list had loaded was dropped, so
an archive or rename made elsewhere while the initial request was out
could not be replayed over that request's response, and the row it had
already moved past was published until the next poll. When a refresh
is in flight, record those frames for its replay instead; with no
request out, the first poll's answer carries them anyway.

Co-authored-by: Kent de Bruin <[email protected]>
The poll reran its detached passes at most twice when a row flush
published under them, then gave up and left the rest to the next poll.
Before the first list that is the wrong trade: giving up marked the
load done with an empty screen and pruned the frames it needed, so a
5.5k-row list under a burst of frames came back one row at a time
until the next poll. Rerun until the first response lands; keep the
cap once a list is on screen.

Co-authored-by: Kent de Bruin <[email protected]>
…rames' into report-handle-live-session-row-frames

Co-authored-by: Kent de Bruin <[email protected]>
Also repair the latest main-line lane assertion so the required repository check type-checks after syncing the PR branch.

Co-authored-by: Kent de Bruin <[email protected]>
@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
🕙 Outdated review — superseded by a newer review below

🤖 OS review · request changes · quality 3/5

Safe once the P1 below is fixed. The latest change addresses the hide race, but a no-op newer poll still leaves older polls eligible to publish.

1 inline comment below.

Reviewed 6a74764 · GPT-5.6 Sol · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

🔁 1 finding → owning session · fix round 5/6

@open-session-os-tella-dev open-session-os-tella-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS review · 6a74764

// The list already says what the response says; the pass
// was accepted at the current revision, so what it found
// to consume still holds.
accept(polled)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 P1 — A no-op newer poll does not supersede an older poll

publishedRefreshSequence advances only when a poll assigns the list. For example, while the socket is disconnected, poll 1 can snapshot title Old; the server then restores title New, the socket reconnects, and its .subscribed event starts poll 2. Poll 2 sees New, matching the currently displayed list, so this branch accepts it without advancing the sequence. If delayed poll 1 then completes, it still passes sequence > publishedRefreshSequence and publishes Old until the next fallback poll. Mark every accepted poll, including a no-op, as published before breaking.

Suggested change
accept(polled)
publishedRefreshSequence = sequence
accept(polled)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9c07463: every poll accepted at a stable revision now advances publishedRefreshSequence, even when its rows already match the displayed list, so an older in-flight poll cannot publish afterward.

open-session-os-tella-dev Bot and others added 2 commits September 10, 2026 11:50
Resolve the actor-service assertion with the authoritative main-line version.

Co-authored-by: Kent de Bruin <[email protected]>
Advance the accepted refresh watermark even when the newer poll matches the displayed list, preventing an older in-flight response from publishing afterward.

Co-authored-by: Kent de Bruin <[email protected]>
@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
🕙 Outdated review — superseded by a newer review below

🤖 OS review · request changes · quality 3/5

Safe once the remaining P1 is fixed. The no-op poll ordering fix addresses the latest finding, but replay still preserves stale hide-clearing decisions.

1 inline comment below.

Reviewed 9c07463 · GPT-6 Astra · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

🔁 1 finding → owning session · fix round 6/6

@open-session-os-tella-dev open-session-os-tella-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS review · 9c07463

return (
applied.sessions,
prepared.archived,
Array(Set(prepared.resurfacedHideKeys).union(applied.resurfacedHideKeys))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 P1 — Recompute resurfaced hides after replaying newer rows

The earlier stale-hide finding remains reproducible despite moving side effects behind acceptance. A poll response contains hidden session X with waitingForInput: true; while preparation runs, a newer row with waitingForInput: false lands. The revision check correctly retries, but prepared(all, ...) again marks X's hide for removal from the old response. Replay updates X to its current state, yet this union retains the stale hide key. The accepted pass then calls HideStore.clear, permanently saving the unwanted removal. Compute resurfaced hide keys from the final replayed active list, rather than unioning decisions from the superseded response. Add a regression test covering a blocked poll row replaced by a non-blocked replay row.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f56683e: poll hide removals are recomputed from the final replayed active rows, rather than unioned with decisions from the stale response. Regression coverage checks a blocked response replaced by a non-blocked row or removal, plus other blocked rows that must still resurface the shared workspace. Verified bun run check, all 1,087 OS1Mac tests, and the OS1 simulator build.

@open-session-os-tella-dev

open-session-os-tella-dev Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

🤖 OS review · request changes · quality 3/5

Safe once the P1 below is fixed. Recomputing hides after replay fixes the previous example, but no-op frames still bypass replay and can permanently clear a hide.

1 inline comment below.

Reviewed f56683e · GPT-6 Astra · open session · labels: os-auto-fix fix and push · os-adversarial deeper pass · os-simplify cleanup

🔁 Still not merge-ready after 6 handed-off fix round(s) — over to humans. (The os-auto-fix label still works for another automated pass.)

@open-session-os-tella-dev open-session-os-tella-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS review · f56683e

}
return
}
guard applied.changed, let grouped else { return }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 P1 — Keep no-op frames in the in-flight poll replay

The stale-hide fix remains incomplete because this guard discards frames before recording them when they do not change the displayed list. For example, another client restores an archived, blocked session X in hidden workspace ws-1; a poll captures X, then that client archives X again while the poll awaits workspace metadata. The server's 250ms coalescer can send only the removal. Since X is absent from the displayed list, drop reports no change and this guard discards the removal. The poll consequently publishes X and permanently clears workspace:ws-1 through accept, even though X is already archived. The next poll removes X but cannot restore the persisted hide. Record accepted no-op frames too, advancing the replay revision while requests are in flight so detached poll passes retry. Add a regression covering an unknown-row removal arriving during a loaded-list refresh, rather than supplying the replay directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant