Skip to content

Experiment: Chat in the design system's clean look (not for release) - #180

Draft
immakermatty wants to merge 34 commits into
mainfrom
lazurio/experiment-ds-skin
Draft

immakermatty wants to merge 34 commits into
mainfrom
lazurio/experiment-ds-skin

Conversation

@immakermatty

@immakermatty immakermatty commented Nov 5, 2026 •

Copy link
Copy Markdown

Experiment, není k Publikaci. Parkováno (Matěj, 2026-11-05): změny jsou zatím malé, přednost má levý sloupec shellu. Draft zůstává otevřený jako podklad. Nemergovat, nevydávat, nenasazovat. Poznatky a cesty dál drží #181.

Proč

Na Prostředí Lazuria má Chat (T3 Code) od #66 a #82 barvy design systému, ale tvary, písmo a hloubku pořád z T3. Proto vedle Apps pořád „vypadá jako T3“. Experiment zkouší, jestli jde Chat obléknout do vzhledu design systému tak, aby se UX ani mechanika nezměnily a aby se kabátek dal levně udržet proti upstreamu.

Co se mění

Na Prostředí, ve standardním vzhledu T3 (téma Prostředí, rozhodnutí 0200 a 0208) a jen tam, platí Čistý vzhled design systému (HumanAndMachine-ai/design-system-lazurio, 2026-10-21):

  • Tlačítka, jednořádková pole, selecty, skupiny polí, odznaky a segmenty jsou pilulky. Ikonová tlačítka jsou čtverce 6 px.
  • Composer, dialogy, ⌘K a karty mají rohy 10 px. Stupnice zaoblení T3 jde z jednoho tokenu (--radius), takže sm je 6 px a lg 10 px jako v design systému.
  • Composer má vlasovou linku design systému bez stínu. Jediný stín (--lz-shadow-float) mají plovoucí vrstvy: menu, popover, select, tooltip, dialog a toast.
  • Nadpisy (markdown v odpovědích, dialogy, prázdné stavy) jsou Inter Tight 600 se sledováním --lz-track-title. Text se řídí --lz-font-sans shellu: s tokeny z main design systému je to Inter.
  • Vybrané vlákno v postranním panelu je tichá plocha o stupeň tmavší, ne vyvýšená karta.

Co se záměrně nemění

Chování, pořadí, texty, velikosti a hustota zůstávají T3. Nic se neskrývá. Značka T3 Code se štítkem Lazurio zůstává. Bez shellu Lazuria vypadá T3 přesně jako upstream a s osobním tématem T3 jako to téma. Obojí je ověřené v prohlížeči: s tématem Grove má composer upstreamových 16 px a systémové písmo, bez /.lazurio/shell.js platí hodnoty upstreamu. Tmavý režim tématu Lazurio zůstává paletou T3.

Mechanismus

  • Jeden soubor apps/web/src/lazurio/skin.css se stejnou podmínkou jako theme.css (značka shellu, žádné vybrané téma T3), importovaný hned po něm v main.tsx. Má 14 pravidel. Míří jen na háčky, které T3 sám renderuje (data-slot, data-*, ARIA, [data-sidebar-row], třída .chat-markdown), nikdy na utility třídy Tailwindu. Hodnoty jsou tokeny --lz-*.
  • Jeden šev v upstream TSX: components/ui/button.tsx (+3 řádky) dává každému Buttonu data-size a data-variant. Zjištění z experimentu: data-slot="button" většině tlačítek přepíše obalující trigger (tooltip-trigger, menu-trigger, popover-trigger), takže na něj skin cílit nemůže. Size a variant přežijí, a tím se odliší pilulka od ikonového čtverce. Upstreamové input.tsx už data-size takhle nese, takže šev jde navrhnout i upstreamu.
  • Guard forku: button.tsx přibyl do allowed_upstream_changes v lazurio-fork-ci.yml s důvodem.
  • Kontrakt: nový test v scripts/lazurio-release-contract.test.mjs hlídá import, šev, podmínku na každém selektoru, háčky bez utility tříd, které tokeny T3 smí skin nastavit a číst, zákaz barevných literálů a !important, a výslovný seznam tokenů s fallbackem (níž).

Co by musel slíbit shell

Skin čte tokeny, které shell dnes deklaruje, ale neslibuje (declares.tokens, LazurioPlatform src/shell/interface.ts), nebo je vůbec nemá. Experiment je proto nese s lokálním fallbackem:

Token Dnes ve shellu Fallback ve skinu
--lz-radius-md deklarovaný, neslíbený 10px
--lz-radius-pill deklarovaný, neslíbený 999px
--lz-shadow-float deklarovaný, neslíbený none
--lz-font-heading chybí (vendored tokeny jsou z doby před Čistým vzhledem) --lz-font-sans
--lz-track-body, --lz-track-title deklarované se staršími hodnotami -0.011em, -0.015em
--lz-weight-akce, --lz-weight-title deklarované (title 700, DS dnes 600) 600

Dokud Platforma nepřevezme tokeny Čistého vzhledu, bude Chat s tímto skinem psaný v Inter Tight (dnešní --lz-font-sans shellu) s nadpisy 700.

Rizika pro rebuildy

  • Šev v button.tsx je jeden blok ve defaultProps. Konflikt hrozí, jen když upstream přepíše právě tyto řádky.
  • Skin závisí na jménech data-slot a na tom, že T3 bere zaoblení z --radius a --control-radius. Přejmenování slotu nebo pevný poloměr v upstreamu skin tiše minou: vzhled se vrátí k T3, nic se nerozbije. Při rebuildu proto stačí projít screenshoty hlavních obrazovek.
  • Pravidla bez !important vyhrávají jen proto, že overlay CSS je mimo @layer Tailwindu. Kdyby se theme.css/skin.css někdy importovaly do vrstvy, přestanou platit.

