Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d6eb386
planning
aidenybai May 3, 2026
52c77c1
feat(preview): in-app browser preview panel
aidenybai May 3, 2026
43e8bbd
fix
aidenybai May 3, 2026
17fb0e4
feat(preview): element-pick attachments + sandboxed picker preload
aidenybai May 4, 2026
9b43123
fix(preview): port browser preview to current main
juliusmarminge Jun 11, 2026
63d42a5
fix(preview): initialize and open browser reliably
juliusmarminge Jun 11, 2026
50bc18b
fix(preview): declare RPC authorization scopes
juliusmarminge Jun 11, 2026
07c6d70
Add preview annotation capture tooling
juliusmarminge Jun 12, 2026
29150b5
Add shared MCP preview automation
juliusmarminge Jun 12, 2026
6c6740e
Refine collaborative browser preview
juliusmarminge Jun 12, 2026
dd739c8
Port browser preview annotations to desktop
juliusmarminge Jun 12, 2026
f7c422d
Refactor MCP services into top-level modules
juliusmarminge Jun 12, 2026
3d9e8de
Refactor desktop preview IPC onto shared manager
juliusmarminge Jun 13, 2026
70ec39a
Port preview manager to Effect-based browser sessions
juliusmarminge Jun 13, 2026
d8fe3d7
Scope preview listeners and control sessions
juliusmarminge Jun 13, 2026
3c7862e
Add SWR preview session state and resubscribe handling
juliusmarminge Jun 13, 2026
b5b769d
Prevent stale preview snapshots from resurrecting sessions
juliusmarminge Jun 13, 2026
85d5df6
Unify browser asset preview routing
juliusmarminge Jun 13, 2026
18a49ab
Fix preview CI test fixtures
juliusmarminge Jun 13, 2026
f452d63
Fix terminal browser test mock
juliusmarminge Jun 13, 2026
1b4317e
Restore terminal drawer header toggle
juliusmarminge Jun 13, 2026
c1792bc
Use real preview tooltips
juliusmarminge Jun 13, 2026
d4ba9e9
Document browser preview phase 0.5 findings and plans
juliusmarminge Jun 14, 2026
cd91ec7
Remove outdated plans for shared HTTP MCP server and visible preview …
juliusmarminge Jun 14, 2026
3a0623f
rm test artifacts
juliusmarminge Jun 14, 2026
56e57de
Fix Bitbucket source control availability toggle
JustMarkDev Jun 14, 2026
1db5858
Guard VCS status updates against stale targets (#3084)
juliusmarminge Jun 14, 2026
31e6822
Wrap authorized clients in fading scroll area (#3085)
juliusmarminge Jun 14, 2026
ce90563
Preserve diff surface when toggling right panel (#3083)
juliusmarminge Jun 14, 2026
a23b833
fix(desktop): mac start:desktop crash from rewritten framework symlin…
TheIcarusWings Jun 14, 2026
4777956
refactor: resolve host process state through Effect (#2959)
juliusmarminge Jun 14, 2026
de8bdc1
Add workspace file browser and preview panel (#3087)
juliusmarminge Jun 15, 2026
c2d44a3
Unify compact subheaders and diff file navigation
juliusmarminge Jun 15, 2026
9d5e632
Tighten workspace control spacing in the header
juliusmarminge Jun 15, 2026
71ea5fa
Show disabled reasons for unavailable right panel surfaces (#3093)
juliusmarminge Jun 15, 2026
d0a7d18
[codex] Fix first browser annotation capture (#3095)
t3dotgg Jun 16, 2026
d12da19
Use `fff` for workspace search queries (#3099)
juliusmarminge Jun 16, 2026
5cd2744
[codex] Fix terminal line height for QR readability (#3096)
StiensWout Jun 16, 2026
708d538
[codex] Trace first-party relay clients (#2995)
juliusmarminge Jun 16, 2026
0bd8754
Merge remote-tracking branch 'upstream/main' into sync/upstream-update
tarik02 Jun 16, 2026
77451ff
fix sync merge gaps for terminal runtime env wiring
tarik02 Jun 16, 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
  •  
  •  
  •  
79 changes: 79 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,46 @@ jobs:
clerk_cli_oauth_client_id: ${{ steps.public_config.outputs.clerk_cli_oauth_client_id }}
relay_url: ${{ steps.public_config.outputs.relay_url }}
env:
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }}
RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }}
CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }}
CLERK_JWT_TEMPLATE: ${{ vars.CLERK_JWT_TEMPLATE }}
CLERK_CLI_OAUTH_CLIENT_ID: ${{ vars.CLERK_CLI_OAUTH_CLIENT_ID }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: |
args:
- --filter=t3code-relay...

- id: relay_state
name: Read production relay tracing config
shell: bash
run: |
vp run --filter t3code-relay deploy \
--stage prod \
--read-state \
--github-output \
--github-env-file "$RUNNER_TEMP/relay-client-tracing.env"

- name: Upload relay client tracing config
uses: actions/upload-artifact@v7
with:
name: relay-client-tracing-config
path: ${{ runner.temp }}/relay-client-tracing.env
if-no-files-found: error
retention-days: 1

- id: public_config
name: Resolve production relay public config
shell: bash
Expand Down Expand Up @@ -337,6 +371,20 @@ jobs:
cache: true
run-install: true

- name: Download relay client tracing config
uses: actions/download-artifact@v8
with:
name: relay-client-tracing-config
path: ${{ runner.temp }}/relay-client-tracing

- name: Load relay client tracing config
shell: bash
run: |
config_path="$RUNNER_TEMP/relay-client-tracing/relay-client-tracing.env"
tracing_token="$(sed -n 's/^T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=//p' "$config_path")"
echo "::add-mask::$tracing_token"
cat "$config_path" >> "$GITHUB_ENV"

- name: Align package versions to release version
run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}"

Expand Down Expand Up @@ -620,6 +668,20 @@ jobs:
- --filter=@t3tools/web...
- --filter=@t3tools/scripts...

- name: Download relay client tracing config
uses: actions/download-artifact@v8
with:
name: relay-client-tracing-config
path: ${{ runner.temp }}/relay-client-tracing

- name: Load relay client tracing config
shell: bash
run: |
config_path="$RUNNER_TEMP/relay-client-tracing/relay-client-tracing.env"
tracing_token="$(sed -n 's/^T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=//p' "$config_path")"
echo "::add-mask::$tracing_token"
cat "$config_path" >> "$GITHUB_ENV"

- name: Align package versions to release version
run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}"

Expand Down Expand Up @@ -782,6 +844,20 @@ jobs:
- --filter=@t3tools/scripts...
- --filter=@t3tools/web...

- name: Download relay client tracing config
uses: actions/download-artifact@v8
with:
name: relay-client-tracing-config
path: ${{ runner.temp }}/relay-client-tracing

- name: Load relay client tracing config
shell: bash
run: |
config_path="$RUNNER_TEMP/relay-client-tracing/relay-client-tracing.env"
tracing_token="$(sed -n 's/^T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=//p' "$config_path")"
echo "::add-mask::$tracing_token"
cat "$config_path" >> "$GITHUB_ENV"

- name: Align package versions to release version
run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}"

Expand Down Expand Up @@ -828,6 +904,9 @@ jobs:
--build-env "T3CODE_CLERK_PUBLISHABLE_KEY=${T3CODE_CLERK_PUBLISHABLE_KEY:-}" \
--build-env "T3CODE_CLERK_JWT_TEMPLATE=${T3CODE_CLERK_JWT_TEMPLATE:-}" \
--build-env "T3CODE_RELAY_URL=${T3CODE_RELAY_URL:-}" \
--build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_URL=${T3CODE_RELAY_CLIENT_OTLP_TRACES_URL:-}" \
--build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET=${T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET:-}" \
--build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=${T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN:-}" \
--build-env "VITE_HOSTED_APP_URL=$router_url" \
--build-env "VITE_HOSTED_APP_CHANNEL=$channel_name"
)"
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
"@t3tools/tailscale": "workspace:*",
"effect": "catalog:",
"electron": "41.5.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"playwright-core": "1.60.0",
"react-grab": "^0.1.32"
},
"devDependencies": {
"@effect/vitest": "catalog:",
"@types/node": "catalog:",
"cross-env": "^10.1.0",
"electron-builder": "26.8.1",
"tailwindcss": "^4.0.0",
"vite-plus": "catalog:"
},
"productName": "T3 Code (Alpha)"
Expand Down
40 changes: 40 additions & 0 deletions apps/desktop/scripts/build-preview-annotation-css.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

