Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const withIdentity = <A, E, R>(
input.legacyPathProbeError
? Effect.fail(input.legacyPathProbeError)
: Effect.succeed(
input.legacyPathExists === true && path.includes("T3 Code (Alpha v2)"),
input.legacyPathExists === true && path.includes("T3 Code (Alpha)"),
),
readFileString: () =>
Effect.succeed(input.packageJson ?? '{"t3codeCommitHash":"abcdef1234567890"}'),
Expand All @@ -147,14 +147,14 @@ describe("DesktopAppIdentity", () => {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
const userDataPath = yield* identity.resolveUserDataPath;

assert.equal(userDataPath, "/Users/alice/Library/Application Support/T3 Code (Alpha v2)");
assert.equal(userDataPath, "/Users/alice/Library/Application Support/T3 Code (Alpha)");
}),
{ legacyPathExists: true },
),
);

it.effect("preserves failures while inspecting the legacy userData path", () => {
const legacyPath = "/Users/alice/Library/Application Support/T3 Code (Alpha v2)";
const legacyPath = "/Users/alice/Library/Application Support/T3 Code (Alpha)";
const cause = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
Expand Down
16 changes: 8 additions & 8 deletions apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe("DesktopConnectionCatalogStore", () => {
_tag: "PermissionDenied",
module: "FileSystem",
method: "readFileString",
pathOrDescriptor: `${baseDir}/userdata-v2/connection-catalog.json`,
pathOrDescriptor: `${baseDir}/userdata/connection-catalog.json`,
});
const fileSystemLayer = Layer.succeed(
FileSystem.FileSystem,
Expand All @@ -271,11 +271,11 @@ describe("DesktopConnectionCatalogStore", () => {
error,
DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreReadError,
);
assert.equal(error.catalogPath, `${baseDir}/userdata-v2/connection-catalog.json`);
assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`);
assert.strictEqual(error.cause, permissionError);
assert.equal(
error.message,
`Failed to read the desktop connection catalog at ${baseDir}/userdata-v2/connection-catalog.json.`,
`Failed to read the desktop connection catalog at ${baseDir}/userdata/connection-catalog.json.`,
);
assert.notEqual(error.message, permissionError.message);
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
Expand All @@ -291,7 +291,7 @@ describe("DesktopConnectionCatalogStore", () => {
_tag: "PermissionDenied",
module: "FileSystem",
method: "makeDirectory",
pathOrDescriptor: `${baseDir}/userdata-v2`,
pathOrDescriptor: `${baseDir}/userdata`,
});
const fileSystemLayer = Layer.succeed(
FileSystem.FileSystem,
Expand All @@ -309,11 +309,11 @@ describe("DesktopConnectionCatalogStore", () => {
DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreWriteError,
);
assert.equal(error.operation, "create-directory");
assert.equal(error.path, `${baseDir}/userdata-v2`);
assert.equal(error.path, `${baseDir}/userdata`);
assert.strictEqual(error.cause, permissionError);
assert.equal(
error.message,
`Desktop connection catalog write failed during create-directory at ${baseDir}/userdata-v2.`,
`Desktop connection catalog write failed during create-directory at ${baseDir}/userdata.`,
);
assert.notEqual(error.message, permissionError.message);
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
Expand Down Expand Up @@ -398,14 +398,14 @@ describe("DesktopConnectionCatalogStore", () => {
DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreProtectionError,
);
assert.equal(error.operation, "decrypt-catalog");
assert.equal(error.catalogPath, `${baseDir}/userdata-v2/connection-catalog.json`);
assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`);
assert.instanceOf(error.cause, ElectronSafeStorage.ElectronSafeStorageDecryptError);
const decryptError = error.cause as ElectronSafeStorage.ElectronSafeStorageDecryptError;
assert.instanceOf(decryptError.cause, Error);
assert.equal(decryptError.cause.message, "invalid encrypted catalog");
assert.equal(
error.message,
`Desktop connection catalog protection failed during decrypt-catalog at ${baseDir}/userdata-v2/connection-catalog.json.`,
`Desktop connection catalog protection failed during decrypt-catalog at ${baseDir}/userdata/connection-catalog.json.`,
);
assert.notEqual(error.message, decryptError.message);
yield* Ref.set(failDecrypt, false);
Expand Down
20 changes: 13 additions & 7 deletions apps/desktop/src/app/DesktopEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe("DesktopEnvironment", () => {
}),
);

it.effect("derives production state paths under userdata-v2", () =>
it.effect("reuses production state and identity paths from existing installations", () =>
Effect.gen(function* () {
const environment = yield* makeEnvironment(
{},
Expand All @@ -88,12 +88,18 @@ describe("DesktopEnvironment", () => {
);

assert.equal(environment.isDevelopment, false);
assert.equal(environment.stateDir, "/tmp/t3/userdata-v2");
assert.equal(environment.logDir, "/tmp/t3/userdata-v2/logs");
assert.equal(environment.browserArtifactsDir, "/tmp/t3/userdata-v2/browser-artifacts");
assert.equal(environment.serverSettingsPath, "/tmp/t3/userdata-v2/settings.json");
assert.equal(environment.userDataDirName, "t3code-v2");
assert.equal(environment.legacyUserDataDirName, "T3 Code (Alpha v2)");
assert.equal(environment.stateDir, "/tmp/t3/userdata");
assert.equal(environment.desktopSettingsPath, "/tmp/t3/userdata/desktop-settings.json");
assert.equal(environment.clientSettingsPath, "/tmp/t3/userdata/client-settings.json");
assert.equal(
environment.savedEnvironmentRegistryPath,
"/tmp/t3/userdata/saved-environments.json",
);
assert.equal(environment.logDir, "/tmp/t3/userdata/logs");
assert.equal(environment.browserArtifactsDir, "/tmp/t3/userdata/browser-artifacts");
assert.equal(environment.serverSettingsPath, "/tmp/t3/userdata/settings.json");
assert.equal(environment.userDataDirName, "t3code");
assert.equal(environment.legacyUserDataDirName, "T3 Code (Alpha)");
}),
);

Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/app/DesktopEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ const make = Effect.fn("desktop.environment.make")(function* (
appVersion: input.appVersion,
});
const displayName = branding.displayName;
const stateDir = path.join(baseDir, isDevelopment ? "dev" : "userdata-v2");
const userDataDirName = isDevelopment ? "t3code-dev" : "t3code-v2";
const legacyUserDataDirName = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha v2)";
const stateDir = path.join(baseDir, isDevelopment ? "dev" : "userdata");
const userDataDirName = isDevelopment ? "t3code-dev" : "t3code";
const legacyUserDataDirName = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
const resourcesPath = input.resourcesPath;

return DesktopEnvironment.of({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ describe("DesktopBackendConfiguration", () => {
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-backend-config-test-",
});
const settingsPath = path.join(baseDir, "userdata-v2", "settings.json");
const settingsPath = path.join(baseDir, "userdata", "settings.json");
const cause = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/backend/DesktopBackendConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ const resolveWslStartConfig = Effect.fn("desktop.backendConfiguration.resolveWsl
};

// Forward the dev-server URL as an explicit CLI flag so the WSL backend's
// config resolution lands in dev/ instead of userdata-v2/. Inheriting through
// config resolution lands in dev/ instead of userdata/. Inheriting through
// WSLENV is unreliable in practice (URL-shaped values with colons /
// slashes get translated unpredictably depending on flags), and the
// packaged build leaves devServerUrl as None anyway.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe("DesktopClientSettings diagnostics", () => {
_tag: "PermissionDenied",
module: "FileSystem",
method: "readFileString",
pathOrDescriptor: `${baseDir}/userdata-v2/client-settings.json`,
pathOrDescriptor: `${baseDir}/userdata/client-settings.json`,
});

return Effect.gen(function* () {
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/settings/DesktopSavedEnvironments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ describe("DesktopSavedEnvironments", () => {
const baseDir = yield* baseFileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-saved-environments-test-",
});
const registryPath = `${baseDir}/userdata-v2/saved-environments.json`;
const registryPath = `${baseDir}/userdata/saved-environments.json`;
const permissionError = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
Expand Down Expand Up @@ -439,7 +439,7 @@ describe("DesktopSavedEnvironments", () => {
_tag: "PermissionDenied",
module: "FileSystem",
method: "makeDirectory",
pathOrDescriptor: `${baseDir}/userdata-v2`,
pathOrDescriptor: `${baseDir}/userdata`,
});
const fileSystemLayer = Layer.succeed(
FileSystem.FileSystem,
Expand All @@ -455,11 +455,11 @@ describe("DesktopSavedEnvironments", () => {
const error = yield* savedEnvironments.setRegistry([savedRegistryRecord]).pipe(Effect.flip);
assert.instanceOf(error, DesktopSavedEnvironments.DesktopSavedEnvironmentsWriteError);
assert.equal(error.operation, "create-directory");
assert.equal(error.path, `${baseDir}/userdata-v2`);
assert.equal(error.path, `${baseDir}/userdata`);
assert.strictEqual(error.cause, permissionError);
assert.equal(
error.message,
`Desktop saved-environment write failed during create-directory at ${baseDir}/userdata-v2.`,
`Desktop saved-environment write failed during create-directory at ${baseDir}/userdata.`,
);
assert.notEqual(error.message, permissionError.message);
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
Expand Down
16 changes: 15 additions & 1 deletion apps/server/src/cli/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
return fd;
});

it.effect("keeps production state in userdata while isolating development state", () =>
Effect.gen(function* () {
const { join } = yield* Path.Path;
const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-state-paths");

const productionPaths = yield* deriveServerPaths(baseDir, undefined);
const developmentPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:5173"));

assert.equal(productionPaths.stateDir, join(baseDir, "userdata"));
assert.equal(developmentPaths.stateDir, join(baseDir, "dev"));
}),
);

it.effect("falls back to effect/config values when flags are omitted", () =>
Effect.gen(function* () {
const { join } = yield* Path.Path;
Expand Down Expand Up @@ -328,7 +341,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
tailscaleServeEnabled: false,
tailscaleServePort: 443,
});
assert.equal(join(baseDir, "userdata-v2"), resolved.stateDir);
assert.equal(join(baseDir, "userdata"), resolved.stateDir);
}),
);

Expand Down Expand Up @@ -377,6 +390,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
]) {
expect(yield* fs.exists(directory)).toBe(true);
}
expect(resolved.stateDir).toBe(path.join(baseDir, "dev"));
expect(resolved.cwd).toBe(path.resolve(customCwd));
}),
);
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const deriveServerPaths = Effect.fn(function* (
devUrl: ServerConfig["Service"]["devUrl"],
): Effect.fn.Return<ServerDerivedPaths, never, Path.Path> {
const { join } = yield* Path.Path;
const stateDir = join(baseDir, devUrl !== undefined ? "dev" : "userdata-v2");
const stateDir = join(baseDir, devUrl !== undefined ? "dev" : "userdata");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/config.ts:96

Changing stateDir from "userdata-v2" back to "userdata" makes the server look for production state in join(baseDir, "userdata") instead of join(baseDir, "userdata-v2"), where the current base revision writes it. There is no migration or fallback lookup, so after upgrading the server creates a fresh empty state.sqlite, settings, logs, and environment IDs under userdata, causing existing user history and configuration to appear lost. If this revert is intentional, consider adding a migration or fallback so existing userdata-v2 state is preserved.

Also found in 1 other location(s)

apps/desktop/src/app/DesktopEnvironment.ts:158

Reverting stateDir to path.join(baseDir, &#34;userdata&#34;) strands the desktop's existing production files from the base revision, which wrote desktop-settings.json, client-settings.json, and saved-environments.json under .../userdata-v2. The upgraded app now reads and writes those files in .../userdata instead, so users coming from the current base build lose their saved environments and desktop settings until they manually copy the old directory.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/config.ts around line 96:

Changing `stateDir` from `"userdata-v2"` back to `"userdata"` makes the server look for production state in `join(baseDir, "userdata")` instead of `join(baseDir, "userdata-v2")`, where the current base revision writes it. There is no migration or fallback lookup, so after upgrading the server creates a fresh empty `state.sqlite`, settings, logs, and environment IDs under `userdata`, causing existing user history and configuration to appear lost. If this revert is intentional, consider adding a migration or fallback so existing `userdata-v2` state is preserved.

Also found in 1 other location(s):
- apps/desktop/src/app/DesktopEnvironment.ts:158 -- Reverting `stateDir` to `path.join(baseDir, "userdata")` strands the desktop's existing production files from the base revision, which wrote `desktop-settings.json`, `client-settings.json`, and `saved-environments.json` under `.../userdata-v2`. The upgraded app now reads and writes those files in `.../userdata` instead, so users coming from the current base build lose their saved environments and desktop settings until they manually copy the old directory.

const dbPath = join(stateDir, "state.sqlite");
const attachmentsDir = join(stateDir, "attachments");
const logsDir = join(stateDir, "logs");
Expand Down
2 changes: 1 addition & 1 deletion packages/ssh/src/tunnel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ describe("ssh tunnel scripts", () => {
assert.include(buildRemoteStopScript(target), 'rm -f "$PID_FILE" "$PORT_FILE" "$MANAGED_FILE"');
assert.include(
buildRemoteLaunchScript(),
'DEFAULT_RUNTIME_FILE="$DEFAULT_SERVER_HOME/userdata-v2/server-runtime.json"',
'DEFAULT_RUNTIME_FILE="$DEFAULT_SERVER_HOME/userdata/server-runtime.json"',
);
assert.include(buildRemoteLaunchScript(), "resolve_default_runtime_port()");
assert.include(
Expand Down
2 changes: 1 addition & 1 deletion packages/ssh/src/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ export const REMOTE_LAUNCH_SCRIPT = `set -eu
STATE_KEY="$1"
STATE_DIR="$HOME/.t3/ssh-launch/$STATE_KEY"
DEFAULT_SERVER_HOME="$HOME/.t3"
DEFAULT_RUNTIME_FILE="$DEFAULT_SERVER_HOME/userdata-v2/server-runtime.json"
DEFAULT_RUNTIME_FILE="$DEFAULT_SERVER_HOME/userdata/server-runtime.json"
PORT_FILE="$STATE_DIR/port"
PID_FILE="$STATE_DIR/pid"
MANAGED_FILE="$STATE_DIR/managed"
Expand Down
Loading