From df13391fb1b321b810cfab0e721ca858c65b931f Mon Sep 17 00:00:00 2001
From: Alex Southwell
Date: Fri, 6 Nov 2026 19:42:48 +0000
Subject: [PATCH 01/67] fix(azure-devops): a fork's same-named branch no longer
hides the branch's own pull request (#29817)
Co-authored-by: Claude Opus 5.5
---
.../AzureDevOpsSourceControlProvider.test.ts | 18 ++++++++++++++++++
.../server/AzureDevOpsSourceControlProvider.ts | 5 +++++
2 files changed, 23 insertions(+)
diff --git a/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.test.ts b/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.test.ts
index 70f0ce906fe4f..5b4e8e91c436e 100644
--- a/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.test.ts
+++ b/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.test.ts
@@ -1,8 +1,11 @@
import { processOutput } from "@t3tools/source-control-testing/TestSourceControlHost";
import { assert, it } from "@effect/vitest";
+import * as Effect from "effect/Effect";
+import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import { ChildProcessSpawner } from "effect/process";
+import * as AzureDevOpsCli from "./AzureDevOpsCli.ts";
import * as AzureDevOpsSourceControlProvider from "./AzureDevOpsSourceControlProvider.ts";
const { parseAuth } = AzureDevOpsSourceControlProvider.discovery;
@@ -25,3 +28,18 @@ it("reads the signed-in Azure account and says when az is signed out", () => {
);
assert.strictEqual(parseAuth(processOutput("\n")).status, "unknown");
});
+
+it.effect(
+ "looks up a branch's pull requests a full page at a time, past forks' same-named branches",
+ () =>
+ Effect.gen(function* () {
+ const provider = yield* AzureDevOpsSourceControlProvider.make;
+ assert.deepStrictEqual(
+ provider.headBranchProbe?.({
+ headSelectors: ["octocat:feature", "feature"],
+ state: "open",
+ }),
+ { headSelectors: ["feature"], limit: 100 },
+ );
+ }).pipe(Effect.provide(Layer.mock(AzureDevOpsCli.AzureDevOpsCli)({}))),
+);
diff --git a/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.ts b/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.ts
index 64c1ada9bc137..c63fa639f2dee 100644
--- a/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.ts
+++ b/packages/source-control-azure-devops/src/server/AzureDevOpsSourceControlProvider.ts
@@ -76,6 +76,11 @@ export const make = Effect.gen(function* () {
return SourceControlProvider.SourceControlProvider.of({
kind: SourceControlProviderKind.make("azure-devops"),
+ // Azure lists fork pull requests by source branch with no owner filter, so read a full page.
+ headBranchProbe: ({ headSelectors }) => ({
+ headSelectors: headSelectors.filter((selector) => !selector.includes(":")),
+ limit: 100,
+ }),
listChangeRequests: Effect.fn("AzureDevOpsSourceControlProvider.listChangeRequests")(
function (input) {
const source = SourceControlProvider.sourceControlRefFromInput(input);
From 8a942c2121c7a2d5b8a164c1ffc247334c62137b Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 6 Nov 2026 11:54:54 -0800
Subject: [PATCH 02/67] perf(server): provider-thread reads start from owned
rows, not every node of the thread (#31012)
Co-authored-by: Claude Opus 5.5
---
.../orchestration-v2/ProjectionStore.test.ts | 56 +++++++++++++++++++
.../src/orchestration-v2/ProjectionStore.ts | 22 +++++---
2 files changed, 70 insertions(+), 8 deletions(-)
diff --git a/apps/server/src/orchestration-v2/ProjectionStore.test.ts b/apps/server/src/orchestration-v2/ProjectionStore.test.ts
index 9ae1bc4882d82..8ff160f0cc2ac 100644
--- a/apps/server/src/orchestration-v2/ProjectionStore.test.ts
+++ b/apps/server/src/orchestration-v2/ProjectionStore.test.ts
@@ -472,6 +472,62 @@ it.effect("record reads restrict provider histories to delivery identities", ()
})).providerThreads,
[],
);
+ const ownerNodeId = NodeId.make("node:delivery-identities:owner");
+ yield* store.apply({
+ id: EventId.make("event:delivery-identities:owner-node"),
+ type: "node.updated",
+ threadId,
+ runId: RunId.make("run:delivery-identities:rolled-back"),
+ nodeId: ownerNodeId,
+ driver,
+ occurredAt: now,
+ payload: {
+ id: ownerNodeId,
+ threadId,
+ runId: RunId.make("run:delivery-identities:rolled-back"),
+ rootNodeId: NodeId.make("node:delivery-identities:rolled-back"),
+ parentNodeId: NodeId.make("node:delivery-identities:rolled-back"),
+ kind: "subagent",
+ status: "completed",
+ countsForRun: false,
+ providerThreadId: null,
+ providerTurnId: null,
+ nativeItemRef: null,
+ runtimeRequestId: null,
+ checkpointScopeId: null,
+ startedAt: now,
+ completedAt: now,
+ },
+ });
+ yield* store.apply({
+ id: EventId.make("event:delivery-identities:owned"),
+ type: "provider-thread.updated",
+ threadId,
+ occurredAt: now,
+ payload: {
+ id: ProviderThreadId.make("provider-thread:owned"),
+ appThreadId: ThreadId.make("thread:delivery-identities:child"),
+ driver,
+ providerInstanceId,
+ providerSessionId: null,
+ ownerNodeId,
+ nativeThreadRef: null,
+ nativeConversationHeadRef: null,
+ lastRunOrdinal: null,
+ handoffIds: [],
+ forkedFrom: null,
+ status: "idle",
+ firstRunOrdinal: 2,
+ createdAt: now,
+ updatedAt: now,
+ },
+ });
+ assert.deepEqual(
+ (yield* store.getThreadRecords(threadId, ["providerThreads"])).providerThreads.map(
+ (row) => row.id,
+ ),
+ ["provider-thread:history", "provider-thread:selected", "provider-thread:owned"],
+ );
// Unselected history must not be decoded, even if its payload is unreadable.
yield* sql`UPDATE orchestration_v2_projection_provider_threads SET payload_json = '{}'
WHERE provider_thread_id = 'provider-thread:history'`;
diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts
index 2f8c972e7ded9..dea68ee7723d1 100644
--- a/apps/server/src/orchestration-v2/ProjectionStore.ts
+++ b/apps/server/src/orchestration-v2/ProjectionStore.ts
@@ -1979,6 +1979,16 @@ export const layer: Layer.Layer =
OR item.payload_json LIKE '%"viewedImagePath"%'
OR item.payload_json LIKE '%"toolName":"%delegate_task"%')`;
+ // Start from the few owned provider threads; a thread can have a large settled node history.
+ const nodesOwningProviderThreads = (threadId: ThreadId) => sql`
+ SELECT owner.node_id
+ FROM orchestration_v2_projection_provider_threads AS owned
+ INDEXED BY orchestration_v2_projection_provider_threads_owner_idx
+ CROSS JOIN orchestration_v2_projection_nodes AS owner
+ ON owner.node_id = owned.owner_node_id
+ WHERE owned.owner_node_id IS NOT NULL AND owner.thread_id = ${threadId}
+ `;
+
const keepRecordedRunField = (payload: Statement.Fragment, path: string) => sql`
CASE
WHEN json_type(excluded.payload_json, ${path}) IS NULL
@@ -3500,11 +3510,7 @@ export const layer: Layer.Layer =
SELECT payload_json
FROM orchestration_v2_projection_provider_threads
WHERE (thread_id = ${threadId}
- OR owner_node_id IN (
- SELECT node_id
- FROM orchestration_v2_projection_nodes
- WHERE thread_id = ${threadId}
- )
+ OR owner_node_id IN (${nodesOwningProviderThreads(threadId)})
OR provider_thread_id IN (
SELECT provider_thread_id
FROM orchestration_v2_projection_subagents
@@ -5085,7 +5091,7 @@ export const layer: Layer.Layer =
sql`SELECT payload_json FROM orchestration_v2_projection_provider_threads
WHERE provider_thread_id = ${target.providerThreadId} AND (
thread_id = ${threadId}
- OR owner_node_id IN (SELECT node_id FROM orchestration_v2_projection_nodes WHERE thread_id = ${threadId})
+ OR owner_node_id IN (${nodesOwningProviderThreads(threadId)})
OR provider_thread_id IN (SELECT provider_thread_id FROM orchestration_v2_projection_subagents WHERE thread_id = ${threadId})
)`,
sql`SELECT payload_json FROM orchestration_v2_projection_provider_turns
@@ -5325,7 +5331,7 @@ export const layer: Layer.Layer =
sql`SELECT payload_json FROM orchestration_v2_projection_provider_threads
WHERE provider_thread_id = ${run?.providerThreadId ?? null}
AND (thread_id = ${threadId}
- OR owner_node_id IN (SELECT node_id FROM orchestration_v2_projection_nodes WHERE thread_id = ${threadId})
+ OR owner_node_id IN (${nodesOwningProviderThreads(threadId)})
OR provider_thread_id IN (SELECT provider_thread_id FROM orchestration_v2_projection_subagents WHERE thread_id = ${threadId}))`,
sql<{ readonly ordinal: number }>`SELECT ordinal_within_scope AS ordinal
FROM orchestration_v2_projection_checkpoints
@@ -5364,7 +5370,7 @@ export const layer: Layer.Layer =
sql`SELECT payload_json FROM orchestration_v2_projection_provider_threads
WHERE provider_thread_id = ${target.providerThreadId}
AND (thread_id = ${threadId}
- OR owner_node_id IN (SELECT node_id FROM orchestration_v2_projection_nodes WHERE thread_id = ${threadId})
+ OR owner_node_id IN (${nodesOwningProviderThreads(threadId)})
OR provider_thread_id IN (SELECT provider_thread_id FROM orchestration_v2_projection_subagents WHERE thread_id = ${threadId}))`,
sql`SELECT payload_json FROM orchestration_v2_projection_checkpoints
WHERE thread_id = ${threadId} AND checkpoint_id = ${target.checkpointId}`,
From 50f0dacbd7695309a58d69318fab2fff502a91ea Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 6 Nov 2026 11:55:02 -0800
Subject: [PATCH 03/67] perf(server): stop tracing the per-event
background-work probe (#31584)
Co-authored-by: Claude Opus 5.5
---
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts | 5 ++---
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts | 5 ++---
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
index 10edb77c5011a..df80f1316fb77 100644
--- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
+++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@@ -11809,9 +11809,8 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
}
return false;
}),
- hasPendingBackgroundWorkForThread: Effect.fn(
- "ClaudeAdapterV2.hasPendingBackgroundWorkForThread",
- )(function* (providerThread, options) {
+ // Untraced: the run's event stream calls this once per provider event.
+ hasPendingBackgroundWorkForThread: Effect.fnUntraced(function* (providerThread, options) {
const nativeThreadId = providerThread.nativeThreadRef?.nativeId;
if (nativeThreadId === undefined || nativeThreadId === null) {
return false;
diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
index 07c4e4e72eca7..369608a289d2e 100644
--- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
+++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
@@ -7071,9 +7071,8 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
}
return false;
}),
- hasPendingBackgroundWorkForThread: Effect.fn(
- "CodexAdapterV2.hasPendingBackgroundWorkForThread",
- )(function* (providerThread) {
+ // Untraced: the run's event stream calls this once per provider event.
+ hasPendingBackgroundWorkForThread: Effect.fnUntraced(function* (providerThread) {
const contexts = [
...(yield* Ref.get(activeTurns)).values(),
...(yield* Ref.get(settledTurns)).values(),
From 8ee2f35eeb2d2a4b9603b5a1e0b7328289c97ac8 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 6 Nov 2026 11:55:10 -0800
Subject: [PATCH 04/67] perf(server): stop tracing each transcript file in
usage scans (#31586)
Co-authored-by: Claude Opus 5.5
---
apps/server/src/usage/UsageService.ts | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/apps/server/src/usage/UsageService.ts b/apps/server/src/usage/UsageService.ts
index 7e2e8586ed95f..8876f0af131f1 100644
--- a/apps/server/src/usage/UsageService.ts
+++ b/apps/server/src/usage/UsageService.ts
@@ -439,9 +439,10 @@ export const make = Effect.gen(function* () {
/**
* Reads run concurrently, and the caller stores updates in walk order rather
* than completion order: saved records of deleted transcripts aggregate in
- * cache order, where the first copy of a duplicate wins.
+ * cache order, where the first copy of a duplicate wins. Untraced: a scan
+ * reads thousands of files, so scanTranscriptDir records the counts instead.
*/
- const readFileRecords = Effect.fn("UsageService.readFileRecords")(function* (
+ const readFileRecords = Effect.fnUntraced(function* (
filePath: string,
size: number,
mtimeMs: number,
@@ -597,6 +598,10 @@ export const make = Effect.gen(function* () {
return { path, records, malformedRecords };
});
const unread = failedPaths + read.filter((file) => file.failed).length;
+ yield* Effect.annotateCurrentSpan({
+ "usage.files": files.length,
+ "usage.files_parsed": read.filter((file) => file.update !== undefined).length,
+ });
return {
provider,
dir,
From c76f94e16a5a6ba22d9f60680efe9b78fd6c64b7 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 6 Nov 2026 11:55:17 -0800
Subject: [PATCH 05/67] perf(cursor): stop tracing the per-event account usage
hash (#31017)
Co-authored-by: Claude Opus 5.5
---
packages/provider-cursor/src/server/accountUsage.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/provider-cursor/src/server/accountUsage.ts b/packages/provider-cursor/src/server/accountUsage.ts
index 2719960eed033..c26d691ad16ee 100644
--- a/packages/provider-cursor/src/server/accountUsage.ts
+++ b/packages/provider-cursor/src/server/accountUsage.ts
@@ -58,7 +58,8 @@ const billedEventIdentity = (...fields: readonly unknown[]) => JSON.stringify(fi
const textEncoder = new TextEncoder();
-const accountHash = Effect.fn("accountHash")(function* (value: string) {
+// Untraced: each usage scan hashes every billed event.
+const accountHash = Effect.fnUntraced(function* (value: string) {
const digest = yield* Digest.Digest;
return Hex.encode(yield* digest.digest(Digest.sha256, textEncoder.encode(value)));
});
From 9db5d56366441ac5795587b0ea3cdbca327c3143 Mon Sep 17 00:00:00 2001
From: Sudharsan <126713117+Sidreyas@users.noreply.github.com>
Date: Sat, 7 Nov 2026 01:29:17 +0530
Subject: [PATCH 06/67] fix(muse): recover from unavailable session views
(#32154)
---
.../provider-muse/src/server/adapter.test.ts | 36 +++++++++++++++----
packages/provider-muse/src/server/adapter.ts | 4 +++
packages/provider-muse/src/server/protocol.ts | 1 +
3 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/packages/provider-muse/src/server/adapter.test.ts b/packages/provider-muse/src/server/adapter.test.ts
index 847c2b4551c59..cc84894ac6ef9 100644
--- a/packages/provider-muse/src/server/adapter.test.ts
+++ b/packages/provider-muse/src/server/adapter.test.ts
@@ -850,16 +850,33 @@ describe("MuseAdapterV2", () => {
}).pipe(Effect.provide(testLayer)),
);
- it.effect(
- "closes a newly opened host when native session registration returns another identity",
- () =>
+ it.effect.each([
+ {
+ reason: "another identity",
+ method: "session/start",
+ sessionId: "unexpected-session",
+ initialNativeThreadId: undefined,
+ history: { items: [] },
+ },
+ {
+ reason: "an unavailable projection",
+ method: "session/resume",
+ sessionId: "saved-session",
+ initialNativeThreadId: "saved-session",
+ history: { items: null, mode: "none", noneReason: "projectionUnavailable" },
+ },
+ ])(
+ "closes a newly opened host when native session registration returns $reason",
+ ({ method, sessionId, initialNativeThreadId, history }) =>
Effect.gen(function* () {
const fake = yield* makeFakeMuse();
- fake.queueResponse("session/start", {
- session: { sessionId: "unexpected-session", modelId: MODEL, activeTurnId: null },
- history: { items: [] },
+ fake.queueResponse(method, {
+ session: { sessionId, modelId: MODEL, activeTurnId: null },
+ history,
});
- const result = yield* makeHarness(fake).pipe(Effect.exit);
+ const result = yield* makeHarness(fake, INSTANCE_ID, initialNativeThreadId).pipe(
+ Effect.exit,
+ );
assert.strictEqual(result._tag, "Failure");
yield* Effect.promise(() => fake.host.exited);
assert.strictEqual(fake.closeCount(), 1);
@@ -1441,6 +1458,11 @@ describe("MuseAdapterV2", () => {
text: "Previous answer",
};
fake.setHistory([nativeItem]);
+ fake.queueResponse("session/resume", {
+ session: { sessionId: "saved-session", modelId: MODEL, activeTurnId: null },
+ viewCursor: "",
+ history: { items: null, mode: "none", noneReason: "excluded" },
+ });
const harness = yield* makeHarness(fake, INSTANCE_ID, "saved-session");
const resumed = yield* fake.takeCall("session/resume");
assert.strictEqual(resumed.params.sessionId, "saved-session");
diff --git a/packages/provider-muse/src/server/adapter.ts b/packages/provider-muse/src/server/adapter.ts
index b29f8d51947bf..fd1526a428c6e 100644
--- a/packages/provider-muse/src/server/adapter.ts
+++ b/packages/provider-muse/src/server/adapter.ts
@@ -1879,6 +1879,10 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (
);
if (result.session.sessionId !== nativeSessionId)
return yield* protocolError("Muse returned a different session identity");
+ if (result.history?.noneReason === "projectionUnavailable")
+ return yield* protocolError(
+ "Muse cannot stream this session because its live view is unavailable",
+ );
if (requestedId)
yield* request("session/setApprovalMode", {
mode: museApprovalMode(args.runtimePolicy.runtimeMode),
diff --git a/packages/provider-muse/src/server/protocol.ts b/packages/provider-muse/src/server/protocol.ts
index 9c3e7c40d957b..0c1fdad7db3b2 100644
--- a/packages/provider-muse/src/server/protocol.ts
+++ b/packages/provider-muse/src/server/protocol.ts
@@ -70,6 +70,7 @@ export const MuseSessionResult = Schema.Struct({
history: Schema.optional(
Schema.Struct({
items: Schema.NullOr(Schema.Array(MuseItem)),
+ noneReason: Schema.optional(Schema.String),
snapshot: Schema.optional(
Schema.NullOr(
Schema.Struct({
From c09060b62bfe3e065e2ef4c273dfe55b0f10d0b3 Mon Sep 17 00:00:00 2001
From: Lakshmi Tanmay
Date: Sat, 7 Nov 2026 01:30:15 +0530
Subject: [PATCH 07/67] feat(web): sort and filter transcription models
(#32045)
---
.../settings/VoiceSettingsPanel.tsx | 100 ++++++++++++++----
.../settings/speechModelBrowser.test.ts | 100 ++++++++++++++++++
.../components/settings/speechModelBrowser.ts | 45 ++++++++
3 files changed, 224 insertions(+), 21 deletions(-)
create mode 100644 apps/web/src/components/settings/speechModelBrowser.test.ts
create mode 100644 apps/web/src/components/settings/speechModelBrowser.ts
diff --git a/apps/web/src/components/settings/VoiceSettingsPanel.tsx b/apps/web/src/components/settings/VoiceSettingsPanel.tsx
index c08844bafe09b..b0fd5d2fff5d5 100644
--- a/apps/web/src/components/settings/VoiceSettingsPanel.tsx
+++ b/apps/web/src/components/settings/VoiceSettingsPanel.tsx
@@ -31,6 +31,7 @@ import {
MicIcon,
RefreshCwIcon,
StarIcon,
+ SlidersHorizontalIcon,
Trash2Icon,
XIcon,
} from "lucide-react";
@@ -65,6 +66,19 @@ import { Badge } from "../ui/badge";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
+import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group";
+import {
+ Menu,
+ MenuCheckboxItem,
+ MenuGroup,
+ MenuGroupLabel,
+ MenuPopup,
+ MenuRadioGroup,
+ MenuRadioItem,
+ MenuRadioItemIndicator,
+ MenuSeparator,
+ MenuTrigger,
+} from "../ui/menu";
import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";
import { Switch } from "../ui/switch";
import { Textarea } from "../ui/textarea";
@@ -72,6 +86,7 @@ import { toastManager } from "../ui/toast";
import { MicrophoneTest } from "./MicrophoneTest";
import { LanguageRow, ModelBenchmarks, orderSpeechLanguages } from "./SpeechModelComparison";
import { languageLabel, SpeechLanguageSelect } from "./SpeechLanguageSelect";
+import { filterSpeechModels, type SpeechModelSort } from "./speechModelBrowser";
import { AdvancedSettings } from "./AdvancedSettings";
import {
SETTINGS_PICKER_TRIGGER_CLASSNAME,
@@ -110,12 +125,6 @@ const spellingsOf = (words: SpeechCustomWords) =>
[term, ...aliases].map((spelling) => spelling.toLocaleLowerCase()),
),
);
-const modelSortOrder = (model: EnvironmentSpeechModel) => {
- if (model.active) return 0;
- if (model.state === "installed") return 1;
- if (model.state === "downloading" || model.state === "verifying") return 2;
- return model.recommended ? 3 : 4;
-};
type ModelCardProps = {
readonly model: EnvironmentSpeechModel;
@@ -717,6 +726,8 @@ function SpeechModelBrowser(props: {
catalog: { status, models, run, controlsDisabled, canCancel, requestedDownload },
} = props;
const [search, setSearch] = useState("");
+ const [sort, setSort] = useState("recommended");
+ const [downloadedOnly, setDownloadedOnly] = useState(false);
const [languageSearch, setLanguageSearch] = useState("");
const [selection, setSelection] = useState(null);
const preferred = navigator.languages.map((language) => language.toLowerCase().split("-")[0]!);
@@ -729,14 +740,20 @@ function SpeechModelBrowser(props: {
(code) =>
!needle || code.includes(needle) || languageLabel(code).toLocaleLowerCase().includes(needle),
);
- const query = search.trim().toLocaleLowerCase();
- const sortedModels = models
- .filter(
- (model) =>
- selectedLanguages.every((language) => model.languages.includes(language)) &&
- (!query || (model.name + " " + model.description).toLocaleLowerCase().includes(query)),
- )
- .toSorted((left, right) => modelSortOrder(left) - modelSortOrder(right));
+ const accuracyLanguage = selectedLanguages[0] ?? "en";
+ const sortLabels: Record = {
+ recommended: "Recommended",
+ size: "Smallest download",
+ accuracy: `Best ${languageLabel(accuracyLanguage)} accuracy`,
+ speed: "Fastest transcription",
+ };
+ const sortedModels = filterSpeechModels(models, {
+ search,
+ languages: selectedLanguages,
+ downloadedOnly,
+ sort,
+ accuracyLanguage,
+ });
const activeModel = models.find((model) => model.active);
const renderedModels =
activeModel && !sortedModels.includes(activeModel)
@@ -808,13 +825,54 @@ function SpeechModelBrowser(props: {
? "Models for " + selectedLanguages.map(languageLabel).join(", ")
: "All models"}
- setSearch(e.target.value)}
- />
+
+ setSearch(e.target.value)}
+ />
+
+
+
+
{
+ expect(
+ filterSpeechModels(models, {
+ search: "",
+ languages: [],
+ downloadedOnly: false,
+ sort,
+ accuracyLanguage,
+ }).map((entry) => entry.id),
+ ).toEqual(expected);
+ expect(models[0]?.id).toBe("active");
+ },
+);
+
+it("combines text, all selected languages, and completed downloads without retaining the active model", () => {
+ const options = {
+ search: " LOCAL ",
+ languages: ["en", "fr"],
+ downloadedOnly: true,
+ sort: "size",
+ accuracyLanguage: "en",
+ } as const;
+ expect(filterSpeechModels(models, options).map((entry) => entry.id)).toEqual(["fast", "active"]);
+ expect(
+ filterSpeechModels(models, { ...options, search: "fast" }).map((entry) => entry.id),
+ ).toEqual(["fast"]);
+ expect(filterSpeechModels(models, { ...options, search: "small" })).toEqual([]);
+});
diff --git a/apps/web/src/components/settings/speechModelBrowser.ts b/apps/web/src/components/settings/speechModelBrowser.ts
new file mode 100644
index 0000000000000..d9cab8d1ef0f0
--- /dev/null
+++ b/apps/web/src/components/settings/speechModelBrowser.ts
@@ -0,0 +1,45 @@
+import type { EnvironmentSpeechModel } from "@t3tools/contracts";
+
+export type SpeechModelSort = "recommended" | "size" | "accuracy" | "speed";
+
+const modelSortOrder = (model: EnvironmentSpeechModel) => {
+ if (model.active) return 0;
+ if (model.state === "installed") return 1;
+ if (model.state === "downloading" || model.state === "verifying") return 2;
+ return model.recommended ? 3 : 4;
+};
+
+export function filterSpeechModels(
+ models: readonly EnvironmentSpeechModel[],
+ options: {
+ readonly search: string;
+ readonly languages: readonly string[];
+ readonly downloadedOnly: boolean;
+ readonly sort: SpeechModelSort;
+ readonly accuracyLanguage: string;
+ },
+) {
+ const query = options.search.trim().toLocaleLowerCase();
+ const score = (model: EnvironmentSpeechModel) => {
+ switch (options.sort) {
+ case "size":
+ return model.size;
+ case "accuracy":
+ return model.benchmark?.accuracy[options.accuracyLanguage]?.errorPercent ?? Infinity;
+ case "speed":
+ return model.benchmark?.secondsPer30s ?? Infinity;
+ case "recommended":
+ return modelSortOrder(model);
+ }
+ };
+ return models
+ .filter(
+ (model) =>
+ options.languages.every((language) => model.languages.includes(language)) &&
+ (!options.downloadedOnly || model.state === "installed") &&
+ (!query || (model.name + " " + model.description).toLocaleLowerCase().includes(query)),
+ )
+ .toSorted(
+ (left, right) => score(left) - score(right) || modelSortOrder(left) - modelSortOrder(right),
+ );
+}
From 6fe0fdf088019052e5b4430882c7d968d23976d7 Mon Sep 17 00:00:00 2001
From: Adam Otradovec <37932502+aotradovec@users.noreply.github.com>
Date: Fri, 6 Nov 2026 21:00:26 +0100
Subject: [PATCH 08/67] fix(azure-devops): existing pull requests are found for
branches containing # (#31928)
Co-authored-by: Adam Otradovec
Co-authored-by: Yash Singh
---
.../src/server/AzureDevOpsCli.test.ts | 32 +++++++++++++++++++
.../src/server/AzureDevOpsCli.ts | 8 ++++-
.../server/AzureDevOpsPullRequestCli.test.ts | 16 ++++++++++
.../src/server/AzureDevOpsPullRequestCli.ts | 3 +-
4 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/packages/source-control-azure-devops/src/server/AzureDevOpsCli.test.ts b/packages/source-control-azure-devops/src/server/AzureDevOpsCli.test.ts
index 3cb69661997b6..9b40406093155 100644
--- a/packages/source-control-azure-devops/src/server/AzureDevOpsCli.test.ts
+++ b/packages/source-control-azure-devops/src/server/AzureDevOpsCli.test.ts
@@ -233,6 +233,38 @@ describe("AzureDevOpsCli.layer", () => {
}).pipe(Effect.provide(layer)),
);
+ it.effect("percent-encodes the source branch while preserving recognized ref prefixes", () =>
+ Effect.gen(function* () {
+ mockRun.mockReturnValueOnce(Effect.succeed(processOutput("[]")));
+
+ const az = yield* AzureDevOpsCli.AzureDevOpsCli;
+ yield* az.listPullRequests({
+ cwd: "/somewhere",
+ headSelector: "user/feat/1-(#2)-slug",
+ state: "open",
+ });
+
+ const args = mockRun.mock.calls.at(-1)?.[0].args ?? [];
+ assert.strictEqual(args[args.indexOf("--source-branch") + 1], "user%2Ffeat%2F1-(%232)-slug");
+
+ for (const prefix of ["refs/heads/", "refs/pull/", "refs/tags/"]) {
+ mockRun.mockReturnValueOnce(Effect.succeed(processOutput("[]")));
+ yield* az.listPullRequests({
+ cwd: "/somewhere",
+ headSelector: "fallback",
+ source: { refName: `${prefix}user/feat/1-(#2)-slug` },
+ state: "open",
+ });
+
+ const refArgs = mockRun.mock.calls.at(-1)?.[0].args ?? [];
+ assert.strictEqual(
+ refArgs[refArgs.indexOf("--source-branch") + 1],
+ `${prefix}user%2Ffeat%2F1-(%232)-slug`,
+ );
+ }
+ }).pipe(Effect.provide(layer)),
+ );
+
it.effect("preserves VCS causes without copying upstream details into messages", () =>
Effect.gen(function* () {
const cause = new VcsProcessExitError({
diff --git a/packages/source-control-azure-devops/src/server/AzureDevOpsCli.ts b/packages/source-control-azure-devops/src/server/AzureDevOpsCli.ts
index 57b9f4086cdaa..eb6151d336263 100644
--- a/packages/source-control-azure-devops/src/server/AzureDevOpsCli.ts
+++ b/packages/source-control-azure-devops/src/server/AzureDevOpsCli.ts
@@ -401,6 +401,11 @@ export const make = Effect.gen(function* () {
return AzureDevOpsCli.of({
execute,
listPullRequests: Effect.fn("AzureDevOpsCli.listPullRequests")(function (input) {
+ const branch = SourceControlProvider.sourceBranch(input);
+ // az checks literal ref prefixes before constructing the request URL.
+ const refPrefix =
+ ["refs/heads/", "refs/pull/", "refs/tags/"].find((prefix) => branch.startsWith(prefix)) ??
+ "";
return executeJson({
cwd: input.cwd,
args: [
@@ -410,7 +415,8 @@ export const make = Effect.gen(function* () {
"--detect",
"true",
"--source-branch",
- SourceControlProvider.sourceBranch(input),
+ // az puts this in the query string unescaped; a `#` would cut the request.
+ refPrefix + encodeURIComponent(branch.slice(refPrefix.length)),
"--status",
toAzureStatus(input.state),
"--top",
diff --git a/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.test.ts b/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.test.ts
index 4b2e46b5a1e93..9bbe1468f8f54 100644
--- a/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.test.ts
+++ b/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.test.ts
@@ -850,6 +850,22 @@ layer("AzureDevOpsPullRequestCli.layer", (it) => {
}),
);
+ it.effect("escapes characters in a file path that would cut the query string", () =>
+ Effect.gen(function* () {
+ mockedExecute.mockReturnValueOnce(Effect.succeed(output(json({ content: "x" }))));
+ const cli = yield* AzureDevOpsPullRequestCli.AzureDevOpsPullRequestCli;
+
+ yield* cli.readItemContent({
+ cwd: "/w",
+ location: { project: "platform", repository: "web" },
+ path: "docs/C#/a&b.md",
+ commit: "a".repeat(40),
+ });
+
+ expect(mockedExecute.mock.calls.at(-1)?.[0].args).toContain("path=%2Fdocs%2FC%23%2Fa%26b.md");
+ }),
+ );
+
it.effect("reports a pull request it cannot place as its own outcome", () =>
Effect.gen(function* () {
mockedExecute.mockReturnValueOnce(
diff --git a/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.ts b/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.ts
index 07d9c2a2057e7..81c9b2e68fb6e 100644
--- a/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.ts
+++ b/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestCli.ts
@@ -603,7 +603,8 @@ export const make = Effect.gen(function* () {
resource: "items",
routeParameters: repositoryRoute(input.location),
queryParameters: [
- `path=${toItemPath(input.path)}`,
+ // `az devops invoke` does not escape query values, so `#` or `&` in a path would truncate it.
+ `path=${encodeURIComponent(toItemPath(input.path))}`,
"versionDescriptor.versionType=commit",
`versionDescriptor.version=${input.commit}`,
"includeContent=true",
From bdb8d5dfc067134f460ea7757e62355fe5f9b2a7 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 6 Nov 2026 12:11:54 -0800
Subject: [PATCH 09/67] perf(server): background PR discovery no longer delays
git status after a restart (#32162)
Co-authored-by: Claude Opus 5.5
---
.../ThreadPullRequestService.ts | 2 +
.../ThreadSettlementService.ts | 2 +
apps/server/src/vcs/GitVcsDriver.ts | 5 ++
apps/server/src/vcs/GitVcsDriverCore.test.ts | 50 +++++++++++++++++++
apps/server/src/vcs/GitVcsDriverCore.ts | 8 ++-
5 files changed, 66 insertions(+), 1 deletion(-)
diff --git a/apps/server/src/orchestration-v2/ThreadPullRequestService.ts b/apps/server/src/orchestration-v2/ThreadPullRequestService.ts
index 1cb01acd4ed9c..d84c532d06ecd 100644
--- a/apps/server/src/orchestration-v2/ThreadPullRequestService.ts
+++ b/apps/server/src/orchestration-v2/ThreadPullRequestService.ts
@@ -20,6 +20,7 @@ import * as Stream from "effect/Stream";
import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
import * as GitManager from "../git/GitManager.ts";
+import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import * as ProjectStore from "./ProjectStore.ts";
import * as PullRequestService from "../pullRequest/PullRequestService.ts";
import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts";
@@ -347,6 +348,7 @@ export const make = Effect.gen(function* () {
const worker = yield* makeDrainableWorker((request: RefreshRequest) =>
synchronize(request).pipe(
+ Effect.provideService(GitVcsDriver.BackgroundGitWork, true),
Effect.catchCauseIf(
(cause) => !Cause.hasInterruptsOnly(cause),
(cause) =>
diff --git a/apps/server/src/orchestration-v2/ThreadSettlementService.ts b/apps/server/src/orchestration-v2/ThreadSettlementService.ts
index aedbb73fc5501..8c852426da323 100644
--- a/apps/server/src/orchestration-v2/ThreadSettlementService.ts
+++ b/apps/server/src/orchestration-v2/ThreadSettlementService.ts
@@ -31,6 +31,7 @@ import * as PullRequestService from "../pullRequest/PullRequestService.ts";
import * as ServerSettings from "../serverSettings.ts";
import { forkParked } from "../serverActivation.ts";
import * as TerminalManager from "../terminal/Manager.ts";
+import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import * as ProjectStore from "./ProjectStore.ts";
import { sourceControlDefinitions } from "../sourceControl/sourceControlDefinitions.ts";
import * as Orchestrator from "./Orchestrator.ts";
@@ -556,6 +557,7 @@ export const make = Effect.gen(function* () {
threadId?: ThreadId,
) =>
sweep(mergedPullRequest, threadId).pipe(
+ Effect.provideService(GitVcsDriver.BackgroundGitWork, true),
Effect.catchCauseIf(
(cause) => !Cause.hasInterruptsOnly(cause),
(cause) =>
diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts
index d120605d6961f..0d97d43c3e9d6 100644
--- a/apps/server/src/vcs/GitVcsDriver.ts
+++ b/apps/server/src/vcs/GitVcsDriver.ts
@@ -301,6 +301,11 @@ export interface GitRemoteStatusOptions {
readonly refreshUpstream?: boolean;
}
+/** Set by background discovery so its Git commands run in their own small lane, not the user's. */
+export const BackgroundGitWork = Context.Reference("t3/vcs/BackgroundGitWork", {
+ defaultValue: () => false,
+});
+
export class GitVcsDriver extends Context.Service<
GitVcsDriver,
{
diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts
index 39427404093af..728c20c034ee2 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.test.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts
@@ -14,6 +14,7 @@ import * as FileSystem from "effect/FileSystem";
import * as Fiber from "effect/Fiber";
import * as Layer from "effect/Layer";
import * as Path from "effect/Path";
+import * as Queue from "effect/Queue";
import * as PlatformError from "effect/PlatformError";
import * as Ref from "effect/Ref";
import * as Result from "effect/Result";
@@ -129,6 +130,55 @@ const makeDriverReleasingLock = (
return { driver, failures };
});
+it.effect("runs background Git in its own lane so user commands never queue behind it", () =>
+ Effect.gen(function* () {
+ const gate = yield* Deferred.make();
+ const starts = yield* Queue.unbounded();
+ const spawner = ChildProcessSpawner.make((command) =>
+ Effect.gen(function* () {
+ yield* Queue.offer(
+ starts,
+ ChildProcess.isStandardCommand(command) ? String(command.args[0]) : "",
+ );
+ return ChildProcessSpawner.makeHandle({
+ ...makeSuccessfulHandle("ok"),
+ exitCode: Deferred.await(gate).pipe(Effect.as(ChildProcessSpawner.ExitCode(0))),
+ });
+ }),
+ );
+ const driver = yield* makeGitVcsDriverCore().pipe(
+ Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
+ Effect.provide(layerServerConfig),
+ );
+ const run = (args: ReadonlyArray) =>
+ driver.execute({ operation: "test.gitLane", cwd: "/repo", args: [...args] });
+ const background = yield* Effect.forEach(
+ Array.from({ length: 12 }),
+ () => run(["background"]),
+ {
+ concurrency: "unbounded",
+ },
+ ).pipe(Effect.provideService(GitVcsDriver.BackgroundGitWork, true), Effect.forkChild);
+ const foreground = yield* Effect.forEach(Array.from({ length: 4 }), () => run(["foreground"]), {
+ concurrency: "unbounded",
+ }).pipe(Effect.forkChild);
+
+ yield* TestClock.adjust("2 seconds");
+ const started = yield* Queue.takeAll(starts);
+ assert.deepStrictEqual([...started].toSorted(), [
+ "background",
+ "background",
+ "foreground",
+ "foreground",
+ "foreground",
+ "foreground",
+ ]);
+ yield* Deferred.succeed(gate, undefined);
+ assert.equal((yield* Fiber.join(background)).length, 12);
+ assert.equal((yield* Fiber.join(foreground)).length, 4);
+ }).pipe(Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))),
+);
+
describe("Git output collection", () => {
// Git writes in arbitrary chunk sizes; one byte per chunk splits every
// multibyte character across reads.
diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts
index d1199f39c6542..85a7e524b1a63 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.ts
@@ -63,6 +63,7 @@ const branchBaseConfigKey = (branch: string) => `branch.${branch}.t3-merge-base`
*/
const legacyBranchBaseConfigKey = (branch: string) => `branch.${branch}.gh-merge-base`;
const gitProcesses = Semaphore.makeUnsafe(8);
+const backgroundGitProcesses = Semaphore.makeUnsafe(2);
const WORKTREE_REMOVE_TIMEOUT_MS = Duration.toMillis(Duration.minutes(5));
const DEFAULT_MAX_OUTPUT_BYTES = 1_000_000;
const OUTPUT_TRUNCATED_MARKER = "\n\n[truncated]";
@@ -1123,7 +1124,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
(execution) =>
input.timeoutMs === null || (input.timeoutMs ?? DEFAULT_TIMEOUT_MS) > DEFAULT_TIMEOUT_MS
? execution
- : gitProcesses.withPermits(1)(execution),
+ : Effect.gen(function* () {
+ const lane = (yield* GitVcsDriver.BackgroundGitWork)
+ ? backgroundGitProcesses
+ : gitProcesses;
+ return yield* lane.withPermits(1)(execution);
+ }),
Effect.withSpan(input.operation, {
kind: "client",
attributes: {
From 8bf1b64cae37abbff80a5cf98d2ae0f41dba5264 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 6 Nov 2026 12:27:56 -0800
Subject: [PATCH 10/67] fix(web): the citation comment box opens at the quote
instead of sliding in from the corner (#32141)
Co-authored-by: Claude Opus 5.5
---
.../AssistantCitationChip.browser.test.tsx | 20 +++++++++++++++++++
apps/web/src/components/ui/popover.tsx | 4 +++-
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx b/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx
index deee8d7afa69d..e1b66e1fed8f2 100644
--- a/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx
+++ b/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx
@@ -129,6 +129,26 @@ describe("citation comment source disappearance", () => {
});
describe("citation comment popup position", () => {
+ it("opens at its anchor instead of sliding in from the window corner", async () => {
+ using source = setup();
+ await using _screen = await mount(
+ source.sourceAnchor,
+ vi.fn(() => true),
+ );
+ const positioner = () =>
+ document.querySelector('[data-slot="popover-positioner"]');
+ await vi.waitFor(() => expect(positioner()?.style.opacity).not.toBe("0"));
+
+ const moves = positioner()!
+ .getAnimations()
+ .filter(
+ (animation) =>
+ animation instanceof CSSTransition &&
+ ["transform", "top", "left"].includes(animation.transitionProperty),
+ );
+ expect(moves).toEqual([]);
+ });
+
it("stays on screen when the cited text ends in a code block's hidden overflow", async () => {
using _source = setup();
const message = document.createElement("div");
diff --git a/apps/web/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx
index 788c8a1043def..e03aee4011593 100644
--- a/apps/web/src/components/ui/popover.tsx
+++ b/apps/web/src/components/ui/popover.tsx
@@ -78,7 +78,9 @@ function PopoverPopup({
collisionAvoidance={collisionAvoidance}
collisionPadding={variant === "panel" ? 0 : undefined}
className={cn(
- "h-(--positioner-height) max-h-(--available-height) w-(--positioner-width) max-w-(--available-width) transition-transform data-instant:transition-none",
+ "h-(--positioner-height) max-h-(--available-height) w-(--positioner-width) max-w-(--available-width)",
+ // Kept-mounted positioners first paint at the window origin, so a transform transition slides them in from there.
+ !keepMounted && "transition-transform data-instant:transition-none",
variant === "panel"
? "z-(--z-sheet) w-[min(calc(var(--thread-details-panel-width)+--spacing(4)),calc(var(--anchor-width)---spacing(2)))] transition-[top] duration-150 ease-out data-instant:transition-[top] motion-reduce:transition-none motion-reduce:data-instant:transition-none"
: "z-[130]",
From 40dc2910ea87a4959325e9210c84dacef37e9c3c Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 6 Nov 2026 12:27:56 -0800
Subject: [PATCH 11/67] fix(clients): dictating a citation comment or preview
annotation uses the full width (#32142)
Co-authored-by: Claude Opus 5.5
---
.../src/preview/AnnotationStyles.generated.ts | 2 +-
apps/desktop/src/preview/PickPreload.ts | 17 +++++++++++++++--
.../chat/AssistantCitationCommentEditor.tsx | 10 +++++++++-
apps/web/src/components/chat/VoiceInputPill.tsx | 5 ++++-
4 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/apps/desktop/src/preview/AnnotationStyles.generated.ts b/apps/desktop/src/preview/AnnotationStyles.generated.ts
index a8c70b5cecec5..3b8e2b7000896 100644
--- a/apps/desktop/src/preview/AnnotationStyles.generated.ts
+++ b/apps/desktop/src/preview/AnnotationStyles.generated.ts
@@ -1,3 +1,3 @@
// Generated by scripts/build-preview-annotation-css.mjs. Do not edit.
export const previewAnnotationStyles =
- '/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties;\n:root, :host {\n --spacing: 0.25rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --blur-xl: 24px;\n --default-font-family: var(--t3-font-sans);\n --default-mono-font-family: var(--t3-font-mono);\n}\n*, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n}\nhtml, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, \'Helvetica Neue\', \'Noto Sans\', Arial, sans-serif, \'Apple Color Emoji\', \'Segoe UI Emoji\', \'Segoe UI Symbol\', \'Noto Color Emoji\');\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n}\nhr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n}\nabbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n}\nh1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n}\na {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n}\nb, strong {\n font-weight: bolder;\n}\ncode, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \'Liberation Mono\', \'Courier New\', monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n}\nsmall {\n font-size: 80%;\n}\nsub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsub {\n bottom: -0.25em;\n}\nsup {\n top: -0.5em;\n}\ntable {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n}\n:-moz-focusring:where(:not(iframe)) {\n outline: auto;\n}\nprogress {\n vertical-align: baseline;\n}\nsummary {\n display: list-item;\n}\nol, ul, menu {\n list-style: none;\n}\nimg, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n}\nimg, video {\n max-width: 100%;\n height: auto;\n}\nbutton, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n}\n:where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n}\n:where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n}\n::file-selector-button {\n margin-inline-end: 4px;\n}\n::placeholder {\n opacity: 1;\n}\n@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n}\ntextarea {\n resize: vertical;\n}\n::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n}\n::-webkit-datetime-edit {\n display: inline-flex;\n}\n::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n}\n::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n}\n::-webkit-calendar-picker-indicator {\n line-height: 1;\n}\n:-moz-ui-invalid {\n box-shadow: none;\n}\nbutton, input:where([type=\'button\'], [type=\'reset\'], [type=\'submit\']), ::file-selector-button {\n appearance: button;\n}\n::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n}\n[hidden]:where(:not([hidden=\'until-found\'])) {\n display: none !important;\n}\n.pointer-events-auto {\n pointer-events: auto;\n}\n.pointer-events-none {\n pointer-events: none;\n}\n.visible {\n visibility: visible;\n}\n.absolute {\n position: absolute;\n}\n.fixed {\n position: fixed;\n}\n.inset-0 {\n inset: 0px;\n}\n.top-1\\/2 {\n top: calc(1 / 2 * 100%);\n}\n.top-2\\.5 {\n top: calc(var(--spacing) * 2.5);\n}\n.right-2 {\n right: calc(var(--spacing) * 2);\n}\n.left-1\\/2 {\n left: calc(1 / 2 * 100%);\n}\n.z-1 {\n z-index: 1;\n}\n.block {\n display: block;\n}\n.flex {\n display: flex;\n}\n.grid {\n display: grid;\n}\n.hidden {\n display: none;\n}\n.inline-flex {\n display: inline-flex;\n}\n.h-5 {\n height: calc(var(--spacing) * 5);\n}\n.h-7 {\n height: calc(var(--spacing) * 7);\n}\n.h-8 {\n height: calc(var(--spacing) * 8);\n}\n.h-9 {\n height: calc(var(--spacing) * 9);\n}\n.h-full {\n height: 100%;\n}\n.max-h-24 {\n max-height: calc(var(--spacing) * 24);\n}\n.max-h-\\[calc\\(100vh-16px\\)\\] {\n max-height: calc(100vh - 16px);\n}\n.max-h-\\[min\\(176px\\,calc\\(100vh-180px\\)\\)\\] {\n max-height: min(176px, calc(100vh - 180px));\n}\n.min-h-7 {\n min-height: calc(var(--spacing) * 7);\n}\n.min-h-8 {\n min-height: calc(var(--spacing) * 8);\n}\n.w-0\\.5 {\n width: calc(var(--spacing) * 0.5);\n}\n.w-6 {\n width: calc(var(--spacing) * 6);\n}\n.w-7 {\n width: calc(var(--spacing) * 7);\n}\n.w-8 {\n width: calc(var(--spacing) * 8);\n}\n.w-64 {\n width: calc(var(--spacing) * 64);\n}\n.w-\\[min\\(360px\\,calc\\(100vw-16px\\)\\)\\] {\n width: min(360px, calc(100vw - 16px));\n}\n.w-full {\n width: 100%;\n}\n.max-w-70 {\n max-width: calc(var(--spacing) * 70);\n}\n.min-w-0 {\n min-width: 0px;\n}\n.flex-1 {\n flex: 1;\n}\n.shrink-0 {\n flex-shrink: 0;\n}\n.origin-center {\n transform-origin: center;\n}\n.-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.cursor-grab {\n cursor: grab;\n}\n.cursor-pointer {\n cursor: pointer;\n}\n.resize {\n resize: both;\n}\n.resize-none {\n resize: none;\n}\n.appearance-none {\n appearance: none;\n}\n.grid-cols-\\[22px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 22px minmax(0,1fr);\n}\n.grid-cols-\\[82px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 82px minmax(0,1fr);\n}\n.flex-col {\n flex-direction: column;\n}\n.items-center {\n align-items: center;\n}\n.items-start {\n align-items: flex-start;\n}\n.justify-between {\n justify-content: space-between;\n}\n.justify-center {\n justify-content: center;\n}\n.justify-end {\n justify-content: flex-end;\n}\n.gap-0\\.5 {\n gap: calc(var(--spacing) * 0.5);\n}\n.gap-1 {\n gap: var(--spacing);\n}\n.gap-2 {\n gap: calc(var(--spacing) * 2);\n}\n.gap-px {\n gap: 1px;\n}\n.truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.overflow-auto {\n overflow: auto;\n}\n.overflow-hidden {\n overflow: hidden;\n}\n.overflow-y-hidden {\n overflow-y: hidden;\n}\n.rounded-full {\n border-radius: calc(infinity * 1px);\n}\n.rounded-lg {\n border-radius: var(--t3-radius);\n}\n.rounded-md {\n border-radius: calc(var(--t3-radius) - 2px);\n}\n.rounded-xl {\n border-radius: calc(var(--t3-radius) + 4px);\n}\n.border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n}\n.border-0 {\n border-style: var(--tw-border-style);\n border-width: 0px;\n}\n.border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n}\n.border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n}\n.border-border {\n border-color: var(--t3-border);\n}\n.border-input {\n border-color: var(--t3-input);\n}\n.border-primary {\n border-color: var(--t3-primary);\n}\n.border-transparent {\n border-color: transparent;\n}\n.border-b-transparent {\n border-bottom-color: transparent;\n}\n.bg-background {\n background-color: var(--t3-background);\n}\n.bg-muted {\n background-color: var(--t3-muted);\n}\n.bg-muted\\/40 {\n background-color: var(--t3-muted);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-muted) 40%, transparent);\n }\n}\n.bg-popover\\/95 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 95%, transparent);\n }\n}\n.bg-popover\\/96 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 96%, transparent);\n }\n}\n.bg-primary {\n background-color: var(--t3-primary);\n}\n.bg-primary\\/10 {\n background-color: var(--t3-primary);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-primary) 10%, transparent);\n }\n}\n.bg-transparent {\n background-color: transparent;\n}\n.p-0 {\n padding: 0px;\n}\n.p-1 {\n padding: var(--spacing);\n}\n.p-2 {\n padding: calc(var(--spacing) * 2);\n}\n.px-0 {\n padding-inline: 0px;\n}\n.px-1 {\n padding-inline: var(--spacing);\n}\n.px-2 {\n padding-inline: calc(var(--spacing) * 2);\n}\n.px-2\\.5 {\n padding-inline: calc(var(--spacing) * 2.5);\n}\n.px-3 {\n padding-inline: calc(var(--spacing) * 3);\n}\n.py-1 {\n padding-block: var(--spacing);\n}\n.py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n}\n.py-2 {\n padding-block: calc(var(--spacing) * 2);\n}\n.pb-2 {\n padding-bottom: calc(var(--spacing) * 2);\n}\n.text-right {\n text-align: right;\n}\n.font-mono {\n font-family: var(--t3-font-mono);\n}\n.font-sans {\n font-family: var(--t3-font-sans);\n}\n.text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n}\n.text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n}\n.text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n}\n.leading-5 {\n --tw-leading: calc(var(--spacing) * 5);\n line-height: calc(var(--spacing) * 5);\n}\n.font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n}\n.font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n}\n.font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n}\n.text-foreground {\n color: var(--t3-foreground);\n}\n.text-muted-foreground {\n color: var(--t3-muted-foreground);\n}\n.text-popover-foreground {\n color: var(--t3-popover-foreground);\n}\n.text-primary {\n color: var(--t3-primary);\n}\n.text-primary-foreground {\n color: var(--t3-primary-foreground);\n}\n.tabular-nums {\n --tw-numeric-spacing: tabular-nums;\n font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);\n}\n.shadow {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-sm {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-xs {\n --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.ring-0 {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.blur {\n --tw-blur: blur(8px);\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n}\n.backdrop-blur-xl {\n --tw-backdrop-blur: blur(var(--blur-xl));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n}\n.outline-none {\n --tw-outline-style: none;\n outline-style: none;\n}\n.select-none {\n -webkit-user-select: none;\n user-select: none;\n}\n.placeholder\\:text-muted-foreground::placeholder {\n color: var(--t3-muted-foreground);\n}\n@media (hover: hover) {\n .hover\\:bg-accent:hover {\n background-color: var(--t3-accent);\n }\n .hover\\:bg-primary\\/90:hover {\n background-color: var(--t3-primary);\n }\n @supports (color: color-mix(in lab, red, red)) {\n .hover\\:bg-primary\\/90:hover {\n background-color: color-mix(in oklab, var(--t3-primary) 90%, transparent);\n }\n }\n .hover\\:text-accent-foreground:hover {\n color: var(--t3-accent-foreground);\n }\n}\n.focus\\:border-b-primary:focus {\n border-bottom-color: var(--t3-primary);\n}\n.focus\\:ring-0:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.focus\\:outline-none:focus {\n --tw-outline-style: none;\n outline-style: none;\n}\n.disabled\\:pointer-events-none:disabled {\n pointer-events: none;\n}\n.disabled\\:opacity-60:disabled {\n opacity: 60%;\n}\n:host {\n --t3-font-sans: "DM Sans Variable", "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,\n sans-serif;\n --t3-font-mono: "SF Mono", "SFMono-Regular", "JetBrains Mono", Consolas, "Liberation Mono", Menlo, monospace;\n --t3-radius: 0.625rem;\n --t3-background: white;\n --t3-foreground: oklch(0.269 0 0);\n --t3-popover: white;\n --t3-popover-foreground: oklch(0.269 0 0);\n --t3-primary: oklch(0.488 0.217 264);\n --t3-primary-foreground: white;\n --t3-destructive: oklch(0.577 0.245 27.325);\n --t3-muted: rgb(0 0 0 / 4%);\n --t3-muted-foreground: oklch(0.556 0 0);\n --t3-accent: rgb(0 0 0 / 4%);\n --t3-accent-foreground: oklch(0.269 0 0);\n --t3-border: rgb(0 0 0 / 8%);\n --t3-input: rgb(0 0 0 / 10%);\n --t3-ring: oklch(0.488 0.217 264);\n color: var(--t3-foreground);\n font-family: var(--t3-font-sans);\n}\n* {\n box-sizing: border-box;\n border-color: var(--t3-border);\n}\nbutton, input, select, textarea {\n font: inherit;\n}\nbutton:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {\n outline: 2px solid var(--t3-ring);\n @supports (color: color-mix(in lab, red, red)) {\n outline: 2px solid color-mix(in srgb, var(--t3-ring) 72%, transparent);\n }\n outline-offset: -2px;\n}\n[hidden] {\n display: none !important;\n}\n@property --tw-translate-x {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-leading {\n syntax: "*";\n inherits: false;\n}\n@property --tw-font-weight {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ordinal {\n syntax: "*";\n inherits: false;\n}\n@property --tw-slashed-zero {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-figure {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-spacing {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-fraction {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: "";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: "";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: "";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: "*";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-sepia {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-alpha {\n syntax: "";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-drop-shadow-size {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: "*";\n inherits: false;\n}\n@layer properties {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-border-style: solid;\n --tw-leading: initial;\n --tw-font-weight: initial;\n --tw-ordinal: initial;\n --tw-slashed-zero: initial;\n --tw-numeric-figure: initial;\n --tw-numeric-spacing: initial;\n --tw-numeric-fraction: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-blur: initial;\n --tw-brightness: initial;\n --tw-contrast: initial;\n --tw-grayscale: initial;\n --tw-hue-rotate: initial;\n --tw-invert: initial;\n --tw-opacity: initial;\n --tw-saturate: initial;\n --tw-sepia: initial;\n --tw-drop-shadow: initial;\n --tw-drop-shadow-color: initial;\n --tw-drop-shadow-alpha: 100%;\n --tw-drop-shadow-size: initial;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n }\n}\n';
+ '/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties;\n:root, :host {\n --spacing: 0.25rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --blur-xl: 24px;\n --default-font-family: var(--t3-font-sans);\n --default-mono-font-family: var(--t3-font-mono);\n}\n*, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n}\nhtml, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, \'Helvetica Neue\', \'Noto Sans\', Arial, sans-serif, \'Apple Color Emoji\', \'Segoe UI Emoji\', \'Segoe UI Symbol\', \'Noto Color Emoji\');\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n}\nhr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n}\nabbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n}\nh1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n}\na {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n}\nb, strong {\n font-weight: bolder;\n}\ncode, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \'Liberation Mono\', \'Courier New\', monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n}\nsmall {\n font-size: 80%;\n}\nsub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsub {\n bottom: -0.25em;\n}\nsup {\n top: -0.5em;\n}\ntable {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n}\n:-moz-focusring:where(:not(iframe)) {\n outline: auto;\n}\nprogress {\n vertical-align: baseline;\n}\nsummary {\n display: list-item;\n}\nol, ul, menu {\n list-style: none;\n}\nimg, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n}\nimg, video {\n max-width: 100%;\n height: auto;\n}\nbutton, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n}\n:where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n}\n:where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n}\n::file-selector-button {\n margin-inline-end: 4px;\n}\n::placeholder {\n opacity: 1;\n}\n@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n}\ntextarea {\n resize: vertical;\n}\n::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n}\n::-webkit-datetime-edit {\n display: inline-flex;\n}\n::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n}\n::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n}\n::-webkit-calendar-picker-indicator {\n line-height: 1;\n}\n:-moz-ui-invalid {\n box-shadow: none;\n}\nbutton, input:where([type=\'button\'], [type=\'reset\'], [type=\'submit\']), ::file-selector-button {\n appearance: button;\n}\n::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n}\n[hidden]:where(:not([hidden=\'until-found\'])) {\n display: none !important;\n}\n.pointer-events-auto {\n pointer-events: auto;\n}\n.pointer-events-none {\n pointer-events: none;\n}\n.visible {\n visibility: visible;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border-width: 0;\n}\n.absolute {\n position: absolute;\n}\n.fixed {\n position: fixed;\n}\n.inset-0 {\n inset: 0px;\n}\n.top-1\\/2 {\n top: calc(1 / 2 * 100%);\n}\n.top-2\\.5 {\n top: calc(var(--spacing) * 2.5);\n}\n.right-2 {\n right: calc(var(--spacing) * 2);\n}\n.left-1\\/2 {\n left: calc(1 / 2 * 100%);\n}\n.z-1 {\n z-index: 1;\n}\n.block {\n display: block;\n}\n.flex {\n display: flex;\n}\n.grid {\n display: grid;\n}\n.hidden {\n display: none;\n}\n.inline-flex {\n display: inline-flex;\n}\n.h-5 {\n height: calc(var(--spacing) * 5);\n}\n.h-7 {\n height: calc(var(--spacing) * 7);\n}\n.h-8 {\n height: calc(var(--spacing) * 8);\n}\n.h-full {\n height: 100%;\n}\n.max-h-24 {\n max-height: calc(var(--spacing) * 24);\n}\n.max-h-\\[calc\\(100vh-16px\\)\\] {\n max-height: calc(100vh - 16px);\n}\n.max-h-\\[min\\(176px\\,calc\\(100vh-180px\\)\\)\\] {\n max-height: min(176px, calc(100vh - 180px));\n}\n.min-h-7 {\n min-height: calc(var(--spacing) * 7);\n}\n.min-h-8 {\n min-height: calc(var(--spacing) * 8);\n}\n.w-0\\.5 {\n width: calc(var(--spacing) * 0.5);\n}\n.w-6 {\n width: calc(var(--spacing) * 6);\n}\n.w-7 {\n width: calc(var(--spacing) * 7);\n}\n.w-8 {\n width: calc(var(--spacing) * 8);\n}\n.w-\\[min\\(360px\\,calc\\(100vw-16px\\)\\)\\] {\n width: min(360px, calc(100vw - 16px));\n}\n.w-full {\n width: 100%;\n}\n.max-w-70 {\n max-width: calc(var(--spacing) * 70);\n}\n.min-w-0 {\n min-width: 0px;\n}\n.flex-1 {\n flex: 1;\n}\n.shrink-0 {\n flex-shrink: 0;\n}\n.origin-center {\n transform-origin: center;\n}\n.-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.cursor-grab {\n cursor: grab;\n}\n.cursor-pointer {\n cursor: pointer;\n}\n.resize {\n resize: both;\n}\n.resize-none {\n resize: none;\n}\n.appearance-none {\n appearance: none;\n}\n.grid-cols-\\[22px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 22px minmax(0,1fr);\n}\n.grid-cols-\\[82px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 82px minmax(0,1fr);\n}\n.flex-col {\n flex-direction: column;\n}\n.items-center {\n align-items: center;\n}\n.items-start {\n align-items: flex-start;\n}\n.justify-between {\n justify-content: space-between;\n}\n.justify-center {\n justify-content: center;\n}\n.justify-end {\n justify-content: flex-end;\n}\n.gap-0\\.5 {\n gap: calc(var(--spacing) * 0.5);\n}\n.gap-1 {\n gap: var(--spacing);\n}\n.gap-2 {\n gap: calc(var(--spacing) * 2);\n}\n.gap-px {\n gap: 1px;\n}\n.truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.overflow-auto {\n overflow: auto;\n}\n.overflow-hidden {\n overflow: hidden;\n}\n.overflow-y-hidden {\n overflow-y: hidden;\n}\n.rounded-full {\n border-radius: calc(infinity * 1px);\n}\n.rounded-lg {\n border-radius: var(--t3-radius);\n}\n.rounded-md {\n border-radius: calc(var(--t3-radius) - 2px);\n}\n.rounded-xl {\n border-radius: calc(var(--t3-radius) + 4px);\n}\n.border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n}\n.border-0 {\n border-style: var(--tw-border-style);\n border-width: 0px;\n}\n.border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n}\n.border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n}\n.border-border {\n border-color: var(--t3-border);\n}\n.border-input {\n border-color: var(--t3-input);\n}\n.border-primary {\n border-color: var(--t3-primary);\n}\n.border-transparent {\n border-color: transparent;\n}\n.border-b-transparent {\n border-bottom-color: transparent;\n}\n.bg-background {\n background-color: var(--t3-background);\n}\n.bg-muted {\n background-color: var(--t3-muted);\n}\n.bg-muted\\/40 {\n background-color: var(--t3-muted);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-muted) 40%, transparent);\n }\n}\n.bg-popover\\/95 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 95%, transparent);\n }\n}\n.bg-popover\\/96 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 96%, transparent);\n }\n}\n.bg-primary {\n background-color: var(--t3-primary);\n}\n.bg-primary\\/10 {\n background-color: var(--t3-primary);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-primary) 10%, transparent);\n }\n}\n.bg-transparent {\n background-color: transparent;\n}\n.p-0 {\n padding: 0px;\n}\n.p-1 {\n padding: var(--spacing);\n}\n.p-2 {\n padding: calc(var(--spacing) * 2);\n}\n.px-0 {\n padding-inline: 0px;\n}\n.px-1 {\n padding-inline: var(--spacing);\n}\n.px-2 {\n padding-inline: calc(var(--spacing) * 2);\n}\n.px-2\\.5 {\n padding-inline: calc(var(--spacing) * 2.5);\n}\n.px-3 {\n padding-inline: calc(var(--spacing) * 3);\n}\n.py-1 {\n padding-block: var(--spacing);\n}\n.py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n}\n.py-2 {\n padding-block: calc(var(--spacing) * 2);\n}\n.pb-2 {\n padding-bottom: calc(var(--spacing) * 2);\n}\n.text-right {\n text-align: right;\n}\n.font-mono {\n font-family: var(--t3-font-mono);\n}\n.font-sans {\n font-family: var(--t3-font-sans);\n}\n.text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n}\n.text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n}\n.text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n}\n.leading-5 {\n --tw-leading: calc(var(--spacing) * 5);\n line-height: calc(var(--spacing) * 5);\n}\n.font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n}\n.font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n}\n.font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n}\n.text-foreground {\n color: var(--t3-foreground);\n}\n.text-muted-foreground {\n color: var(--t3-muted-foreground);\n}\n.text-popover-foreground {\n color: var(--t3-popover-foreground);\n}\n.text-primary {\n color: var(--t3-primary);\n}\n.text-primary-foreground {\n color: var(--t3-primary-foreground);\n}\n.tabular-nums {\n --tw-numeric-spacing: tabular-nums;\n font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);\n}\n.shadow {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-sm {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-xs {\n --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.ring-0 {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.blur {\n --tw-blur: blur(8px);\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n}\n.backdrop-blur-xl {\n --tw-backdrop-blur: blur(var(--blur-xl));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n}\n.outline-none {\n --tw-outline-style: none;\n outline-style: none;\n}\n.select-none {\n -webkit-user-select: none;\n user-select: none;\n}\n.placeholder\\:text-muted-foreground::placeholder {\n color: var(--t3-muted-foreground);\n}\n@media (hover: hover) {\n .hover\\:bg-accent:hover {\n background-color: var(--t3-accent);\n }\n .hover\\:bg-primary\\/90:hover {\n background-color: var(--t3-primary);\n }\n @supports (color: color-mix(in lab, red, red)) {\n .hover\\:bg-primary\\/90:hover {\n background-color: color-mix(in oklab, var(--t3-primary) 90%, transparent);\n }\n }\n .hover\\:text-accent-foreground:hover {\n color: var(--t3-accent-foreground);\n }\n}\n.focus\\:border-b-primary:focus {\n border-bottom-color: var(--t3-primary);\n}\n.focus\\:ring-0:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.focus\\:outline-none:focus {\n --tw-outline-style: none;\n outline-style: none;\n}\n.disabled\\:pointer-events-none:disabled {\n pointer-events: none;\n}\n.disabled\\:opacity-60:disabled {\n opacity: 60%;\n}\n:host {\n --t3-font-sans: "DM Sans Variable", "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,\n sans-serif;\n --t3-font-mono: "SF Mono", "SFMono-Regular", "JetBrains Mono", Consolas, "Liberation Mono", Menlo, monospace;\n --t3-radius: 0.625rem;\n --t3-background: white;\n --t3-foreground: oklch(0.269 0 0);\n --t3-popover: white;\n --t3-popover-foreground: oklch(0.269 0 0);\n --t3-primary: oklch(0.488 0.217 264);\n --t3-primary-foreground: white;\n --t3-destructive: oklch(0.577 0.245 27.325);\n --t3-muted: rgb(0 0 0 / 4%);\n --t3-muted-foreground: oklch(0.556 0 0);\n --t3-accent: rgb(0 0 0 / 4%);\n --t3-accent-foreground: oklch(0.269 0 0);\n --t3-border: rgb(0 0 0 / 8%);\n --t3-input: rgb(0 0 0 / 10%);\n --t3-ring: oklch(0.488 0.217 264);\n color: var(--t3-foreground);\n font-family: var(--t3-font-sans);\n}\n* {\n box-sizing: border-box;\n border-color: var(--t3-border);\n}\nbutton, input, select, textarea {\n font: inherit;\n}\nbutton:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {\n outline: 2px solid var(--t3-ring);\n @supports (color: color-mix(in lab, red, red)) {\n outline: 2px solid color-mix(in srgb, var(--t3-ring) 72%, transparent);\n }\n outline-offset: -2px;\n}\n[hidden] {\n display: none !important;\n}\n@property --tw-translate-x {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-leading {\n syntax: "*";\n inherits: false;\n}\n@property --tw-font-weight {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ordinal {\n syntax: "*";\n inherits: false;\n}\n@property --tw-slashed-zero {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-figure {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-spacing {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-fraction {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: "";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: "";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: "";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: "*";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-sepia {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-alpha {\n syntax: "";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-drop-shadow-size {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: "*";\n inherits: false;\n}\n@layer properties {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-border-style: solid;\n --tw-leading: initial;\n --tw-font-weight: initial;\n --tw-ordinal: initial;\n --tw-slashed-zero: initial;\n --tw-numeric-figure: initial;\n --tw-numeric-spacing: initial;\n --tw-numeric-fraction: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-blur: initial;\n --tw-brightness: initial;\n --tw-contrast: initial;\n --tw-grayscale: initial;\n --tw-hue-rotate: initial;\n --tw-invert: initial;\n --tw-opacity: initial;\n --tw-saturate: initial;\n --tw-sepia: initial;\n --tw-drop-shadow: initial;\n --tw-drop-shadow-color: initial;\n --tw-drop-shadow-alpha: 100%;\n --tw-drop-shadow-size: initial;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n }\n}\n';
diff --git a/apps/desktop/src/preview/PickPreload.ts b/apps/desktop/src/preview/PickPreload.ts
index 0549e166dd634..f54963d10916f 100644
--- a/apps/desktop/src/preview/PickPreload.ts
+++ b/apps/desktop/src/preview/PickPreload.ts
@@ -633,6 +633,8 @@ function startAnnotation(
" h-7 w-7 shrink-0 rounded-full border-primary bg-primary p-0 text-primary-foreground hover:bg-primary/90";
const voiceSkip = createButton("Skip", "Skip transcript cleanup");
voiceSkip.className += " h-7 shrink-0 rounded-full px-2.5";
+ // createButton's rounded-md sorts after rounded-full in the generated CSS, so it would win.
+ for (const button of [voiceCancel, voiceFinish, voiceSkip]) button.classList.remove("rounded-md");
const voiceStatus = document.createElement("div");
voiceStatus.className = "flex h-7 min-w-0 flex-1 items-center gap-2";
voiceStatus.setAttribute("role", "status");
@@ -744,16 +746,27 @@ function startAnnotation(
}
const recording = state.phase === "recording";
const active = state.phase !== "idle" && state.phase !== "error";
+ const wasActive = voicePill.parentElement === composerRow;
voiceButton.hidden = !state.available || active;
voiceButton.disabled = voiceBusy;
const label = voiceSettings ? "Open voice settings" : "Start voice input";
voiceButton.title =
!voiceSettings && state.shortcutLabel ? `${label} (${state.shortcutLabel})` : label;
voiceButton.setAttribute("aria-label", label);
- voiceRow.hidden = state.phase === "idle";
+ // An active dictation takes the textarea's place; it takes focus once (sr-only) so Enter still attaches.
+ voiceRow.hidden = state.phase === "idle" || active;
+ comment.classList.toggle("sr-only", active);
+ if (active && !wasActive) comment.focus({ preventScroll: true });
+ submit.hidden = active;
+ // Re-inserting on every level update would blur a focused Finish or Cancel.
+ if (active && voicePill.parentElement !== composerRow) comment.after(voicePill);
+ else if (!active && voicePill.parentElement !== voiceRow) voiceRow.appendChild(voicePill);
voicePill.className = active
- ? "flex h-9 w-64 min-w-0 items-center gap-2 rounded-full border border-border bg-background p-1"
+ ? "flex h-8 min-w-0 flex-1 items-center gap-2"
: "flex min-w-0 items-center gap-1";
+ // A draft that arrived while the textarea was sr-only was measured at 1px wide.
+ if (wasActive && !active) resizeComment();
+ else if (phaseChanged) queueEditorLayout();
voiceCancel.title =
state.phase === "error" ? "Dismiss voice input error" : "Cancel voice input";
voiceCancel.setAttribute("aria-label", voiceCancel.title);
diff --git a/apps/web/src/components/chat/AssistantCitationCommentEditor.tsx b/apps/web/src/components/chat/AssistantCitationCommentEditor.tsx
index cdd8e1249cfc7..f97c66c7f07e1 100644
--- a/apps/web/src/components/chat/AssistantCitationCommentEditor.tsx
+++ b/apps/web/src/components/chat/AssistantCitationCommentEditor.tsx
@@ -113,7 +113,15 @@ export function AssistantCitationCommentEditor({
) : null}
-
+
+
+