Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
01e05c1
docs(claude): clarify OpenRouter model selection (#11369)
shivamhwp Sep 14, 2026
0dec07d
fix(web): keep large image previews from stalling composer typing (#1…
shivamhwp Sep 14, 2026
8ef478e
fix(server): avoid extra round trips for terminal output (#11407)
Bil0000 Sep 14, 2026
6f00d38
fix(web): remember panel width for each thread (#11310)
shivamhwp Sep 14, 2026
9375c77
fix(release): preserve updates from npm-based services (#11732)
t3dotgg Sep 14, 2026
8b1ea4d
fix(desktop): restore Node discovery for WSL providers (#11741)
akj Sep 14, 2026
ae67c5b
fix(release): stop npm from pruning the platform packages' shipped no…
juliusmarminge Sep 14, 2026
955b787
fix(server): parse CLI versions with a "v" prefix (#11738)
NikodemNowak Sep 14, 2026
05e3bcb
fix(desktop): keep preview releases out of the nightly update changel…
juliusmarminge Sep 14, 2026
ec5ede5
fix(web): open video attachment thumbnails in the viewer (#11734)
chrisdeeming Sep 14, 2026
494cfac
Allow setting T3CODE_OTLP_HEADERS (#11218)
bahlo Sep 14, 2026
47ace94
fix(web): use consistent PR section toggles (#11763)
Bil0000 Sep 14, 2026
33118d9
Add T3CODE_OTLP_PROTOCOL to allow protobuf protocol (#11224)
bahlo Sep 14, 2026
9130d93
feat(web): add composer and PR number shortcuts (#11615)
Bil0000 Sep 14, 2026
f328db3
chore(server): keep the legacy service entry point to the npm package…
juliusmarminge Sep 14, 2026
112a708
fix(web): use project monograms for automatic icon fallbacks (#11572)
ShpetimA Sep 14, 2026
8d7c700
feat(web): clone repositories in the background instead of holding th…
juliusmarminge Sep 14, 2026
549d182
feat(mobile): clone repositories in the background and gate the draft…
juliusmarminge Sep 14, 2026
9d4bb55
fix(mobile): scale inline pills with Dynamic Type (#11792)
juliusmarminge Sep 14, 2026
bcc2024
chore(deps): bump the Clerk stack to current releases (#11764)
juliusmarminge Sep 14, 2026
0f21fcb
feat(mobile): add a T3 Connect page to the Clerk profile (#11765)
juliusmarminge Sep 14, 2026
dc0869b
feat(server): use Clerk's device authorization grant for headless con…
juliusmarminge Sep 14, 2026
8419238
Add new GitHub user f-trycua
juliusmarminge Sep 14, 2026
7931227
fix(server): stop refreshing providers on every config subscription (…
juliusmarminge Sep 14, 2026
5bf43c9
fix(web): align monogram project icons in menus (#11806)
juliusmarminge Sep 14, 2026
9a49d6d
ci(desktop): sign fork PR macOS previews without exposing signing sec…
juliusmarminge Sep 14, 2026
014016a
fix(web): make copy PR link discoverable in keybindings (#11826)
Bil0000 Sep 15, 2026
3be02ae
feat: add custom snooze dates and durations (#11800)
juliusmarminge Sep 15, 2026
ea6af59
feat(mobile): redesign the Android agent activity card (#11645)
SunkenInTime Sep 15, 2026
6dbea7e
chore(mobile): bump app version to 1.2.0
t3-code[bot] Sep 15, 2026
5ea6439
feat(web): inline worktree setup rows and async setup scripts (#11832)
juliusmarminge Sep 15, 2026
b5b29e7
fix(server): stream tight list items one at a time in paragraph mode …
juliusmarminge Sep 15, 2026
7cafe52
fix(server): keep thread titles tied to user intent (#10720)
t3dotgg Sep 15, 2026
e9b0555
Remove labels from effect service conventions
juliusmarminge Sep 15, 2026
537dc0f
Remove labels from ui-consistency.md
juliusmarminge Sep 15, 2026
970a873
Change conclusion status from failure to neutral
juliusmarminge Sep 15, 2026
8b9f6d3
Change conclusion from 'failure' to 'neutral'
juliusmarminge Sep 15, 2026
08abda9
refactor(server): resolve title links through source control provider…
juliusmarminge Sep 15, 2026
a62e7d6
refactor(server): align title generation with Effect conventions (#11…
juliusmarminge Sep 15, 2026
5623089
fix(server): disable color probes in worktree setup (#11843)
juliusmarminge Sep 15, 2026
0310cbf
fix: keep worktree setup visible after leaving and reopening the thre…
t3dotgg Sep 15, 2026
b20d29d
fix(desktop): prevent startup from running twice (#11857)
juliusmarminge Sep 15, 2026
26b8f98
feat(mobile): add iPad keyboard shortcuts and command palette (#11679)
bmdavis419 Sep 15, 2026
2c19283
feat(server): persist the worktree setup send and progress on the thr…
juliusmarminge Sep 15, 2026
cc839c4
feat(web): queue messages sent client-side while the agent is working…
t3dotgg Sep 15, 2026
5b377e2
fix(server): bound Git process bursts to keep connections responsive …
Bil0000 Sep 15, 2026
a37b852
perf(server): speed up worktree fetch and checkout (#11633)
Bil0000 Sep 15, 2026
9ea892e
fix(client): show thread state changes before remote replies (#11408)
Bil0000 Sep 15, 2026
6ecc15f
fix(mobile): restrict row highlighting to pointer input (#11863)
juliusmarminge Sep 15, 2026
50ff4c3
fix(mobile): ensure a compatible native client before verification (#…
juliusmarminge Sep 15, 2026
3c4c9a1
fix(web): restore composer focus after closing option menus (#11884)
Bil0000 Sep 15, 2026
bf3be75
fix(web): center refresh devices in the empty state (#11808)
shivamhwp Sep 15, 2026
e33b710
fix(mobile): match command palette colors to sheets (#11861)
juliusmarminge Sep 15, 2026
ae53072
fix(web): keep the composer ready during background worktree setup (#…
Bil0000 Sep 15, 2026
c1b2210
fix(desktop): keep the sidebar brand and window buttons aligned (#11906)
shivamhwp Sep 15, 2026
4b1b372
chore(sync): import upstream main before RTVision conflict overlays
kalvenschraut Sep 15, 2026
e653558
fix(sync): retain RTVision integrations for release 0.0.54
kalvenschraut Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .agents/skills/ios-debugger-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Avoid generic Mac window automation for switching among Simulator windows. Expli

## Choose build or launch

For T3 Code Mobile, run `node scripts/mobile-native-client.ts ensure ios <simulator-udid>` from the checkout on the simulator host first. It checks the local Expo native fingerprint against the installed client and builds/installs when stale, missing, or unknown. Then launch with the intended Metro bundle. Authorized verification includes native builds and installs; do not stop because the existing client is old. Use `check` instead of `ensure` only when the user explicitly prohibits rebuilding or requests a read-only check.

- Use `build_run_sim` when native source, native dependencies, entitlements, or project configuration changed.
- Use `test_sim` for the smallest relevant native test target or test cases; do not run an entire workspace test matrix routinely.
- Use `launch_app_sim` when a compatible app is already installed and no native rebuild is needed.
Expand Down
32 changes: 17 additions & 15 deletions .agents/skills/test-t3-mobile/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,28 @@ Inspect the host and the affected code before launching processes:

- On macOS with Xcode, prefer one representative iOS Simulator when the change is cross-platform so the user can watch through serve-sim. Load and follow [`ios-debugger-agent`](../ios-debugger-agent/SKILL.md), and load [`ios-simulator-browser`](../ios-simulator-browser/SKILL.md) when live streaming is available.
- On macOS, Linux, or Windows with the Android SDK, use one Android Emulator when Android is the affected surface or iOS tooling is unavailable.
- When the change is platform-specific, test that platform. When neither platform is viable, report the missing SDK, emulator, or dev-client prerequisite rather than claiming verification.
- When the change is platform-specific, test that platform. When neither platform is viable, report the missing SDK or emulator prerequisite rather than claiming verification. A missing development client is a build step, not a blocker.

Do not treat unavailable iOS tooling as a blocker when Android is a valid representative target.

## Choose the lightest valid launch path
## Ensure a compatible native client

- For JavaScript, TypeScript, or asset-only changes, reuse a compatible installed development client and start Metro. Do not rebuild native code merely to load a new bundle.
- For native source, native dependencies, entitlements, config plugins, or generated project changes, rebuild the affected platform.
- Use `vp run ios:dev` or `vp run android:dev` only when an Expo clean prebuild is actually required; both commands regenerate the native project.
- If the user requested no native rebuild and no compatible app is installed, reuse an existing compatible `.app` or `.apk` artifact when available. Otherwise report the missing dev client instead of silently rebuilding.
Authorized mobile verification includes building and installing a development client. A missing, stale, or unknown native client is not a reason to skip verification or leave a PR in draft. Build and install it, then continue. Respect an explicit user instruction not to rebuild; otherwise do not ask for separate permission.

The development identity on both platforms is:
Run this from the checkout being tested, on the machine that hosts the selected simulator or emulator. Select and boot one explicit iOS UDID or Android emulator serial first:

- App: `T3 Code Dev`
- Bundle/package identifier: `com.t3tools.t3code.dev`
- URL scheme: `t3code-dev`
```bash
node scripts/mobile-native-client.ts ensure ios <simulator-udid>
node scripts/mobile-native-client.ts ensure android <emulator-serial>
```

`ensure` compares the checkout's local Expo development fingerprint and the installed app's binary contents against the last successful build record. It reuses a matching client; otherwise it runs a clean prebuild, builds and installs the development app, and records the successful result. It does not start Metro. Start Metro below after it succeeds. On hosts with an `agent-job` requirement, run the entire `ensure` command through that queue.

For a read-only decision, use `check` in place of `ensure`. Exit 0 means compatible, 2 means build required, and 1 means an operational error. An app installed outside this helper is initially unknown and gets rebuilt once. Records are local to the simulator host under `~/.cache/t3code/native-clients` and work across checkouts. Do not copy records between machines or write them manually.

A JavaScript-only diff, bundle identifier, app version, or recent install date does not prove native compatibility. Always check the whole checkout. Expo fingerprints are computed locally with `APP_VARIANT=development`; no EAS credentials or cloud build are required. Generated `ios/` and `android/` directories are excluded by `.fingerprintignore`, so edit native source modules or config plugins rather than generated output.

Bundle or package presence proves the correct variant, not native compatibility. Reuse it only when the current changes did not alter its Expo SDK, native dependencies, config plugins, entitlements, generated project, or native source.
The development identity is `T3 Code Dev`, bundle/package `com.t3tools.t3code.dev`, scheme `t3code-dev`. If a build fails, investigate the build error and fix the local prerequisites. Report the concrete failure if it cannot be resolved, not “no compatible client.”

## Start one disposable T3 environment

Expand Down Expand Up @@ -98,21 +102,19 @@ Use `ios-debugger-agent` to select one UDID and set these XcodeBuildMCP session
- Simulator ID: the selected UDID
- Bundle ID: `com.t3tools.t3code.dev`

Check the installed client with:
After `ensure` succeeds, open the Metro URL:

```bash
xcrun simctl get_app_container <simulator-udid> com.t3tools.t3code.dev app
xcrun simctl openurl <simulator-udid> <printed-dev-client-url>
```

Accept the iOS confirmation prompt and dismiss the developer menu when it obscures the app.

### Android launch

Select one running emulator serial from `adb devices` and check the installed client:
Use the emulator serial already checked by `ensure`:

```bash
adb -s <emulator-serial> shell pm path com.t3tools.t3code.dev
adb -s <emulator-serial> reverse tcp:<metro-port> tcp:<metro-port>
adb -s <emulator-serial> shell am start -W \
-a android.intent.action.VIEW \
Expand Down
1 change: 1 addition & 0 deletions .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ github:D3OXY
github:dbalders
github:eggfriedrice24
github:extoci
github:f-trycua
github:flamboh
github:FllipEis
github:gbarros-dev
Expand Down
76 changes: 76 additions & 0 deletions .github/scripts/stage-preview-bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Stage an untrusted preview ZIP without letting it replace packaging code."""

import shutil
import stat
import sys
import zipfile
from pathlib import Path

ROOTS = ("server/dist", "desktop/dist-electron")
REQUIRED_FILES = {
"server/dist/bin.mjs",
"server/dist/client/index.html",
"desktop/dist-electron/main.cjs",
}
# The current bundle is about 32 MiB compressed. Bound extraction on the
# trusted runner even when the PR replaces the uploader entirely.
MAX_ARCHIVE_BYTES = 512 * 1024 * 1024
MAX_EXPANDED_BYTES = 2 * 1024 * 1024 * 1024
MAX_ENTRIES = 50_000


def stage_bundle(archive: Path, destination: Path):
if archive.stat().st_size > MAX_ARCHIVE_BYTES:
raise ValueError("Preview archive is too large")
with zipfile.ZipFile(archive) as bundle:
entries = bundle.infolist()
if len(entries) > MAX_ENTRIES:
raise ValueError("Preview archive has too many entries")
if sum(entry.file_size for entry in entries) > MAX_EXPANDED_BYTES:
raise ValueError("Expanded preview bundle is too large")
seen = set()
files = set()
for entry in entries:
name = entry.filename.removesuffix("/")
parts = name.split("/")
# Reject ambiguous paths before normalization, including names
# that would alias on the macOS signing runner.
if (
entry.orig_filename != entry.filename
or any(part in ("", ".", "..") for part in parts)
or any(char in name for char in "\\:")
or not name.isascii()
or any(ord(char) < 32 or ord(char) == 127 for char in name)
):
raise ValueError(f"Unsafe preview path: {entry.filename!r}")
allowed = any(name.startswith(root + "/") for root in ROOTS)
if entry.is_dir():
allowed |= any(root == name or root.startswith(name + "/") for root in ROOTS)
if not allowed:
raise ValueError(f"Unexpected preview path: {name!r}")
kind = stat.S_IFMT(entry.external_attr >> 16)
if kind not in (0, stat.S_IFDIR if entry.is_dir() else stat.S_IFREG):
raise ValueError(f"Non-regular preview entry: {name!r}")
if name.casefold() in seen:
raise ValueError(f"Duplicate preview path: {name!r}")
seen.add(name.casefold())
if not entry.is_dir():
files.add(name)
if not REQUIRED_FILES <= files:
raise ValueError("Preview bundle is missing required entry points")
# Validate all names before writing anything. This is a fresh directory
# outside the checkout; neither pre-existing links nor trusted files
# can be followed or overwritten. ZIP permissions are never restored.
destination.mkdir(parents=True, exist_ok=False)
for entry in entries:
target = destination / entry.filename
if entry.is_dir():
target.mkdir(parents=True, exist_ok=True)
else:
target.parent.mkdir(parents=True, exist_ok=True)
with bundle.open(entry) as source, target.open("xb") as output:
shutil.copyfileobj(source, output)


if __name__ == "__main__":
stage_bundle(Path(sys.argv[1]), Path(sys.argv[2]))
97 changes: 97 additions & 0 deletions .github/scripts/stage-preview-bundle.test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import importlib.util
import stat
import tempfile
import unittest
import zipfile
from pathlib import Path
from unittest.mock import patch

spec = importlib.util.spec_from_file_location(
"stage_preview_bundle", Path(__file__).with_name("stage-preview-bundle.py")
)
staging = importlib.util.module_from_spec(spec)
spec.loader.exec_module(staging)


class StagePreviewBundleTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.archive = self.root / "bundle.zip"
self.destination = self.root / "staged"

def bundle(self, extra=(), missing=None):
with zipfile.ZipFile(self.archive, "w") as bundle:
for name in sorted(staging.REQUIRED_FILES - {missing}):
bundle.writestr(name, b"bundle data, never executed")
for name, content in extra:
bundle.writestr(name, content)

def stage(self):
staging.stage_bundle(self.archive, self.destination)

def test_preserves_valid_bundle_layout_and_bytes(self):
self.bundle([("server/", b""), ("server/dist/", b""),
("desktop/dist-electron/chunks/helper.cjs", b"chunk")])
self.stage()
for name in staging.REQUIRED_FILES:
self.assertEqual((self.destination / name).read_bytes(), b"bundle data, never executed")
self.assertEqual((self.destination / "desktop/dist-electron/chunks/helper.cjs").read_bytes(), b"chunk")

def test_rejects_builder_overwrite_and_unsafe_paths_before_writing(self):
for name in [
"desktop/node_modules/electron-builder/cli.js",
"desktop/package.json",
"server/dist/../../desktop/package.json",
"../package.json",
"/server/dist/absolute",
"server/dist/./alias",
"server/dist//alias",
"server/dist/back\\slash",
"server/dist/file:stream",
"server/dist/BIN.MJS",
]:
with self.subTest(name=name):
self.bundle([(name, b"untrusted")])
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_rejects_links_and_special_files(self):
for mode in [stat.S_IFLNK, stat.S_IFIFO, stat.S_IFCHR]:
with self.subTest(mode=mode):
entry = zipfile.ZipInfo("server/dist/link")
entry.create_system = 3
entry.external_attr = (mode | 0o777) << 16
self.bundle([(entry, b"../../../desktop/node_modules")])
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_requires_entry_points(self):
self.bundle(missing="desktop/dist-electron/main.cjs")
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_bounds_archive_size_expanded_size_and_entry_count(self):
for limit in ["MAX_ARCHIVE_BYTES", "MAX_EXPANDED_BYTES", "MAX_ENTRIES"]:
with self.subTest(limit=limit), patch.object(staging, limit, 1):
self.bundle()
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_refuses_existing_destination(self):
self.bundle()
self.destination.mkdir()
sentinel = self.destination / "trusted"
sentinel.write_text("untouched")
with self.assertRaises(FileExistsError):
self.stage()
self.assertEqual(sentinel.read_text(), "untouched")


if __name__ == "__main__":
unittest.main()
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ jobs:
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Test preview artifact validation
run: python3 -B .github/scripts/stage-preview-bundle.test.py

- name: Test nightly release checks
run: node --test .github/scripts/check-nightly-release.test.cjs

Expand Down
Loading
Loading