Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
83a82a4
chore(review): remove the custom Approvability check (#17018)
esthor Oct 8, 2026
f9e16b3
perf(mobile): show the cached thread list sooner and stop the freeze …
juliusmarminge Oct 8, 2026
9fba209
fix(server): replaying a command no longer freezes the server (#17041)
SunkenInTime Oct 8, 2026
04ad174
fix(server): opening a long thread no longer blocks the server for a …
SunkenInTime Oct 8, 2026
0f09627
fix(server,web,mobile): thread links reference the thread id, not a b…
juliusmarminge Oct 8, 2026
6f83358
fix(web): messages with quotes or links no longer collapse when short…
flamboh Oct 8, 2026
843c593
refactor(web): PR loading skeleton shares the detail panel's layout (…
flamboh Oct 8, 2026
2b7fa21
fix(web): PR file headers keep the file name in narrow panels (#15562)
flamboh Oct 8, 2026
b24f0fb
fix(web): PR timeline no longer shifts when its scrollbar appears (#1…
flamboh Oct 8, 2026
0bfd9dd
fix(server): held queued wakes no longer keep delegated tasks running…
juliusmarminge Oct 8, 2026
c28cd90
fix(server): a message sent during a rollback no longer undoes it (#1…
t3dotgg Oct 8, 2026
6053a78
fix(web): PR file stats ignore the hide-whitespace toggle (#16162)
flamboh Oct 8, 2026
28f5516
perf(mobile): omit duplicated turn items from bounded thread snapshot…
juliusmarminge Oct 8, 2026
eb17e0d
perf(mobile): pause elapsed-time timers on hidden thread screens (#15…
juliusmarminge Oct 8, 2026
65404eb
fix(mobile): pause hidden home thread list updates (#15705)
juliusmarminge Oct 8, 2026
e803242
fix(mobile): restore file viewer insets and glass header (#17073)
juliusmarminge Oct 8, 2026
ed27eb7
fix(web): PR code toolbar no longer overlaps in narrow panels (#15561)
flamboh Oct 8, 2026
98b5e1b
fix(web): PR commit menu no longer stretches across the window (#15560)
flamboh Oct 8, 2026
647d8af
fix(web): command palette scrollbar no longer clipped at the top (#17…
flamboh Oct 8, 2026
85a4139
fix(web): Usage breadcrumb stays centered on small viewports (#15552)
flamboh Oct 8, 2026
70eeb39
fix(mobile): stop refreshing Git status on streamed thread updates (#…
juliusmarminge Oct 8, 2026
12a4c05
fix(mobile): skip move indexes for empty and single-thread sections (…
juliusmarminge Oct 8, 2026
8a1fc41
perf(mobile): reuse encoded rows in shell cache saves (#16129)
juliusmarminge Oct 8, 2026
6651aaa
perf(mobile): skip showcase subscriptions in normal builds (#16131)
juliusmarminge Oct 8, 2026
6eed953
perf(mobile): remove unused Home project sorting (#16177)
juliusmarminge Oct 8, 2026
7873504
perf(mobile): reduce move-menu index allocations (#16256)
juliusmarminge Oct 8, 2026
6c1ce06
perf(mobile): skip impossible thread-key lookups (#16263)
juliusmarminge Oct 8, 2026
c001e19
fix(mobile): collect UI runtime garbage on iOS memory warnings (#16296)
juliusmarminge Oct 8, 2026
5c2798d
fix(mobile): stop Git sheet refresh loop (#16305)
juliusmarminge Oct 8, 2026
4fec120
fix(mobile): refresh Git status after reconnect (#16329)
juliusmarminge Oct 8, 2026
833a03a
fix(mobile): update the iOS Git header menu when status changes (#16330)
juliusmarminge Oct 8, 2026
b402f5f
perf(mobile): reuse the settled sort when settled rows are unchanged …
juliusmarminge Oct 8, 2026
33afd5a
perf(mobile): highlight source files in small batches that keep gramm…
juliusmarminge Oct 8, 2026
12865b4
feat(web): quote chips show what you said about the quote (#15703)
flamboh Oct 8, 2026
ff9eb8b
feat(web): projectless threads show their machine in the sidebar (#17…
flamboh Oct 8, 2026
57f96e6
fix(lineage): keep agent effort and speed after completion (#16925)
Bil0000 Oct 8, 2026
97a65a3
fix(mobile): align diff scrolling with glass headers (#17085)
juliusmarminge Oct 8, 2026
73e097b
fix(web): workspace page headers can no longer grow past the top-bar …
maria-rcks Oct 8, 2026
30cc788
feat(mobile): redesign the Add environment sheet (#17092)
juliusmarminge Oct 8, 2026
d6f47ff
feat(providers): run Muse Code as a native provider (#17082)
t3dotgg Oct 8, 2026
0647c48
feat(web): compact-before-send is a chip that shows the token count (…
t3dotgg Oct 8, 2026
2ea7684
fix(clients): copy button is back in its old spot, before fork (#17137)
t3dotgg Oct 8, 2026
4d1f24b
fix(usage): repeat usage scans no longer decode unchanged Antigravity…
t3dotgg Oct 8, 2026
1f31906
fix(server): shell snapshots no longer block other database reads whi…
t3dotgg Oct 8, 2026
10e29a2
perf(server): ACP tool updates no longer persist a snapshot per strea…
darjss Oct 8, 2026
13c5328
feat(web): filter the Usage page to just the providers you want (#16970)
t3dotgg Oct 8, 2026
9381533
fix(usage): Cursor account history loads about 4x faster (#17140)
t3dotgg Oct 8, 2026
0ce6b9d
fix(server): threads settle as soon as branch status sees their PR me…
t3dotgg Oct 8, 2026
8597558
perf(usage): the Usage page shows numbers in under a second and dims …
t3dotgg Oct 8, 2026
37eaf5d
fix(server): an agent can settle its own thread when its turn ends (#…
t3dotgg Oct 8, 2026
5e22256
feat(web): Add provider button sits with the provider list (#17152)
t3dotgg Oct 8, 2026
a4c9494
fix(web): link pull requests to threads in folders that aren't Git re…
shivamhwp Oct 8, 2026
12069ee
feat(web): find messages and plans in the current thread (#10439)
Noojuno Oct 8, 2026
9a3070b
feat(web): improve terminal scrollback navigation and snapshots (#17091)
StiensWout Oct 8, 2026
a65b438
docs(internals): add a checklist for adding a provider (#17229)
juliusmarminge Oct 8, 2026
a6ec88f
fix(mobile): keep native screens ordered during stack pops (#17231)
juliusmarminge Oct 8, 2026
4daec10
fix(server): pairing tokens work on Node versions that cannot bind bo…
chisewaguri Oct 8, 2026
61b9790
fix(mobile): HTML pages in a thread no longer trap scrolling on Andro…
SunkenInTime Oct 8, 2026
07a8b9e
fix(web): centered scrollers no longer shift when the scrollbar appea…
maria-rcks Oct 8, 2026
62a5d37
fix(web): distinguish thread search matches from code tints (#17263)
Yash-Singh1 Oct 8, 2026
f999eb2
fix(server): Pi extension wakes get an owned continuation turn (#17214)
StiensWout Oct 8, 2026
de72286
fix(server): Pi discovers optional T3 tools on demand (#17220)
StiensWout Oct 8, 2026
eb1cf7b
fix(web): stack merge dialog closes as soon as you confirm (#17116)
flamboh Oct 8, 2026
e8f5850
fix(server): Pi editor dialogs prefill the answer composer (#17206)
StiensWout Oct 8, 2026
a0066d5
fix(desktop): generate valid User-Agent that follows RFC 9110 guideli…
jztmanyl Oct 8, 2026
0b6ec43
fix(server): Pi discovers workspace skills and commands (#17190)
StiensWout Oct 8, 2026
805967a
fix(mobile): preserve navigation after native swipe back (#17268)
juliusmarminge Oct 8, 2026
fd25c42
fix(server): keep newly discovered models out of legacy groups (#14314)
Bil0000 Oct 8, 2026
df616cc
feat(editors): open remote projects in JetBrains IDEs over SSH (#17271)
juliusmarminge Oct 8, 2026
740f591
test(desktop): expect JetBrains IDEs among remote editors (#17291)
juliusmarminge Oct 8, 2026
cdd331b
fix(server): recognize authenticated GitHub Enterprise hosts (#11059)
alimek Oct 8, 2026
2a93885
fix(connect): relay client updates itself and skips incompatible clou…
juliusmarminge Oct 8, 2026
bd565bd
fix(shared): relay client install waits out a brief Windows file lock…
ScottN-PV Oct 8, 2026
c377d1c
fix(shared): release relay install locks on cancellation (#10585)
yashranaway Oct 8, 2026
af3d842
chore(shared): bump managed cloudflared to 2026.10.0 (#11184)
bompus Oct 8, 2026
5809487
fix(shared): bound cloudflared download with 10-minute timeout (#14139)
kvnloo Oct 8, 2026
9eea28b
refactor(provider-core): add provider-core and provider-testing packa…
juliusmarminge Oct 8, 2026
2c11607
refactor(settings): drop the legacy per-driver providers map (#17300)
juliusmarminge Oct 8, 2026
9b0df13
refactor(provider-pi): move Pi into its own provider package (#17302)
juliusmarminge Oct 8, 2026
7b57b72
feat(models): tell users when a CLI update unlocks a new model (#17307)
juliusmarminge Oct 8, 2026
48b71f0
fix(web): collapsed composer reserves room for wide send actions (#17…
maria-rcks Oct 8, 2026
d81afa0
fix(muse): workflow subagents no longer stall on hidden approvals (#1…
t3dotgg Oct 8, 2026
d7f7576
Merge upstream v0.0.46-nightly.20261008.2849 into custom/usage-badge
Oct 9, 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
  •  
  •  
  •  
23 changes: 1 addition & 22 deletions .coderabbit.config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import { defineConfig, type CodeRabbitContext } from "@coderabbitai/config";

const approvability = `Fail when a maintainer should read this pull request before CodeRabbit approves it, and name the rule and file. Fail if it:

- Changes a product default: a setting's default value, or what users get without opting in. Making a feature do what it already promises is a bug fix, not a default change.
- Adds or broadens a directive that disables or suppresses a lint, type-checker, LSP, or other static-analysis diagnostic, including file-level, line-level, and configuration-level overrides.
- Adds a subsystem or user workflow, or is a large refactor across apps or packages.
- Changes packages/contracts or persisted data in a way that existing clients or stored data might not accept.
- Changes authentication, pairing, credentials, secrets, or remote connection trust.
- Adds or changes an external side effect, such as acting on GitHub, publishing a release, or calling a webhook.
- Adds, upgrades, or patches a dependency.
- Changes CI or release configuration, agent or contributor instructions, or any review tool's configuration, including .github/, AGENTS.md, CONTRIBUTING.md, .agents/, and .coderabbit.config.ts.

Otherwise pass. A focused bug fix, copy or layout fix, revert, or docs-only or test-only change passes unless a rule above applies. If you cannot decide, fail rather than report inconclusive. When failing, say that the pull request needs a maintainer's review.
`;

// Org members and collaborators merge their own pull requests. On anyone else's, CodeRabbit
// requests changes until its comments are resolved and its checks pass, then approves.
// requests changes until its comments are resolved, then approves.
const UNGATED_AUTHORS = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
const isGated = ({ pr }: CodeRabbitContext) => !UNGATED_AUTHORS.has(pr?.authorAssociation ?? "");

Expand All @@ -31,13 +17,6 @@ export default defineConfig((ctx) => ({
pre_merge_checks: {
docstrings: { mode: "off" },
override_requested_reviewers_only: isGated(ctx),
custom_checks: [
{
name: "Approvability",
mode: isGated(ctx) ? "error" : "off",
instructions: approvability,
},
],
},
path_filters: [
// Vendored read-only reference checkouts of upstream Effect and Alchemy
Expand Down
35 changes: 34 additions & 1 deletion apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ describe("DesktopAppIdentity", () => {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
yield* identity.configure;

assert.deepEqual(calls.setName, ["T3 Code (Alpha)"]);
assert.deepEqual(calls.setName, ["T3 Code Alpha"]);
assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "T3 Code (Alpha)");
assert.equal(calls.setAboutPanelOptions[0]?.applicationVersion, "1.2.3");
assert.equal(calls.setAboutPanelOptions[0]?.version, "0123456789ab");
Expand All @@ -237,6 +237,39 @@ describe("DesktopAppIdentity", () => {
);
});

it.effect.each([
{ stage: "Alpha", environment: {} },
{
stage: "Nightly",
environment: { appVersion: "0.0.43-nightly.20260929.2428" },
},
{
stage: "Dev",
environment: { env: { VITE_DEV_SERVER_URL: "http://localhost:5173" } },
},
])("uses a valid native User-Agent product name for $stage", ({ stage, environment }) => {
const calls: ElectronAppCalls = {
setAboutPanelOptions: [],
setDockIcon: [],
setName: [],
};

return withIdentity(
Effect.gen(function* () {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
yield* identity.configure;

const runtimeName = calls.setName[0];
assert.isDefined(runtimeName);
assert.equal(runtimeName, `T3 Code ${stage}`);
// RFC 9110's token grammar, after Electron removes ASCII spaces.
assert.match(runtimeName.replaceAll(" ", ""), /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/);
assert.equal(calls.setAboutPanelOptions[0]?.applicationName, `T3 Code (${stage})`);
}),
{ calls, environment },
);
});

it.effect("sets the dock icon only when running unpackaged", () => {
const calls: ElectronAppCalls = {
setAboutPanelOptions: [],
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/app/DesktopAppIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ export const make = Effect.gen(function* () {

const configure = Effect.gen(function* () {
const commitHash = yield* resolveAboutCommitHash;
yield* electronApp.setName(environment.displayName);
// Electron removes spaces from this name to build the native User-Agent
// product token, but leaves parentheses intact. Keep the runtime name valid
// without rewriting preview sessions (which breaks Turnstile, #7110).
yield* electronApp.setName(
`${environment.branding.baseName} ${environment.branding.stageLabel}`,
);
yield* electronApp.setAboutPanelOptions({
applicationName: environment.displayName,
applicationVersion: environment.appVersion,
Expand Down
22 changes: 20 additions & 2 deletions apps/desktop/src/electron/ElectronShell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ describe("ElectronShell", () => {
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("opens the JetBrains Toolbox ssh deep link", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);

const url =
"jetbrains://gateway/ssh/environment?h=example.com&launchIde=true&ideHint=IU&projectHint=%2Fhome%2Fuser%2Fproject";
const electronShell = yield* ElectronShell.ElectronShell;
const result = yield* electronShell.openExternal(url);

assert.equal(result, true);
assert.deepEqual(openExternalMock.mock.calls, [[url]]);
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("does not open editor URLs that mix up link shapes", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);
Expand All @@ -114,9 +128,12 @@ describe("ElectronShell", () => {
const results = yield* Effect.all([
electronShell.openExternal("zed://extension/attacker"),
electronShell.openExternal("vscode://ssh/example.com/home/user/project"),
electronShell.openExternal("jetbrains://idea/navigate/reference?project=x&path=y"),
electronShell.openExternal("jetbrains://gateway/ssh/environment?launchIde=true"),
electronShell.openExternal("jetbrains://gateway/com.example.provider/connect?h=x"),
]);

assert.deepEqual(results, [false, false]);
assert.deepEqual(results, [false, false, false, false, false]);
assert.equal(openExternalMock.mock.calls.length, 0);
}).pipe(Effect.provide(ElectronShell.layer)),
);
Expand All @@ -134,9 +151,10 @@ describe("ElectronShell", () => {
"vscode://:secret@vscode-remote/ssh-remote+example.com/home/user/project",
),
electronShell.openExternal("zed://ssh/user@example.com/home/user/project"),
electronShell.openExternal("jetbrains://user@gateway/ssh/environment?h=example.com"),
]);

assert.deepEqual(results, [false, false, false]);
assert.deepEqual(results, [false, false, false, false]);
assert.equal(openExternalMock.mock.calls.length, 0);
}).pipe(Effect.provide(ElectronShell.layer)),
);
Expand Down
14 changes: 9 additions & 5 deletions apps/desktop/src/electron/ElectronShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import * as Option from "effect/Option";
import * as Electron from "electron";

// 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.
// `zed://ssh/<host>/<path>`, `jetbrains://gateway/ssh/environment?h=…`) 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 @@ -31,9 +31,13 @@ const isRemoteEditorUrl = (url: URL) =>
url.password.length === 0 &&
(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);
: url.protocol === "jetbrains:"
? url.host === "gateway" &&
url.pathname === "/ssh/environment" &&
(url.searchParams.get("h") ?? "").length > 0
: 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
2 changes: 1 addition & 1 deletion apps/desktop/src/ipc/methods/window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,6 @@ it.effect.skipIf(HostProcessPlatform.defaultValue() === "win32")(
);
assert.include(editors, "cursor");
assert.include(editors, "vscode");
assert.notInclude(editors, "webstorm");
assert.include(editors, "webstorm");
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);
36 changes: 35 additions & 1 deletion apps/desktop/src/window/DesktopApplicationMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const layerElectronMenu = (
const configureMenu = (
selectedAction: Deferred.Deferred<string>,
applicationMenuTemplate: Deferred.Deferred<readonly Electron.MenuItemConstructorOptions[]>,
environment: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
) =>
Effect.gen(function* () {
const menu = yield* DesktopApplicationMenu.DesktopApplicationMenu;
Expand All @@ -116,7 +117,7 @@ const configureMenu = (
Layer.provideMerge(layerElectronDialog),
Layer.provideMerge(layerElectronApp),
Layer.provideMerge(
DesktopEnvironment.layer(environmentInput).pipe(
DesktopEnvironment.layer({ ...environmentInput, ...environment }).pipe(
Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))),
),
),
Expand All @@ -125,6 +126,39 @@ const configureMenu = (
);

describe("DesktopApplicationMenu", () => {
it.effect("keeps display branding in the macOS application menu", () =>
Effect.gen(function* () {
const selectedAction = yield* Deferred.make<string>();
const applicationMenuTemplate =
yield* Deferred.make<readonly Electron.MenuItemConstructorOptions[]>();

yield* configureMenu(selectedAction, applicationMenuTemplate, {
platform: "darwin",
appVersion: "0.0.43-nightly.20260929.2428",
});

const template = yield* Deferred.await(applicationMenuTemplate);
const applicationMenu = template[0];
assert.isDefined(applicationMenu);
assert.equal(applicationMenu.label, "T3 Code (Nightly)");
if (!Array.isArray(applicationMenu.submenu)) {
throw new Error("Expected application menu submenu to be an array.");
}
assert.equal(
applicationMenu.submenu.find((item) => item.role === "about")?.label,
"About T3 Code (Nightly)",
);
assert.equal(
applicationMenu.submenu.find((item) => item.role === "hide")?.label,
"Hide T3 Code (Nightly)",
);
assert.equal(
applicationMenu.submenu.find((item) => item.role === "quit")?.label,
"Quit T3 Code (Nightly)",
);
}),
);

it.effect("installs the native menu and routes Settings through DesktopWindow", () =>
Effect.gen(function* () {
const selectedAction = yield* Deferred.make<string>();
Expand Down
11 changes: 4 additions & 7 deletions apps/desktop/src/window/DesktopApplicationMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as Schema from "effect/Schema";
import type * as Electron from "electron";

import { makeComponentLogger } from "../app/DesktopObservability.ts";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as ElectronDialog from "../electron/ElectronDialog.ts";
import * as ElectronMenu from "../electron/ElectronMenu.ts";
import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
Expand Down Expand Up @@ -107,10 +106,8 @@ const handleCheckForUpdatesMenuClick = Effect.gen(function* () {

/** @public Service construction is part of the canonical Effect module API. */
export const make = Effect.gen(function* () {
const electronApp = yield* ElectronApp.ElectronApp;
const electronMenu = yield* ElectronMenu.ElectronMenu;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const appName = yield* electronApp.name;
const context = yield* Effect.context<DesktopApplicationMenuRuntimeServices>();
const runPromise = Effect.runPromiseWith(context);

Expand Down Expand Up @@ -157,9 +154,9 @@ export const make = Effect.gen(function* () {

if (environment.platform === "darwin") {
template.push({
label: appName,
label: environment.displayName,
submenu: [
{ role: "about" },
{ role: "about", label: `About ${environment.displayName}` },
{
label: "Check for Updates...",
click: checkForUpdatesClick,
Expand All @@ -173,11 +170,11 @@ export const make = Effect.gen(function* () {
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hide", label: `Hide ${environment.displayName}` },
{ role: "hideOthers" },
{ role: "unhide" },
{ type: "separator" },
{ role: "quit" },
{ role: "quit", label: `Quit ${environment.displayName}` },
],
});
}
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { fileBasename } from "@t3tools/shared/path";
import { formatFilePathPosition } from "@t3tools/shared/fileLinks";
import {
fileBasename,
formatFilePathPosition,
inlineCodeFilePathCandidate,
normalizeMarkdownLinkDestination,
parseMarkdownFileLink,
} from "@t3tools/client-runtime/markdown-links";
} from "@t3tools/shared/markdownLinks";
import { parseThreadLinkHref } from "@t3tools/shared/threadLinks";
import { videoMimeType } from "@t3tools/shared/video";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { MarkdownNode } from "react-native-nitro-markdown/headless";
import { isMarkdownFileLinkLabel } from "@t3tools/client-runtime/markdown-links";
import { isMarkdownFileLinkLabel } from "@t3tools/shared/markdownLinks";
import { collectComposerInlineTokens } from "@t3tools/shared/composerInlineTokens";
import { imageMimeType } from "@t3tools/shared/image";
import { isWindowsAbsolutePath } from "@t3tools/shared/path";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import ExpoModulesCore
import UIKit

/// One continuous blur with a rounded-rect hole, horizontally centered. A
/// single masked effect view avoids the seams of tiling separate blurs.
final class T3FrostedCutoutView: ExpoView {
var cutoutTop: CGFloat = 0 { didSet { setNeedsLayout() } }
var cutoutWidth: CGFloat = 0 { didSet { setNeedsLayout() } }
var cutoutHeight: CGFloat = 0 { didSet { setNeedsLayout() } }
var cutoutRadius: CGFloat = 0 { didSet { setNeedsLayout() } }
/// Follows the app theme, which can differ from the system appearance.
var appearance: String = "dark" {
didSet {
effectView.effect = UIBlurEffect(
style: appearance == "light" ? .systemThinMaterialLight : .systemThinMaterialDark)
}
}

private let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterialDark))
private let cutoutMaskView = UIView()
private let maskLayer = CAShapeLayer()

required init(appContext: AppContext? = nil) {
super.init(appContext: appContext)
isUserInteractionEnabled = false
maskLayer.fillRule = .evenOdd
cutoutMaskView.layer.addSublayer(maskLayer)
effectView.mask = cutoutMaskView
addSubview(effectView)
}

override func layoutSubviews() {
super.layoutSubviews()
effectView.frame = bounds
cutoutMaskView.frame = bounds
maskLayer.frame = bounds
let path = UIBezierPath(rect: bounds)
if cutoutWidth > 0, cutoutHeight > 0 {
let hole = CGRect(
x: (bounds.width - cutoutWidth) / 2,
y: cutoutTop,
width: cutoutWidth,
height: cutoutHeight
)
path.append(UIBezierPath(roundedRect: hole, cornerRadius: cutoutRadius))
}
maskLayer.path = path.cgPath
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ public final class T3NativeControlsModule: Module {
}
}

View(T3FrostedCutoutView.self) {
ViewName("FrostedCutout")
Prop("cutoutTop") { (view: T3FrostedCutoutView, value: Double) in view.cutoutTop = CGFloat(value) }
Prop("cutoutWidth") { (view: T3FrostedCutoutView, value: Double) in view.cutoutWidth = CGFloat(value) }
Prop("cutoutHeight") { (view: T3FrostedCutoutView, value: Double) in view.cutoutHeight = CGFloat(value) }
Prop("cutoutRadius") { (view: T3FrostedCutoutView, value: Double) in view.cutoutRadius = CGFloat(value) }
Prop("appearance") { (view: T3FrostedCutoutView, value: String) in view.appearance = value }
}

View(T3ContextSheetSizeView.self) {
ViewName("ContextSheetSize")
Prop("contentHeight") { (view: T3ContextSheetSizeView, height: Double) in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public class T3ReviewDiffModule: Module {
view.setContentWidth(CGFloat(contentWidth))
}

Prop("contentInsetTop") { (view: T3ReviewDiffView, inset: Double) in
view.setContentInsetTop(CGFloat(inset))
}

Prop("contentInsetBottom") { (view: T3ReviewDiffView, inset: Double) in
view.setContentInsetBottom(CGFloat(inset))
}

Prop("initialRowIndex") { (view: T3ReviewDiffView, initialRowIndex: Double) in
view.setInitialRowIndex(initialRowIndex)
}
Expand Down
Loading