Repository navigation
feat: a thread's Environment browser tabs on T3 Code's server, Nová karta behind "+", and dragging (F47 step 4) - #52
Conversation
Backport of upstream 4107930 to v0.0.45, so a person can drag the right panel's tabs into the order they want, the overlay's surfaces like any other. It goes at the 0.0.46 refresh, where upstream owns it. Adapted to v0.0.45: its store has no updateThread(state, ...) yet, so moveSurface goes through automaticUpdate, which likewise leaves the user-action revision alone, and the sortable wrapper wraps v0.0.45's tab markup (its audio icon, rename ring and "+" condition stay). The two files the port adds to the overlay join the fork CI's allowlist. (cherry picked from commit 4107930) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
CHANGES_REQUESTED on exact head cd98b44. The CI checks are green and the isolated release-contract test passed 22/22. The holder format/keptTabs, route scopes and cookie-Origin boundary, t3-only cleanup call sites, and release-order documentation look aligned. I did not merge or run a browser canary. Three ordering gaps still violate the thread-tab contract:
-
[Blocking] Archive can resurrect a holder. In
apps/server/src/lazurio/threadTabs.ts:692-700,holdchecksretiredand awaitsliveThreadOfoutside the serialized file operation;recordhas the same split at :564-574. If retirement starts after that check, queuesremoveFile(:639-649), and then the pending hold queuesholdTab, the latter re-reads the missing file and writes the tab back (:318-333). The 30-second sweep only reconciles live tabs, not retired thread identities, so this can retain a tab after archive/delete. Put the live/retired fence in the same serialized critical section as the write (and cover both HTTP hold and agent record with a deferred-read race test). -
[Blocking] Undo and close can be reversed by their own retries. In
apps/web/src/lazurio/threadTabs.ts:550-563, a failed release schedules another POST without checking whetherrestoreTabs/holdTabhas since undone it (:617-637); the retry can release the just-restored hold. Conversely a failed hold scheduled at :511-523 is still sent after × has released the tab, and its successful response adds the closed surface again. Give each tab's pending operation a generation/cancellation fence and test transient failure → ×/Vrátit → delayed retry in both directions. The 7-second Undo currently cannot be trusted under ordinary transient network failures. -
[Blocking] Reject old snapshots before mutating a device's strip.
receiveSnapshot(:452-480) ignoresepoch/revisionordering, although the server supplies both. A POST response from an earlier hold can arrive after a later release SSE (e.g. another device closes it); it then restores the old tab indefinitely because unchanged periodic refreshes publish no replacement event. Track highest accepted revision per epoch, reconcile only newer snapshots, and test delayed POST response versus SSE and reconnect. This is needed for the stated same-tabs-on-every-device behavior.
Please repair these in the PR branch and request a fresh exact-head review after checks. The Platform-first → T3 canary → Matěj browser check → stable release order remains in force.
…file Each thread keeps its tabs of the Environment browser in the holder file the view's hub reads, $XDG_RUNTIME_DIR/lazurio-browser/holders/t3-<thread>.json (LazurioPlatform F47 points 4, 5 and 10), so a tab the thread shows stays alive and every device sees the same strip. Beside the ids the file keeps each tab's record time on the boot clock, so the server reconciles with the hub's tabs.json as the Launchpad does: at the start, on every change of the list, every 30 s and before every write. A write re-reads its file first and never writes over one it cannot read. T3 Code's database stays upstream's. The web reaches a thread's tabs at /api/lazurio/thread-tabs: a snapshot, a server-sent event stream (no-transform, a whole snapshot per event, another Environment's tab as a live-only event) and hold and release, authenticated as every other HTTP route is, with upstream's preview scopes. A tab joins the strip when the person holds it, when preview_open opens or adopts it (revealed unless open: false), and when the thread's own agent-browser session binds another tab. A snapshot names the other places that hold a tab and whether an agent works in it. At most 128 tabs per thread. holders.ts copies the Platform's helpers from src/browser/people/holds.ts at 49d54c9 and runs its vectors; it also carries the daemon stop the next commit uses. The overlay's server layers join upstream's routes through one line of server.ts. The service is off without an absolute XDG_RUNTIME_DIR or while the Environment declares no browser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stops its agent's daemon When a thread is archived or deleted, its holder file goes, so the hub ends every tab nothing else keeps, and the thread's agent-browser daemon is stopped: SIGTERM to the process its .pid names, only when that is a live process whose command line is agent-browser's, so a reused pid is never signalled. Tabs another thread or the Launchpad holds stay. At the start, the same happens for threads that are no longer live, including those archived while T3 Code was down, after command readiness so the projection has caught up; names outside t3- are never touched. An unarchived thread holds tabs again, and a renamed thread's file takes its new title as its label. threadLifecycle.ts is the one adapter to T3 Code's orchestration: it reads v0.0.45's domain events and the shape of upstream's orchestration v2, so the 0.0.46 refresh swaps only the stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…strip, not its activities preview_open of another Lazurio Environment's view now tells the thread's devices through the thread's tab stream, a live-only foreign event revealed unless open: false, as every tab preview_open opens or adopts here already joins the thread's holder file. So the previewTab seam in upstream's ActivityPayloadProjection.ts, which carried preview_open's tab to the web inside the thread's activities, goes with its test: the file is upstream's again, and the 0.0.46 refresh, where upstream deletes it, has one conflict fewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a Lazurio Environment the web client's right panel now shows the tabs the thread keeps on the server, the same on every device. It follows the thread's event stream and reconciles the strip to the list: new tabs at the end in the list's order, existing ones where each device has them (dragging stays each device's choice), another Environment's tabs each device's own. A page's new tab and a plain click on a link to a tab in the chat hold the tab for the thread. The person's x releases the thread's hold: a toast says whether the tab stays (an agent works in it, or Apps or another thread holds it) or ends, and Vrátit within the hub's grace holds it again; Close all is one toast. A tab whose view ended leaves at once. The panel opens by itself only on the server's new reveal request (preview_open, a new agent window), and only inline, no longer on every agent click. The strip marks a tab an agent works in or another place holds, and its tooltip and a line under the view say where else it is. Retired: browserUse.ts, the thread's own tab, the interim "+ Browser" new tabs at the view's bare origin, the web's copy of the session name, RightPanelTabs's titles prop and profile seam, and ChatView's Browser seams (Browser stays upstream's on the web). The store keeps one generic overlay action through upstream's automatic or user updates. The CI allowlist follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where the Environment's shell defines <lazurio-new-tab> and the server keeps the thread's tabs, "+" in the right panel adds a Nová karta tab and the empty panel shows Nová karta, instead of upstream's menu and list (LazurioPlatform F47 points 2 and 10). The element frames the Launchpad's /new-tab with the thread's strip. A page or a tab chosen there takes Nová karta's place, in place and active, and the thread holds it; a tab the strip has comes to the front instead. Its tools are upstream's own "+" actions without Browser, which the address bar replaces: each runs in Nová karta's place, also by its letter while Nová karta is shown. Elsewhere (an Environment without the element, the desktop app, a thread of another environment) upstream's "+" menu and empty state stay; no fallback is carried. LazurioNewTab.tsx is Nová karta; RightPanelTabs reaches it, as the strip's words, through LazurioEnvironmentBrowser.tsx, so its seams are the hook's actions and two conditionals. The CI allowlist follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ová karta The release contract now holds task 501's overlay to its seams, so a rebuild on a new upstream tag that loses one fails: the thread's holder file behind its own route, with upstream's preview scopes and an event stream that is never compressed or buffered, and the Platform's helpers copied with their source named; the host recording preview_open's tabs in the thread's strip and the session's new bindings; the panel opening only on the server's reveal and the chat's links; the store's one generic action and one surface member; RightPanelTabs's one import, hook and two Nová karta conditionals; ChatView's one hook call and wrapper; and the element's name and tools. The checks of the retired paths (browserUse.ts, the own tab, the interim new tab, the web's session name and the previewTab seam) go, and the allowlist check follows the new files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unbook The Overlay table gains the holder-file, archive, Nová karta and drag-port rows, renames the right panel's row to the thread's tabs and changes the browser tools' row; the rows of the panel that opened on every agent click and of the interim "+ Browser" go. "Prohlížeč Environmentu" now describes the thread's tabs (holder file, reconciliation, route, reveals, x with Vrátit, the shared hint), Nová karta and archiving, with the seam paragraph matching the overlay. A new paragraph ties lazurio.8 to the Platform release with F47 steps 1-3 and fixes the order (Platform, preview canary, Matěj's browser check, stable), and "Testování před vydáním" gains that browser check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@agentrozjedemeai All three ordering blockers from your review of cd98b44 are fixed in 02db3af. Each fix is folded into the commit it belongs to (autosquash), so the history is still the 8 semantic commits; I pushed with an exact lease. Re-review requested once CI is green.
Each new test fails with the pre-fix implementation swapped in. The runbook gained four sentences on these contracts. Checks re-run by me on 02db3af:
The implementing agent also ran |
cd98b44 to
02db3af
Compare
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty APPROVED for exact head 02db3af (base 36d4868). The three prior ordering blockers are addressed: archive/live-thread fencing now happens in the serialized holder write with deferred-read races; per-tab intent generations cancel superseded hold/release retries and answers; per-epoch snapshot revisions reject delayed older POST answers after SSE without losing the current reconciliation. I checked daemon stop is limited at its call sites to t3- sessions and verifies a live agent-browser executable before SIGTERM; the route uses upstream preview scopes, EnvironmentAuth cookie POST Origin, and uncompressed SSE. The copied holder rules (including keptTabs/boot clock/fail-closed read), release semantics, upstream seams, allowlist and F47 release-order runbook are consistent. My isolated checkout passed server lazurio 90 (12 DevTools skips), web lazurio 69, rightPanelStore 68, release contract 22/22, server/web typechecks, formatting and git diff --check; exact-head CI is green, zero review threads. Browser canary remains for F47 step 5. Do not merge yet: Platform steps 1–3 release first, then T3 preview canary and Matěj browser check before stable 0.0.45-lazurio.8.
|
Cleanup: the local worktree DEV-6646-thread-tabs was removed on Matěj's Mac after verifying every commit is on main (git cherry) and the remote branch holds the same head (02db3af). |
Why
F47 step 4 (DEV-6646 task 2026-10-11-501). T3 Code's right panel shows ONE thread's Environment browser tabs, the same on every device. "+" opens the Platform's Nová karta. Archiving or deleting a thread closes its tabs. Matěj's decisions:
Platform steps 1–3 (Lazurio/LazurioPlatform#316, pingdotgg#318, pingdotgg#324, all approved) and pingdotgg#333 provide what this builds on: the hub reading holder files,
tabs.json,lazurio-browser:closed,/new-taband<lazurio-new-tab>.What changes (8 commits, rebase-merge)
Drag to reorder the right panel's tabs: a backport of upstream 4107930 (feat(web): reorder right panel tabs by dragging pingdotgg/t3code#17730), with its author kept. It goes away at the 0.0.46 rebuild, where upstream owns it. No new dependency (
@dnd-kit/*is already inapps/web).A thread's tabs live in its holder file.
apps/server/src/lazurio/holders.tsis a copy of the Platform's helpers at 49d54c9, tested with the Platform's vectors.threadTabs.tskeeps the tabs in$XDG_RUNTIME_DIR/lazurio-browser/holders/t3-<thread>.json, withrecordedAton the boot clock:tabs.jsonbykeptTabsat start, on change, every 30 s and before every write;T3 Code's database and migrations stay upstream's. The route
/api/lazurio/thread-tabsserves a snapshot, SSE withno-transform, and hold/release, using upstream's preview scopes (requiredScopeForRpcMethod). There is no RPC change.Archive and delete let go of the thread's tabs and stop its agent-browser daemon: SIGTERM only to a live process whose
/proccommand line is agent-browser. The daemon disconnects without closing a tab, and T3 Code never closes a target. A small adapter reads both orchestrations' events, and a startup sweep catches threads archived while the server was down.preview_open's tabs reach the web through the thread's strip, not its activities.ActivityPayloadProjection.tsis byte-equal to upstream again.The web strip follows the server's list:
browserUse.tsand its test, the own-tab singleton, the interim "+ → Browser", and the web copy of the session name."+" and the empty panel open Nová karta (
<lazurio-new-tab strip tools>, with upstream's "+" actions as T3's tools). A choice turns the tab into the thread's browser tab or into a T3 surface.The release contract test for the new seams.
The runbook:
Upstream seams (overlay lines, port excluded):
RightPanelTabs.tsxrightPanelStore.tsChatView.tsxserver.tsActivityPayloadProjection.tsOverall, the web overlay shrinks by about 1,500 lines.
What doesn't change
packages/contract change, no migration.Verification
holders.test.ts(18, the Platform's vectors),threadTabs.test.ts(32, including F47's nine reconciliation cases),threadTabsRoute.test.ts(5),threadLifecycle.test.ts(2);threadTabs.test.ts(19),LazurioNewTab.test.tsx(8);moveSurfacetest.src/lazurio: 87 pass, 12 skipped (DevTools suites without an endpoint);src/lazurio: 65 pass;rightPanelStore.test.ts: 68 pass;vp lint(0 errors; the 89 warnings are pre-existing in upstream files),vp fmt --check,src/server.test.ts(212), and per-commit typechecks.Risks
🤖 Generated with Claude Code