authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-24 14:01:47+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-24 14:01:47+02:00
logf7cc7764887a3c0616973043fa77e12671e3f9ef
tree00e6c1244b3a0e63bd2f65557471c2faba621d43
parent15e875a2d9bdda2fc08909c4b1582a363d288403
signature Signed by PGP key B5690EEEBB952194

Restore model picker layout and retain iterative test state (#4450)

Co-authored-by: codex <codex@users.noreply.github.com>

5 files changed, 39 insertions(+), 13 deletions(-)

.agents/skills/test-t3-app/SKILL.md+21-3
......@@ -1,6 +1,6 @@
11---
22name: test-t3-app
3description: Launch and test the T3 Code web app in isolated development environments, including first-try browser authentication with one-time pairing URLs, pairing-token recovery, worktree-safe state directories, dev server lifecycle, and direct SQLite inspection or fixture seeding. Use when an agent needs to run T3 locally, test UI behavior in a browser, recover from an expired or consumed pairing token, isolate dev state, or prepare test data in state.sqlite.
3description: Launch, retain, and test the T3 Code web app in isolated development environments, including first-try browser authentication with one-time pairing URLs, pairing-token recovery, worktree-safe state directories, cross-turn dev server lifecycle, and direct SQLite inspection or fixture seeding. Use when an agent needs to run T3 locally, iteratively test UI behavior with a human, recover from an expired or consumed pairing token, isolate dev state, or prepare test data in state.sqlite.
44---
55
66# Test T3 App
......@@ -20,6 +20,16 @@ Treat a base directory as disposable only when it was created or deliberately se
2020
2121The dev runner disables browser auto-open by default. Do not pass `--browser` during automated testing: an automatically opened page can consume the one-time bootstrap token before the controlled browser uses it.
2222
23## Preserve the environment while iterating
24
25Treat the overall testing or implementation loop—not an assistant turn or one verification pass—as the environment lifecycle boundary.
26
27- Keep the dev process, base directory, selected ports, authenticated browser tab, registered projects, and seeded fixtures alive while the user may inspect the result or request follow-up changes.
28- Do not stop the server merely because one verification pass completed or because you are yielding a response to the user.
29- Before starting another environment, check whether the existing process and browser tab still serve the task. Reuse them when healthy instead of discarding useful state.
30- On a later turn, verify that the existing process is alive and reuse its printed ports and base directory. If it exited, restart with the same base directory; create a new pairing token only when the browser session is no longer valid.
31- Tell the user when a test environment remains available, including its non-secret web URL when useful. Never include a pairing token.
32
2333## Authenticate the browser on the first navigation
2434
25351. Wait for the server log that says authentication is required and includes a URL ending in `/pair#token=...`.
......@@ -58,9 +68,17 @@ Read [references/sqlite-fixtures.md](references/sqlite-fixtures.md) before chang
5868
5969The helper refuses to write to the shared `~/.t3` directory by default and creates a database backup before each mutation.
6070
61## Finish the test
71## Tear down only when the testing loop is finished
72
73Tear down when the user explicitly asks, confirms the iteration is finished, or the overall task is genuinely complete with no pending human review. Do not infer completion from the end of an assistant turn.
74
75When teardown is appropriate:
76
771. Stop the dev process with its terminal interrupt.
782. Preserve the isolated base directory when it contains useful reproduction evidence or state for a likely follow-up.
793. Otherwise remove only a path created for this test after resolving and verifying the exact target.
6280
63Stop the dev process with its terminal interrupt. Preserve the isolated base directory when it contains useful reproduction evidence; otherwise remove only a path that was created for this test after resolving and verifying the exact target. A fresh isolated base directory is the safest reset when authentication, migrations, or fixture state becomes ambiguous.
81If completion is uncertain, keep the environment alive and mention that it is retained for further iteration. A fresh isolated base directory remains the safest reset when authentication, migrations, or fixture state becomes ambiguous.
6482
6583## Troubleshoot predictably
6684
.agents/skills/test-t3-app/agents/openai.yaml+2-2
......@@ -1,4 +1,4 @@
11interface:
22 display_name: "T3 App Testing"
3 short_description: "Launch and seed isolated T3 test environments"
4 default_prompt: "Use $test-t3-app to launch an isolated T3 development environment and test it in the browser."
3 short_description: "Launch and retain isolated T3 test environments"
4 default_prompt: "Use $test-t3-app to launch an isolated T3 environment and iteratively test it in the browser while preserving state."
apps/web/src/components/chat/ModelPickerContent.tsx+10-5
......@@ -521,7 +521,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
521521 return (
522522 <TooltipProvider delay={0}>
523523 <div
524 className="dropdown-glass model-picker-surface relative flex h-screen max-h-96 w-screen max-w-100 flex-row overflow-hidden rounded-xl text-popover-foreground"
524 className="dropdown-glass model-picker-surface relative flex h-screen max-h-96 w-screen max-w-100 flex-row overflow-hidden rounded-lg text-popover-foreground"
525525 data-model-picker-content="true"
526526 >
527527 {/* Sidebar */}
......@@ -568,18 +568,23 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
568568 handleModelSelect(slug, instanceId);
569569 }}
570570 >
571 <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
571 <div
572 className={cn(
573 "flex min-h-0 flex-1 flex-col overflow-hidden bg-muted/40",
574 showSidebar && "border-l",
575 )}
576 >
572577 {/* Search bar */}
573578 <div className="px-4 pt-2.5">
574 <div className="-translate-y-px rounded-lg bg-accent px-2 py-1">
579 <div className="-translate-y-px border-b border-border/70 pb-2.5 transition-colors focus-within:border-ring">
575580 <ComboboxInput
576581 ref={searchInputRef}
577582 className="[&_input]:h-6.5 [&_input]:font-sans [&_input]:leading-6.5"
578 inputClassName="rounded-none bg-transparent text-sm placeholder:text-muted-foreground/80"
583 inputClassName="rounded-none bg-transparent text-sm"
579584 placeholder="Search models..."
580585 showTrigger={false}
581586 startAddon={
582 <SearchIcon className="-translate-x-0.5 size-4 shrink-0 text-muted-foreground/80" />
587 <SearchIcon className="-translate-x-0.5 size-4 shrink-0 text-muted-foreground/55" />
583588 }
584589 value={searchQuery}
585590 onChange={(e) => setSearchQuery(e.target.value)}
apps/web/src/components/chat/ModelPickerSidebar.tsx+5-2
......@@ -97,7 +97,10 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: {
9797 }, [props.instanceEntries, props.selectedInstanceId, showFavorites]);
9898
9999 return (
100 <div className="w-12 shrink-0 overflow-hidden bg-muted/60" data-model-picker-sidebar="true">
100 <div
101 className="w-12 shrink-0 overflow-hidden border-r bg-muted/30"
102 data-model-picker-sidebar="true"
103 >
101104 <div className="h-full overflow-y-auto overscroll-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
102105 <div
103106 ref={sidebarContentRef}
......@@ -115,7 +118,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: {
115118 ) : null}
116119 {/* Favorites section */}
117120 {showFavorites ? (
118 <div className="mb-1 pb-1">
121 <div className="mb-1 border-b pb-1">
119122 <div className="relative w-full">
120123 <Tooltip>
121124 <TooltipTrigger
apps/web/src/components/chat/ProviderModelPicker.tsx+1-1
......@@ -188,7 +188,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
188188 <PopoverPopup
189189 align="start"
190190 className="border-0 bg-transparent p-0 shadow-none before:hidden [--viewport-inline-padding:0]"
191 viewportClassName="!overflow-hidden p-0"
191 viewportClassName="rounded-lg !overflow-hidden p-0 [clip-path:inset(0_round_var(--radius-lg))]"
192192 >
193193 <ModelPickerContent
194194 activeInstanceId={activeInstanceId}