import { compile } from "tailwindcss";

const directory = dirname(fileURLToPath(import.meta.url));
const appRoot = join(directory, "..");
const sourcePath = join(appRoot, "src", "preview", "Annotation.css");
const preloadPath = join(appRoot, "src", "preview", "PickPreload.ts");
const outputPath = join(appRoot, "src", "preview", "AnnotationStyles.generated.ts");
const require = createRequire(import.meta.url);
const tailwindRoot = dirname(require.resolve("tailwindcss/package.json"));

const [annotationSource, preloadSource, themeSource, preflightSource] = await Promise.all([
readFile(sourcePath, "utf8"),
readFile(preloadPath, "utf8"),
readFile(join(tailwindRoot, "theme.css"), "utf8"),
readFile(join(tailwindRoot, "preflight.css"), "utf8"),
]);

const candidates = new Set(
Array.from(preloadSource.matchAll(/!?-?[A-Za-z0-9_:@/.[\]()%,-]+/g), (match) => match[0]),
);
const compilerInput = [
themeSource,
preflightSource,
annotationSource.replace('@import "tailwindcss";', "@tailwind utilities;"),
].join("\n");
const compiler = await compile(compilerInput, { base: appRoot });
const css = compiler.build([...candidates]);
const encodedCss = `'${css
.replaceAll("\\", "\\\\")
.replaceAll("'", "\\'")
.replaceAll("\r", "\\r")
.replaceAll("\n", "\\n")}'`;
const moduleSource = `// Generated by scripts/build-preview-annotation-css.mjs. Do not edit.\nexport const previewAnnotationStyles =\n ${encodedCss};\n`;

