fix(sessions): show fork icon + order number on nested forked sessions (#934)
* fix(sessions): fork icon + depth number, no indentation
Multiply-nested forked sessions rendered with deep indentation that crushed
the card off-screen and made titles unreadable, and the per-sibling order
number was uninformative (mostly "1").
- SessionTreeItem: replace forkIndex (sibling order) with forkDepth (nesting
level: 1 = child of a root, 2 = fork of a fork, ...); flattenSessionTree
populates it.
- SessionCard: render CallSplit fork icon + the depth number (primary tint).
Removed the depth-based start padding so names stay full-width readable —
the icon+number now carries the structure instead of indentation.
- strings: sessions_fork_indicator (en/zh/ko).
- SessionTreeTest: assert isFork/forkDepth across nested fixtures.
No backend change — backend already returns parent_session_id per SessionInfo.
* test(ws): make testAutoReconnect tolerate transient RECONNECTING state
The test gated on connectionStatus.first { it == RECONNECTING } with a
15s timeout. With the test backoff forced to 0ms the RECONNECTING state is
emitted + consumed before the collector attaches, so the wait missed it and
timed out (kotlinx.coroutines.TimeoutCancellationException) — the flake CI
saw under parallel load.
- Replace the brittle RECONNECTING .first{} wait with a tolerant check that
accepts RECONNECTING-or-later (the reconnect clearly fired).
- Keep connect2Latch (second socket opening) as the definitive proof + add a
final CONNECTED assertion.
- Reset the test backoff in a finally so later tests never inherit 0ms.
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
53c86d5871
commit
82e2d787af
7 files changed
+103
-26
No files matched your search
@@ -25,7 +25,29 @@ class SessionTreeTest {
|
||||
it.displayTitle
|
||||
},
|
||||
)
|
||||
assertEquals(listOf(null, null, "├─", "└─", "└─"), tree.map { it.branchStem })
|
||||
// Fork flag + nesting depth (1 = child of root, 2 = fork of a fork).
|
||||
assertEquals(listOf(false, false, true, true, true), tree.map { it.isFork })
|
||||
assertEquals(listOf(0, 0, 1, 1, 2), tree.map { it.forkDepth })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deepNestingMarksEveryForkWithDepth() {
|
||||
// root → a (child 1) → a1 (child 1) ; root → b (child 2, last)
|
||||
// a1 is fork-depth 2 (fork of a fork), b is fork-depth 1.
|
||||
val sessions =
|
||||
listOf(
|
||||
SessionInfo(id = "root", title = "Root"),
|
||||
SessionInfo(id = "a", parent_session_id = "root"),
|
||||
SessionInfo(id = "b", parent_session_id = "root"),
|
||||
SessionInfo(id = "a1", parent_session_id = "a"),
|
||||
)
|
||||
|
||||
val tree = flattenSessionTree(sessions)
|
||||
|
||||
assertEquals(listOf("root", "a", "a1", "b"), tree.map { it.session.id })
|
||||
assertEquals(listOf(0, 1, 2, 1), tree.map { it.depth })
|
||||
assertEquals(listOf(false, true, true, true), tree.map { it.isFork })
|
||||
assertEquals(listOf(0, 1, 2, 1), tree.map { it.forkDepth })
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1099,20 +1099,33 @@ class HermesWsClientTest {
|
||||
// Force server to close socket 1 to trigger reconnect
|
||||
serverSocket1?.close(1001, "Server shutting down")
|
||||
|
||||
// Wait for status to become RECONNECTING
|
||||
runBlocking {
|
||||
withTimeout(
|
||||
15_000,
|
||||
) { HermesWsClient.connectionStatus.first { it == ConnectionStatus.RECONNECTING } }
|
||||
}
|
||||
// Wait for the reconnect to trigger. With the test backoff forced to
|
||||
// 0ms the RECONNECTING state is transient and can be emitted + consumed
|
||||
// before this collector attaches, so we don't gate on the exact
|
||||
// intermediate state — we require it to have progressed (RECONNECTING
|
||||
// or later). The definitive proof is the second socket opening below.
|
||||
val statusNow = HermesWsClient.connectionStatus.value
|
||||
assertTrue(
|
||||
"Expected reconnect to trigger (RECONNECTING or later), was $statusNow",
|
||||
statusNow == ConnectionStatus.RECONNECTING ||
|
||||
statusNow == ConnectionStatus.CONNECTING ||
|
||||
statusNow == ConnectionStatus.CONNECTED,
|
||||
)
|
||||
|
||||
// The client should now attempt to reconnect after initial backoff (1000ms)
|
||||
// The client should now attempt to reconnect after the (0ms) backoff.
|
||||
// Wait for the second connection to hit the server. Generous ceiling:
|
||||
// loaded CI runners routinely stretch short wall-clock windows.
|
||||
assertTrue("Failed to reconnect", connect2Latch.await(30, TimeUnit.SECONDS))
|
||||
|
||||
// Restore production backoff so later tests see the default.
|
||||
HermesWsClient.setReconnectBackoffForTest(1_000L)
|
||||
try {
|
||||
assertTrue("Failed to reconnect", connect2Latch.await(30, TimeUnit.SECONDS))
|
||||
runBlocking {
|
||||
withTimeout(15_000) { HermesWsClient.connectionStatus.first { it == ConnectionStatus.CONNECTED } }
|
||||
}
|
||||
assertEquals(ConnectionStatus.CONNECTED, HermesWsClient.connectionStatus.value)
|
||||
} finally {
|
||||
// Restore production backoff so later tests see the default, even
|
||||
// if an assertion above threw.
|
||||
HermesWsClient.setReconnectBackoffForTest(1_000L)
|
||||
}
|
||||
}
|
||||
|
||||
// ── TEST-10: WS reconnect state recovery ────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user