feat(FN-4134): stabilize research view layout

Stabilized the ResearchView layout by refactoring CSS and component structure, with 175 lines of fixes spanning the CSS module, component TSX, and new regression tests. Also includes a patch changeset for the release note.

Fusion-Task-Id: FN-4134

Fusion-Task-Lineage: ca3ad67b-766b-4da6-be7f-c3927d718acd
This commit is contained in:
Fusion
2026-05-12 11:18:39 -07:00
committed by gsxdsm
parent fe1ef21daa
commit c420e1acd0
3 changed files with 235 additions and 62 deletions

View File

@@ -44,6 +44,17 @@ async function cleanupTmpDirsAsync(): Promise<void> {
await Promise.all(cleanup.map((dir) => removeTrackedTmpDir(dir)));
}
function removeTrackedTmpDirSync(dir: string | undefined): void {
if (!dir) return;
try {
rmSync(dir, { recursive: true, force: true });
} catch {
// best-effort fallback during teardown
} finally {
createdTmpDirs.delete(dir);
}
}
function cleanupTmpDirsSync(): void {
const cleanup = Array.from(createdTmpDirs);
for (const dir of cleanup) {
@@ -368,7 +379,7 @@ describe("Database", () => {
expect(freshDb.integrityCheckLastRunAt).toBeTruthy();
} finally {
freshDb.close();
rmSync(freshDir, { recursive: true, force: true });
removeTrackedTmpDirSync(freshDir);
integritySpy.mockRestore();
vi.useRealTimers();
}
@@ -391,7 +402,7 @@ describe("Database", () => {
expect(integritySpy).toHaveBeenCalledTimes(1);
} finally {
freshDb.close();
rmSync(freshDir, { recursive: true, force: true });
removeTrackedTmpDirSync(freshDir);
integritySpy.mockRestore();
vi.useRealTimers();
}
@@ -424,7 +435,7 @@ describe("Database", () => {
} finally {
dbA.close();
dbB.close();
rmSync(freshDir, { recursive: true, force: true });
removeTrackedTmpDirSync(freshDir);
integritySpy.mockRestore();
vi.useRealTimers();
}
@@ -457,7 +468,7 @@ describe("Database", () => {
} finally {
dbA.close();
dbB.close();
rmSync(freshDir, { recursive: true, force: true });
removeTrackedTmpDirSync(freshDir);
integritySpy.mockRestore();
vi.useRealTimers();
}
@@ -2570,40 +2581,62 @@ describe("migration v67 drops orphan project auth tables", () => {
const temp = makeTmpDir();
const fusion = join(temp, ".fusion");
const localDb = new Database(fusion);
localDb.init();
// Simulate a user who ran the old migration 63 (schema version 6366) and
// therefore has the orphan project_auth_* tables sitting in their DB. We
// recreate them by hand and roll the schemaVersion back so the new
// migration runs on the next init.
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_users (id TEXT PRIMARY KEY)`);
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_memberships (id TEXT PRIMARY KEY, userId TEXT, FOREIGN KEY (userId) REFERENCES project_auth_users(id) ON DELETE CASCADE)`);
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_providers (id TEXT PRIMARY KEY, userId TEXT, FOREIGN KEY (userId) REFERENCES project_auth_users(id) ON DELETE CASCADE)`);
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_sessions (id TEXT PRIMARY KEY, userId TEXT, membershipId TEXT, FOREIGN KEY (userId) REFERENCES project_auth_users(id) ON DELETE CASCADE, FOREIGN KEY (membershipId) REFERENCES project_auth_memberships(id) ON DELETE CASCADE)`);
localDb.prepare("UPDATE __meta SET value = '66' WHERE key = 'schemaVersion'").run();
localDb.close();
let migrated: Database | undefined;
const migrated = new Database(fusion);
migrated.init();
expect(migrated.getSchemaVersion()).toBe(72);
const tables = migrated
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'project_auth_%'")
.all() as Array<{ name: string }>;
expect(tables).toEqual([]);
migrated.close();
rmSync(temp, { recursive: true, force: true });
try {
localDb.init();
// Simulate a user who ran the old migration 63 (schema version 6366) and
// therefore has the orphan project_auth_* tables sitting in their DB. We
// recreate them by hand and roll the schemaVersion back so the new
// migration runs on the next init.
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_users (id TEXT PRIMARY KEY)`);
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_memberships (id TEXT PRIMARY KEY, userId TEXT, FOREIGN KEY (userId) REFERENCES project_auth_users(id) ON DELETE CASCADE)`);
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_providers (id TEXT PRIMARY KEY, userId TEXT, FOREIGN KEY (userId) REFERENCES project_auth_users(id) ON DELETE CASCADE)`);
localDb.exec(`CREATE TABLE IF NOT EXISTS project_auth_sessions (id TEXT PRIMARY KEY, userId TEXT, membershipId TEXT, FOREIGN KEY (userId) REFERENCES project_auth_users(id) ON DELETE CASCADE, FOREIGN KEY (membershipId) REFERENCES project_auth_memberships(id) ON DELETE CASCADE)`);
localDb.prepare("UPDATE __meta SET value = '66' WHERE key = 'schemaVersion'").run();
localDb.close();
migrated = new Database(fusion);
migrated.init();
expect(migrated.getSchemaVersion()).toBe(72);
const tables = migrated
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'project_auth_%'")
.all() as Array<{ name: string }>;
expect(tables).toEqual([]);
} finally {
try {
migrated?.close();
} catch {
// already closed
}
try {
localDb.close();
} catch {
// already closed
}
removeTrackedTmpDirSync(temp);
}
});
it("is a no-op on fresh DBs that never had the auth tables", () => {
const temp = makeTmpDir();
const fusion = join(temp, ".fusion");
const fresh = new Database(fusion);
fresh.init();
expect(fresh.getSchemaVersion()).toBe(72);
const tables = fresh
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'project_auth_%'")
.all() as Array<{ name: string }>;
expect(tables).toEqual([]);
fresh.close();
rmSync(temp, { recursive: true, force: true });
try {
fresh.init();
expect(fresh.getSchemaVersion()).toBe(72);
const tables = fresh
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'project_auth_%'")
.all() as Array<{ name: string }>;
expect(tables).toEqual([]);
} finally {
try {
fresh.close();
} catch {
// already closed
}
removeTrackedTmpDirSync(temp);
}
});
});