await writeFile(outputPath, moduleSource);
18 changes: 13 additions & 5 deletions apps/desktop/scripts/dev-electron.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { spawn, spawnSync } from "node:child_process";
import { watch } from "node:fs";
import * as NodeOS from "node:os";
import { join } from "node:path";

import { desktopDir, resolveDevProtocolClient, resolveElectronPath } from "./electron-launcher.mjs";
import {
desktopDir,
resolveDevProtocolClient,
resolveElectronLaunchCommand,
} from "./electron-launcher.mjs";
import { waitForResources } from "./wait-for-resources.mjs";

const devServerUrl = process.env.VITE_DEV_SERVER_URL?.trim();
Expand All @@ -29,6 +34,8 @@ const forcedShutdownTimeoutMs = 1_500;
const restartDebounceMs = 120;
const childTreeGracePeriodMs = 1_200;
const remoteDebuggingPort = process.env.T3CODE_DESKTOP_REMOTE_DEBUGGING_PORT?.trim();
// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone dev script has no Effect runtime.
const hostPlatform = NodeOS.platform();

await waitForResources({
baseDir: desktopDir,
Expand All @@ -53,15 +60,15 @@ const expectedExits = new WeakSet();
const watchers = [];

function killChildTreeByPid(pid, signal) {
if (process.platform === "win32" || typeof pid !== "number") {
if (hostPlatform === "win32" || typeof pid !== "number") {
return;
}

spawnSync("pkill", [`-${signal}`, "-P", String(pid)], { stdio: "ignore" });
}

function cleanupStaleDevApps() {
if (process.platform === "win32") {
if (hostPlatform === "win32") {
return;
}

Expand All @@ -79,7 +86,8 @@ function startApp() {
const launchArgs = devProtocolClient
? electronArgs
: [...electronArgs, `--t3code-dev-root=${desktopDir}`, "dist-electron/main.cjs"];
const app = spawn(resolveElectronPath(), launchArgs, {
const electronCommand = resolveElectronLaunchCommand(launchArgs);
const app = spawn(electronCommand.electronPath, electronCommand.args, {
cwd: desktopDir,
env: childEnv,
stdio: "inherit",
Expand Down Expand Up @@ -189,7 +197,7 @@ function startWatchers() {
}

function killChildTree(signal) {
if (process.platform === "win32") {
if (hostPlatform === "win32") {
return;
}

Expand Down
48 changes: 44 additions & 4 deletions apps/desktop/scripts/electron-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
writeFileSync,
} from "node:fs";
import { createRequire } from "node:module";
import * as NodeOS from "node:os";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { ensureElectronRuntime } from "./ensure-electron-runtime.mjs";
Expand All @@ -30,9 +31,11 @@ export const APP_BUNDLE_ID = isDevelopment
? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`
: "com.t3tools.t3code";
const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
const LAUNCHER_VERSION = 10;
const LAUNCHER_VERSION = 11;
const defaultIconPath = join(desktopDir, "resources", "icon.icns");
const developmentMacIconPngPath = join(repoRoot, "assets", "dev", "blueprint-macos-1024.png");
// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime.
const hostPlatform = NodeOS.platform();

function resolveDevelopmentProtocolCallbackPort() {
const configuredPort = Number.parseInt(process.env.T3CODE_PORT ?? "", 10);
Expand Down Expand Up @@ -295,7 +298,11 @@ function buildMacLauncher(electronBinaryPath) {
}

rmSync(targetAppBundlePath, { recursive: true, force: true });
cpSync(sourceAppBundlePath, targetAppBundlePath, { recursive: true });
// verbatimSymlinks keeps the framework's relative symlinks intact
// (e.g. Resources -> Versions/Current/Resources). Without it cpSync
// rewrites them to absolute paths into node_modules, which escape the
// bundle and crash sandboxed helper processes (icudtl.dat not found).
cpSync(sourceAppBundlePath, targetAppBundlePath, { recursive: true, verbatimSymlinks: true });
patchMainBundleInfoPlist(targetAppBundlePath, iconPath);
patchHelperBundleInfoPlists(targetAppBundlePath);
if (isDevelopment) {
Expand All @@ -307,21 +314,54 @@ function buildMacLauncher(electronBinaryPath) {
return targetBinaryPath;
}

function isLinuxSetuidSandboxConfigured(electronBinaryPath) {
if (hostPlatform !== "linux") {
return true;
}

const sandboxPath = join(dirname(electronBinaryPath), "chrome-sandbox");
try {
const sandboxStat = statSync(sandboxPath);
return sandboxStat.uid === 0 && (sandboxStat.mode & 0o4777) === 0o4755;
} catch {
return false;
}
}

function resolveLinuxSandboxArgs(electronBinaryPath) {
if (isLinuxSetuidSandboxConfigured(electronBinaryPath)) {
return [];
}

console.warn(
"[desktop-launcher] Electron chrome-sandbox is not root-owned with mode 4755; launching local Electron with --no-sandbox.",
);
return ["--no-sandbox"];
}

export function resolveElectronPath() {
ensureElectronRuntime();

const require = createRequire(import.meta.url);
const electronBinaryPath = require("electron");

if (process.platform !== "darwin") {
if (hostPlatform !== "darwin") {
return electronBinaryPath;
}

return buildMacLauncher(electronBinaryPath);
}

export function resolveElectronLaunchCommand(args = []) {
const electronPath = resolveElectronPath();
return {
electronPath,
args: [...resolveLinuxSandboxArgs(electronPath), ...args],
};
}

export function resolveDevProtocolClient() {
if (process.platform !== "darwin" || !isDevelopment) {
if (hostPlatform !== "darwin" || !isDevelopment) {
return null;
}

Expand Down
Loading
Loading