Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
dfc70a3
test(contracts): keep driver default lookup private (#9968)
juliusmarminge Sep 5, 2026
32142cf
test(server): remove Azure permissions constant snapshot (#9973)
juliusmarminge Sep 5, 2026
1568b3f
refactor(shared): remove unused viewport formatters (#9970)
juliusmarminge Sep 5, 2026
c1e279e
refactor(mobile): remove unused provider option summary (#9971)
juliusmarminge Sep 5, 2026
1782a2a
refactor(client-runtime): remove unused connection phase message (#9972)
juliusmarminge Sep 5, 2026
eda0cec
refactor(mobile): remove unused layout calculations (#9974)
juliusmarminge Sep 5, 2026
044a6e1
refactor(client-runtime): remove unused file position predicate (#9976)
juliusmarminge Sep 5, 2026
5fe29c8
refactor(mobile): remove unused font size steppers (#9975)
juliusmarminge Sep 5, 2026
b3f8dd9
test(server): cover thread lookup through command invariants (#9978)
juliusmarminge Sep 5, 2026
2fc630c
test(server): remove provider equality wrapper fixture (#9979)
juliusmarminge Sep 5, 2026
1550f1b
test(server): assert the dispatched welcome thread model (#9980)
juliusmarminge Sep 5, 2026
14da634
refactor(desktop): remove unused keyring remediation text (#9981)
juliusmarminge Sep 5, 2026
a9caf7b
refactor(desktop): remove test-only Electron error predicates (#9982)
juliusmarminge Sep 5, 2026
ea0487c
refactor(web): remove unused pull request state label (#9984)
juliusmarminge Sep 5, 2026
aefef95
perf(web): keep timeline row reuse engaged while text streams (#9909)
juliusmarminge Sep 5, 2026
3e13333
fix(web): reset markdown widgets when the previewed file changes (#9910)
juliusmarminge Sep 5, 2026
50027bb
fix(mobile): keep highlighting review diffs after a long line (#9911)
juliusmarminge Sep 5, 2026
fc1f543
fix(marketing): align the endorsement carousel with its heading (#9912)
juliusmarminge Sep 5, 2026
f87ecf0
fix(client): keep warm thread resumes live instead of flashing sync (…
juliusmarminge Sep 5, 2026
371e323
test(server): remove authorization prompt snapshots (#9985)
juliusmarminge Sep 5, 2026
10421bc
test(server): remove static OAuth page snapshots (#9986)
juliusmarminge Sep 5, 2026
9867eb1
test(server): remove provider label identity assertion (#9987)
juliusmarminge Sep 5, 2026
94d1fa7
test(server): consolidate agent activity opt-in coverage (#9988)
juliusmarminge Sep 5, 2026
aca2afc
refactor(shared): remove unused preview URL predicate (#9989)
juliusmarminge Sep 5, 2026
c6410d3
refactor(shared): remove unused mention path serializer (#9990)
juliusmarminge Sep 5, 2026
da1bebb
refactor(shared): remove retired PATH capture parser (#9991)
juliusmarminge Sep 5, 2026
3bcde91
refactor(client-runtime): remove unused subagent selectors (#9992)
juliusmarminge Sep 5, 2026
487d176
refactor(web): test the live usage column builder (#9993)
juliusmarminge Sep 5, 2026
c7e93f5
refactor(web): remove unused aspect ratio reconciler (#9994)
juliusmarminge Sep 5, 2026
bf1c1b0
refactor(web): remove obsolete cloud listing helpers (#9995)
juliusmarminge Sep 5, 2026
4f1dc55
test(web): remove composer control style snapshots (#9996)
juliusmarminge Sep 5, 2026
11cb88e
test(web): keep the preview profile label helper private (#9997)
juliusmarminge Sep 5, 2026
3fedf52
test(server): cover raw OpenCode deltas through the adapter (#9977)
juliusmarminge Sep 5, 2026
ba873b8
refactor(web): remove obsolete pull request link opener (#9983)
juliusmarminge Sep 5, 2026
ac93fbf
test(relay): keep the stage slug helper private (#9998)
juliusmarminge Sep 5, 2026
1e24b43
refactor(mobile): keep project selection helper private (#9999)
juliusmarminge Sep 5, 2026
1c59d3b
refactor(mobile): keep review default ID helper private (#10000)
juliusmarminge Sep 5, 2026
3382b26
refactor(mobile): remove unused native style constants (#10001)
juliusmarminge Sep 5, 2026
393d1ff
refactor(mobile): test terminal palettes through public theme API (#1…
juliusmarminge Sep 5, 2026
8d48a31
refactor(mobile): remove unused file tree walkers (#10003)
juliusmarminge Sep 5, 2026
1584076
refactor(shared): keep persisted settings helpers private (#10004)
juliusmarminge Sep 5, 2026
4e5e17f
test(mobile): remove mocked UUID shape assertions (#10006)
juliusmarminge Sep 5, 2026
1449dec
refactor(mobile): test final connection status presentation (#10007)
juliusmarminge Sep 5, 2026
4e59b06
test(web): keep pull request menu items private (#10016)
juliusmarminge Sep 5, 2026
3e544f8
refactor(shared): test favicon selection through public API (#10005)
juliusmarminge Sep 5, 2026
270e021
refactor(server): remove test-only pricing normalizer (#10017)
juliusmarminge Sep 5, 2026
93d4dfa
refactor(web): remove unused desktop update visibility helper (#10014)
juliusmarminge Sep 5, 2026
c9b76e6
refactor(web): remove obsolete provider update helpers (#10015)
juliusmarminge Sep 5, 2026
b7fc81d
refactor(web): remove unused terminal context preview formatter (#10009)
juliusmarminge Sep 5, 2026
82c2b7f
refactor(web): test environment-scoped draft promotion (#10010)
juliusmarminge Sep 5, 2026
6270a6f
fix(web): retain wrapped row heights during edits (#10018)
juliusmarminge Sep 5, 2026
cb58dfd
refactor(shared): remove unused Clerk hostname predicate (#10008)
juliusmarminge Sep 5, 2026
1d58f2e
refactor(tailscale): keep package internals private (#10011)
juliusmarminge Sep 5, 2026
cd92a7e
ci: reject unused tailscale exports with Knip (#10012)
juliusmarminge Sep 5, 2026
eced382
fix(web): keep chat media at a stable size while it loads (#9938)
juliusmarminge Sep 5, 2026
31fb210
refactor(server): keep manifest age parsing private (#10028)
juliusmarminge Sep 5, 2026
37bf4e6
refactor(mobile): remove unused awareness relay URL normalizer (#10029)
juliusmarminge Sep 5, 2026
f5d9d12
refactor(server): remove unused startup heartbeat launcher (#10030)
juliusmarminge Sep 5, 2026
86f0799
refactor(shared): keep search ranking comparator private (#10031)
juliusmarminge Sep 5, 2026
68aa7aa
refactor(server): keep telemetry identity errors private (#10032)
juliusmarminge Sep 5, 2026
83a2897
refactor(mobile): test composer persistence through the live decoder …
juliusmarminge Sep 5, 2026
5cb6965
refactor(web): remove unused sidebar selectors (#10034)
juliusmarminge Sep 5, 2026
ad3721e
refactor(server): keep Cursor fallback models private (#10038)
juliusmarminge Sep 5, 2026
07fb04d
refactor(web): remove unused xterm link range helpers (#10040)
juliusmarminge Sep 5, 2026
c059d09
refactor(mobile): remove obsolete review list builder (#10039)
juliusmarminge Sep 5, 2026
a21c0d7
test(server): remove duplicate VCS error constructor checks (#10042)
juliusmarminge Sep 5, 2026
5e828bf
refactor(mobile): keep appearance calculations private (#10043)
juliusmarminge Sep 5, 2026
5a4287c
refactor(web): remove unused sidebar menu action (#10044)
juliusmarminge Sep 5, 2026
6615d3d
refactor(web): test live Ghostty link resolution directly (#10041)
juliusmarminge Sep 5, 2026
df370c3
test(server): exercise Codex prompts through public assembly (#10045)
juliusmarminge Sep 5, 2026
62e74cd
refactor(shared): remove unused elapsed-time adapter (#10046)
juliusmarminge Sep 5, 2026
91ba05e
refactor(web): remove unused preview thread reset helper (#10049)
juliusmarminge Sep 5, 2026
56a2f42
refactor(desktop): remove test-only error predicates (#10047)
juliusmarminge Sep 5, 2026
c2aff91
refactor(mobile): keep review reset hashing private (#10048)
juliusmarminge Sep 5, 2026
a98dad7
test(web): remove AppRoot element order snapshot (#10052)
juliusmarminge Sep 5, 2026
62e4ae4
refactor(codex): keep app-server client internals private (#10035)
juliusmarminge Sep 5, 2026
4631000
ci: reject unused Codex client exports with Knip (#10036)
juliusmarminge Sep 5, 2026
45f5a5f
refactor(server): simplify native telemetry error internals (#10057)
juliusmarminge Sep 5, 2026
5b7f6bc
refactor(mobile): remove write-only terminal font cache (#10058)
juliusmarminge Sep 5, 2026
80f775a
refactor(web): remove obsolete HSL theme generator (#10061)
juliusmarminge Sep 5, 2026
d524eb9
refactor(mobile): remove obsolete native diff token stream (#10062)
juliusmarminge Sep 5, 2026
bc24d98
test(server): remove title prompt editorial snapshots (#10063)
juliusmarminge Sep 5, 2026
160e337
test(server): remove repeated runtime prompt interpolation cases (#10…
juliusmarminge Sep 5, 2026
43700b8
refactor(web): observe preview tests through the live registry (#10064)
juliusmarminge Sep 5, 2026
fedf84a
test(server): remove keybinding default assignment snapshot (#10065)
juliusmarminge Sep 5, 2026
4db2c54
refactor(mobile): remove obsolete whole-file review highlighters (#10…
juliusmarminge Sep 5, 2026
0acf05f
test(server): cover CLI runner detection through command suggestions …
juliusmarminge Sep 5, 2026
ee150e1
refactor(mobile): remove unused cloud relay URL normalizer (#10068)
juliusmarminge Sep 5, 2026
009c13f
refactor(web): test live keybinding resolvers directly (#10069)
juliusmarminge Sep 5, 2026
25cbcd6
test(server): cover Grok skill parsing through discovery (#10070)
juliusmarminge Sep 5, 2026
38812c1
test(web): remove mocked diff view prop snapshot (#10073)
juliusmarminge Sep 5, 2026
19ea3c5
test(web): remove mocked annotation options snapshot (#10074)
juliusmarminge Sep 5, 2026
688e594
refactor(web): keep pending action labels private (#10075)
juliusmarminge Sep 5, 2026
b92a812
refactor(mobile): remove unused cloud pending-status mapper (#10071)
juliusmarminge Sep 5, 2026
ab0933a
refactor(web): remove unused model picker hint helpers (#10072)
juliusmarminge Sep 5, 2026
c843c19
fix(server): resume checkpointing after git init (#10078)
Yash-Singh1 Sep 5, 2026
09aac71
feat(web): first-run welcome wizard with agent setup and project impo…
t3dotgg Sep 5, 2026
2271a27
fix(server): keep Homebrew mise shims manual-only (#10085)
juliusmarminge Sep 5, 2026
39802c0
fix(ssh): report remote package installation failures accurately (#10…
juliusmarminge Sep 5, 2026
ce18558
merge: sync upstream through 39802c0611
github-actions[bot] Sep 5, 2026
17bc8fb
fix: restore fork APIs after knip unused-export sync
github-actions[bot] Sep 5, 2026
0f95a3a
fix(ssh): pass HOME in remote install diagnostic fixtures
github-actions[bot] Sep 5, 2026
6a03cc4
fix(web): isolate settings-hydration tests from shared vite workers
github-actions[bot] Sep 5, 2026
dee6bb3
fix(server): notice git init despite VCS detection cache
github-actions[bot] Sep 5, 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
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ jobs:
- name: Ensure Electron runtime is installed
run: vp run --filter @t3tools/desktop ensure:electron

# Export cleanup is still a manual audit; files and dependencies have no baseline.
- name: Check unused files and dependencies
# Files/dependencies are repo-wide; export checks cover clean workspaces only.
- name: Check unused code
run: vp run knip:check

- name: Check
Expand Down
3 changes: 0 additions & 3 deletions apps/desktop/src/backend/DesktopServerExposure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,6 @@ describe("DesktopServerExposure", () => {
modeError,
DesktopServerExposure.DesktopServerExposureModePersistenceError,
);
assert.isTrue(DesktopServerExposure.isDesktopServerExposureSetModeError(modeError));
assert.isTrue(DesktopServerExposure.isDesktopServerExposureError(modeError));
assert.equal(modeError.mode, "network-accessible");
assert.strictEqual(modeError.cause, settingsFailure);
assert.strictEqual(modeError.cause.cause, diskFailure);
Expand All @@ -290,7 +288,6 @@ describe("DesktopServerExposure", () => {
tailscaleError,
DesktopServerExposure.DesktopTailscaleServePersistenceError,
);
assert.isTrue(DesktopServerExposure.isDesktopServerExposureError(tailscaleError));
assert.equal(tailscaleError.enabled, true);
assert.equal(tailscaleError.port, 8443);
assert.strictEqual(tailscaleError.cause, settingsFailure);
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/backend/DesktopServerExposure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,13 @@ export const DesktopServerExposureSetModeError = Schema.Union([
DesktopServerExposureModePersistenceError,
]);
export type DesktopServerExposureSetModeError = typeof DesktopServerExposureSetModeError.Type;
export const isDesktopServerExposureSetModeError = Schema.is(DesktopServerExposureSetModeError);

export const DesktopServerExposureError = Schema.Union([
DesktopServerExposureNoNetworkAddressError,
DesktopServerExposureModePersistenceError,
DesktopTailscaleServePersistenceError,
]);
export type DesktopServerExposureError = typeof DesktopServerExposureError.Type;
export const isDesktopServerExposureError = Schema.is(DesktopServerExposureError);

export interface DesktopServerExposureBackendConfig {
readonly port: number;
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/electron/ElectronDialog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ describe("ElectronDialog", () => {
);

assert.instanceOf(error, ElectronDialog.ElectronDialogPickFolderError);
assert.isTrue(ElectronDialog.isElectronDialogError(error));
assert.strictEqual(error.ownerWindowId, 7);
assert.strictEqual(error.defaultPath, "/workspace");
assert.strictEqual(error.cause, cause);
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/electron/ElectronDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ export const ElectronDialogError = Schema.Union([
ElectronDialogShowErrorBoxError,
]);
export type ElectronDialogError = typeof ElectronDialogError.Type;
export const isElectronDialogError = Schema.is(ElectronDialogError);

export interface ElectronDialogPickFolderInput {
readonly owner: Option.Option<Electron.BrowserWindow>;
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/electron/ElectronTheme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ describe("ElectronTheme", () => {
const error = yield* Effect.flip(electronTheme.setSource("dark"));

assert.instanceOf(error, ElectronTheme.ElectronThemeSetSourceError);
assert.isTrue(ElectronTheme.isElectronThemeSetSourceError(error));
assert.strictEqual(error.source, "dark");
assert.strictEqual(error.cause, cause);
assert.include(error.message, "dark");
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/electron/ElectronTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export class ElectronThemeSetSourceError extends Schema.TaggedErrorClass<Electro
}
}

export const isElectronThemeSetSourceError = Schema.is(ElectronThemeSetSourceError);

export class ElectronTheme extends Context.Service<
ElectronTheme,
{
Expand Down
3 changes: 0 additions & 3 deletions apps/desktop/src/electron/ElectronUpdater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ describe("ElectronUpdater", () => {
const error = yield* updater.checkForUpdates.pipe(Effect.flip);

assert.instanceOf(error, ElectronUpdater.ElectronUpdaterCheckForUpdatesError);
assert.isTrue(ElectronUpdater.isElectronUpdaterError(error));
assert.equal(error.channel, "beta");
assert.strictEqual(error.cause, cause);
assert.equal(error.message, "Electron updater failed to check for updates on channel beta.");
Expand All @@ -89,7 +88,6 @@ describe("ElectronUpdater", () => {
const error = yield* updater.downloadUpdate.pipe(Effect.flip);

assert.instanceOf(error, ElectronUpdater.ElectronUpdaterDownloadUpdateError);
assert.isTrue(ElectronUpdater.isElectronUpdaterError(error));
assert.equal(error.channel, "nightly");
assert.strictEqual(error.cause, cause);
assert.equal(
Expand Down Expand Up @@ -126,7 +124,6 @@ describe("ElectronUpdater", () => {
.pipe(Effect.flip);

assert.instanceOf(error, ElectronUpdater.ElectronUpdaterQuitAndInstallError);
assert.isTrue(ElectronUpdater.isElectronUpdaterError(error));
assert.equal(error.channel, "alpha");
assert.equal(error.isSilent, true);
assert.equal(error.isForceRunAfter, false);
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/electron/ElectronUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export const ElectronUpdaterError = Schema.Union([
ElectronUpdaterQuitAndInstallError,
]);
export type ElectronUpdaterError = typeof ElectronUpdaterError.Type;
export const isElectronUpdaterError = Schema.is(ElectronUpdaterError);

export class ElectronUpdater extends Context.Service<
ElectronUpdater,
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/electron/ElectronWindow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ describe("ElectronWindow", () => {
const error = yield* electronWindow.create(options).pipe(Effect.flip);

assert.instanceOf(error, ElectronWindow.ElectronWindowCreateError);
assert.isTrue(ElectronWindow.isElectronWindowCreateError(error));
assert.deepEqual(error.options, {
title: "T3 Code",
width: 1100,
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/electron/ElectronWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ export class ElectronWindowCreateError extends Schema.TaggedErrorClass<ElectronW
}
}

export const isElectronWindowCreateError = Schema.is(ElectronWindowCreateError);

export class ElectronWindowOperationError extends Schema.TaggedErrorClass<ElectronWindowOperationError>()(
"ElectronWindowOperationError",
{
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/ipc/DesktopIpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ describe("DesktopIpc", () => {
const error = yield* Effect.flip(Effect.scoped(ipc.handle(invokeMethod)));

assert.instanceOf(error, DesktopIpc.DesktopIpcRegistrationError);
assert.isTrue(DesktopIpc.isDesktopIpcError(error));
assert.strictEqual(error.handlerKind, "invoke");
assert.strictEqual(error.channel, invokeMethod.channel);
assert.strictEqual(error.cause, cause);
Expand Down Expand Up @@ -69,7 +68,6 @@ describe("DesktopIpc", () => {
if (exit._tag === "Success") return;
const error = Cause.squash(exit.cause);
assert.instanceOf(error, DesktopIpc.DesktopIpcUnregistrationError);
assert.isTrue(DesktopIpc.isDesktopIpcError(error));
assert.strictEqual(error.handlerKind, "sync");
assert.strictEqual(error.channel, syncMethod.channel);
assert.strictEqual(error.cause, cause);
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/ipc/DesktopIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export const DesktopIpcError = Schema.Union([
DesktopIpcUnregistrationError,
]);
export type DesktopIpcError = typeof DesktopIpcError.Type;
export const isDesktopIpcError = Schema.is(DesktopIpcError);

export interface DesktopIpcMethod<E, R> {
readonly channel: string;
Expand Down
77 changes: 0 additions & 77 deletions apps/desktop/src/linuxSecretStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { describe, expect, it } from "vite-plus/test";
import {
normalizeLinuxPasswordStorePreference,
resolveLinuxPasswordStoreSwitch,
resolveLinuxSecretStorageUnavailableMessage,
} from "./linuxSecretStorage.ts";

const autoSwitch = (env: NodeJS.ProcessEnv) =>
Expand Down Expand Up @@ -124,80 +123,4 @@ describe("linuxSecretStorage", () => {
}),
).toBe("gnome-libsecret");
});

it("uses GNOME Keyring remediation for libsecret and unknown backends", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "gnome_libsecret",
env: { XDG_CURRENT_DESKTOP: "niri" },
}),
).toContain("GNOME Keyring");
});

it("prefers explicit libsecret selection over KDE desktop heuristics", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "gnome-libsecret",
selectedBackend: "unknown",
env: { XDG_CURRENT_DESKTOP: "KDE" },
}),
).toContain("GNOME Keyring");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "gnome_libsecret",
env: { XDG_CURRENT_DESKTOP: "KDE" },
}),
).toContain("GNOME Keyring");
});

it("prefers explicit KWallet preference over selected gnome-libsecret backend", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "kwallet6",
selectedBackend: "gnome_libsecret",
env: { XDG_CURRENT_DESKTOP: "niri" },
}),
).toContain("KWallet");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "kwallet",
selectedBackend: "gnome-libsecret",
env: {},
}),
).toContain("KWallet");
});

it("uses KWallet remediation wording for KDE-looking sessions", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "kwallet6",
env: {},
}),
).toContain("KWallet");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "unknown",
env: { XDG_CURRENT_DESKTOP: "KDE" },
}),
).toContain("KWallet");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "unknown",
env: { DESKTOP_SESSION: "plasmawayland" },
}),
).toContain("KWallet");
// A desktop name outranks a bare KDE marker when choosing the wording.
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "unknown",
env: { GDMSESSION: "gnome", KDE_FULL_SESSION: "true" },
}),
).toContain("GNOME Keyring");
});
});
99 changes: 0 additions & 99 deletions apps/desktop/src/linuxSecretStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ const ELECTRON_KDE_DESKTOP = "KDE";
// Chromium recognizes LXQt and still selects basic text for it, so it does need a forced backend.
const ELECTRON_UNPROTECTED_DESKTOPS = new Set(["LXQt"]);

const KDE_NAME_PREFIXES = ["kde", "plasma"];
const NEGATIVE_FLAG_VALUES = new Set(["0", "false", "no", "off"]);

export function normalizeLinuxPasswordStorePreference(
value: unknown,
): LinuxPasswordStorePreference {
Expand Down Expand Up @@ -77,102 +74,6 @@ function electronSelectsProtectedBackend(env: NodeJS.ProcessEnv): boolean {
return false;
}

export function resolveLinuxSecretStorageUnavailableMessage(input: {
readonly configuredPreference: LinuxPasswordStorePreference;
readonly selectedBackend: string | null;
readonly env: NodeJS.ProcessEnv;
}): string {
if (input.configuredPreference === "gnome-libsecret") {
return getGnomeKeyringRemediationMessage();
}

if (
input.configuredPreference === "kwallet" ||
input.configuredPreference === "kwallet5" ||
input.configuredPreference === "kwallet6"
) {
return getKWalletRemediationMessage();
}

const backend = normalizeSelectedStorageBackend(input.selectedBackend);
if (backend === "gnome-libsecret") {
return getGnomeKeyringRemediationMessage();
}

if (
backend === "kwallet" ||
backend === "kwallet5" ||
backend === "kwallet6" ||
looksLikeKdeSession(input.env)
) {
return getKWalletRemediationMessage();
}

return getGnomeKeyringRemediationMessage();
}

function getGnomeKeyringRemediationMessage(): string {
return "T3 Code could not access GNOME Keyring to save this environment credential. Install and start GNOME Keyring, then restart T3 Code.";
}

function getKWalletRemediationMessage(): string {
return "T3 Code could not access KWallet to save this environment credential. Enable the KDE wallet subsystem in System Settings, then restart T3 Code.";
}

// Advisory only: this picks between the GNOME Keyring and KWallet wording in the failure notice. It
// never decides which backend to select, so a loose match costs a user slightly wrong instructions
// rather than an unprotected credential store.
function looksLikeKdeSession(env: NodeJS.ProcessEnv): boolean {
const currentDesktopNames = nonEmptyDesktopNames(env.XDG_CURRENT_DESKTOP);
if (currentDesktopNames.length > 0) {
return currentDesktopNames.some(isKdeDesktopName);
}

const legacyNames = legacyDesktopNames(env);
if (legacyNames.length > 0) {
return legacyNames.some(isKdeDesktopName);
}

return isSet(env.KDE_SESSION_VERSION) || isAffirmativeFlag(env.KDE_FULL_SESSION);
}

function isKdeDesktopName(name: string): boolean {
return KDE_NAME_PREFIXES.some((prefix) => name.startsWith(prefix));
}

function legacyDesktopNames(env: NodeJS.ProcessEnv): string[] {
return [env.XDG_SESSION_DESKTOP, env.DESKTOP_SESSION, env.GDMSESSION].flatMap((entry) => {
const normalized = normalizeDesktopName(entry);
return normalized ? [normalized] : [];
});
}

function nonEmptyDesktopNames(value: string | undefined): string[] {
return splitDesktopNameList(value).flatMap((entry) => {
const normalized = normalizeDesktopName(entry);
return normalized ? [normalized] : [];
});
}

function isSet(value: string | undefined): boolean {
return Boolean(value?.trim());
}

function isAffirmativeFlag(value: string | undefined): boolean {
const normalized = value?.trim().toLowerCase();
return normalized ? !NEGATIVE_FLAG_VALUES.has(normalized) : false;
}

function splitDesktopNameList(value: string | undefined): string[] {
return value?.split(":") ?? [];
}

function normalizeDesktopName(value: string | undefined): string | null {
const normalized = value?.trim().toLowerCase();
return normalized && normalized.length > 0 ? normalized : null;
}

function normalizeSelectedStorageBackend(value: string | null): string | null {
const normalized = value?.trim().toLowerCase().replace(/_/gu, "-");
return normalized && normalized.length > 0 ? normalized : null;
}
6 changes: 0 additions & 6 deletions apps/desktop/src/preview/BrowserSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,6 @@ describe("BrowserSession", () => {
const error = yield* browserSessions.getPartition("environment-a").pipe(Effect.flip);

assert.instanceOf(error, BrowserSession.BrowserSessionPartitionDerivationError);
assert.isTrue(BrowserSession.isBrowserSessionGetSessionError(error));
assert.isTrue(BrowserSession.isBrowserSessionError(error));
assert.equal(error.scope, "environment-a");
assert.strictEqual(error.cause, platformCause);
assert.strictEqual(error.cause.reason.cause, nativeCause);
Expand All @@ -196,8 +194,6 @@ describe("BrowserSession", () => {
const error = yield* browserSessions.getSession("environment-b").pipe(Effect.flip);

assert.instanceOf(error, BrowserSession.BrowserSessionCreationError);
assert.isTrue(BrowserSession.isBrowserSessionGetSessionError(error));
assert.isTrue(BrowserSession.isBrowserSessionError(error));
assert.equal(error.scope, "environment-b");
assert.equal(error.partition, partition);
assert.strictEqual(error.cause, cause);
Expand Down Expand Up @@ -270,7 +266,6 @@ describe("BrowserSession", () => {
const storageError = yield* browserSessions.clearCookies().pipe(Effect.flip);

assert.instanceOf(storageError, BrowserSession.BrowserSessionStorageClearError);
assert.isTrue(BrowserSession.isBrowserSessionError(storageError));
assert.equal(storageError.partition, secondPartition);
assert.strictEqual(storageError.cause, storageCause);
assert.equal(
Expand All @@ -287,7 +282,6 @@ describe("BrowserSession", () => {
const cacheError = yield* browserSessions.clearCache().pipe(Effect.flip);

assert.instanceOf(cacheError, BrowserSession.BrowserSessionCacheClearError);
assert.isTrue(BrowserSession.isBrowserSessionError(cacheError));
assert.equal(cacheError.partition, firstPartition);
assert.strictEqual(cacheError.cause, cacheCause);
assert.equal(
Expand Down
Loading
Loading