-
Notifications
You must be signed in to change notification settings - Fork 22
feat(tui): Session Detail modal - full session drill-down with turn timeline and diff #445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jaeko44
wants to merge
7
commits into
main
Choose a base branch
from
task/cfd631a87666-feat-tui-session-detail-modal-full-session-drill
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+501
−116
Open
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
23f7e99
chore: auto-commit agent work (cfd631a8-766)
jaeko44 ce7f882
fix(tui): accept ink enter sequence in session detail
jaeko44 db3dfa0
fix(ci): restore workflow markers and detail regressions
jaeko44 71a286b
Merge origin/main into task/cfd631a87666-feat-tui-session-detail-moda…
jaeko44 6af339b
Merge branch 'main' into task/cfd631a87666-feat-tui-session-detail-mo…
jaeko44 e955e37
Merge remote-tracking branch 'origin/main' into HEAD
jaeko44 4c5db8b
Merge remote-tracking branch 'origin/main' into HEAD
jaeko44 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -119,6 +119,117 @@ describe("tui screen rendering", () => { | |||
| await view.unmount(); | ||||
| }); | ||||
|
|
||||
| it("opens a session detail modal on enter and renders timeline, diff, and logs", async () => { | ||||
| const bridge = createMockBridge(); | ||||
| const view = await renderInk( | ||||
| React.createElement(AgentsScreen, { | ||||
| wsBridge: bridge, | ||||
| host: "127.0.0.1", | ||||
| port: 3080, | ||||
| sessions: sessionsFixture, | ||||
| stats: monitorStatsFixture, | ||||
| }), | ||||
| { columns: 220, rows: 60 }, | ||||
| ); | ||||
|
|
||||
| await waitFor(() => view.text().includes("Backoff queue (1)")); | ||||
| await view.press("`r", 80); | ||||
| await waitFor(() => view.text().includes("Session Detail")); | ||||
|
|
||||
| expect(view.text()).toContain("Task ID"); | ||||
| expect(view.text()).toContain("Turn Timeline"); | ||||
| expect(view.text()).toContain("Latest Diff"); | ||||
| expect(view.text()).toContain("Stdout"); | ||||
| expect(view.text()).toContain("[S]teer"); | ||||
|
|
||||
| await view.unmount(); | ||||
| }); | ||||
|
|
||||
| it("sends a steer message from session detail and shows confirmation", async () => { | ||||
| const bridge = createMockBridge(); | ||||
| const view = await renderInk( | ||||
| React.createElement(AgentsScreen, { | ||||
| wsBridge: bridge, | ||||
| host: "127.0.0.1", | ||||
| port: 3080, | ||||
| sessions: sessionsFixture, | ||||
| stats: monitorStatsFixture, | ||||
| }), | ||||
| { columns: 220, rows: 60 }, | ||||
| ); | ||||
|
|
||||
| await waitFor(() => view.text().includes("Backoff queue (1)")); | ||||
| await view.press("`r", 80); | ||||
| await waitFor(() => view.text().includes("Session Detail")); | ||||
|
|
||||
| await view.press("s", 40); | ||||
| await waitFor(() => view.text().includes("Steer message:")); | ||||
| await view.press("Please continue with focused logging", 50); | ||||
| await view.press("`r", 100); | ||||
|
|
||||
| await waitFor(() => view.text().includes("Steer sent ✓")); | ||||
| expect(globalThis.fetch).toHaveBeenCalledWith( | ||||
| expect.stringContaining("/api/sessions/session-active-1/message?workspace=all"), | ||||
| expect.objectContaining({ method: "POST" }), | ||||
| ); | ||||
|
|
||||
| await view.unmount(); | ||||
| }); | ||||
|
|
||||
| it("scrolls the turn timeline independently inside session detail", async () => { | ||||
| const bridge = createMockBridge(); | ||||
| const view = await renderInk( | ||||
| React.createElement(AgentsScreen, { | ||||
| wsBridge: bridge, | ||||
| host: "127.0.0.1", | ||||
| port: 3080, | ||||
| sessions: sessionsFixture, | ||||
| stats: monitorStatsFixture, | ||||
| }), | ||||
| { columns: 220, rows: 28 }, | ||||
| ); | ||||
|
|
||||
| await waitFor(() => view.text().includes("Backoff queue (1)")); | ||||
| await view.press("`r", 80); | ||||
| await waitFor(() => view.text().includes("Session Detail")); | ||||
|
|
||||
| expect(view.text()).toContain("| 2026-03-23 00:00:00.000 |"); | ||||
| expect(view.text()).not.toContain("| 2026-03-23 00:00:17.000 |"); | ||||
|
|
||||
| await view.press("\u001b[6~", 80); | ||||
| await waitFor(() => view.text().includes("| 2026-03-23 00:00:17.000 |")); | ||||
|
|
||||
| await view.unmount(); | ||||
| }); | ||||
|
|
||||
| it("streams live stdout into the right panel while detail is open", async () => { | ||||
| const bridge = createMockBridge(); | ||||
| const view = await renderInk( | ||||
| React.createElement(AgentsScreen, { | ||||
| wsBridge: bridge, | ||||
| host: "127.0.0.1", | ||||
| port: 3080, | ||||
| sessions: sessionsFixture, | ||||
| stats: monitorStatsFixture, | ||||
| }), | ||||
| { columns: 220, rows: 60 }, | ||||
| ); | ||||
|
|
||||
| await waitFor(() => view.text().includes("Backoff queue (1)")); | ||||
| await view.press("`r", 80); | ||||
| await waitFor(() => view.text().includes("Session Detail")); | ||||
|
|
||||
| bridge.emit("logs:stream", { | ||||
| sessionId: "session-active-1", | ||||
| timestamp: "2026-03-23T00:00:31.000Z", | ||||
| stream: "stdout", | ||||
| line: "Steer message accepted by running session", | ||||
| }); | ||||
|
|
||||
| await waitFor(() => view.text().includes("Steer message accepted by running session")); | ||||
| await view.unmount(); | ||||
| }); | ||||
|
|
||||
| it("navigates app tabs with numeric key input", async () => { | ||||
| const bridge = createMockBridge(); | ||||
| const view = await renderInk( | ||||
|
|
@@ -151,3 +262,5 @@ describe("tui screen rendering", () => { | |||
| expect(bridge.disconnect).toHaveBeenCalled(); | ||||
| }); | ||||
| }); | ||||
|
|
||||
|
|
||||
|
Comment on lines
+265
to
+266
|
||||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test emits a
logs:streampayload with{sessionId, stream, line}, but the repo’s canonicallogs:streamevent contract requires{logType, raw, line, level, timestamp, filePath}(and forbids additional properties). The test should use the canonical shape (or, if the intent is to addsessionIdto the contract, update the schema + emitter and then align this test with that new contract).