Repository navigation
Sync with upstream v0.1.3-nightly.20261024.3617 (review only, do not merge) - #39
Conversation
5f151ce to
6c52d30
Compare
Build and nested Agent testTested the Fork release build of 6c52d30, run 40107236833, version Against a nested Agent from this branch's
Spot checks of the features behind the conflicts and fixes:
|
6c52d30 to
ee02861
Compare
Build and nested Agent test, after the dev Agent changeTested the Fork release build of ee02861, run 40125733503, version This Sandbox's
Only |
Review only — do not merge. This shows the rewrite of
mainfor the upstream sync. Once it is approved,mainis force-pushed to this branch, the old tip is archived asarchive/main-2026-10-24, and this PR is closed.Base
Upstream nightly
v0.1.3-nightly.20261024.3617(27e0c68), the latest, moving fromv0.1.2-nightly.20261022.3526(f054d1b).The stack was first rebuilt on
v0.1.3-nightly.20261023.3579assync/2026-10-23. That branch was never reviewed, and a newer nightly arrived before it was, so this branch replaces it. Moving from.3579to.3617was conflict-free and left every patch identical (git range-diffshows all five commits as=).Mapping
main#38 (the dev Agent on the agentctl desktop base image) is folded into the setup commit. Its message's dev Agent paragraph now describes the image on the base.
Fixes for upstream changes
A review of the sync against the current release (
v0.1.2-preview.20261022.22) found no regressions, but found two places where upstream's new code doesn't account for the fork's features. Each is fixed in the commit that owns the fork side, with a test that fails without the fix.upstream#23647) now tells you to restart a reused remote server that answers 500 on pairing and runs a different version, but it compares against the desktop's ownAPP_VERSION. WithT3CODE_REMOTE_SERVER_VERSION, as when testing a sync build, the desktop runs another release on the host. In that case every 500 was diagnosed as a mismatch, and you were told to install the wrong version. The SSH bootstrap now reports the release the desktop runs on the host (remoteServerVersion), and the diagnosis compares against that, falling back toAPP_VERSION.upstream#23924) made those filters search the host within the listing's scope, but the scope droppedexcludeRepositories. That's how the page lists a repository that several Sandboxes clone only once, so each search queried that repository again on every Sandbox that holds it.PullRequestFilterOptionsInputnow takesexcludeRepositories, and the page forwards it. Mobile doesn't use exclusions, so its filters already match its list.Conflicts
vpprovides the pinned Node version, so the separate Node 24 requirement is gone. The fork's link to Vite+'s getting started guide replaces upstream's link to the root README's#install-vpsection, because the fork's README has no such section.remarksis combined with upstream'sno-checks,now,owedChecksandchecksAwaitedSince, so watch evaluation keeps both, and the tests cover both..3617. The fork's PR code overlaps 12 files upstream changed in that nightly, including the PR list filters andPullRequestService, but they merged cleanly and the checks below cover them.Everything else in the commit 3 range-diff is context drift.
Requested change
/, but the folder path kept plain slashes, so it readagents / self-dev/full. Every level now uses the same separator:agents / self-dev / fullin the trigger, andself-dev / fullin the list.ghcr.io/digdir/agentctl:main, pinned by digest to its build of Reset codex overrides button pushed out of card with long Codex binary path pingdotgg/t3code#284 (v0.1.0-preview.12.main.g07b3ae5), instead of aruststage compiling a pinned commit under a made-up version. Nested Agents therefore get Reset codex overrides button pushed out of card with long Codex binary path pingdotgg/t3code#284's generated# Agent sandboxsection, theagentctlskill and logins that need no flags.ubuntu-26.04-desktopdigest, which brings Right-click menu anywhere instantly selects first option pingdotgg/t3code#285's tools, sounzipleaves our apt list.instructions.mddrops what the generated section and theagentctlandcomputer-useskills now say: the sandbox and root sentence, the boot-clone detail, the whole Nested Agents section with its login and env-file recipe, and the generic desktop facts. It keeps the fork's own workflow and what only T3 Code needs on the desktop.Co-Authored-Byis replaced byhome/.claude/settings.jsonturning off Claude Code's commit and PR attribution, as digdir-agents Git diff view crashes with "Checkpoint ref is unavailable for turn" and shows no patch pingdotgg/t3code#225 did for its self-dev Agent, since Claude Code didn't reliably follow the instruction.podman buildofagents/devsucceeds,agent-image-checkpasses, and both binaries reportv0.1.0-preview.12.main.g07b3ae5.Range-diff
The full
git range-diff f054d1b801..e3db001403 27e0c684c5..ee02861281is about 100 KB, mostly #38's own diff appearing in commit 1. The fork's own files (scripts/fork,.github/workflows/fork-*.yml,Makefile,README.md) are identical tomain's,agentsdiffers only by the dev Agent change above, and apart from the two fixes above,git diff origin/main sync/2026-10-24touches only files upstream changed.Commit 1: message, development.md and the SSH version fix
@@ Commit message scripts/fork/seed-sandbox-server.ts installs that build's server in a Sandbox for testing, and a run on main with from_run publishes the tested build once main points at its commit. T3CODE_REMOTE_SERVER_VERSION and - T3CODE_REMOTE_SERVER_RELEASE_BASE_URL choose another release. + T3CODE_REMOTE_SERVER_RELEASE_BASE_URL choose another release, and the SSH + bootstrap reports that version, so a reused server on another build is + told to run it rather than the desktop's own. scripts/fork/install-desktop.ts installs a release, or builds one with --from-source, beside an upstream install. The dev Agent. agents/dev is an agentctl Agent (t3code-dev) for developing - the fork: an Ubuntu 24.04 image with Node, vp, Rust, ImageMagick, Claude - Code, Codex, gh, Podman and agentctl; a boot-time clone of the fork; and a - nested variant for testing the image from inside a Session. Its VNC desktop - shows the Electron client, with the `desktop` helper and the computer-use - skill to drive it, gnome-keyring for Electron's safeStorage, and SSH aliases - for nested Agents. instructions.md describes how the fork is maintained. The - image pins the Claude Code and Codex versions that agentctl and agentd - release tests, and carries Inter and JetBrains Mono for its demo-video - skill, which makes captioned demo videos of T3 Code from real screen - recordings of the desktop with fframes: a storyboard, scripts that record, - steer the pointer, cut takes and make contact sheets, and a fframes project - template whose kit holds the look and scenes so a new video rewrites only - its lib.rs. + the fork, built on digdir-agents' ubuntu-26.04-desktop agentctl base image, + which provides agentd's image contract, Claude Code and Codex at the versions + agentd is tested with, gh, the VNC desktop with the `desktop` helper and + computer-use skill, gnome-keyring for Electron's safeStorage and the + boot-time workspace clone. The image adds what developing T3 Code needs: + Node 26, Playwright's headless shell for the web browser tests, Vite+, Rust, + ImageMagick, Podman with nested networking, agentctl and agentd built from a + pinned digdir-agents commit, the kvm grant for nested Sandboxes, SSH aliases + for nested Agents, a Norwegian keyboard and a 1600x900 display, and a + drop-in that keeps one OOM kill over SSH from ending the other sessions. Its + nested variant, for testing the image from inside a Session, builds from + source without nested Sandboxes. instructions.md describes how the fork is + maintained. The demo-video skill, with Inter beside the base's JetBrains + Mono, makes captioned demo videos of T3 Code from real screen recordings of + the desktop with fframes: a storyboard, scripts that record, steer the + pointer, cut takes and make contact sheets, and a fframes project template + whose kit holds the look and scenes so a new video rewrites only its + lib.rs. AGENTS.md says how to release and install the fork, and that nothing published on the fork may reference an upstream issue, pull request or @@ apps/desktop/src/ssh/remoteServerOverrides.ts (new) + return runner.nodeScriptPath ? runner : { ...runner, ...overrides }; +} + ## apps/web/src/connection/platform.test.ts ## +@@ apps/web/src/connection/platform.test.ts: describe("desktop SSH pairing", () => { + }), + ); + +- const failPairing = (serverVersion: string, message: string) => { ++ const failPairing = (serverVersion: string, message: string, remoteServerVersion?: string) => { + const bridge = makeBridge([]); ++ if (remoteServerVersion !== undefined) { ++ vi.spyOn(bridge, "ensureSshEnvironment").mockResolvedValue({ ++ ...SSH_BOOTSTRAP, ++ remoteServerVersion, ++ }); ++ } + const fetchDescriptor = bridge.fetchSshEnvironmentDescriptor; + vi.spyOn(bridge, "fetchSshEnvironmentDescriptor").mockImplementation(async (url) => ({ + ...(await fetchDescriptor(url)), +@@ apps/web/src/connection/platform.test.ts: describe("desktop SSH pairing", () => { + }), + ); + ++ it.effect("names the server release the desktop runs on the host, not its own version", () => ++ Effect.gen(function* () { ++ const preview = "0.1.3-preview.20261024.25"; ++ ++ const error = yield* failPairing(APP_VERSION, PAIRING_500, preview); ++ expect(error).toBeInstanceOf(ConnectionBlockedError); ++ expect(error.detail).toContain(`so it runs ${preview}`); ++ ++ expect(yield* failPairing(preview, PAIRING_500, preview)).toBeInstanceOf( ++ ConnectionTransientError, ++ ); ++ }), ++ ); ++ + it.effect("keeps retrying pairing failures that are not a version mismatch", () => + Effect.gen(function* () { + expect(yield* failPairing(APP_VERSION, PAIRING_500)).toBeInstanceOf(ConnectionTransientError); + + ## apps/web/src/connection/platform.ts ## +@@ apps/web/src/connection/platform.ts: export const provisionDesktopSshEnvironment = Effect.fn( + detail: `That host reaches ${descriptor.label}, a different machine. Add it as its own environment instead.`, + }); + } ++ // A build can choose another server release for SSH hosts than its own version. ++ const expectedVersion = bootstrap.remoteServerVersion ?? APP_VERSION; + const access = yield* Effect.tryPromise({ + try: () => bridge.bootstrapSshBearerSession(bootstrap.httpBaseUrl, pairingToken), + catch: (cause) => { +@@ apps/web/src/connection/platform.ts: export const provisionDesktopSshEnvironment = Effect.fn( + // the desktop's runner just minted, and answers 500 on every retry. + if ( + error._tag === "ConnectionBlockedError" || +- descriptor.serverVersion === APP_VERSION || ++ descriptor.serverVersion === expectedVersion || + !error.detail.includes("[ssh_http:500]") + ) { + return error; + } + return new ConnectionBlockedError({ + reason: "configuration", +- detail: `${descriptor.label} runs T3 Code ${descriptor.serverVersion}, but this desktop is ${APP_VERSION}, and that server could not accept its pairing. Restart the T3 server on ${descriptor.label} so it runs ${APP_VERSION}, then retry.`, ++ detail: `${descriptor.label} runs T3 Code ${descriptor.serverVersion}, but this desktop pairs with ${expectedVersion}, and that server could not accept its pairing. Restart the T3 server on ${descriptor.label} so it runs ${expectedVersion}, then retry.`, + }); + }, + }); + ## docs/README.md ## @@ docs/README.md: source alone does not explain. Most code changes do not need an internal documen ### Runbooks @@ docs/operations/development.md ## First checkout --Install `vp` using the [root README](../../README.md#install-vp). The checkout requires Node 24; -+Install `vp` with its [getting started guide](https://viteplus.dev/guide/). The checkout requires Node 26; - Bun is optional. From the repository root: +-Install `vp` using the [root README](../../README.md#install-vp). It provides the Node version ++Install `vp` with its [getting started guide](https://viteplus.dev/guide/). It provides the Node version + pinned in `package.json` (Node 26), so no separate Node install is needed. From the repository root: ```sh @@ docs/operations/fork-desktop-build.md (new) +- `T3CODE_REMOTE_SERVER_RELEASE_BASE_URL`: where release archives are downloaded from, such as + `https://github.com/pingdotgg/t3code/releases/download` for upstream's. + ## packages/contracts/src/ipc.ts ## +@@ packages/contracts/src/ipc.ts: export interface DesktopSshEnvironmentBootstrap { + pairingToken: string | null; + remotePort?: number; + remoteServerKind?: "external" | "managed"; ++ /** The T3 release the desktop runs on the host, which mints its pairing credentials. */ ++ remoteServerVersion?: string; + } + + export const DesktopSshEnvironmentBootstrapSchema = Schema.Struct({ +@@ packages/contracts/src/ipc.ts: export const DesktopSshEnvironmentBootstrapSchema = Schema.Struct({ + pairingToken: Schema.NullOr(Schema.String), + remotePort: Schema.optionalKey(Schema.Number), + remoteServerKind: Schema.optionalKey(Schema.Literals(["external", "managed"])), ++ remoteServerVersion: Schema.optionalKey(Schema.String), + }); + + export interface DesktopSshPasswordPromptRequest { + + ## packages/ssh/src/tunnel.test.ts ## +@@ packages/ssh/src/tunnel.test.ts: describe("ssh tunnel scripts", () => { + assert.equal(tunnels, 2); + assert.isTrue(remoteRunning); + assert.equal(first.httpBaseUrl, second.httpBaseUrl); ++ assert.equal(first.remoteServerVersion, ARCHIVE.archiveVersion); + }).pipe( + Effect.ensuring(Deferred.succeed(finishShutdown, undefined)), + Effect.provide(layer), + + ## packages/ssh/src/tunnel.ts ## +@@ packages/ssh/src/tunnel.ts: const makeSshEnvironmentManager = Effect.fn("ssh/tunnel.SshEnvironmentManager.ma + pairingToken, + remotePort: entry.remotePort, + ...(entry.remoteServerKind ? { remoteServerKind: entry.remoteServerKind } : {}), ++ ...(runner?.archiveVersion?.trim() ++ ? { remoteServerVersion: runner.archiveVersion.trim() } ++ : {}), + }; + }), + ); + ## scripts/fork/install-desktop.test.ts (new) ## @@ +// @effect-diagnostics nodeBuiltinImport:off globalDate:off - tests the script, which only uses Node built-ins.Commits 2 to 4 (3 holds the picker change, 4 the filter search fix)
2: b3e0f4d980 ! 2: 3aec799060 fix(server): wake watched pull requests for users sharing the agent account (#5) @@ apps/server/src/orchestration-v2/pullRequestWatch.forgejoComments.test.ts: descr ); ## apps/server/src/orchestration-v2/pullRequestWatch.test.ts ## -@@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: import type { - } from "@t3tools/contracts"; - import { assert, describe, it } from "@effect/vitest"; - --import { PULL_REQUEST_WATCH_WAKE_LIMIT, evaluatePullRequestWatch } from "./pullRequestWatch.ts"; -+import { -+ PULL_REQUEST_WATCH_WAKE_LIMIT, -+ evaluatePullRequestWatch, +@@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: import { assert, describe, it } from "@effect/vitest"; + import { + PULL_REQUEST_WATCH_WAKE_LIMIT, + evaluatePullRequestWatch as evaluate, + pullRequestWatchMessage, -+} from "./pullRequestWatch.ts"; - - const STARTED = "2026-10-02T12:00:00.000Z"; + } from "./pullRequestWatch.ts"; + // These cases read the head once or at one moment, so the no-checks notice never comes due. @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePullRequestWatch", () => { const late = { ...remark("bot", "2026-10-02T12:06:00Z"), id: "late" }; const reported = evaluatePullRequestWatch(watch(), detail(), [first]); @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePul const conflicting = detail({ mergeability: "conflicting" }); const first = evaluatePullRequestWatch(watch(), conflicting, noRemarks); @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePullRequestWatch", () => { - assert.equal(pushed.next.wakes, 1); + assert.isTrue(anHourLater(watch(), azure).every((kinds) => kinds.length === 0)); }); }); + @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePul +}); ## apps/server/src/orchestration-v2/pullRequestWatch.ts ## -@@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import type { +@@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import * as DateTime from "effect/DateTime"; /** * Wakes in a row that bring only comments. Check, conflict, or push news resets the count, so @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import type { + * this stops a chatty bot, or the agent's own unmarked replies, from looping it. */ export const PULL_REQUEST_WATCH_WAKE_LIMIT = 10; + /** How long a GitHub head may go without any check before the agent is told none reported. */ + const PULL_REQUEST_WATCH_NO_CHECKS_MS = 15 * 60_000; const LISTED_ITEMS = 10; const SNIPPET_LENGTH = 200; +/** @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import type { + /** The agent's own login, to point out its unmarked remarks, which are likely the user's. */ + readonly own: string | undefined; + } + | { readonly kind: "no-checks"; readonly minutes: number } | { readonly kind: "conflicting" }; - export interface PullRequestWatchReport { @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: export function evaluatePullRequestWatch( const own = (detail.viewer ?? detail.author?.login)?.toLowerCase(); const through = Date.parse(watch.remarksThrough); @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: function changeLines( + : []), ]; + } - case "conflicting": - return [`- The branch now conflicts with ${context.baseBranch}.`]; - } + case "no-checks": + return [ + `- No checks have reported${context.commit} after ${change.minutes} minutes. None may be configured to run on this change.`, ## apps/server/src/orchestration-v2/runtimeLayer.test.ts ## @@ apps/server/src/orchestration-v2/runtimeLayer.test.ts: it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => { 3: 06f16197b6 ! 3: 272c27e7f0 feat: agentctl Sandboxes as desktop environments (#2, #13, #20, #30, #32, #34, #35) @@ apps/web/src/components/Sidebar.tsx: import { buildThreadRouteParams, resolveAct import { ProjectEnvironmentBadge } from "./ProjectEnvironmentBadge"; import { buildDraftActionMenuItems, -@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar() { +@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve } > <SidebarGroup className="flex-1" role="presentation"> @@ apps/web/src/components/Sidebar.tsx: export default function Sidebar() { {isSearchingThreads ? ( threadSearchResults.length > 0 ? ( <TooltipProvider -@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar() { +@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve <PlusIcon className="-mx-0.5 size-3" /> Add project </button> @@ apps/web/src/routes/__root.tsx: import { RelayClientInstallDialog } from "../com +import { NewSandboxDialogHost } from "../sandboxes/NewSandboxDialog"; import { DesktopWebLinkCoordinator } from "../components/desktop/DesktopWebLinkCoordinator"; import { RunningThreadKeepAlive } from "../components/desktop/RunningThreadKeepAlive"; - import { ProviderUpdateLaunchNotification } from "../components/ProviderUpdateLaunchNotification"; + import { DesktopUpdateLaunchNotice } from "../components/sidebar/updateEverythingRun"; @@ apps/web/src/routes/__root.tsx: function RootRouteView() { > {primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null} @@ apps/web/src/sandboxes/ManifestPicker.tsx (new) @@ +import type { AgentctlSandboxManifest } from "@t3tools/contracts"; +import { ChevronsUpDownIcon, FolderGit2Icon } from "lucide-react"; -+import { useRef, useState } from "react"; ++import { Fragment, useRef, useState } from "react"; + +import { Button } from "../components/ui/button"; +import { @@ apps/web/src/sandboxes/ManifestPicker.tsx (new) + > + {repository && agent ? ( + <span className="truncate"> -+ {repository.name} -+ <span className="text-muted-foreground"> / </span> -+ {agent.label} ++ <PathSegments segments={[repository.name, ...agent.label.split("/")]} /> + </span> + ) : ( + <span className="text-muted-foreground">Choose an agent</span> @@ apps/web/src/sandboxes/ManifestPicker.tsx (new) + value={candidate.directory} + className="ms-6" + > -+ <span className="truncate">{candidate.label}</span> ++ <span className="truncate"> ++ <PathSegments segments={candidate.label.split("/")} /> ++ </span> + {candidate.agentName !== null && candidate.agentName !== candidate.label ? ( + <span className="truncate text-muted-foreground text-xs"> + {candidate.agentName} @@ apps/web/src/sandboxes/ManifestPicker.tsx (new) + ); +} + ++/** Separates a repository and its folders alike, so the levels read as one path. */ ++function PathSegments({ segments }: { segments: ReadonlyArray<string> }) { ++ return segments.map((segment, index) => ( ++ <Fragment key={segments.slice(0, index + 1).join("/")}> ++ {index > 0 ? <span className="text-muted-foreground"> / </span> : null} ++ {segment} ++ </Fragment> ++ )); ++} ++ +function ManifestPath({ manifest }: { manifest: AgentctlSandboxManifest }) { + return ( + <span className="min-w-0 font-mono text-muted-foreground text-xs"> 4: f446965627 ! 4: 5ecddad15b feat: pull requests for repositories in a workspace folder (#19, #22, #23) @@ Commit message Numbered Sandboxes that each clone the same repository listed each of its pull requests once per Sandbox. A pull request listing can now leave out - repositories by canonical key, without reading them from the host. The page + repositories by canonical key, without reading them from the host, and so can + the host search behind its label and author filters. The page assigns each repository to one connected server, folders' checkouts included, in a fork-owned helper beside upstream's project assignment, asks the others to skip it, and the picker offers each Sandbox's checkout of it. @@ apps/server/src/pullRequest/PullRequestService.test.ts: function makeService(inp + }), +); + ++it.effect("searches filter options only in the repositories its listing reads", () => ++ Effect.gen(function* () { ++ const searched: Array<string> = []; ++ const repositories = ["acme/app", "acme/infra"].map( ++ (repository) => ++ project({ id: "member", title: "", workspaceRoot: "", repository }).repositoryIdentity!, ++ ); ++ const service = yield* makeService({ ++ projects: [project({ id: "sandbox", title: "sandbox", workspaceRoot: "/workspace" })], ++ resolveWorkspaceRepositories: () => ++ Effect.succeed( ++ repositories.map((identity) => ({ ++ ...identity, ++ rootPath: `/workspace/${identity.displayName!.split("/")[1]}`, ++ })), ++ ), ++ providers: [ ++ fakeProvider(SourceControlProviderKind.make("github"), { ++ listRepositoryLabels: ({ repository }) => ++ Effect.sync(() => { ++ searched.push(repository); ++ return { items: [], truncated: false }; ++ }), ++ }), ++ ], ++ }); ++ ++ yield* service.filterOptions({ ++ kind: "labels", ++ excludeRepositories: ["GitHub.com/Acme/Infra"], ++ }); ++ ++ assert.deepStrictEqual(searched, ["acme/app"]); ++ }), ++); ++ +it.effect( + "lists a repository through the project rooted in it, not a workspace that holds it", + () => @@ apps/web/src/components/pullRequest/PullRequestsPage.tsx: export function PullRe ...(search.host ? { host: search.host } : {}), } satisfies PullRequestListInput, })); + })(); + const facetQuery = usePullRequestList(facetTargets); +- const filterOptionScope = environmentQueries.map(({ environmentId, projectIds }) => ({ +- environmentId, +- input: { +- ...(scopedProjectId ? { projectId: scopedProjectId } : {}), +- ...(projectIds ? { projectIds } : {}), +- ...(search.host ? { host: search.host } : {}), +- }, +- })); ++ const filterOptionScope = environmentQueries.map( ++ ({ environmentId, projectIds, excludeRepositories }) => ({ ++ environmentId, ++ input: { ++ ...(scopedProjectId ? { projectId: scopedProjectId } : {}), ++ ...(projectIds ? { projectIds } : {}), ++ ...(excludeRepositories ? { excludeRepositories } : {}), ++ ...(search.host ? { host: search.host } : {}), ++ }, ++ }), ++ ); + const partitionsWanted = search.involvement === "all" && typedQuery.length === 0; + const partitionTargets = (() => { + // The main list goes first. Besides putting the visible rows on screen sooner, it proves @@ apps/web/src/components/pullRequest/PullRequestsPage.tsx: export function PullRequestsPage() { return { authored: NO_LIST_TARGETS, reviewing: NO_LIST_TARGETS }; } @@ packages/contracts/src/pullRequest.ts: export const PullRequestListInput = Schem host: Schema.optional(TrimmedNonEmptyString), limit: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 500 }))), cursors: Schema.optional(PullRequestListCursors), +@@ packages/contracts/src/pullRequest.ts: export const PullRequestFilterOptionsInput = Schema.Struct({ + kind: PullRequestFilterOptionKind, + projectId: Schema.optional(ProjectId), + projectIds: Schema.optional(Schema.Array(ProjectId).check(Schema.isMaxLength(100))), ++ excludeRepositories: PullRequestListInput.fields.excludeRepositories, + host: Schema.optional(TrimmedNonEmptyString), + query: Schema.optional(TrimmedNonEmptyString.check(Schema.isMaxLength(100))), + }); ## packages/shared/src/composerPullRequestMatches.test.ts ## @@ packages/shared/src/composerPullRequestMatches.test.ts: import {Checks
vp run check:changed v0.1.3-nightly.20261024.3617, which covers every file the stack touches: format, lint, knip, graphql, typecheck (32 packages), and 1063 tests passed, 1 skipped.MuseReplayFixtures.integration.test.ts: 8 of 8 passed.ci.yml,release.ymlandrelease-desktop.ymlare unchanged upstream, sofork-ci.ymlandfork-release.ymlneed no updates. Upstream addedrelay-preview.yml,relay-preview-cleanup.ymlandstale-needs-more-info.yml. They aren't onfork-guard.yml's allowlist, so it disables them.