Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
a29a7cc
fix(mobile): blur glass fallbacks to prevent background text bleed (#…
juliusmarminge Sep 9, 2026
e16b8b0
feat(web): add provider model bulk toggle (#10947)
UtkarshUsername Sep 9, 2026
50f918c
fix(web): allow expanding duplicate tool call commands (#10981)
Yash-Singh1 Sep 9, 2026
75e4ceb
fix(mobile): prevent Android chat rows overlapping during sync (#10983)
SunkenInTime Sep 9, 2026
383cc40
fix(mobile): prevent text leaking through Android glass (#10998)
juliusmarminge Sep 9, 2026
afb8489
feat(pull-requests): link multiple pull requests to threads (#10839)
juliusmarminge Sep 9, 2026
f0401c6
feat(search): find threads by linked pull request (#10870)
juliusmarminge Sep 9, 2026
de37964
feat(prs): navigate, merge and rebase GitHub stacks (#10875)
juliusmarminge Sep 9, 2026
33242d0
fix(server): preserve recent PR reads across server restarts (#11007)
juliusmarminge Sep 9, 2026
8d8189e
feat(web): zoom and pan expanded images (#10869)
maria-rcks Sep 9, 2026
c79ab38
fix(server): tell agents how to nest fenced code inside code blocks
Sep 9, 2026
b7b3ef1
fix(ui): use available space for composer model names (#11002)
juliusmarminge Sep 9, 2026
addfb13
fix(web): restore pr list diff counts to the top right (#10609)
maria-rcks Sep 10, 2026
df93043
Merge branch 'main' into t3code/fix-markdown-code-blocks
pc-style Sep 10, 2026
385cc0a
fix(web): show message copy buttons on touch devices (#11020)
maria-rcks Sep 10, 2026
d1eeb16
fix(web): middle-click pastes in the terminal on Linux (#11018)
maria-rcks Sep 10, 2026
0f602b3
fix(editors): open remote projects in Zed (#11022)
maria-rcks Sep 10, 2026
bb5e824
feat: add blue and orange diff color palette (#10671)
maria-rcks Sep 10, 2026
d29c56a
fix(server): resolve project identity before legacy pr relinks (#11045)
t3-code[bot] Sep 10, 2026
4bd554c
Merge branch 'main' into t3code/fix-markdown-code-blocks
pc-style Sep 10, 2026
71178d0
refactor(server): shorten the nested fence guidance to one plain sent…
Sep 10, 2026
444fd8b
fix(mobile): keep Android markdown icons aligned with text (#11079)
SunkenInTime Sep 10, 2026
3836890
Revert "fix(mobile): keep Android markdown icons aligned with text" (…
juliusmarminge Sep 10, 2026
e784975
fix(ui): simplify multiple linked pull request badges (#11104)
maria-rcks Sep 10, 2026
0882431
fix(preview): return to pip when closing the right panel (#11102)
maria-rcks Sep 10, 2026
0527ddf
fix: quiet settled threads and simplify PR badges (#11101)
juliusmarminge Sep 10, 2026
f814983
fix(web): emphasize primary pull request actions (#11105)
juliusmarminge Sep 10, 2026
21a5ccf
fix(web): prevent seams in the topbar scroll fade (#10914)
caezium Sep 10, 2026
21d7440
fix(web): fit provider update text inside sidebar notices (#11034)
MatthewFeroz Sep 10, 2026
3997b9a
fix(web): align floating browser preview corners (#10915)
caezium Sep 10, 2026
60eff99
fix(web): save PR body edits with Cmd/Ctrl+Enter (#10660)
flamboh Sep 10, 2026
502131a
fix(web): collapse a tool call by clicking its expanded label (#11017)
maria-rcks Sep 10, 2026
dca7b59
feat(devices): add simulator and emulator support (#10677)
juliusmarminge Sep 10, 2026
e022fa4
feat(devices): scope targets and sessions to their hosts (#10854)
juliusmarminge Sep 10, 2026
7734c6d
feat(devices): target concurrent agent sessions across hosts (#10855)
juliusmarminge Sep 10, 2026
d2eeacd
feat(devices): connect simulator hosts over SSH (#10856)
juliusmarminge Sep 10, 2026
dfa345b
feat(web): use a compact right-panel surface menu (#11111)
maria-rcks Sep 10, 2026
32b6909
fix(mobile): keep Android markdown icons aligned (#11118)
none23 Sep 10, 2026
47dbb06
fix(mobile): add close controls to tablet files and terminal (#11115)
juliusmarminge Sep 10, 2026
96f4370
fix(mobile): preserve the final composer animation frame (#11114)
juliusmarminge Sep 10, 2026
859304b
fix(mobile): keep composer transitions aligned (#11127)
juliusmarminge Sep 10, 2026
1a9336b
refactor(mobile): name shared markdown renderer without iOS suffixes …
SunkenInTime Sep 10, 2026
a5ac766
fix(media): preserve playback during fullscreen transitions (#11113)
maria-rcks Sep 10, 2026
39ca417
fix(marketing): redirect /app to app.t3.codes (#11145)
t3-code[bot] Sep 10, 2026
2afa02a
chore(marketing): update to 300k users and 22k stars (#11146)
t3-code[bot] Sep 10, 2026
c52b8d9
feat(command-palette): show environments in search results (#10722)
Cyberlane Sep 10, 2026
6a2d246
fix(pr): update labels and reviewers without redundant reloads (#11117)
maria-rcks Sep 11, 2026
27eb79d
fix(chat): fold question answers into tool activity (#11014)
maria-rcks Sep 11, 2026
48654c1
fix(usage): flag unpriced model activity instead of showing $0.00 (#1…
maria-rcks Sep 11, 2026
5735693
fix(server): let Claude launch args override the derived permission m…
maria-rcks Sep 11, 2026
20ef250
fix(editors): accept root paths and Windows servers in Zed remote lin…
maria-rcks Sep 11, 2026
4d06156
fix(web): center pull request unavailable states (#11110)
maria-rcks Sep 11, 2026
0a37240
fix(web): remove sidebar pull request link icon (#11179)
maria-rcks Sep 11, 2026
02297e3
fix(ui): color linked pr counts by aggregate status (#11180)
maria-rcks Sep 11, 2026
6c69534
fix(preview): render website favicons for browser tool activity (#11032)
maria-rcks Sep 11, 2026
18c5a1d
fix(web): simplify pull request summary sections (#10612)
maria-rcks Sep 11, 2026
ef6fa11
fix(web): preserve drafts when compacting context (#11103)
maria-rcks Sep 11, 2026
57aee3e
fix(server): queue messages during context compaction (#11107)
maria-rcks Sep 11, 2026
8fc2536
perf(web): format minimap previews only when opened (#11181)
juliusmarminge Sep 11, 2026
a9dabbf
perf(web): reuse completed Markdown prefixes while streaming (#11193)
juliusmarminge Sep 11, 2026
d7d7f8f
perf(web): resume syntax highlighting from completed lines (#11196)
juliusmarminge Sep 11, 2026
8078c53
perf(web): preserve completed code-line DOM while streaming (#11198)
juliusmarminge Sep 11, 2026
211618f
perf(web): huge-thread switch no longer blanks the chat pane (#11169)
juliusmarminge Sep 11, 2026
9f55cf4
Merge branch 'main' into t3code/fix-markdown-code-blocks
pc-style Sep 11, 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
  •  
  •  
  •  
36 changes: 35 additions & 1 deletion apps/desktop/src/electron/ElectronShell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,39 @@ describe("ElectronShell", () => {
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("opens Zed's ssh deep link", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);

const electronShell = yield* ElectronShell.ElectronShell;
const results = yield* Effect.all([
electronShell.openExternal("zed://ssh/example.com/home/user/project"),
electronShell.openExternal("zed://ssh/example.com/"),
]);

assert.deepEqual(results, [true, true]);
assert.deepEqual(openExternalMock.mock.calls, [
["zed://ssh/example.com/home/user/project"],
["zed://ssh/example.com/"],
]);
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("does not open editor URLs that mix up link shapes", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);

const electronShell = yield* ElectronShell.ElectronShell;
const results = yield* Effect.all([
electronShell.openExternal("zed://extension/attacker"),
electronShell.openExternal("vscode://ssh/example.com/home/user/project"),
]);

assert.deepEqual(results, [false, false]);
assert.equal(openExternalMock.mock.calls.length, 0);
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("does not open remote editor URLs with userinfo", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);
Expand All @@ -100,9 +133,10 @@ describe("ElectronShell", () => {
electronShell.openExternal(
"vscode://:secret@vscode-remote/ssh-remote+example.com/home/user/project",
),
electronShell.openExternal("zed://ssh/user@example.com/home/user/project"),
]);

assert.deepEqual(results, [false, false]);
assert.deepEqual(results, [false, false, false]);
assert.equal(openExternalMock.mock.calls.length, 0);
}).pipe(Effect.provide(ElectronShell.layer)),
);
Expand Down
16 changes: 11 additions & 5 deletions apps/desktop/src/electron/ElectronShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ const SYSTEM_SETTINGS_URLS: Record<SystemSettingsPane, string> = {
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AllFiles",
};

// Remote open-in-editor deep links (`vscode://vscode-remote/ssh-remote+…`)
// must reach the OS handler; every other non-web scheme stays blocked.
// Remote open-in-editor deep links (`vscode://vscode-remote/ssh-remote+…`,
// `zed://ssh/<host>/<path>`) must reach the OS handler; every other non-web
// scheme stays blocked.
const SAFE_WEB_PROTOCOLS = new Set(["http:", "https:"]);
const REMOTE_EDITOR_PROTOCOLS = new Set(
REMOTE_CAPABLE_EDITOR_IDS.flatMap((id) => {
Expand All @@ -34,13 +35,18 @@ const REMOTE_EDITOR_PROTOCOLS = new Set(
}),
);

// Zed's host sits in the first path segment, so it needs its own userinfo ban.
const ZED_SSH_PATHNAME = /^\/[^/@:]+\/.*$/;

const isRemoteEditorUrl = (url: URL) =>
REMOTE_EDITOR_PROTOCOLS.has(url.protocol) &&
url.username.length === 0 &&
url.password.length === 0 &&
url.host === "vscode-remote" &&
url.pathname.startsWith("/ssh-remote+") &&
url.pathname.length > "/ssh-remote+".length;
(url.protocol === "zed:"
? url.host === "ssh" && ZED_SSH_PATHNAME.test(url.pathname)
: url.host === "vscode-remote" &&
url.pathname.startsWith("/ssh-remote+") &&
url.pathname.length > "/ssh-remote+".length);

export function parseSafeExternalUrl(rawUrl: unknown): Option.Option<string> {
if (typeof rawUrl !== "string") {
Expand Down
4 changes: 2 additions & 2 deletions apps/marketing/src/lib/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export const ANDROID_PLAY_STORE_URL =
"https://play.google.com/store/apps/details?id=com.t3tools.t3code";

export const MARKETING_STATS = {
githubStars: "21k+",
users: "200,000",
githubStars: "22k+",
users: "300,000",
} as const;
7 changes: 7 additions & 0 deletions apps/marketing/vercel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ export const config: VercelConfig = {
installCommand: "npm install -g vite-plus && vp install --filter '@t3tools/marketing...'",
buildCommand: "vp run --filter @t3tools/marketing build",
outputDirectory: "dist",
redirects: [
{
source: "/app",
destination: "https://app.t3.codes",
permanent: true,
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package expo.modules.t3markdowntext
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.ReplacementSpan
import android.view.ActionMode
Expand All @@ -18,6 +20,14 @@ import kotlin.math.min

private const val OBJECT_REPLACEMENT_CHARACTER = "\uFFFC"

// Match React Native's measurement buffer. Android orders tied line-height
// spans differently in SpannableString, shifting inline images once RN's
// span priorities are exhausted.
private object MarkdownSpannableFactory : Spannable.Factory() {
override fun newSpannable(source: CharSequence): Spannable =
SpannableStringBuilder(source)
}

private fun copyTextWithoutInlineImages(
text: CharSequence,
start: Int,
Expand Down Expand Up @@ -85,6 +95,7 @@ class T3MarkdownTextSelectionModule : Module() {
if (currentCallback is SanitizingSelectionActionModeCallback) {
return@runOnUiQueueThread
}
textView.setSpannableFactory(MarkdownSpannableFactory)
textView.customSelectionActionModeCallback =
SanitizingSelectionActionModeCallback(textView, currentCallback)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/modules/t3-markdown-text/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"./links": "./src/markdownLinks.ts",
"./markdown": "./src/nativeMarkdownText.ts",
"./primitive": "./src/MarkdownTextPrimitive.tsx",
"./renderer": "./src/SelectableMarkdownText.ios.tsx",
"./renderer": "./src/SelectableMarkdownText.tsx",
"./types": "./src/SelectableMarkdownText.types.ts"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { MarkdownNode } from "react-native-nitro-markdown/headless";
import { CopyTextButton } from "./CopyTextButton";
import { MarkdownTextPrimitive } from "./MarkdownTextPrimitive";
import { nativeMarkdownDocumentRuns, nativeMarkdownListItemBlocks } from "./nativeMarkdownText";
import { NativeMarkdownSelectableText } from "./NativeMarkdownSelectableText.ios";
import { NativeMarkdownSelectableText } from "./NativeMarkdownSelectableText";
import type {
MarkdownCodeHighlighter,
MarkdownHighlightedToken,
Expand Down

This file was deleted.

108 changes: 105 additions & 3 deletions apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
import type { SelectableMarkdownTextProps } from "./SelectableMarkdownText.types";
import { useMemo } from "react";
import { View } from "react-native";
import { parseMarkdownWithOptions } from "react-native-nitro-markdown/headless";

import {
nativeMarkdownChunkSpacing,
nativeMarkdownDocumentChunks,
nativeMarkdownDocumentRuns,
nativeMarkdownWithPreservedSoftBreaks,
} from "./nativeMarkdownText";
import { MarkdownImageRendererContext, NativeMarkdownBlock } from "./NativeMarkdownBlock";
import {
MarkdownFileContextMenuContext,
NativeMarkdownSelectableText,
type MarkdownFileContextMenuHandlers,
} from "./NativeMarkdownSelectableText";
import type {
SelectableMarkdownSkill,
SelectableMarkdownTextProps,
} from "./SelectableMarkdownText.types";

const EMPTY_SKILLS: ReadonlyArray<SelectableMarkdownSkill> = [];

export type {
MarkdownCodeHighlighter,
Expand All @@ -10,6 +31,87 @@ export type {
SelectableMarkdownTextProps,
} from "./SelectableMarkdownText.types";

export function SelectableMarkdownText(_props: SelectableMarkdownTextProps) {
return null;
export function hasNativeSelectableMarkdownText(): boolean {
return true;
}

export function SelectableMarkdownText({
markdown,
skills = EMPTY_SKILLS,
textStyle,
highlightCode,
preserveSoftBreaks = false,
onLinkPress,
fileContextMenu,
onFileContextMenuAction,
renderImage,
marginTop = 0,
marginBottom = 0,
}: SelectableMarkdownTextProps) {
const chunks = useMemo(() => {
const parsedDocument = parseMarkdownWithOptions(markdown, {
gfm: true,
html: true,
math: false,
});
const document = preserveSoftBreaks
? nativeMarkdownWithPreservedSoftBreaks(parsedDocument)
: parsedDocument;
return nativeMarkdownDocumentChunks(document).map((chunk) =>
chunk.kind === "selectable"
? {
...chunk,
runs: nativeMarkdownDocumentRuns(chunk.node, skills),
}
: chunk,
);
}, [markdown, preserveSoftBreaks, skills]);

const fileContextMenuHandlers = useMemo<MarkdownFileContextMenuHandlers | null>(
() =>
fileContextMenu && onFileContextMenuAction
? { fileContextMenu, onFileContextMenuAction }
: null,
[fileContextMenu, onFileContextMenuAction],
);

return (
<MarkdownImageRendererContext.Provider value={renderImage ?? null}>
<MarkdownFileContextMenuContext.Provider value={fileContextMenuHandlers}>
{/* A percentage width here creates a cyclic intrinsic measurement inside
shrink-to-fit containers such as user-message bubbles. Yoga then gives
the native text node an unbounded second pass and the parent only clips
the resulting single-line width instead of reflowing it. */}
<View style={{ flexShrink: 1, minWidth: 0, marginTop, marginBottom }}>
{chunks.map((chunk, index) => {
const content =
chunk.kind === "rich" ? (
<NativeMarkdownBlock
node={chunk.node}
skills={skills}
textStyle={textStyle}
highlightCode={highlightCode}
onLinkPress={onLinkPress}
/>
) : (
<NativeMarkdownSelectableText
runs={chunk.runs}
textStyle={textStyle}
onLinkPress={onLinkPress}
/>
);

return (
<View
key={chunk.key}
style={{ paddingTop: nativeMarkdownChunkSpacing(chunks[index - 1], chunk) }}
>
{content}
</View>
);
})}
</View>
</MarkdownFileContextMenuContext.Provider>
</MarkdownImageRendererContext.Provider>
);
}
1 change: 1 addition & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
"buildFromSource": [
"expo-notifications",
"react-native-screens",
"react-native-reanimated",
"@react-native-menu/menu",
"expo-audio"
]
Expand Down
Loading