diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 770ce9705..5fe27b90b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -359,10 +359,12 @@ jobs: - name: Test run: vp run --filter @t3tools/web test --shard ${{ matrix.shard }}/${{ strategy.job-total }} - # apps/server sets `fileParallelism: false`, so its 239 files run strictly - # one at a time. Sharding spreads them over separate runners instead of - # separate workers, so no two server test files ever share a machine and the - # isolation that flag buys is preserved exactly. + # apps/server sets `fileParallelism: false`, so its files run strictly one at + # a time. Sharding spreads them over separate runners instead of separate + # workers, so no two server test files ever share a machine and the isolation + # that flag buys is preserved exactly. Each shard deals every 4th file of the + # sorted paths (scripts/lib/spread-shard.ts): about 340 s of tests each, where + # three hash slices ran 390–525 s and the heaviest neared the 10-minute limit. test_server: name: Test Server ${{ matrix.shard }} runs-on: ubuntu-latest @@ -370,7 +372,7 @@ jobs: strategy: fail-fast: false matrix: - shard: [1, 2, 3] + shard: [1, 2, 3, 4] steps: - name: Checkout uses: actions/checkout@v6 diff --git a/apps/hq/test/harness/testPostgres.test.ts b/apps/hq/test/harness/testPostgres.test.ts index 6a3200b32..427d1e17b 100644 --- a/apps/hq/test/harness/testPostgres.test.ts +++ b/apps/hq/test/harness/testPostgres.test.ts @@ -94,6 +94,29 @@ it.effect( }).pipe(Effect.scoped), ); +// A pool's close returns before PostgreSQL's backend exits; the template must not race that exit. +it.live("freezes a database whose closed connection's backend is still exiting", () => + Effect.gen(function* () { + const owner = yield* lease; + const template = yield* Effect.promise(owner.createDatabase); + yield* Effect.scoped( + Effect.gen(function* () { + const source = yield* connection(template); + yield* source.query("CREATE TABLE baseline (value text)"); + // The backend notices its client left only once this statement ends. + yield* Effect.forkDetach(source.query("SELECT pg_sleep(0.5)")); + yield* Effect.sleep("50 millis"); + }), + ); + yield* Effect.promise(() => owner.freezeDatabase(template)); + const clone = yield* Effect.promise(() => owner.cloneDatabase(template)); + const copied = yield* connection(clone); + assert.deepEqual((yield* copied.query("SELECT to_regclass('baseline')::text AS t")).rows, [ + { t: "baseline" }, + ]); + }).pipe(Effect.scoped), +); + it.effect.each(["SIGKILL", "exit"] as const)( "reclaims a worker database after %s before the next owner's work", (mode) => diff --git a/apps/server/vite.config.ts b/apps/server/vite.config.ts index 755b917a2..2548c3c79 100644 --- a/apps/server/vite.config.ts +++ b/apps/server/vite.config.ts @@ -5,6 +5,7 @@ import { defineConfig, mergeConfig } from "vite-plus"; import baseConfig from "../../vite.config.ts"; import { loadRepoEnv } from "../../scripts/lib/public-config.ts"; import packageJson from "./package.json" with { type: "json" }; +import { SpreadShardSequencer } from "../../scripts/lib/spread-shard.ts"; // The bundle used to inline only workspace packages, leaving every third-party // runtime dep external. External deps must exist on the real filesystem (the WSL @@ -86,6 +87,8 @@ export default mergeConfig( // The server suite exercises sqlite, git, temp worktrees, and orchestration // runtimes heavily. Running files in parallel introduces load-sensitive flakes. fileParallelism: false, + // CI's shards each take every n-th file, so its slow crew and engine suites never share one. + sequence: { sequencer: SpreadShardSequencer }, // Appended to the root setup, which mergeConfig concatenates. setupFiles: ["./src/testUtils/gitConfig.setup.ts"], // Server integration tests exercise sqlite, git, and orchestration together. diff --git a/apps/web/src/components/ui/tooltip.test.tsx b/apps/web/src/components/ui/tooltip.test.tsx index b9e1a92a8..28ba5e39a 100644 --- a/apps/web/src/components/ui/tooltip.test.tsx +++ b/apps/web/src/components/ui/tooltip.test.tsx @@ -137,3 +137,53 @@ describe("tooltip scroll dismissal", () => { } }); }); + +describe("tooltip trigger element", () => { + // A chat file link renders a menu button until its open action is known, then a link. + it.each([{ area: "inside the conversation" }, { area: "outside the conversation" }])( + "$area, a trigger that becomes a link still opens its tooltip on hover", + async ({ area }) => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + vi.useFakeTimers(); + const container = document.createElement("div"); + document.body.append(container); + const root = createRoot(container); + const tooltip = (link: boolean) => ( + + :