Ověřeno

  • 2026-11-05 večer přeneseno na nový main po rebuildu na upstream v0.1.7 (Rebuild the Lazurio overlay on upstream T3 Code v0.1.7 #174) jediným commitem bez konfliktu; kontrakt 52 z 52 a vp run check:changed (format, lint, knip, typecheck, react compiler) na novém základu znovu prošly. Screenshoty níž jsou ze základu před rebuildem.
  • Vlastní instance T3 z této větve (produkční build webu, vlastní --base-dir, loopback, syntetický projekt „Demo shop“ a vlákna z vývojového provideru Replay) se skutečným /.lazurio/shell.js z Platformy main (scripts/preview-launchpad.ts se syntetickou složkou). Varianta „tokeny DS main“ má v lokální kopii Platformy vendored tokens.css nahrazené tokens.css z main design systému. Do Platformy ani do design systému se nic nezapisovalo.
  • Screenshoty 1440×900, světlý režim, headless Playwright: nové vlákno a výběr modelu, vlákno s kódem, tabulkou a tool calls, diff, postranní panel, kontextové menu, dialog, ⌘K a Nastavení, před a po. Pravý panel prohlížeče Prostředí v sadě chybí: potřebuje sdílený Chromium Prostředí s agent-browser, který na loopbacku synteticky nejde. Skin v něm mění jen tlačítka panelu, stejně jako jinde.
  • node --test scripts/lazurio-release-contract.test.mjs: 52 z 52. vp run check:changed: format, lint, knip, typecheck a react compiler projdou. Test apps/server/src/provider/AgentContextBudget.test.ts lokálně padá stejně i na čistém origin/main, takže s touto změnou nesouvisí.

Screenshoty jsou ve vlákně, které experiment připravilo. Na GitHub je nenahrávám, protože by to znamenalo commitnout soubory jen pro PR.

Navazující issues


Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread b2ba4875-1104-4818-a273-d3ea899738dc

🤖 Generated with Claude Code

immakermatty and others added 8 commits November 4, 2026 13:36
T3CODE_CLIENT_SESSION_TTL sets the default lifetime of newly issued client
sessions, so a hosted Environment's paired browser stays signed in longer than
upstream's fixed 30 days. The value is a positive duration such as 365d; an
invalid one fails config resolution. The seam in SessionStore stays one line.

On v0.1.3: upstream added privateNetworkOnly next to the row's config fields
(pingdotgg#24588); both stay. The config test now uses upstream's minimalWebFlags helper.

On v0.1.4: unchanged; the seam applies cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T3CODE_EXTERNAL_ORIGIN names the canonical HTTPS origin of a server that
listens on loopback behind the Environment's gateway. Auth then treats it as
remote-reachable (one-time-token pairing only) and issues a Secure, host-only
__Host- session cookie that sibling subdomains cannot set or overwrite. The
value must be a bare https origin; anything else fails config resolution.

On v0.1.3: the row's externalOrigin fields sit beside upstream's new
privateNetworkOnly (pingdotgg#24588); both stay. The auth seams are unchanged. Since
v0.1.3 (pingdotgg#24588) a launcher-managed server without a host listens on 0.0.0.0, so the
Environment's explicit T3CODE_HOST=127.0.0.1 is now load-bearing. The config
test uses upstream's minimalWebFlags helper.

On v0.1.4: unchanged; the seam applies cleanly.

On v0.1.7: upstream moved the policy choice into resolveServerAuthPolicy(),
which the pre-bind managed reachability check now calls as well. The
external-origin condition moves into that resolver, so the managed limits see
the same remote-reachable policy as auth. The hosted cookie test takes
SecureRandom from NodeCrypto (Effect 4.1.2 dropped effect/Crypto) and the
ManagedPolicy test layer the auth routes now require.

Upstream's thread links for outside MCP clients now open through
T3CODE_HOSTED_APP_URL, app.t3.codes by default (pingdotgg#26290), where v0.1.6 used
the request's own origin. Behind an external origin people open threads on
that origin, so the MCP authenticator links there instead, and
hostedThreadLinks.test.ts fails without it. Without an external origin the
link is upstream's. The authenticator reads ServerConfig as optional, so
its layer's requirements stay upstream's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T3CODE_ENVIRONMENT_LABEL, when set, is the Environment's label ahead of the
pretty hostname and the hostname, so a hosted Environment shows the name its
Organization gave it rather than the VM's hostname.

On v0.1.3: ServerEnvironmentLabel.ts is unchanged upstream; the hunk and its
overlay test replay as they were.

On v0.1.4: unchanged; the seam applies cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T3CODE_RELEASE_REPOSITORY (owner/name) selects whose GitHub Releases the
release index, `t3 update`, the boot service, remote self-update and the
install scripts use; unset, the Lazurio distribution installs and updates from
Lazurio/t3code. A server run by the service launcher checks its own channel
there at startup and every six hours (never when unmanaged, on preview builds,
or with T3CODE_UPDATE_CHECK_ENABLED=false) and advertises a strictly newer
release as `availableServerUpdate`. The index picks the highest version per
channel, not the first one published. Once the launcher has committed this
server's version, the server points the user's `t3` link at its own runtime,
so the CLI and the server stay on one version after an in-app Update.

On v0.1.3 the server, CLI and boot-service seams are unchanged (launcher
protocol 3). Upstream removed the composer's "Server update available" notice
(pingdotgg#25401) and now offers updates from the update button at the bottom of the
sidebar and Settings -> Environments. The advertised release therefore moves
onto that button: collectServerUpdateTargets takes a server's advertised
release as its target (it is strictly newer by construction, so the
version-core comparison, which ignores a fork's prerelease suffix, is skipped),
and useUpdateEverything no longer holds such a target until it is on npm,
because the release lives in the server's own channel. The composer keeps only
the running/failed notice; its Retry targets the advertised version. The
install scripts are rebuilt on upstream's new lookup (pingdotgg#22907): stable reads
github.com/<repo>/releases/latest, the other trains page the API up to ten
pages, and each answer is filtered by the runtime's train rule instead of
upstream's narrow vX.Y.Z pattern, which rejects X.Y.Z-lazurio.N.
install-scripts.test.ts now serves releases/latest (with its Accept header)
and two API pages, and checks which URLs each train reads. Overlay comments
are cut to the new four-line limit.

On v0.1.4 the client no longer asks a server to continue its threads
(continueRunningThreads is gone from the web, pingdotgg#26238): a server prepares
the continuation itself on every graceful stop. The five web seams that
passed the request along keep only the overlay's targetVersion. The update
dialog shows one row per machine (pingdotgg#26210); a server whose target differs
from the version in its header shows its own targetVersion (servers offer
their channels' releases, stable or preview), and the dialog promises that
threads may continue only to a server with the threadRestartContinuation
capability from 0.0.46-0 (SHUTDOWN_CONTINUATION_SINCE in
updateEverything.logic.ts), since older releases continued only on the
client's request. watchAvailableServerUpdate gets FetchHttpClient.layer
inside ServerEnvironment's make, so CLI commands that build the layer need
no HTTP client of their own. That provision shadowed the test layer's
HTTP client, so the guard of ServerEnvironment.test.ts now stubs
FetchHttpClient.Fetch, lets the forked check run and expects no request
from a web or desktop server; forcing managed: true makes it fail.
releaseIndex.test's cases count the requests the same way, with a
managed stable server as the case that does ask, so dropping the
unmanaged early return fails them too. Upstream's own installer test
(install.test.ts, pingdotgg#22490) now names pingdotgg/t3code in
T3CODE_RELEASE_REPOSITORY, because its fixture serves upstream's
repository; without it the installer asks for Lazurio/t3code and exits.
The v0.1.3 line missed this because the fork CI ran only its fixed gate
and changed tests, and install.test.ts was neither. The launcher
protocol stays 3.

On v0.1.5 upstream adds switching between Stable and Nightly with a
database migration check (pingdotgg#26288). On v0.1.6 the web no longer waits for
a release to reach npm (pingdotgg#27847), so the overlay's exception for an
advertised target is gone and useUpdateEverything.ts stays upstream's;
serverUpdateOffer.test.ts covers the advertised target. Upstream's new
"Registries and proxies" section in docs/user/updating.md gets one
sentence that a Lazurio server installs from its own channel. The service
unit takes upstream's memory limits (pingdotgg#27176) unchanged. The track
switch stays upstream's:
a Lazurio web client hides it and crossesReleaseTrack does not treat
Lazurio's channels as a switch. docs/user/updating.md says the
distribution has no Nightly, so a desktop's switch to Nightly fails for
a Lazurio server when it downloads the release and the server keeps the
one it runs (#148).

On v0.1.7 upstream adds an organization update mirror to the managed
policy (pingdotgg#27197, pingdotgg#29911): t3 update and remote self-update download from the
mirror's feed URL and keep a cached runtime only while the mirror still
lists it. The release repository applies where no mirror is set:
cliReleaseAssetUrl, upstream's new asset URL helper, takes the repository
beside the base URL, so the checksums and the archive come from the
configured channel, and a mirror still wins. Upstream also classifies an
unpublished release itself (isUnpublishedRelease, pingdotgg#29911), so the overlay's
PinnedRuntimeReleaseNotPublishedError is gone; without a mirror both
callers now say the version is not published in the repository's
releases, and with a base URL they name the setting rather than the URL,
whose query can hold a token. repointLauncher now takes the T3 home and
records the launcher before it moves (pingdotgg#24289), so launcherLink passes
baseDir and prune keeps the runtime the repointed link runs. selfUpdate.test.ts
refuses an unpublished version through a base URL with a token in its
query: the reason names T3CODE_RELEASE_BASE_URL and the request goes to
the base URL, and it fails when the base URL does not reach
cliReleaseAssetUrl. The update test keeps upstream's Duration import
beside the page size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Launchpad's "+ Nový modul" opens Chat with
#lazurio-prompt=<id>&lazurio-org=<login> in the fragment. main.tsx takes it off
the address before the router reads it; once the primary environment is live,
the text and project folder come from this origin's /.lazurio/prompts/<id>,
and a new thread opens in that project (in a managed Environment, its Lazurio
Folder project) with the text in the composer, never sent. Upstream's
/new?prompt= links carry the text in the address, so the overlay keeps its own.
The tab keeps only the id and the login across the pairing reload, never the
text; it is taken once and expires after two minutes.

On v0.1.3 the router, history and provider-auth delivery moved from main.tsx
into startAppServices() in appInit.tsx (pingdotgg#25004), so the capture now runs on
the line before `const router = startAppServices()`, still before any router
exists; it now also runs before provider-auth delivery, which acts only on
its own fragment. ChatRouteLayout was rewritten for Settings and Usage as
modals over the thread (pingdotgg#24847); <LazurioPromptDraft /> is its first child and
stays mounted while a modal is open. The component's one effect carries a
justified suppression of t3code/no-unreasoned-state-sync and its comments are cut to four
lines. It imports prefersCzech from row 9's threadTabs.ts and reads
lazurioFolder from row 15's server config, so this row alone does not
typecheck.

On v0.1.4: upstream deleted lib/projectPaths.ts (pingdotgg#26238); the draft reads findProjectByPath and inferProjectTitleFromPath from @t3tools/client-runtime/state/projects. Upstream renamed the lint rule react-effect-budget to t3code/no-unreasoned-state-sync (pingdotgg#26161), and the suppression carries the new name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
index.html loads the Environment's Lazurio shell (/.lazurio/shell.js, served
by the Launchpad and absent outside Lazurio) and places its <lazurio-rail>
beside #root and <lazurio-buddy> after it. #root is padded by the rail's
width, T3's fixed-position sidebar is contained beside the rail, and the
shell's colour roles derive from T3's own sidebar tokens so the shell follows
every T3 theme. SidebarChromeHeader now renders the original top row and,
under it, the shell's <lazurio-column-head>; undefined, the element renders
nothing. Outside Buddy's bubble the Chat composer keeps the corner of
Buddy's launcher free: while the shell draws the launcher,
--lazurio-buddy-clearance on the document is the width it takes from the right
edge, and the composer stack moves left, then narrows, just enough
(LazurioPlatform F60, addendum of 2026-10-25). Unset, the composer stays where
T3 puts it.

On v0.1.3 both files merge cleanly: the new boot-error screens (pingdotgg#25317,
pingdotgg#25345), the Inter font (pingdotgg#24865) and the sidebar footer changes (pingdotgg#25270) do
not touch the seams. Settings is now a modal over the thread (pingdotgg#24847) and no
longer renders its own column head; the thread and legacy sidebars still do.

On v0.1.4: unchanged; the seams apply cleanly. The launcher clearance comes
back to this row from row 28's commit, which carried it on v0.1.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…session

On a Lazurio Environment every thread's agents work in the shared Environment
browser through the agent-browser CLI, each thread in its own window. The CLI
takes its session from AGENT_BROWSER_SESSION, so every process T3 starts for
a thread carries the thread's session name: agent processes through a
decorator around the server's AgentScope, Codex threads through their thread
config (one app-server serves every thread), and a thread's terminals through
the environment their shells start with. The variable only names the session.

On v0.1.3 the three seams apply unchanged. AgentScope gained `stop` and a
launch `unit` (pingdotgg#21299); the decorator spreads the inner scope and the launch,
so upstream's reaping of an exited agent's systemd scope still receives its
unit. The test mocks get `stop`, and the pass-through test now also checks
that the decorated scope forwards `stop`. The terminal seam sits above
upstream's shortened managed-install comment. Overlay comments are cut to the
four-line limit.

On v0.1.4: the Claude runner needs PortScanner.PortDiscovery since pingdotgg#22542, so the overlay's Claude test provides PortScanner.layerTest. pingdotgg#23913 only adds Delegate=yes to the agent scope; the AgentScope seam is unchanged.

On v0.1.6 the agent scope gains kill, a thread's Force stop (pingdotgg#27252). The
decorator spreads upstream's scope, so kill passes through as stop does;
the test now checks both, and the recording scope stubs kill.

On v0.1.7 effect/Crypto is gone and makeCodexAdapterV2 takes a Digest in
its place; the app-server client factory also needs PortDiscovery and a
spawned command's args are Words. The tests pass Digest, provide
PortScanner.layerTest and NodeCrypto beside NodeServices, and read the
args as strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a Lazurio Environment the web client has no browser of its own: one shared
Chromium runs on the Environment, and its view on the gateway shows one remote
tab per address. The right panel shows the thread's tabs from the server's
list (an event stream from the thread's holder file), each in a frame that
stays mounted for instant switching; a new reveal opens the inline panel, a
person's × lets go of the tab with Vrátit, and a plain click on a remote-tab
link in the chat opens it in the panel. Another Lazurio Environment's tab
opens as a tab of its own with a coloured border. Upstream's own server
browser stays hidden there because the server omits capabilities.serverBrowser
(row 10). The seams are one hook call and one wrapper in ChatView, the strip's
title, icon, tooltip and menu in RightPanelTabs, and the surface kind in
rightPanelStore.

On v0.1.3 RightPanelTabs and rightPanelStore merge cleanly (pingdotgg#25464 moved the
tab-list listeners, storage version is still 14). ChatView's only conflict is
the import list next to upstream's new preview canvas imports (pingdotgg#25006); the
hook call and the wrapper merge cleanly. ChatView now stays mounted, inert,
under the Settings and Usage modals (pingdotgg#24847), so the thread's stream and
frames stay live there. Overlay comments are cut to the four-line limit and
the six effects of LazurioEnvironmentBrowser.tsx (stream, listeners, timer,
draft lifecycle) carry justified suppressions of
t3code/no-unreasoned-state-sync, upstream's name since v0.1.4 (pingdotgg#26161). The row
imports LazurioNewTab.tsx and a singletonSurface case of row 13, so it
typechecks and its browser tests run only with row 13.

On v0.1.4 upstream's workspaceLabel calls surfaceTitle(...) for the
focused surface (pingdotgg#25877); the overlay passes that call its frame titles
(lazurio.titles) as it does for the tab's title, so a focused
Environment browser tab is labelled by its page. Upstream's new Servers
panel (pingdotgg#22542) opens a discovered server through openDiscoveredPort into
its own preview, which a Lazurio Environment does not have; with the
thread's Environment browser the click now opens the server's address as
a new tab of that browser, in front (openPageInPanel in
lazurio/threadTabs.ts, one line in ThreadServersPanel.tsx). Without a
strip (the Environment browser not declared or not yet answered) the
click goes to upstream's preview only where the client or the
Environment has one (isPreviewAvailableFor); on a Lazurio Environment it
says the Environment browser is not available instead of opening a dead
preview tab. threadServersPanel.browser.test.tsx checks all three: a
click opens an Environment browser surface; without a strip it shows the
notice; and without a strip where the client has a preview it leaves the
click to upstream's openDiscoveredPort. The tab's view frame carries
data-workspace-focus-default, as Nová karta does.

On v0.1.6 upstream adds side chats (pingdotgg#26901): a side-chat surface in the
panel, a "Side chat" action beside the overlay's, and SideChatPane, which
renders an embedded ChatView whose panel actions go to its parent. The
overlay keeps both sides of the panel store and the tabs. Upstream turns
off its own panel and proactive panels in an embedded ChatView, and so
does the overlay: an embedded side chat calls useLazurioThreadTabs with
no thread, so it opens no tab stream and no click listener of its own,
and a link clicked in it opens in the parent's strip.

On v0.1.7 the servers panel pages long lists (pingdotgg#28684) and imports
MorphIcon; the overlay import sits beside it and the seam is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty and others added 20 commits November 5, 2026 20:30
On a Lazurio Environment (marked by T3CODE_LAZURIO_FOLDER) T3's own browser
tools drive the Environment browser, the one shared Chromium the agents
already use through agent-browser, through a host that registers with the
preview broker under upstream's server-browser client id once the Environment
declares its browser. Upstream's server browser is never built there (a gate
in server.ts) and the descriptor omits capabilities.serverBrowser, so clients
open no server tabs. preview_open answers with the view of the tab, the
address where a person sees it; another Environment's view is never loaded
here, only handed to the person's panel.

On v0.1.3 the broker protocol and the page engine's exports are unchanged;
upstream (pingdotgg#25423) merged eight action tools into preview_act, which maps to the same
broker operations, so the host needs no change. ServerBrowser.layer now needs
PreviewAgentActivity (pingdotgg#25184), so the gate wraps
ServerBrowser.layer.pipe(Layer.provide(Layer.merge(DesktopBrowserChannel.layer,
PreviewAgentActivity.layer))) under upstream's memoization comment. The
preview_open description takes upstream's shortened text (pingdotgg#24850) plus the
overlay's view sentence. serverBrowser.test.ts gives its DesktopBrowserChannel
mock the new `turn` and provides PreviewAgentActivity. The disabled server
browser uses Effect.fn and liveTab uses Effect.undefined, for the new Effect
diagnostics; overlay comments are cut to the four-line limit. The host imports
row 11's threadTabs.ts, so this row alone does not typecheck.

On v0.1.4: upstream added peerLinks, mcpModeLimitHeader and linkFence to the environment capabilities (pingdotgg#16655, pingdotgg#16695); they stay beside the overlay's serverBrowser gate. The row's later test change (agent-turn activity of the gated server browser) is folded in. EnvironmentBrowserPages.open gives the Servers panel (row 9) a new window of this Environment's browser in the thread's strip; it refuses another Environment's address and answers unavailable without a declared browser.

On v0.1.5 ServerEnvironment.ts gains desktopAppChannelSwitch and
threadProjectFromScratch (pingdotgg#26288, pingdotgg#26767) next to the overlay's import;
serverBrowser is still left out only on a Lazurio Environment.
Upstream's pre-commit hook now runs knip over the repository (pingdotgg#26167),
so ENVIRONMENT_BROWSER_ENDPOINT, which nothing imports, is
module-private.
EnvironmentBrowserPages.open now holds the new window in the thread's
strip before it records it, and closes the window again when the strip
refuses the hold (too many tabs, a holder file it cannot write): the open
fails with the strip's reason instead of answering with a window no
thread holds. The connection gains closeWindow (Target.closeTarget).

On v0.1.6 upstream's server browser keeps each client's reported browser
profiles apart (pingdotgg#21745): ws.ts takes a profileReporter for every
WebSocket session instead of calling reportProfiles. The disabled server
browser hands out a reporter that keeps nothing, and its test reports
through one.

On v0.1.7 preview_open takes a dev-server `target` (pingdotgg#28027): the
description keeps upstream's sentence beside the overlay's view sentence,
and the parameters are upstream's. The Environment browser answers open
with the tab's view, and upstream's handler answered an open with a target
with the navigate result alone; it now adds the open answer's view to the
navigate result, so the view stays and nothing else of the open answer (a
dialog it saw, for one) replaces what navigate reports. A test fails
without either. The Environment browser
resolves the port target to localhost, which is where its own Environment's
dev server is. Effect 4.1.2 moved SecureRandom out of NodeServices, so the
server browser gate test also provides NodeCrypto.

On v0.1.7 the host also keeps LazurioPlatform F50 point 2 for every view
host (the contract's isViewHost): `browser.` and a name in `lazurio.io`,
in any case and with the DNS root's trailing dot, this Environment's own
host included, never loads in this browser. preview_open and
EnvironmentBrowserPages.open of this Environment's own view take the tab
it shows instead of loading it, its bare origin, with or without its `/`
as F50 writes it, opens a new blank tab of this browser, and
preview_navigate to a view host, like any other address on one, fails. A
view counts only exactly as F50 writes it, not as new URL normalizes it
(an upper-case host, `:443`, a `..` path), for this Environment's own
view and another's alike; its tab id, which F50 takes in either case,
is handed on in upper case as Chromium writes it. Before, the trailing
dot let another Environment's view through, and this Environment's own
view loaded in the thread's window, while its bare origin was refused,
a normalized view of a tab was taken as the view, and a view with a
lower-case tab id did not find its tab (review of #174).
The tests of all five fail without the change; browserView.ts, the
contract's copy, is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…file

Each T3 Code thread keeps its tabs of the Environment browser only in its
holder file, $XDG_RUNTIME_DIR/lazurio-browser/holders/t3-<thread>.json, the
file the view's hub reads to keep a remote tab alive. The server reconciles
every file with the hub's list of live tabs (at start, on change, every 30 s
and before every write), serializes writes through one queue and never
overwrites a file it cannot read, so a restart finds the tabs intact and T3's
database stays upstream's. The web reaches a thread's tabs through
/api/lazurio/thread-tabs (snapshot, event stream, hold, release) under
upstream's preview scopes. holders.ts copies the Platform's holder helpers,
and holders.test.ts runs the Platform's vectors against the copy. One line in
server.ts joins the overlay's layer into layerMakeRoutes, where it shares the
routes' one preview broker.

On v0.1.3 the row merges cleanly: layerMakeRoutes gained GuestHttp (pingdotgg#24104)
and the PreviewAgentActivity provide (pingdotgg#25184), and the overlay line still sits
before the preview broker. Guest tokens never reach this route. ThreadTabs'
methods use Effect.fn for the new Effect diagnostics, and comments are cut to
the four-line limit; the holders.ts provenance line the contract pins stays.
threadTabs.ts and the overlay layer import row 12's threadLifecycle.ts, so
this row alone does not typecheck.

On v0.1.4: upstream added the RemoteDelegation starter to the route layers (pingdotgg#16719); the overlay's layer follows it. POST /api/lazurio/thread-tabs/open, under the preview open scope, opens a page for the Servers panel (row 9) through row 10's EnvironmentBrowserPages, which overlay.ts now provides to the route.

On v0.1.5 upstream's pre-commit hook runs knip (pingdotgg#26167), so
agentBrowserShutdownAction, which nothing imports, is module-private.

On v0.1.7 EnvironmentAuth.layer needs ManagedPolicy and the crypto
services left NodeServices, so the route test provides
ManagedPolicy.layerTest() and NodeCrypto beside NodeServices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stops its agent's daemon

threadLifecycle.ts is the one adapter between the threads' tabs and T3's
orchestration: archiving or deleting a thread retires it, which removes its
holder file and stops its agent-browser daemon over the session's socket;
unarchiving restores none of its ended tabs; at startup, files and daemons of
threads that are no longer live go too. The domain-event stream is a live
tail that can fail, so the adapter resubscribes after a failure.

On v0.1.3 the orchestrator's getShellSnapshot, getThreadShell and
streamDomainEvents keep their signatures, and getShellSnapshot({location:
"active"}) is still unpaged, which the startup sweep relies on. The new
thread.guest-share-updated event is ignored. pingdotgg#21299 now reaps the daemons of
exited Claude, ACP, Pi and Muse agents through their systemd scope, but
upstream still does nothing on archive or delete, for Codex's shared scope,
for terminals, or without systemd, and never touches holder files, so the row
stays whole. liveThread uses Effect.fn and the header comment is cut to the
four-line limit.

On v0.1.4: unchanged; the seam applies cleanly.

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, "+", Mod+T and the empty right panel open Nová karta, a
tab that frames the Launchpad's /new-tab, instead of upstream's "+" menu and
empty list. A choice turns the tab into a page of the Environment browser,
which the thread holds, or opens one of T3 Code's own surfaces through
upstream's action. Its tools are upstream's "+" actions without Browser, with
the same letters, blocked under any open or closing popup; elsewhere
upstream's menu and empty state stay.

On v0.1.3 the row merges cleanly: onNewSurfaceKeyDown, the "+" action labels
and shortcuts, LAUNCHER_SHORTCUT_BLOCKING_LAYERS and the composer's
data-surface-launcher-keys contract are unchanged, and the new Settings modal
is a dialog popup, so Nová karta's letters do not fire under it. The header
comment is cut to the four-line limit, bare upstream issue numbers are
dropped, and the file's three effects (publishing the tools, the element's
choice event, the letters' keydown listener) carry justified
suppressions of t3code/no-unreasoned-state-sync, upstream's name since
v0.1.4 (pingdotgg#26161).

On v0.1.4: F6 and opening the right panel focus the element marked
data-workspace-focus-default (pingdotgg#25877), which upstream's empty panel carries.
Nová karta replaces that panel, so it carries the mark and takes focus too,
and it is named "Nová karta" for assistive technology as upstream's panel
names itself ("Open a surface").

On v0.1.6 upstream's Side chat action (pingdotgg#26901, letter S) becomes a Nová
karta tool with the element's default icon (#148, decision
K). It opens its tab only once the server has forked the thread, so
ChatView hands the action's promise on (sideChat.open() rather than void)
and runTool gives the side chat Nová karta's place once it opens;
otherwise the Nová karta tab stayed beside the new side chat. A side
chat that did not start (the promise answers false) leaves Nová karta
in place, whichever tab the person is on by then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nciliation

With T3CODE_LAZURIO_FOLDER a hosted Environment's Chat starts in its Lazurio
Folder: decorators over the services orchestration-v2/runtimeLayer.ts builds
keep projects, commands and agent sessions in the Folder and offer no Scratch,
the startup adopts or creates the Folder's project through ProjectService, and
`t3 lazurio model` writes the Platform's recommended model as the Folder
project's own override. The web leaves "Add project" and "Copy prompt link"
out when every Environment is managed.

On v0.1.3 every upstream seam merges cleanly: useHandleNewThread moved into
createNewThreadHandler (pingdotgg#25366) and the managedNewThread seam moved with it;
runtimeLayer gained GuestShareService and PreviewAgentActivity (pingdotgg#24104,
upstream's offline-machine block (pingdotgg#23967) and the overlay's managed-entry
block. For the new lint rules (pingdotgg#21514, pingdotgg#25099) the overlay's long comments are
cut to four lines, a bare issue number is dropped, and the guard decorators
use Effect.fn("LazurioManagedFolder.<op>") like the existing update guard.
This also adds lazurioFolder to ServerConfig, which row 6's prompt draft reads.

On v0.1.4: Sidebar.tsx keeps upstream's sidebar tree and delegated-thread
hooks next to the managed-Folder hook. ProjectService.listPage (new
upstream) is read-only and stays unguarded. Upstream's fallback model
(packages/shared/src/usageLimits.ts) applies only when
fallbackModelSelection is set, which defaults to null, and the default
provider's usage window is used up; t3_thread_launch keeps it under that
condition, as upstream's own launches do.

On v0.1.5 upstream adds "Turn into project" (pingdotgg#26767). runtimeLayer.ts
gives ScratchThreadPromotion the guarded project service and
guardScratch's project folders, so on a managed Environment promote
fails closed (no Scratch root, no Scratch project; covered in
managedFolderGuards.test.ts), and the web offers no action because the
server sends no scratchWorkspaceRoot. The imports in runtimeLayer.ts and
ws.ts keep both sides. Hiding GitActionsControl's git part for the
Folder holds with the new showActions (pingdotgg#26904).

On v0.1.6 upstream adds pinning and archiving projects (pingdotgg#26154) and
mutateProjectWithClone (pingdotgg#24915); the overlay keeps its managedFolder
import beside it in ws.ts, and the command palette's Add project keeps
the overlay's condition under upstream's new name, addProjectAction.
guardProjects refuses archiving the Folder's project: archived, a new
chat from the sidebar would lead to a palette whose one choice, Add
project, a managed Environment refuses. Pinning it and archiving earlier
projects stay allowed. The app on a managed Environment also triages
nothing (#147): triage clones and compares upstream's
source without the overlay, and since v0.1.6 drafts an issue there with
the Environment's diagnosis (pingdotgg#24955). The descriptor no longer offers
capabilities.triage there (beside serverBrowser), and guardTriage, where
server.ts builds upstream's TriageService, refuses prepare. The t3
triage command stays upstream's, as on v0.1.3: a person runs it in a
terminal whose environment does not carry the Folder, and it asks before
it files anything. Side chats
(pingdotgg#26901) are thread.fork with sideChat, which guardCommands already keeps
in the Folder's root. Resolving the config logs when it removes
expired attachment uploads (config.ts, as on v0.1.3), and the line
reached stdout before the JSON the Platform parses. postToRunningService
now resolves with upstream's new quietLogs (pingdotgg#27817), as t3 auth --json
does.

On v0.1.7 the Environment descriptor gained oneTimeSchedules beside the
triage capability the overlay keeps off a Lazurio Environment, and
AnalyticsService reads the managed policy beside the guarded triage layer.
The settings writer now takes the client's patch for the managed policy
(pingdotgg#27197); the overlay's updateSettingsWith has none and passes {}, so the
policy still vets what the change does and refuses it at a locked setting,
which a new test shows. Effect 4.1.2 moved SecureRandom out of
NodeServices: the Folder project's id comes from SecureRandom, and the
tests provide NodeCrypto and the test managed policy.

The live-service test builds auth as EnvironmentAuth.layerRuntime does,
but on the same ManagedPolicy.layerTest() its Settings layer reads, as
server.ts gives both one policy. layerRuntime reads the host's policy file,
so a policy on the machine running the tests could change them; the CLI
commands under test still build their own runtime and read the host's
policy, as the service does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Root decision 0200, point 2: inside the app the distribution is "T3 Code
(Lazurio)". The build's stage label is "Lazurio", a "Lazurio" label with the
approved symbol stands beside the wordmark, previews are not called
"Nightly", and the web manifest and index.html carry the name.

On v0.1.3 branding.ts, branding.logic.ts and the manifest are unchanged
upstream; the document title moved to appInit.tsx (pingdotgg#25004) and still reads
APP_DISPLAY_NAME. Upstream's new "failed to start" screen (lib/bootError.ts,
pingdotgg#25317, pingdotgg#25345) now names the app with APP_DISPLAY_NAME in its heading and its
triage hint; its issue link and the `npx t3 triage` fallback stay upstream's,
as decided. bundledDev.test.ts copies branding.ts and branding.logic.ts beside
bootError.ts, which now imports them. The badge's comment is cut to the
four-line limit.

On v0.1.4: upstream added nightly/dev artwork modes next to the seam
(pingdotgg#26138); the Lazurio label stays in every mode.

On v0.1.6 upstream's sidebar title gains history buttons and imports
Button beside the overlay's LazurioBadge. Upstream now asks for a star on
its GitHub repository after five sent prompts (pingdotgg#27832); a Lazurio
Environment never asks: useComposerSend counts a prompt through
lazurio/starPrompt.ts, which skips an Environment with a Lazurio Folder,
so the toast and its star check never run there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Root decision 0200, point 4: in the standard look the web client takes the
design system's colours, type and radii from the --lz-* tokens the
Environment's shell declares (LazurioPlatform F36). One file,
apps/web/src/lazurio/theme.css, imported right after index.css in main.tsx;
without the shell nothing matches.

On v0.1.3 the row applies cleanly. main.tsx now imports the Inter font before
index.css, and the theme import still follows index.css directly. index.css
(pingdotgg#25323, pingdotgg#25251, pingdotgg#24865 and others) removed no token the file maps; it added
guest and diff-icon tokens, which stay T3's. The default "New design"
(pingdotgg#24865) maps none of these tokens in light mode; its dark-mode surfaces stay
T3's, to be checked on canary for Environment themes with a dark variant.

On v0.1.4: upstream removed the unused --update-surface token (pingdotgg#26238), so
the mapping is gone; pingdotgg#25778 made the new design the only one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urns them on

Root decision 0201, point 4 (#46): with T3CODE_LAZURIO_FOLDER,
Codex and Claude sign in with their own CLIs from the Launchpad's Getting
started page (T3CODE_LAZURIO_TOOLS_URL). Where the web offered its own
"Continue with ChatGPT" it links to that page, the server refuses T3's own
Codex sign-in and profile import for every client, and `t3 lazurio
providers` turns the providers on and reads their sign-in again at once.

On v0.1.3 ProviderAuthService keeps the same methods, and no new way into
Codex's own sign-in appeared. CodexSetupSection.tsx moved its logic into
CodexSetupSection.logic.ts (pingdotgg#25524); the only conflict was that import
beside the overlay's, and both stay. The guard's start and importProfile
overrides use Effect.fn for the new effectFnOpportunity diagnostic (pingdotgg#25099),
and the overlay's comments are cut to four lines without bare issue numbers
(pingdotgg#21514).

On v0.1.4 the server and web seams are unchanged. LazurioSignIn's
browser test mocks ../state/server by spreading the real module, as
upstream's own tests do: its threadTabs import now reaches that module
through previewRuntime (row 9's Servers panel), and the partial mock
lacked its other exports.

On v0.1.6 upstream's provider dialog imports useSecretSources for a local
ACP agent's variables (pingdotgg#27115) beside the overlay's LazurioSignInChoice;
secret sources stay off in Lazurio's build (T3CODE_FEATURE_SECRETS is not
set). Codex's new "Use existing CLI" (pingdotgg#23160) sits in ManagedCodexSetup,
which a Lazurio Environment replaces with LazurioSignInRow.

Upstream's ChatGPT handoff (unchanged since v0.1.3) ran a ChatGPT sign-in
outside this guard (#145): a client asks the server on its
own device, its primary Environment, to sign in in memory and hand the
account to another Environment's instance. A Lazurio Environment now runs
none: ws.ts passes chatGptHandoffSubscribe through handoff, which fails
with LAZURIO_CHATGPT_HANDOFF whenever T3CODE_LAZURIO_FOLDER is set,
whatever the instance, because the server cannot tell a peer's Codex from
another provider. The guard also refuses reconnectProfile, the export that
only feeds a handoff, for a Lazurio Environment's Codex; it reads only
T3's own store, which there holds at most an account from before the
guard, one the person can only disconnect. The web does not start a handoff
through a Lazurio primary (runsChatGptHandoff in readSignInRouting), so
the target signs in itself, as it does with no primary connected.

The Launchpad parses `t3 lazurio providers` as it parses the model
command, and both post through row 15's postToRunningService, which
resolves the config with quietLogs; the test covers both commands.

On v0.1.7: upstream's managed policy can lock Codex's setupMode, which hides
the ChatGPT button and denies ChatGPT's own runtime. The lock pins Codex to
setupMode "existing", the host's own CLI (PIN_BYPASS_FIELDS in
managedProviders.ts), so it rules out only T3's managed runtime, not the
CLI sign-in Getting started does. In Add provider the lock therefore hides
only upstream's ChatGPT button, and the overlay's sign-in choice around it
still links to Getting started. A browser test shows the link under the
lock, and that the dialog hands the choice upstream's button only without
it; the test fails if the lock check in AddProviderInstanceDialog.tsx goes.
A Codex instance's settings row keeps Lazurio's row, which offers Getting
started under that lock too;
CodexSetupSection reads the policy before its Lazurio early return so the
hook order stays the same. The overlay's provider turn-on goes through
updateSettingsWith, which the managed policy vets. Tests provide
ManagedPolicy.layerTest() and NodeCrypto, since Effect 4.1.2 moved
SecureRandom out of NodeServices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Admin's decision of 2026-10-17: a page without a T3 session asks its own
origin for a pairing credential (GET /.lazurio/chat/pairing), which the
Launchpad issues with `t3 lazurio pairing` and the operator's scopes (T3's
standard client scopes without relay:write), and exchanges it, so the person
never sees a token and a deep link keeps its place.

On v0.1.3 the row applies cleanly: bootstrapServerAuth keeps its order of
credential sources (only comments changed, pingdotgg#21514), and the scope literals,
AuthStandardClientScopes and sessionHasLegacyPermissions are identical, so
no browser paired on v0.1.2 pairs again. WebSocket tickets now name their
route (pingdotgg#24554), which pairing does not touch. The overlay's comments are cut
to the four-line limit.

On v0.1.4: unchanged.

On v0.1.5 upstream's pre-commit hook runs knip (pingdotgg#26167), so
OPERATOR_PAIRING_SCOPES and ENVIRONMENT_PAIRING_PATH, which nothing
imports, are module-private.

On v0.1.6 `t3 auth pairing create --json` resolves its config with
quietLogs (pingdotgg#27817), because removing expired attachment uploads logs
to stdout. `t3 lazurio pairing`, which the Launchpad reads the same
way, now does too, so it prints only the token.

On v0.1.7 the crypto services left NodeServices, so the pairing test
provides NodeCrypto beside it.

The pairing test gets its auth from the test managed policy as well
(layerAuth in cli.test.ts); with a policy that allows only the desktop
client it fails, so the test reads that policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ceiling

Root decision 0210, addendum 3: an agent without a browser (Buddy over SSH,
a script) gets an MCP session with the runtime-mode ceiling that only an
approved MCP sign-in gives otherwise. `t3 auth session issue --subject
mcp-client --runtime-mode-ceiling <mode>` issues it; without the flag such a
session stays capped at approval-required.

On v0.1.3 issueSession, authenticateMcpClient and issueMcpClientSession keep
their shapes; WebSocket tickets gained a route audience (pingdotgg#24554), which does
not touch the seam. The only conflict was the comment above
MCP_CLIENT_SUBJECT, shortened upstream (pingdotgg#21514): upstream's one-line comment
stays, and the constant keeps the overlay's export, which the agent CLI and
the release contract use.

On v0.1.4 the seam merges cleanly: upstream's own import of mcpClientScopes
from @t3tools/contracts (pingdotgg#17129) sits next to the overlay's RuntimeMode
import. Since pingdotgg#26642, sessions issued with --subject mcp-client get
http://127.0.0.1:<port>/ thread links from the request's origin when they
list, read or send to threads, and also from t3_thread_launch,
t3_thread_search and a scheduled task's run_now. Only threads an agent
creates from inside a thread and task results keep t3-thread:// links,
as upstream does. The agent CLI's sessions keep t3-thread:// links
everywhere (row 24).

On v0.1.6 upstream validates --base-url with Schema (pingdotgg#23343) and keeps
session issue's logs quiet under --json or --token-only (pingdotgg#27817), so the
ceiling's JSON output is clean as is; the imports keep upstream's Schema
beside the overlay's CliError.

On v0.1.7 the crypto services left NodeServices, so the CLI test
provides NodeCrypto beside it.

The CLI test builds auth on ManagedPolicy.layerTest() in place of
EnvironmentAuth.layerRuntime, which reads the host's policy file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…quest, queue and events

Root decision 0210, addendum 3: Buddy, and with it every agent in an
Environment, works with T3's threads as their owner. It reads and summarizes
them, launches, messages, steers and stops them, answers their questions,
decides their permission requests and follows their changes, through `t3
thread`, `t3 request`, `t3 queue` and `t3 events`. Each command is a thin
client of the running server: T3's own MCP tools where one exists, the
person's client surface only where none does.

On v0.1.3 upstream merged its thread tools into action tools (pingdotgg#25423). The
commands keep their surface, sessions, labels and exit codes; only their MCP
calls move: `t3 queue *` calls t3_queue with list, read, edit, cancel,
reorder or promote_to_steer, `t3 request respond` calls t3_pending_request
with respond, and `t3 thread fork` calls t3_thread_transfer with fork. The
tool names are strings the compiler cannot check, so agentCli.test.ts gains a
case that drives every queue action and the fork through the real thread
toolkit; with the old names it and the respond case fail. Upstream's shell
snapshot now takes a query (pingdotgg#24648, pingdotgg#24723) and a WebSocket upgrade names its route
(pingdotgg#24554), so the connection passes an empty query and the test's /ws passes
"rpc". `send --wait` uses Effect.filterOrElse for the new
flatMapConditionalToFilterOrFail diagnostic, and the long header comments are
cut to four lines (pingdotgg#21514).

On v0.1.4: listItemFromShell takes the caller's scope for each thread's link (pingdotgg#26239, pingdotgg#26642), so t3 events passes this Environment's id and its lines carry the same t3-thread:// link as t3 thread list. mcpClientScopes comes from @t3tools/contracts, and the tests stand in for the linked-environment services and server settings that upstream's orchestrator handlers now need (pingdotgg#17129). The fake t3_thread_launch stamps linked callers as upstream's does.

On v0.1.5 upstream's pre-commit hook runs knip (pingdotgg#26167), so
requestCommand, queueCommand and eventsCommand, which only agentCli.ts
uses, are module-private.

On v0.1.6 upstream adds top-level t3 up and t3 status (pingdotgg#27996); they sit
beside the overlay's t3 thread in binCli.ts. Upstream also keeps config
resolution's logs off standard output for t3 auth --json (pingdotgg#27817): every
agent command prints its result there, so withAgentCliConnection resolves
with quietLogs as well, and a test with an abandoned upload, as upstream's,
checks that t3 thread list --json prints only its one line. t3 thread list
leaves out side chats (pingdotgg#26901) as upstream's list does, and t3 events
--subagents follows delegated threads but no side chat, as that list.

On v0.1.7 effect/Crypto is gone: a decision's command id comes from
effect/crypto/SecureRandom. layerMcpTransport now takes the tool
registrations as its argument, so the live-server test passes them in,
and the tests provide NodeCrypto beside NodeServices.

The live-server test and the sessions check build auth on
ManagedPolicy.layerTest() in place of EnvironmentAuth.layerRuntime, which
reads the host's policy file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Root decision 0211: an agent asks the person through the question tool, not
only in prose. Codex offers request_user_input in Default mode only with its
feature default_mode_request_user_input; T3 turns it on for the app-servers
it starts for its threads (-c ahead of the launch arguments, so their own -c
or --disable still wins), and every turn's additional context tells the
agent to ask with it. The user's ~/.codex/config.toml is not touched (0161).

Shrunk on v0.1.3: upstream now uses Codex's native collaboration modes
(pingdotgg#24737) and deleted T3's mode texts with buildCodexDeveloperInstructions, so
the overlay's edit of the Default-mode text is gone. Codex's own Default text
keeps the tool for optional questions and asks required ones in plain text,
so the lazurio_questions entry now says explicitly that it overrides the
collaboration mode's instructions on asking. It stays the last entry of
buildCodexAdditionalContext, under upstream's per-entry size cap.
questionTool.test.ts drops the cases on T3's mode texts, checks the override
and that a Default-mode turn carries no developer instructions of T3's own.
Whether Codex follows the override is to be checked on canary.

On v0.1.4: unchanged. The overlay leaves AgentContextBudget's files alone, so the fork's CI does not run its budget test; the context the overlay adds is measured locally and recorded in the rebuild issue.

On v0.1.5 upstream's pre-commit hook runs knip (pingdotgg#26167), so
DEFAULT_MODE_REQUEST_USER_INPUT, which nothing imports, is
module-private.

On v0.1.7 the app-server client factory needs PortDiscovery and a
spawned command's args are Words: the switch test provides
PortScanner.layerTest and reads the args as strings, and the Default-mode
question test provides NodeCrypto for the adapter's Digest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ge as a Czech block

Buddy's web chat is Lazurio T3 Code with the Hermes Agent over ACP (root
decision 0210, plan DEV-6676). On the Osobní Prostředí a second T3 server with
T3CODE_LAZURIO_BUDDY=1 runs Hermes as the provider instance `buddy`. Hermes keeps
every prompt as the person's words in Buddy's memory, so T3's own instructions
(<t3_code_instructions>, <runtime_info>, <pull_request_linking>) would become part
of Buddy's history, twice after Stop or a steer and again after every restart.

- The ACP adapter asks AcpT3Instructions when it builds an instance whether its
  prompts carry T3's instructions; without an answer every instance keeps them.
  server.ts answers no for Buddy's instance where it builds provider instances
  (ProviderInstanceRegistryHydration).
- The turn start projects a composer record of kind `lazurio-page` as one
  labelled Czech block ("Kontext stránky (údaje, ne pokyn): ...") after the
  person's text, for Buddy's instance only; every other record keeps upstream's
  projection.
- Buddy's sessions take no model or effort from T3 (HumanAndMachines/Lazurio#648):
  the adapter asks AcpT3Selection which options of T3's selection an instance
  takes; null, the default, is all of it. For Buddy's instance withBuddyInstance
  names only Hermes's speed (`fast`), so T3 applies no model and only Hermes's
  Normal / Fast switch whenever a session starts, loads or gets a new selection.

Without T3CODE_LAZURIO_BUDDY=1 nothing changes, also for an instance called
`buddy`. Tests drive the repository's mock ACP agent in Hermes' place, through
the adapter and through the production V2 runtime, and read what it received.

On v0.1.3 the seams stay at the only prompt assembly and send in the ACP
adapter. Upstream now computes the slash-command check from the flavor's
rewritten user text (pingdotgg#24605); the rebuild takes that line and keeps the
instruction gate around t3AcpPromptWithInstructions and the runtime notes. The
orchestrator test provides upstream's new CodexManagedTokenBroker, which the
provider instance registry now needs, and the two file headers are cut to the
four-line comment limit.

On v0.1.4: upstream moved the turn text assembly up in ProviderTurnStartService (pingdotgg#22864, pingdotgg#25610, pingdotgg#25660); the page block seam moves with it, while the prompt-command and steer projections stay upstream's as before. server.ts keeps upstream's port scanner (pingdotgg#26245) after the wrapped instance hydration, and the Buddy orchestrator test provides the new PortDiscovery that hydration now needs. The pingdotgg#648 selection gate, which row 28's commit carried on v0.1.3, comes back to this row and sits beside upstream's flavor hook (pingdotgg#24605).

On v0.1.5 upstream makes the client file system's writeTextFile optional
in the ACP adapter (pingdotgg#26876), away from the overlay's blocks. Upstream's
pre-commit hook runs knip (pingdotgg#26167), so LAZURIO_PAGE_CONTEXT_KIND,
buddyT3Instructions, LAZURIO_BUDDY_ENV and isBuddySurface, which nothing
imports, are module-private.

On v0.1.6 the ACP adapter checks a turn's model against the session's
models before the turn starts (pingdotgg#26623). Where AcpT3Selection names the
options, the host sets the agent's model and T3 applies none, so
validateModelSelection skips that check there, as it does for a flavor
that applies the selection itself: a model the session no longer offers
would otherwise refuse every turn of Buddy's. The V2 adapter test sends
such a model and the turn runs. The adapter test imports upstream's
acpPerTurnPromptUsage beside AcpT3Selection.

On v0.1.7 the ACP adapter filters T3's options through the flavor's own
options and a runtime-mode ceiling (flatMap); the AcpT3Selection gate is
one more early return there, after the flavor's owned options and before
the ceiling. The runtime notes, which upstream now builds from the active
session setup, stay behind sendsT3Instructions. Effect 4.1.2 dropped
effect/Crypto, so the adapter imports Context beside upstream's
SecureRandom, and the Buddy tests provide NodeCrypto beside NodeServices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds Buddy's thread

budíček wakes Buddy with a message into Buddy's T3 thread (root decision 0210,
plan DEV-6676) and sends it again after a crash. It needs the send to happen once
per wake, to know which thread is Buddy's, and to tell a final refusal from one
worth retrying.

- `t3 thread send --idempotency-key <key>` passes T3 a SHA-256 digest of the key
  with the thread and the sender (T3CODE_ACTING_SENDER and T3CODE_ACTING_FOR of
  the "Buddy za …" trace) as the MCP tool's clientRequestId. T3 derives the
  command and message ids from it within the MCP client's request namespace. A
  decorator where server.ts provides the MCP client authenticator puts the agent
  commands' sessions (label "t3 agent cli") in one namespace, so the same key
  names the same command across commands, sessions and restarts, and T3's
  command receipt runs it once. Every other MCP client keeps upstream's
  namespace.
- After a keyed send the command reads what became of it through
  POST /api/lazurio/agent-cli/send-state (orchestration:read): T3's receipt of
  the key's command and the message it made. A repeat prints the first send's
  result with its commandId; the same key with another message fails with
  `idempotency_conflict` and sends nothing; a key T3 rejected fails with
  `command_rejected` and stays rejected; a repeat T3 cannot replay because it
  checks the thread first (archived since) prints `"delivery": "delivered"`
  with the message's id, run and commandId. The conflict check compares the
  trimmed text, as T3's tool trims the message it decodes. Other failures keep
  their codes and are worth sending again with the same key.
- `POST /api/lazurio/buddy/thread` finds or makes Buddy's one thread: thread
  `buddy` on the instance `buddy` in Buddy's project (~/Lazurio/.agents/buddy),
  made by a fixed command so two first asks make one thread, and brought back if
  it was archived. A thread `buddy` in another project or on another instance
  is never taken: the route answers 409 `thread-conflict`. 404 outside Buddy's
  T3 Code, 409 without Buddy's project. `t3 lazurio buddy thread --json` asks it
  as an agent command.
- `t3 lazurio capabilities` says so with "threadSendIdempotency",
  "threadSendOutcome" and "buddyThread".

The "budíček" label on the message is a claim of the "Buddy za ..." trace and
not part of this change. The end-to-end test runs separate `t3 thread send`
commands against T3's own MCP tool over the production runtime; with the raw
key, without the shared namespace, or without the conflict, rejection or
delivered branches, it fails.

On v0.1.3 the key keeps its place in row 21's `send`: the key is evaluated
first, then the message is sent through `t3_thread_send`, and `--wait` waits
only on the run of that send or of the receipt T3 replays. That replaces the
rebuilt row 21's `Effect.filterOrElse` around the send. Command ids still come
from the MCP client's request namespace and the key (stableCommandId is
unchanged), and `t3_thread_send`'s new `link` field (pingdotgg#24784) is ignored by the
result schema. The namespace decorator's `authenticate` is an `Effect.fn`, as
upstream's McpOAuth.authenticate now is (pingdotgg#25099, effectFnOpportunity is an
error), the Buddy test runtime provides upstream's new CodexManagedTokenBroker,
and the file headers are cut to the four-line comment limit.

On v0.1.4: upstream gives every outside MCP client a web origin for its thread links (pingdotgg#26642); the agent commands' sessions drop it in agentCliRequestScope, so their links stay t3-thread:// links T3 Code's chat opens, as on v0.1.3, and the namespace test asserts both sides. The Buddy test runtime wraps provider hydration with withBuddyInstance as server.ts does (row 23) and provides upstream's new PortDiscovery, and the agent CLI tests provide RemoteDelegation, the peer services and the server settings T3's thread tools now need.

On v0.1.5 upstream's pre-commit hook runs knip (pingdotgg#26167), so
agentCliRequestScope, which nothing imports, is module-private.

On v0.1.6 upstream's projects carry pinnedAt and archivedAt (pingdotgg#26154), so the
Buddy thread test's project sets both to null.

On v0.1.7 effect/Crypto is gone: the send key hashes with
effect/crypto/Digest (sha256), and the Buddy surface testkit provides
NodeCrypto beside NodeServices. Upstream renamed the MCP client's
webOrigin to hostedAppUrl, so the agent commands' scope strips
hostedAppUrl to keep their thread links `t3-thread://`; any other MCP
client keeps the hosted app link McpOAuth sets. The CLI test checks that
link against McpOAuth's own value, so a rewrite of it fails the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the bubble

Root decision 0210, plan DEV-6676 (row 25). With T3CODE_LAZURIO_BUDDY=1 the web
client is Buddy's surface: a header with Mattyčus, the conversation's days in a
pane where the window has room and a date line above each day's first message,
and no thread list, new thread, fork, rollback or thread details card. `/`
opens Buddy's one thread through the thread route, and a message sent during a
turn steers it; queueing is the explicit alternative. In Buddy's bubble the
surface speaks the frame's side of lazurio.buddy.* v1, written from the
protocol's description: it says it is ready, keeps the last valid page context
from its parent on the host's exact origin, and asks for its window's size from
its header and on Escape, which asks for the panel unless a dialog, menu or
list is open in the surface or something inside stopped the key (the editor
marks every Escape as handled). A day in the pane turns off following the end
first, as the timeline's minimap does, so the jump stays. In the bubble the
surface wears the host page's appearance through T3's own appearance
preference; where T3 cannot wear it (storage blocked, or a saved
single-appearance theme), the document wears T3's standard palette in the
host's appearance (request of 2026-10-25).

The server holds the same for every client and agent. Decorators where
it builds the orchestrator and the checkpoint store refuse forks,
rollbacks, other threads and deleting Buddy's thread, and take no
checkpoints in Buddy's project, which is the managed Folder's
(T3CODE_LAZURIO_FOLDER=<Folder>/.agents/buddy) and else
~/Lazurio/.agents/buddy. A thread.create with Buddy's thread id passes
only while Buddy's thread route makes the thread (MakingBuddysThread),
so a client cannot spend the route's fixed command on a thread of its
own. A startup phase before recovery turns off continuing threads after
an update, the agent browser, and Codex and Claude, once, recorded in
<state>/lazurio/buddy-surface.json so that later choices stay. `GET
/api/lazurio/buddy/days` lists the conversation's days for the pane. The
day lines and the day jump check Buddy mode once per timeline, for the
timeline's own Environment, and the rows read it from the timeline's
shared state, so no other Environment's timeline does that work or takes
a jump.

On v0.1.3 two web seams are rebuilt and one server refusal is added.

- Upstream installs the chat route's shortcuts once at app start (pingdotgg#25366
  removed ChatRouteGlobalShortcuts), so Buddy's condition joins their `when`
  in shortcuts/chatRouteCommands.ts: `isBuddySurfaceNow()` reads the primary
  server config from the app's atom registry when a key is pressed. Without it
  Shift+Mod+N on Buddy's surface would try a new thread that the server then
  refuses.
- The timeline no longer takes onManualNavigation (pingdotgg#25463); the day jump is
  called below the timeline's own navigateTimeline and passes it, and the day
  line sits beside upstream's AudioPreviewUnmountContext (pingdotgg#21363).
- Upstream's guest share of a thread (pingdotgg#24104) is refused in Buddy's T3 Code:
  thread.guest-share.open, .redeem and thread.guest-message.submit fail in the
  command guard, because Buddy's conversation belongs to a Personalspace, which
  is never shared (root decision 0091); closing a share stays possible. The
  root renders no GuestShareDialogHost on Buddy's surface. A test drives a
  guest share of Buddy's thread through the server's orchestrator.

Settings and Usage are modals over the thread now (pingdotgg#24847); the prompt-link
draft stays off Buddy's surface inside the rewritten ChatRouteLayout, and the
first-run gate stays off on NEW's slimmer root. The guards are Effect.fn
decorators (effectFnOpportunity), the two effects of BuddySurface.tsx carry
justified react-effect-budget suppressions, and the file headers are cut to
the four-line comment limit.

Any other thread of Buddy's Environment, opened by a link or a typed address,
leads to Buddy's thread as `/` does: the chat layout shows BuddySurfaceIndex,
which asks the server for Buddy's thread and replaces the address, so that
thread never renders under Buddy's presentation. A thread of another
Environment stays T3's, and ChatView and the thread details ask about the
thread's own Environment. A browser test opens such an address directly.

On v0.1.4: the root keeps upstream's local-Environment switch beside the
Buddy surface check (pingdotgg#26207); the timeline finds upstream's unmatched
pending approvals (pingdotgg#26255) after the day lines and adds the day keys to
upstream's list data next to its new footer. ThreadDetailsPanel no
longer exports its props type (pingdotgg#26238), so Buddy's panel takes
ComponentProps of it, and the effect suppressions use the renamed lint
rule t3code/no-unreasoned-state-sync (pingdotgg#26161). The host appearance,
which row 28's commit carried on v0.1.3, comes back to this row: T3 no
longer re-applies its theme from useTheme's effect (pingdotgg#25007, pingdotgg#25461), so
the stand-in follows every application of T3's saved theme through
subscribeRenderedAppearance, and the theme tests apply T3's stored
appearance as app start does. Upstream's linked Environments
(pingdotgg#16655, pingdotgg#16684, pingdotgg#17129) let a peer read and send into this server's threads; a
Personalspace is never shared (0091), so withoutLinkedEnvironments reads
linkedEnvironmentsEnabled as false in Buddy's T3 Code whatever is
stored, which turns off every upstream gate that reads it (peer-link
OAuth, a linked client's /mcp, outbound links, link requests, remote
tasks). Every settings write through it also stores the switch off, the
startup's Buddy defaults included, so a value stored before Buddy is
read as off and the next settings write through it stores it off (the
settings file's own migration write at load keeps what is stored, which
every read still masks); `t3 environment
link`, which builds its own settings (cli/environment.ts), takes the
same guard, so a command run without T3CODE_LAZURIO_BUDDY reads what
Buddy's server stored.

On v0.1.5 the imports in runtimeLayer.ts and ws.ts sit next to
ScratchThreadPromotion (pingdotgg#26767). Upstream's pre-commit hook runs knip
(pingdotgg#26167), so BUDDY_DAYS_ROUTE, withBuddyDefaults and
buddySurfaceProtocol.ts's constants and helpers that only that file uses
are module-private.

On v0.1.6 a side chat (pingdotgg#26901) renders ChatView embedded, without the
header, the branch restore host or the thread details; Buddy's hidden
header and BuddyThreadDetailsPanel sit inside upstream's new embedded
branches. Buddy's one conversation offers no side chat: sideChatsOffered
is false on Buddy's surface, so neither the panel nor the slash menu offer
one, and a typed /side or /btw gets "Buddy's conversation has no side
chats." as the reason instead of reaching Buddy as text (the server would
refuse its thread.fork anyway). The timeline's day lines sit before
upstream's new set of matched approval requests (pingdotgg#27420).

On v0.1.7: upstream now gives the CLI's settings layer the managed policy
(`ManagedPolicy.layer`). The CLI keeps the Buddy guard around
`ServerSettings.layer` and provides `ManagedPolicy.layer` beside the
secret store and persistence. The startup test provides
`ManagedPolicy.layerTest()` and `NodeCrypto.layer`, which Effect 4.1.2 no
longer brings with `NodeServices.layer`. ServerSettingsService on v0.1.7
also carries the managed policy summary (managedPolicy), which clients read
to lock their controls; withoutLinkedEnvironments passes it on, and
buddySurfaceGuards.test.ts fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…son can remove

Root decision 0210, plan DEV-6676 (row 26). Buddy's surface shows the host
page's last valid lazurio.buddy.context as a chip above the composer, for
example "Apps /apps", with a button that removes it. While the chip is there,
a message the person writes carries the page as a composer record of kind
`lazurio-page` with a reference in its text. Buddy's T3 Code projects it for
Hermes as the labelled Czech block of row 23. An empty message carries
nothing. A removed chip stays removed for its page, also after the host visits
another page and comes back; another page brings its own chip. The page hook
takes the chat's Environment (useBuddyPage(environmentId)), so a thread of
another Environment never carries Buddy's page record.

The record is the contracts' generic UnknownContextRecord with the payload
buddyPrompt.ts reads, so no contract changes. The reference joins only the
regular send's text, and that send's context takes the record from that same
text; the queued-message edit and the plan follow-up carry no page, and the
chip is hidden while a queued message is edited. The person's words stay T3's
messageTextForSend, and the page joins a text of its own, pageTextForSend,
which only the record and the texts on the wire read: a failed send, single or
multi-model, gives the composer back the words, and the retry carries the page
the chip shows then, or none. The composer's and the sent message's
presentation registries show a page reference as a page chip in their
fallback, where T3 would call an unknown context unavailable.
buddySurfacePage.test.ts covers the record, its id, its label, the wire schema
and the empty message.

On v0.1.3 the row applies as it is. The send's snapshot still keeps
messageTextForSend as the person's words (pingdotgg#25710 touched the optimistic message
path, not the snapshot), and upstream's new approval-feedback send (pingdotgg#23657)
sends the person's words without the page, which suits approvals that Buddy
with full access rarely asks for. The chip stays above ChatComposer after the
queue moved to the timeline's end (pingdotgg#25401). Only ChatView's imports conflicted,
beside upstream's new canvas and preview-source imports, and the record's file
header is cut to the four-line comment limit.

On v0.1.4: the message context also carries upstream's linked Environment contexts (pingdotgg#17130) inside the page record wrapper, and the context presentation keeps upstream's linked Environments import beside the page chip.

On v0.1.6 upstream gives pasted text, voice notes and files their own
chips (pingdotgg#27377) and imports userMessageFileLabel beside the overlay's
buddyPageReferenceChip. The words the person typed still travel apart
from the page: useComposerSend's new side chat and star prompt paths do
not touch pageTextForSend, and Buddy's surface offers no side chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r decided a request

Root decision 0210, evening addendum, point 5 (plan DEV-6676, row 27): what
Buddy writes or decides in a thread carries the visible mark „Buddy za <user>“
("Buddy for <user>"). Delegace's wake in Buddy's own thread is not shown at all,
as on Telegram (decided 2026-10-22), so the person sees only Buddy's reply.

Who a session acts as is a claim signed into its token beside the runtime-mode
ceiling (`act`), from a closed set of kinds: buddy with the user's name, or
delegace; budicek is no alias. The agent commands issue their sessions with it
when T3CODE_ACTING_SENDER (with T3CODE_ACTING_FOR and
T3CODE_ACTING_FOR_ACCUSATIVE) names one, and refuse any other value before they
read anything. The server reads it from the session alone: an MCP client's
invocation carries it, the client surface runs a dispatch as its session, and a
decorator of the orchestrator records the message or the request's resolution
in a file per thread in the T3 home, only when its own dispatch committed that
step. A replayed command id, an overlapping dispatch of the same id or a retry
takes no one else's step, and a step's first record stands. The web reads
/api/lazurio/acting-senders. The thread's rows leave out Delegace's wake and
hold an agent's message from outside the thread until the answer covers it,
five seconds at most; a server without the route, or an error, shows the thread
at once as upstream does. Buddy's message and a request Buddy decided show
„Buddy za …“. The person's own steps, T3's messages, automations and other
threads' agents get no mark and never wait. Tests: rejections before
successes, the real CLI against /mcp and the client surface through to the
route the web reads, replays on upstream's own orchestrator, the claim in the
token, and the web's label, parse and loader.

On v0.1.3 three small conflicts are resolved by intent. Upstream computes the
folded agent message's sender label once (pingdotgg#24104) and also reads it out in
the accessible heading (pingdotgg#23780); the overlay wraps that senderLabel in
LazurioActingSenderLabel on the visible fold, which is the one place the
mark goes. McpOAuth's authenticate is upstream's Effect.fn now (pingdotgg#25099), and
the client gets the one actingSender spread line there. EnvironmentAuth keeps
row 20's MCP_CLIENT_SUBJECT export. Upstream's guest attribution (pingdotgg#24104)
is a message-context record of its own and does not mark who acted for a
person, so the overlay's session claim stays. The orchestrator decorator's
dispatch is an Effect.fn (effectFnOpportunity), the two effects that ask
the server's record carry justified react-effect-budget suppressions
(now t3code/no-unreasoned-state-sync), the agent CLI test keeps row 21's
queue and fork case beside this row's cases, and long comments are cut to
the four-line limit.

Buddy's day pane counts the messages the timeline shows: the server's day list
(buddySurfaceDays.ts) leaves out Delegace's wake and an agent's message the
record has not answered yet, by the predicate in actingSenderVisibility.ts,
which the server and the web hold as one identical file that the release
contract test keeps equal. A day that opens with a wake jumps to the person's
first message, and a day of wakes alone is not listed.

On v0.1.4: the acting sender sits beside upstream's peer link (pingdotgg#16695) in the session, its signed claims, the MCP client and its caller, next to upstream's narrowed modes and web origin (pingdotgg#16653, pingdotgg#26642); the test takes mcpClientScopes from contracts, where upstream moved it.

On v0.1.5 upstream's pre-commit hook runs knip (pingdotgg#26167), so
ACTING_SENDER_NAME_MAX_LENGTH, ClientSurfaceActingSender,
actingSenders.ts's make and ACTING_SENDERS_ROUTE, which nothing imports,
are module-private. actingSenderVisibility.ts must stay the same file on
the server and in the web, and each side uses part of it, so its
functions keep their exports and are @public to knip.

On v0.1.7: upstream renamed the MCP caller's `webOrigin` to
`hostedAppUrl` and sets it in the OAuth authenticator for every client
that is not a peer link. The acting sender rides beside it unchanged. The
auth tests provide `ManagedPolicy.layerTest()`, which `EnvironmentAuth`
now needs, and `NodeCrypto.layer`, which Effect 4.1.2 no longer brings
with `NodeServices.layer`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Root decision 0210, addendum of 2026-10-22, point 1, and its plain-chat
addendum (HumanAndMachines/Lazurio#648; plan DEV-6676, row 28): the person
sees and hears only what Buddy writes in human words. In Buddy's surface
(lazurioBuddy) only:

- The timeline keeps the messages and what the person acts on; T3's record
  of the work stays out. While a turn runs, or work still runs after it, one
  quiet static line says "Buddy na tom pracuje…" / "Buddy is on it…". A
  failed turn shows one human line, and the thread's error banner says
  "Něco se nepovedlo. Zkuste to prosím znovu." instead of T3's text.
- None of the turn's work shows at the end of the timeline, and Buddy's
  queue holds and counts only the person's own messages, never an agent's
  such as Delegace's wake.
- The composer is a message box ("Napište Buddymu…") whose only choice is
  Hermes's Normal / Fast switch, the one option row 23's AcpT3Selection
  lets through, and Buddy's thread keeps full access, which the server
  guard also holds.
- The surface never shows T3's terminal or right panel, their keys stay the
  browser's, and a link in the conversation opens in a new browser tab.

The terminal and right-panel states are read inside the store selectors, so
the React Compiler keeps ChatView's panel presence values memoized
(chatViewPanelPresence.test.ts guards it, #134).

On v0.1.3 upstream moved the work banners and the queue to the end of the
timeline and removed the composer's task list (pingdotgg#25401). The work items
(childInputNow, goalNow, backgroundWorkNow) are masked on Buddy's surface
with inline conditionals in ChatView's body, where hasThreadNow, ThreadNow
and the scroll button's summary read them, so no plain function wraps a
hook result in render. buddyBackgroundWork is whether backgroundWorkNow
exists. useBuddyQueuedRunCount drops an agent's queued message from the
footer's count, which upstream's getUserQueuedThreadRuns keeps, and
buddyWorkBanners and the task-list seam are gone. useBuddyChatShortcuts
takes upstream's `!backgroundThread` condition (pingdotgg#24847), ChatTerminalDrawers
lost focusRequestId (pingdotgg#25009), the placeholder falls back to
DEFAULT_COMPOSER_PLACEHOLDER, and ReopenClosedViewShortcut stays off on the
slimmer root. Effects carry
justified react-effect-budget suppressions and comments are cut to the
four-line limit.

The error banner asks about the thread's own Environment (ChatBannerLane
passes it), so a thread of another Environment keeps T3's own text.

On v0.1.4: upstream dropped the resting composer's control strip, so Buddy's composer hook only gives the placeholder and hasControls is gone; Buddy's controls still replace T3's in ComposerControlsBar ahead of upstream's controls ownership. Upstream shows an approval on its own row (pingdotgg#26255); Buddy's mask leaves no work rows, so the approval falls to upstream's footer for unmatched approvals, and a browser test shows a pending approval there with Approve and Decline and no work row or quiet line. The link_request card stays off Buddy's surface, where link requests are refused (row 25). useChatShortcuts no longer exports its input type, so the overlay takes it from the hook. The tests follow upstream's props and labels: no activeTurnInProgress or onRollbackCheckpoint, the queue's "1 queued message", background work rows with turn items and "Running …" / "Waiting on …" titles, and a single subagent as a [data-v2-item-type=subagent] row. Effect suppressions use the renamed no-unreasoned-state-sync rule. Row 23's AcpT3Selection, row 25's host appearance and row 7's launcher clearance, which this commit carried on v0.1.3, are back in their rows.

On v0.1.6 useThreadLifecycle returns attentionNow, which is the run's Stop
once it went unanswered (Force stop, pingdotgg#27252) and the child's question
otherwise, and workNow, which is the Stop while it is under way and the
background work otherwise; backgroundWorkNow is gone. The inline mask
hides the child's question, the work and the goal on Buddy's surface,
while an unanswered Stop still shows there, so the person can force it;
the contract test pins every mask line, including that one, and
useThreadLifecycle's return. buddyBackgroundWork is whether workNow
exists. Upstream's provider traits menu content
(renderProviderTraitsMenuContent, pingdotgg#28057 and pingdotgg#28076) is not needed in
ComposerControlsBar, which on Buddy's surface returns BuddyComposerControls
before it. A side chat (pingdotgg#26901) renders ChatView embedded, so the thread
tabs and terminal drawers are off for it as they are on Buddy's surface.

On v0.1.7: upstream wraps the timeline in `WorkspaceMutationIdContext`,
so `buddyBackgroundWork` sits inside it after `isWorking`. Upstream now
passes `environmentId` to the banner lane itself, so the overlay drops its
own copy of that prop and only uses it for Buddy's error banner. The work
line outside Buddy reads "Running · <label>" and "Waiting · <label>" on
v0.1.7, and the timeline test pins those.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From v0.1.3 a follow-up steers the running turn by default
(pingdotgg#24718). A steer into a turn that waits on a pending
runtime request aborts the provider's pending callback: Claude steers with
priority "now", a pending AskUserQuestion resolves as cancelled and an
approval as cancel, and the person never sees the question
(#139). #119 guarded only sendToThread's auto
mode; the composer's explicit steer, the auto branch of the delivery intent
and promoting a queued message still steered through it.

The guard now lives in one place, CommandPolicy.resolveMessageDispatchIntent:
while the thread has a pending runtime request, any steer_active becomes
queue_after_active. It covers a steer requested as a delivery intent (auto or
steer) and one requested as a dispatch mode (sendToThread, older clients).
The orchestrator reads the thread with its runtime requests on every
message.dispatch, so no caller has to load them. Promoting a queued message
to a steer checks the same predicate (waitsOnPendingRequest) and puts the
message first in the queue instead, so it runs right after the answer.
Restart is unchanged.

sendToThread's own check from #119 is gone, and
ThreadManagementService.ts is upstream's again; its test resolves the
dispatched mode through the guard, and an explicit steer with a pending
request now queues too. The allowlist trades ThreadManagementService.ts for
CommandPolicy.ts, Orchestrator.ts and their tests, and the user guides for
the composer and the agent CLI say a steer queues while a question waits.

On v0.1.4: upstream renamed stopRunForGoalCommand to
stopRunForQueuedCommand with a required reason (pingdotgg#25837); the guard
passes 'Queued behind a pending request' and stays after the compaction
and /goal branches. pingdotgg#25846 renamed the queued message action to Send
now, and composer.md names it. A delegated task's completion wake, which
v0.1.4's remote delegation can also produce (RemoteDelegation.ts), set
steer_active after the guard, as it did on the v0.1.3 line; it now
steers only while waitsOnPendingRequest is false, and
SteeringCompletion.integration.test.ts queues such a wake behind a
pending request.

On v0.1.5 pingdotgg#26715 queues a steer for a provider that can neither steer
its running turn nor restart it. The overlay's pending-request check
runs before that branch and still holds; the row's
ThreadManagementService test gives its projection empty providerThreads
and providerSessions, which the new branch reads.

On v0.1.6 upstream still steers into a turn that waits on a pending
runtime request, and the guard still reads the thread's runtimeRequests on
every message.dispatch. Claude's MCP elicitations now wait on the approval
card (pingdotgg#21903) as pending mcp-elicitation runtime requests
(ClaudeAdapterV2.ts), and a steer would cancel them like any other request,
so CommandPolicy.test.ts covers that kind as well. Upstream's Force stop
cases (pingdotgg#27252) and this row's sendToThread cases are two separate
it.effect.each blocks in ThreadManagementService.test.ts.

On v0.1.7: upstream lets a delegated task's question steer a busy parent
turn as well (pingdotgg#29909), on the same path as a completion
wake. A steer into a turn that waits on the person would cancel that
person's question, so the guard now covers both: `waitsOnPendingRequest`
comes before the completion-wake and task-question conditions. The
pending-request test also sends a task question and checks that it stays
queued; without the guard it steers.

Also on v0.1.7, a follow-up sent before the run's provider turn started
waited for a turn of its own (#170). resolveDeliveryIntent
queues automatic input while the active run is preparing or starting, or
has no running provider turn of its attempt, and upstream's web composer
turns a send made while the run is preparing or starting into Queue
(planComposerTurn in ChatView.logic.ts). A cold start, such as Buddy's
agent over ACP, leaves that gap open for seconds, so the message ran after
the turn instead of steering it. Such a queued run now notes the run it
was sent during (steersRunId, optional in orchestrationV2.ts, from
runAwaitingProviderTurn in CommandPolicy.ts). When that run's provider
turn reports running, the orchestrator promotes those runs to a steer
under the thread lock, only if upstream's resolveMessageDispatchIntent
would steer auto input into that run now (pending request, maintenance and
compaction targets, steering capability) and stopReachedRun allows it;
held runs, /goal commands, notifications and delegation wakes are left
alone. A provider that cannot steer, or a turn that never starts, keeps
upstream's queue. SteeringCompletion.integration.test.ts sends a follow-up
before the turn starts and expects it steered in; without the reaction it
waits in the queue. composer.md follows.

A running provider-turn update repeats with every usage tick. The first
running update of each provider turn takes the thread lock
(makeFirstRunningUpdate in CommandPolicy.ts), because a follow-up committed
alongside it is visible only under the lock; a later one first reads the
thread's queued runs without the lock and takes it only while a follow-up
waits for its turn. Under the lock the reaction reads the queued runs again
and reads the whole projection only while a follow-up waits.
CommandPolicy.test.ts covers runAwaitingProviderTurn and
makeFirstRunningUpdate, and the contract test pins the lock's order; no
test drives the race itself, because nothing between message.dispatch's
read and its commit can be held from a test.

The web composer now keeps auto for a send made while the run is set up,
which the server steers in; planComposerTurn still turns the other choices
and a send that compacts first into Queue, and ChatView.logic.test.ts fails
without the change. A send the person makes as Queue (Queue as the default
action, or the alternate key over a steer default) stays queued.
resolveComposerDispatchMode takes turnStarting, which ChatComposer.tsx and
composerCommandKey.ts set while the phase is connecting, and
composerDispatch.test.ts fails without it. Row 5's allowlist names
ChatView.logic.ts and its test, composerCommandKey.ts, composerDispatch.ts
and its test, and orchestrationV2.ts. The official mobile app is upstream's and
still sends queue while a turn starts.

Each queued follow-up gets one promotion, under its own command id
(command:steer-awaiting-turn:<runId>). A refused promotion leaves its
message queued, logs a warning and does not hold back the next follow-up,
and a later usage tick does not retry it. Two integration tests cover
this: a /compact sent during the start no longer blocks the follow-up
after it, and a refused promotion is tried once and the next follow-up
still steers in.

A /goal the person sends into a turn that waits on a question still
interrupts that turn, as on main and upstream; #173 carries
the fix to upstream's pull request. A follow-up sent to Buddy during a
cold start now steers, so row 33 gives the steer Buddy's page as the
Czech block (#150).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion closes

Effect RPC interrupts every fiber of a client whose socket closes. The git
stacked action ran in its stream's fiber, so a dropped connection stopped it
halfway: a commit without its push, a push without its pull request. It now
runs detached from the socket, the same pattern upstream pingdotgg#18980 gave the
server update, and the pull request link and git status refresh still run
after it. The client reports a lost transport as a lost connection rather than
a failed action; the result shows through the git status and thread pull
request link it already resyncs.

Row 31 of the patch stack (#125): allowlisted in
lazurio-fork-ci.yml and listed in the release runbook.

On v0.1.3 the row applies as it is: the gitRunStackedAction handler still
runs gitWorkflow.runStackedAction inside the RPC stream's fiber (it moved
within ws.ts), effect is still 4.0.5, and upstream's only detached progress
stream is still the self-update one. The allowlist entry and the runbook
note go to the distribution row.

On v0.1.4: replays unchanged; pingdotgg#21046 publishes local git status before the remote lookup and does not touch the detached progress stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty and others added 4 commits November 5, 2026 20:30
…3 line

Up to v0.1.2 the default follow-up was queue, and the web client saved it with
the whole settings object, so most people hold followUpBehavior "queue"
without having chosen it. On 2026-10-28 the Organization Admin decided that
everyone moves to steer once on the first update to the v0.1.3 line; who
wants queue sets it again in Settings (#140). The steer guard
from #139 keeps a steer from cancelling a pending question.

The web client's one read of its settings (readBrowserClientSettings, key
t3code:client-settings:v1) runs a migration from apps/web/src/lazurio/: a
saved "queue" without the marker becomes "steer" and is written back with
the marker lazurioFollowUpSteer: true. Every write on the line stores the
marker next to the settings, so a queue chosen afterwards sticks. A fresh
client has no saved settings and keeps the default steer. The marker lives
only in this storage schema; ClientSettings and the contracts do not change.

Desktop and mobile are left alone. Both are the official upstream apps,
which this fork does not build or ship: desktop keeps its client settings in
its own file through desktopBridge, and mobile in its own preferences.

On v0.1.4: replays unchanged; the default follow-up is still steer. The CI allowlist lines go to row 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ad of cancelling it

A message sent while an ACP agent works is steered by cancelling the
running turn (session/cancel) and sending the message as the next
prompt, because ACP has no steering request. For Buddy (Lazurio Hermes
over ACP) every such message stopped his work and he started it again.

An agent that lists "lazurio.activeSteer": 1 in its initialize
capabilities' _meta now gets supportsActiveSteering for its session, and
steerTurn sends the extension request _lazurio/steer {sessionId, text}
with the message's text and its attachments as saved paths. The turn
goes on. An answer other than steered waits up to 10 s for the prompt
to settle and fails: a turn that ended leaves the message to the next
turn; a prompt the agent has not started yet is steered by the effect's
retry. Agents without the meta keep steering by interrupt and restart.
effect-acp's initialize normalization now keeps capabilities._meta.

Tests: the mock ACP agent advertises the meta under
T3_ACP_LAZURIO_ACTIVE_STEER=1 and answers _lazurio/steer. The adapter
test steers a running turn, which completes with the steer's text and
without session/cancel, and a steer after the turn fails. A second test
keeps restart steering for an agent without the meta.

The row also carries the two review rounds of #141: a
steer names its turn and message, so it reaches only that turn and only
once, and a steered message becomes the next turn only when the agent
refused it.

On v0.1.4: pingdotgg#25610 added commitReplacementSession for a session that
replaces the running one (handoff recovery, rollback).
commitReplacementSession sets the adapter's activeSteering from the
replacement's initialize answer, so whether a prompt names its turn and
a steer goes through _lazurio/steer follows the session that runs. The
session's supportsActiveSteering capability is still the one negotiated
at startSession; a replacement does not change it.

On v0.1.7: upstream added native ACP steering through `flavor.steering` (Kiro),
plus compaction and fork-from-turn. An agent that advertises
`lazurio.activeSteer` still steers through `_lazurio/steer`, checked first in
upstream's `steerTurn`, whose error mapping now passes a refused
`ProviderAdapterSteerRunError` through unchanged. `steerActiveTurn` is
untraced because `steerTurn` carries the span. The turn meta rides on
upstream's prompt options.

The steer's text goes through LazurioBuddyPrompt's
projectComposerContextForProvider, as a turn start does, so Buddy's
Hermes gets the page as row 23's Czech block and never upstream's
<t3_context> envelope (#150). With row 30's cold-start
steer that is the path of every follow-up sent while Buddy starts. The
Buddy steer test sends a page record and fails without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Lazurio workspace gateway lets the native client through without its own
sign-in only for a fixed list of requests. Since v0.1.3 the client asks for
the rpc ticket first and falls back to the bare path only on a 404, so a
gateway that did not list it answered 302 and desktop and mobile pairing
stopped before the socket. This test runs the production entry points of a
direct pairing, preparePairingRegistration and authorizeBearer, against a
gateway emulator holding a copy of the Machines allowlist, and fails when
any request before the socket is not on it.

On v0.1.7: the runbook lines of this commit moved into the runbook row; the
test passes unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The distribution's files on the new base, upstream T3 Code v0.1.7
(322f654), last in the stack: CI, the
CLI archive, release and upstream watch workflows, the image, the issue
form, README, SECURITY.md, the runbook, the overlay spec, the contract
test, the release version check and the rebuild's skill. They are
main's files, made current for v0.1.7 (#172). Main's base
is v0.1.6 (#148); the notes for v0.1.4 to v0.1.6 below are
kept from that rebuild, which brought v0.1.4's and v0.1.5's changes to
main.

On v0.1.4:

- The allowlist gains five files: chat/ThreadServersPanel.tsx, where a
  click on a thread's server opens it in the Environment browser (row 9);
  sidebar/UpdateEverythingDialog.tsx and updateEverything.logic.test.ts,
  for the per-server target and the continuation gate of v0.1.4's update
  dialog (row 4); upstream's installer test install.test.ts, whose fixture
  needs upstream's repository named (row 4); and cli/environment.ts, whose
  settings take Buddy's linked-Environments guard (row 25).
  ServerEnvironment.test.ts keeps the update-check guard (row 4).
- The contract test's pins follow the seams that moved: upstream deleted
  LegacySidebar.tsx (pingdotgg#21039), so the shell slot's column
  head is pinned in the thread sidebar only; the sidebar reads
  useDelegatedUnderParent before the managed check; the empty panel and
  Nová karta carry the workspace focus default; the MCP session issue
  calls mcpClientScopes directly; an agent command's scope drops the
  session's web origin so its thread links stay t3-thread:// links; the
  Buddy composer hook takes only the environment, since v0.1.4's resting
  composer has no strip of its own; the timeline has no paint-only gate
  (pingdotgg#25787); the steer guard returns through
  stopRunForQueuedCommand and also holds a delegated task's completion
  wake; the Servers panel's click opens the Environment browser; and
  Buddy's server settings layer turns linked Environments off.
- Rows 30, 32 and 33, which main carried as commits after row 31, now
  sit in their rows; row 28's addenda are back in rows 7, 23 and 25.
  Upstream renamed the lint rule react-effect-budget to
  t3code/no-unreasoned-state-sync (pingdotgg#26161).
- The skill's inventory groups cover rows 23 to 33.
- Tests, fixtures and docs of rows 9, 10, 21, 23, 25, 26 and 27 name
  only fictional people and hosts (Alena, Bob, example-person,
  vm-0x.example.lazurio.io), since the repository is public.

On v0.1.5:

- CI and the contract test pin v0.1.5 and build 0.1.5-lazurio.0; the
  issue form's version example is 0.1.5-lazurio.1. Node 26.12.1 and pnpm
  12.14.0 are unchanged, so the toolchain pins hold. SECURITY.md's
  upstream section equals the new tag's policy.
- The Linux CLI archive now ships lib/libatomic.so.1 and patchelf sets
  t3's RUNPATH to it (pingdotgg#26912, pingdotgg#26932), so
  lazurio-cli-archives.yml installs patchelf before the build, the way
  upstream's release-desktop.yml does; the contract test pins the order.
- useUpdateEverything.ts leaves the allowlist: row 4 now keeps a
  server's advertised release out of waitForNpm in
  updateEverything.logic.ts (pingdotgg#26917), and the contract
  test pins that seam there. The fixed test gate is unchanged, and every
  gated path exists at v0.1.5.
- The contract test's pairing pins follow the constants that knip made
  module-private (upstream's pre-commit hook runs knip:check since
  pingdotgg#26167).
- The runbook's Overlay table describes every row on v0.1.5, the Cílový
  stack names each row's new commit, the swap step names the workflows
  to disable after the swap from the v0.1.3 base (agent-context-budget.yml,
  mobile-fingerprint-label.yml; v0.1.5 adds none), and stage 3 and the
  skill say to replay with --no-verify and run knip over the finished
  stack.

On v0.1.6:

- CI and the contract test pin v0.1.6 and build 0.1.6-lazurio.0; the
  issue form's version example is 0.1.6-lazurio.1. The toolchain pins
  hold, and SECURITY.md's upstream section equals the new tag's policy.
- The allowlist is unchanged. Its comments say why ServerEnvironment.ts
  and server.ts offer and build no triage on a Lazurio Environment
  (row 15, #147), why ws.ts runs no ChatGPT handoff there
  and the web starts none through a Lazurio primary (row 18,
  #145), that useComposerSend.ts counts a prompt toward
  upstream's star request (pingdotgg#27832) only outside a Lazurio
  Environment (row 16), and that no update waits on npm since
  pingdotgg#27847 (row 4).
- The contract test pins those seams: the triage guard and its one
  construction, the star prompt's check and its one call, the handoff
  route, reconnectProfile and the web's routing, and on Buddy's surface
  the inline mask over useThreadLifecycle's attentionNow and workNow,
  every line of it (React Compiler, #135), the items'
  counts in useThreadLifecycle.tsx and its one caller, the promise
  ChatView hands Nová karta for a side chat (row 13), and that Buddy's
  surface offers no side chat (row 25). It also follows
  the seams upstream moved: no waitForNpm left in the update dialog's
  logic, the palette's addProjectAction, the sidebar's pickable project
  groups (pingdotgg#26154), and an embedded side chat
  (pingdotgg#26901) without thread tabs, terminal drawers or the
  right panel. Claude's mcp-elicitation requests and the agent scope's
  kill are covered by rows 30 and 8's own tests.
- The runbook's Overlay table describes every row on v0.1.6, the Cílový
  stack names each row's new commit and the files rows 13, 15, 16 and 18
  now change, and the swap step adds mobile-eas-preview-fork.yml to the
  workflows to disable after the swap. The runbook and the skill say to
  address a fix written over the finished stack to the row that added
  the place it changes.

On v0.1.7:

- CI and the contract test pin v0.1.7 and build 0.1.7-lazurio.0; the
  issue form's version example is 0.1.7-lazurio.1. The toolchain pins
  hold, and SECURITY.md's upstream section equals the new tag's policy.
  v0.1.7 adds no workflow, so the swap step's list is unchanged.
- The allowlist gains toolkits/preview/handlers.ts and its test: since
  v0.1.7 preview_open takes a dev-server target (pingdotgg#28027),
  and upstream's handler answered an open with a target with the
  navigate result alone; row 10 keeps the open answer underneath, so the
  Environment browser's view stays. The fixed test gate is unchanged.
- The contract test follows the seams that moved: the settings writer
  takes the client's patch for the managed policy
  (updateAndPersistSettings, pingdotgg#27197), Hermes's runtime
  notes come from the active session setup, the MCP caller's webOrigin
  is now hostedAppUrl, the steer guard's waitsOnPendingRequest also
  holds a delegated task's question (pingdotgg#29909), and the
  ACP adapter filters T3's options through a flatMap.
- For row 30 the allowlist gains apps/web's components/ChatView.logic.ts
  and its test and components/chat/composerCommandKey.ts,
  client-runtime's state/composerDispatch.ts and its test, and
  contracts' orchestrationV2.ts: eight files with the two above. The
  contract test pins that the web keeps auto input auto while the run is
  set up; that /compact and /logout wait for no turn; that a running
  update takes the thread lock on its provider turn's first report or
  while a follow-up waits, then checks again under the lock; and that
  each follow-up gets one promotion, so a held one, one already promoted
  and a refused one stay queued without holding back the next.
- Stage 3's checkout command names the release version check's script
  and test (#165), which the v0.1.6 runbook left out.
- The runbook's Overlay table describes every row on v0.1.7, and the
  Cílový stack names each row's new commit. Main's Cílový stack still
  named older hashes from row 10 on, so the runbook and the skill now say
  to take each row's commit from main's log by subject. The runbook also
  records the Effect 4.1.2 test recipe (NodeCrypto beside NodeServices,
  ManagedPolicy.layerTest()) and that the overlay's settings writes go
  through updateSettingsWith, so the managed policy vets them.
- The runbook records what the independent check of this rebuild found:
  Codex 0.166.0, T3's managed runtime since v0.1.7, still has
  default_mode_request_user_input off by default (row 22); mcp-client
  sessions get hosted thread links on the external origin (row 20);
  upstream's star prompt now stars through the Environment's account
  (row 16); the policy lock reads the macOS and Windows sources too
  (row 18); two upstream policy paths in the update flow do not follow
  row 4's target and are offered upstream; row 30's refused promotion,
  /goal and cold-start steer; and row 33's steer page block. The overlay
  CLI tests build auth on the test managed policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty and others added 2 commits November 6, 2026 00:40
LazurioPlatform F50 point 2 (`viewAddressOf`, `isViewHost` in
`src/browser/contract/pure.ts`) takes a view address only as written, on a
base of one or two DNS labels, and refuses every other address on a view
host. Two gaps remained in the Environment browser host:

- `lazurioBrowserView` took any number of base labels, of any length and
  with a hyphen at either end, so `browser.a.vm-02.acme.lazurio.io/t/<id>`
  or `browser.-vm-02.acme.lazurio.io/t/<id>` was answered as another
  Environment's tab. It now takes a base of one or two labels that are DNS
  labels, as `environmentIdOf` does. The web's copy changes alike, so the
  right panel opens no tab for such a link either.
- The refusal read only what `normalizePreviewUrl` made of the address.
  `https:/browser.<base>.lazurio.io/t/<id>` became
  `https://https/browser...` and loaded. The refusal now reads the address
  as written as well, in preview_open, preview_navigate (`url` and
  `target.url`) and pages.open.
- A chat link opened in the right panel as the browser read it (`link.href`),
  so `https://BROWSER.<base>.lazurio.io/t/<id>` or `...lazurio.io:443/t/<id>`
  opened a tab. The panel now reads the link as written (its `href`
  attribute), and `environmentBrowserTab` takes a view only as written.

The tests run a table of F50 point 2's cases through preview_open,
preview_navigate and pages.open, each on a fresh host: the own bare origin
and tab, another Environment's tab, every off-contract form on the own and
another view host (trailing dot, upper case, port, scheme, path, query,
fragment, credentials, target id, bare-origin variants, schemeless and
padded input), on the own, another and a person's view host, bases F50 has
no Environment for, and look-alike hosts that load. The per-finding address
lists they replace are rows of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de's components

On a Lazurio Environment, Chat takes the design system's colours (theme.css) but keeps T3's
shapes, type and depth, so it still reads as T3 beside Apps. As an experiment, not for release,
one more overlay stylesheet (apps/web/src/lazurio/skin.css) dresses T3's own components in the
design system's clean look behind theme.css's gate: pill buttons and fields, square icon
buttons, 10 px cards, composer and dialogs, Inter Tight 600 headings, hairlines and one floating
shadow. Behaviour, order, text and sizes stay T3's; without the shell, or with a picked T3
theme, nothing changes.

The one upstream seam: components/ui/button.tsx marks each Button with its size and variant,
because a trigger that renders a Button takes over its data-slot. The release contract keeps the
skin to T3's own hooks and the design system's tokens, and lists the tokens the shell would have
to promise.

Claude Opus 5.5 in Claude Code (T3 Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant