feat: add Runtime read-only TUI#1802
Conversation
| @@ -0,0 +1,103 @@ | |||
| import { useNavigate } from "react-router"; | |||
There was a problem hiding this comment.
I wouldn't be against sorting these into folders since I could picture this getting unwieldy as time goes on. The reason I held off was because I didn't know if maybe we just sort the primitive ones or if maybe we just want to sort into a folder called "pickers".
There was a problem hiding this comment.
I actually prefer a flat directory of components! I tend to think a flat directory is easier to maintain over time and that it makes it easier to find what you're looking for.
| emptyPageMessage: string; | ||
| } | ||
|
|
||
| export function TokenPagedTablePicker<TItem, TRow extends Record<string, unknown>>({ |
There was a problem hiding this comment.
TokenPagedTablePicker centralizes the interaction state shared by Runtime and Harness resource pickers like terminal-derived page sizing, continuation-token history, loading/retry/empty states, page transitions, resize resets, filtering, selection, and keyboard navigation.
Resource-specific adapters still own their API calls, query keys, row mapping, columns, sorting, labels, selectors, and navigation destinations. This removes duplicated pagination state machines without forcing different resources into one data model, and gives the shared behavior one focused test suite.
| @@ -0,0 +1,7 @@ | |||
| export function withoutSdkMetadata(data: unknown): unknown { | |||
There was a problem hiding this comment.
This PR strips SDK $metadata at the Runtime handler boundary so both headless and TUI output remain resource-focused. I think we should decide whether transport metadata removal belongs in Core, JsonRenderer, or another shared normalization layer, then apply this contract consistently across resource families. What do you think, it could be done in this PR or a followup?
There was a problem hiding this comment.
I agree that we shouldn't show $metadata. Let's handle globally in a follow up.
There was a problem hiding this comment.
Makes sense. I’ll remove the Runtime-specific handling from this PR and address $metadata globally in a follow-up. I'll add a backlog item for it.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## refactor #1802 +/- ##
============================================
+ Coverage 94.08% 94.84% +0.75%
============================================
Files 130 143 +13
Lines 6631 7120 +489
============================================
+ Hits 6239 6753 +514
+ Misses 392 367 -25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
AlexanderRichey
left a comment
There was a problem hiding this comment.
Looks good! Blocking things are:
- Simplifying the column handling where
TokenPagedTablePickeris used. We might want to do something similar to what you have here in a later PR, but for now let's try to keep things as simple as possible. - Cleaning up some of the tests, particularly the ones for the
RouterScreen.
| showFooter = true, | ||
| emptyMessage = "No data", | ||
| focus = true, | ||
| selectionResetKey, |
There was a problem hiding this comment.
This handles terminal-height resizes. When pageSize changes, pagination resets but DataTable would otherwise retain its selected row and internal page. This resets those states while preserving filter and sort state.
| {searchMode ? ( | ||
| <Text> | ||
| <Text color={theme.colors.primary}>/ Filter: </Text> | ||
| <Text color={theme.colors.primary}>/ {searchPlaceholder}: </Text> |
There was a problem hiding this comment.
What makes changing this necessary?
| } | ||
|
|
||
| return keys.filter((_, index) => selected.has(index)); | ||
| } |
There was a problem hiding this comment.
These changes add a lot of complexity. I see the upshot, but I'm not sure that now is the time to add all this. Let me pull these changes down and test the behavior a bit.
| @@ -0,0 +1,103 @@ | |||
| import { useNavigate } from "react-router"; | |||
There was a problem hiding this comment.
I actually prefer a flat directory of components! I tend to think a flat directory is easier to maintain over time and that it makes it easier to find what you're looking for.
| }; | ||
| }} | ||
| toRow={toRow} | ||
| columns={(terminalColumns) => { |
There was a problem hiding this comment.
We're adding a whole lot of complexity here. I tend to think we shouldn't worry about this problem now. When we do take on this problem, we should solve it in a general way and not in a way that will require every user of DataTable to solve the problem again, as I think the current approach would.
| breadcrumb={breadcrumb} | ||
| keyHints={[ | ||
| { key: "↑↓/kj", label: "navigate" }, | ||
| ...(error && onRetry ? [{ key: "r", label: "retry" }] : []), |
There was a problem hiding this comment.
Should this be "retry" or "reload"?
There was a problem hiding this comment.
I intentionally used "retry" because this action only appears after a failed query and invokes refetch to retry that request. I could switch it to "reload" if that makes more sense or you think would be more consistent for other places where we may want to use the "r" letter.
| const showCreatedAt = terminalColumns >= 90; | ||
| const statusWidth = showStatus ? 20 : 0; | ||
| const createdAtWidth = showCreatedAt ? 30 : 0; | ||
| const versionWidth = Math.max(8, terminalColumns - 2 - statusWidth - createdAtWidth); |
There was a problem hiding this comment.
Same here. I like the idea of a component that handles this stuff, but I think we need to simplify the approach here a bit.
There was a problem hiding this comment.
The version picker now passes static columns without terminal-specific width calculations.
| agentRuntimeVersion: "7", | ||
| createdAt: new Date("2026-07-19T01:02:03.000Z"), | ||
| lastUpdatedAt: new Date("2026-07-20T12:34:56.000Z"), | ||
| roleArn: "arn:aws:iam::123456789012:role/runtime-role", |
There was a problem hiding this comment.
All of this stuff seems out of scope for RouterScreen tests. We should be testing the behavior of the router screen and not all this other stuff here.
| @@ -0,0 +1,7 @@ | |||
| export function withoutSdkMetadata(data: unknown): unknown { | |||
There was a problem hiding this comment.
I agree that we shouldn't show $metadata. Let's handle globally in a follow up.
f7c3938 to
8a45204
Compare
AlexanderRichey
left a comment
There was a problem hiding this comment.
This is looking good! Let's merge this one. We have one follow up to handle which is to get the column lengths right in the data tables. Let's discuss offline on a strategy for this.
| @@ -1,4 +1,4 @@ | |||
| import React, { useState } from "react"; | |||
| import React, { useEffect, useState } from "react"; | |||
There was a problem hiding this comment.
unrelated to change here but noticed the path has data-table. Should we swap to dataTable to be consistent in another PR?
| onEscape?: () => void; | ||
| } | ||
|
|
||
| // HarnessEndpointPicker fetches a harness's endpoints and renders them as a navigable |
There was a problem hiding this comment.
Patrick had a nice comment a bit ago about standardizing on TSDoc format. Do we think its worth trying to follow that standard?
I would recommend using TSDoc format consistently so you can get auto generated docs. I've spotted some functions throughout that just use //.
There was a problem hiding this comment.
Updated the exported Harness picker documentation to TSDoc for consistency. I left implementation-only comments as regular comments.
|
|
||
| return ( | ||
| <Layout | ||
| <TokenPagedTablePicker |
There was a problem hiding this comment.
I really like this abstraction btw. One nit is the name TokenPagedTablePicker feels kinda odd to me. What do we think of PaginatedTablePicker? (also open to original name if we think it makes more sense)
There was a problem hiding this comment.
I agree the name is currently a mouthful. Updating to your suggestion it is better.
432a397
8394594 to
432a397
Compare
Sounds good. For the follow-up, my initial direction is to make DataTable own terminal-aware column sizing rather than having each picker calculate widths. Column definitions could declare sizing intent such as minimum/maximum width and priority, while DataTable allocates the available width, truncates predictably, and hides optional columns only when necessary. We can align offline on the exact format and validate it. |
Description
Adds an interactive, read-only TUI for the Runtime control-plane commands introduced in #1797.
The Runtime TUI provides route-based flows for:
Runtime follows the established Harness interaction model. Bare Runtime groups and leaves open their corresponding TUI routes, operation flags keep commands headless, and
--jsonsuppresses interactive rendering. Nested endpoint and version flows preserve route history so Escape returns through the selectors and command menus in order.This change also introduces a shared token-paged table picker used by Runtime and Harness. It centralizes loading, empty, error, retry, filtering, responsive columns, transition locking, pagination, resize resets, and Escape behavior while keeping each resource's API calls, columns, row mapping, sorting, labels, and destinations in its adapter.
Runtime detail and headless output omit SDK
$metadata. The shared table filter accepts pasted text delivered as a single terminal input event.Runtime invocation, mutations, deployment or project resolution, logs, traces, and other resource families remain outside this change.
Related Issue
N/A
Documentation PR
N/A. README command and interaction documentation is included in this PR.
Type of Change
Testing
Verified from current HEAD
023f2af:bun test(319 passed, 0 failed)bun run typecheckbun run lint:checkbun run format:checkChecklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.