From b0b8b8478857ed5d49a2fe2be19aea95ef6b17fa Mon Sep 17 00:00:00 2001 From: Matias Piipari Date: Mon, 9 Mar 2026 13:30:38 +0200 Subject: [PATCH 1/2] Just the docker images (PR 1308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Docker test infrastructure for cloudflare workers Add Docker-based test infrastructure for running the cloudflare worker test suite in containers. Includes multi-shard test execution, Docker Compose services, and image build pipeline. Co-Authored-By: Claude Opus 4.6 * Patch vitest-pool-workers to handle SQLite WAL sidecar files @cloudflare/vitest-pool-workers' isolated storage mechanism (pushStackedStorage / popStackedStorage) asserts that every file in a Durable Object namespace directory ends with ".sqlite". However, SQLite in WAL journal mode creates two transient sidecar files alongside each database: ".sqlite-shm" (shared memory index) and ".sqlite-wal" (write-ahead log). When running in singleWorker mode, there is a race condition: abortAllDurableObjects() evicts all DOs but workerd may not have fully checkpointed the WAL before the Node.js side runs readdir(). If a .sqlite-shm or .sqlite-wal file is still on disk, the assertion fires and the test fails with "Isolated storage failed". The fix handles each function differently: - pushStackedStorage (snapshot before test): skip WAL files entirely. They are transient and SQLite regenerates them when reopening the DB, so they don't need to be part of the snapshot. - popStackedStorage (restore after test): delete WAL files but skip the .sqlite assertion. This is critical - if we merely skipped them, the stale .sqlite-wal would be replayed by SQLite when the next test opens the restored .sqlite file, leaking the previous test's writes and breaking test isolation. Co-Authored-By: Claude Opus 4.6 * Migrate comment search embeddings from Cohere to Voyage AI The queue consumer (comments-search.ts) that indexes comments already used Voyage AI, but the search endpoint (api/lib/comments.ts) that embeds user queries still imported from ~/cohere. This caused test failures because the test mocks only intercept Voyage AI and Turbopuffer origins - Cohere API calls were blocked. More importantly, this completes the Cohere→Voyage migration: - Import embed/model/dimension from ~/voyage instead of ~/cohere - Change inputType from "search_query" to "query" (Voyage API) - Remove embeddingTypes param (Voyage doesn't use it) - Use env.VOYAGE_API_KEY instead of env.COHERE_API_KEY Co-Authored-By: Claude Opus 4.6 * Docker test infrastructure improvements Improvements to the Docker-based test runner for cloudflare workers: Dockerfile.base: - Bump Node.js from 20.18.1 to 20.19.1 (required by Prisma 7.x) - Include prisma.config.ts in the image (required by prisma migrate deploy in Prisma 7, which no longer supports --url flag or datasource.url in the schema) m2m-api entrypoint: - Remove duplicate @liveblocks/core from nested node_modules before starting the server to prevent "Multiple copies of Liveblocks" runtime error (root has 3.14.0 stable, shared/common has 3.14.0-rc1) docker-compose.dev.yml: - Add AWS_LB_M2M_API_URL_US_EAST_1 and EU_CENTRAL_1 env vars vitest.config.base.ts: - Add __DOCKER__ define (set from DOCKER=1 env var) for conditional test skipping in Docker - Override M2M API URL bindings from env vars in Docker - Configure outbound network access for workerd (0.0.0.0/0) Test changes: - Skip editThreadMetadata "metadata keys exceed limit" test in Docker (unreliably slow with 50 metadata keys in singleWorker mode) - Use env-based M2M API URL in search and queue tests - Increase waitUntil timeouts to 10s for Docker - Use dynamic M2M URL in svix-utils run-all-shards.sh / run-shard.sh: - Improved shard orchestration and result parsing - Resolve m2m-api container IP for workerd DNS (workerd can't resolve Docker service names) Co-Authored-By: Claude Opus 4.6 * Fix SQLite WAL cleanup in restore-sqlite-backup tests The restore-sqlite-backup tests were failing because the restore endpoint forces a DO restart via ctx.waitUntil(blockConcurrencyWhile(throw ...)), which leaves .sqlite-shm WAL files behind. The vitest-pool-workers isolated storage checker then fails with "Expected .sqlite, got .sqlite-shm". - Add state.storage.sync() to runInSQLiveRoomDO (parity with runInLiveRoomDO) as a workaround for cloudflare/workers-sdk#11031 - Add syncSQLiveRoomDO() helper for tests that access SQLite DOs via HTTP APIs - Call syncSQLiveRoomDO() at the end of both restore-sqlite-backup tests to checkpoint WAL before isolated storage cleanup Co-Authored-By: Claude Opus 4.6 * Consolidate test/backend scripts into extensionless Node runners Replace run-tests.sh and run-all-shards.sh with a unified `run-tests` Node script that handles both simple test runs and sharded orchestration. Add `run-backend` for starting the dev server with dependencies. Co-Authored-By: Claude Opus 4.6 * Document Docker image tagging strategy in CI workflow Expand the determine-tags job comment to explain the exact tag derivation rules (PR, branch, version, SHA) and which images receive which tags. Co-Authored-By: Claude Opus 4.6 * Extract inline CI test env into .env.test.ci file Move the inline .env.test creation from cloudflare-docker.yml into a committed .env.test.ci file. The CI step now just copies it over .env.test before starting services. Co-Authored-By: Claude Opus 4.6 * Rename INTERNAL_NETWORK env var to AIR_GAPPED Co-Authored-By: Claude Opus 4.6 * Moves the resources around * Address PR review feedback - Drop bundle size analysis from build.js - Make Docker CI workflow manual dispatch only (remove PR/push triggers) - Remove test result publishing (merge-reports job) - Revert non-Docker-related changes in setupServers.ts (startPostgres, setupPostgres, kill logic) - Remove redundant comments from vitest.config.base.ts, revert timeout changes - Remove @cloudflare/workers-types from root devDependencies - Add lint compose service, simplify docker/lint script to use it Co-Authored-By: Claude Opus 4.6 * Rebase onto main and update dependencies - Regenerate package-lock.json after rebase - Remove @liveblocks/core from root devDependencies - Update @cloudflare/workers-types patch for 4.20260219.0 * Copy Dockerfiles into temp deploy folders for CDK turbo prune doesn't include docker/ since it's outside the workspace. Copy the production Dockerfiles into the pruned output so CDK can find them at the new paths. * Address PR review feedback: revert unrelated changes, simplify workflow - Revert miniflare-utils.ts, comments.ts, aws-m2m-api-utils.ts, and restore-sqlite-backup.test.ts to main (changes addressed elsewhere) - Revert test-cloudflare.yml to main (keep existing CI working) - Remove push_images workflow parameter (always push) - Add patches/* to base image hash in build-images script Co-Authored-By: Claude Opus 4.6 * Update package-lock.json after rebase onto main Regenerate lockfile to reflect zenrouter removal and decoders upgrade. Co-Authored-By: Claude Opus 4.6 * Restore apps/cloudflare/test/docker-compose.yml for existing CI The test-cloudflare.yml workflow references this file at the original path. Restore it so the existing PR test workflow continues to work. Co-Authored-By: Claude Opus 4.6 * Fix HTTPS in tests: always enable TLS, remove stray externals - Always include tlsOptions in outboundService network config, not just in Docker mode. Without tlsOptions, workerd can't make HTTPS requests, breaking Turbopuffer and Voyage AI calls in tests. - Remove "crypto" and "canvas" from esbuild externals (incorrectly added during rebase conflict resolution). Co-Authored-By: Claude Opus 4.6 * Fix Cursor bot findings: globalSetup, singleWorker, workerd path, prisma - Restore globalSetup falsy check for nosetup vitest config - Respect options.singleWorker parameter alongside env var - Remove hardcoded arm64 workerd path from docker-compose (let Dockerfile symlinks handle architecture detection) - Revert prisma connection_limit change (not Docker-related) Co-Authored-By: Claude Opus 4.6 * Set MINIFLARE_WORKERD_PATH only on arm64 in test-entrypoint On arm64, miniflare can't resolve the workerd binary without an explicit path. Set it conditionally in the test entrypoint rather than hardcoding arm64 in docker-compose.dev.yml. Co-Authored-By: Claude Opus 4.6 * Fix Node.js version in m2m-api Dockerfile to 20.19.0 Match the version used in the rest of the repo (package.json engine constraint, base Dockerfile). Co-Authored-By: Claude Opus 4.6 * Restore rm -rf dist/ in mongo build, keep build:docker The build script should clean dist/ before compiling (matching other shared packages). The build:docker script skips cleanup since the dist dir is fresh in Docker. Co-Authored-By: Claude Opus 4.6 * Add clean script to shared/mongo matching other shared packages Co-Authored-By: Claude Opus 4.6 * Revert non-Docker changes in build.js Keep only Docker-related changes (GIT_SHA env var fallback, conditional minify). Revert cosmetic and unrelated changes (unused result variable, formatting, error logging). Co-Authored-By: Claude Opus 4.6 * Restore original build script for shared/postgres-prisma The build script was changed from rsync --ignore-existing to cp -r, which overwrites tsc output with raw generated files. This likely caused the getRoomThreads OR operator test to fail (500 instead of 422). Only build:docker and clean should be added, not modifying the existing build script. Co-Authored-By: Claude Opus 4.6 * Fix docker inspect IP resolution for multi-network containers Use newline separator and head -1 to extract only the first IP address, preventing concatenation if the container is attached to multiple networks. Co-Authored-By: Claude Opus 4.6 * Align base Dockerfile Node version with production (20.19.0) Match the node:20.19.0 used in production aws-m2m-api Dockerfile and the engine constraint in apps/cloudflare/package.json. Co-Authored-By: Claude Opus 4.6 * Fix minification default to preserve production behavior Default to minify when NODE_ENV is unset (production deploys don't set it). Only disable minification when NODE_ENV is explicitly set to something other than "production" (e.g. test, development). Co-Authored-By: Claude Opus 4.6 * Enable globstar for recursive glob in build-images Without shopt -s globstar, ** only matches one directory level in bash, so apps/aws-m2m-api/src/**/*.ts would miss nested files. Co-Authored-By: Claude Opus 4.6 * Remove unused PRISMA_QUERY_ENGINE_LIBRARY and run-all-shards - Remove PRISMA_QUERY_ENGINE_LIBRARY env var from docker-compose.dev.yml: Prisma 7.x with engineType="client" doesn't use the native query engine - Remove run-all-shards script: duplicates run-tests shard orchestration - Clean up references to run-all-shards in run-shard, run-tests, README Co-Authored-By: Claude Opus 4.6 * Regenerate package-lock.json from main to fix zenrouter hoisting The previous lockfile had @liveblocks/zenrouter duplicated as 4 separate copies (one per consuming package) instead of hoisted to root. This caused instanceof ValidationError checks to fail across package boundaries, resulting in 500 instead of 422 for invalid thread queries. Regenerated by taking main's lockfile and running npm install to reconcile with branch package.json changes (scripts only, no dep changes). Co-Authored-By: Claude Opus 4.6 * Fix entrypoint debug logging, Docker tag whitespace, and lockfile - Remove set -x from m2m-api entrypoint to avoid leaking env vars in logs - Fix leading whitespace in Docker image tags in cloudflare-docker.yml - Regenerate package-lock.json from main to fix zenrouter hoisting (4 separate copies → 1 hoisted copy, fixing instanceof checks) Co-Authored-By: Claude Opus 4.6 * Make Docker-specific vitest config conditional to fix workerd crash Gate outboundService.network and miniflare log on DOCKER=1, restore hardcoded localhost:3003 bindings as defaults, remove minWorkers: 1, and restore env: {} from main. * Fix lint errors: simplify Docker detection, fix import sort order * Remove unnecessary IP resolution from run-shard, fix shard average Docker DNS resolves service names within the compose network, so the manual IP resolution via docker inspect was unnecessary. The M2M API URLs are already set in docker-compose.dev.yml using the service name. Also fix average-per-shard calculation to divide by actual results count instead of totalShards when sequential mode stops early. * Remove misplaced syntax directives, fix cleanup path in run-shard-ci The # syntax=docker/dockerfile:1 directive was on line 3 in all Dockerfiles, preceded by comments, so Docker ignored it. Since modern Docker defaults to BuildKit, just remove the no-op directives. Fix cleanup function in run-shard-ci to use absolute path for the compose file, since the working directory changes later in the script. * Add build:docker task to postgres-prisma turbo config The build:docker script needs prisma:generate to have run first (it copies src/generated to dist), but only the build task had this dependency declared. * Fix build:docker dependency to use ^build:docker not ^build Aligns with root turbo.json which uses ^build:docker, ensuring upstream deps run their Docker-specific build (without rm -rf dist). * Fix workflow_dispatch branch tag to use ref instead of event name workflow_dispatch sets github.ref to refs/heads/ but event_name is 'workflow_dispatch' not 'push', so the branch tag always fell through to 'main'. Check the ref pattern instead. * Remove shared/zenrouter references from Docker files zenrouter was moved to its own repo and is now consumed as an npm dependency (@liveblocks/zenrouter). The Dockerfile and compose files were still referencing shared/zenrouter which no longer exists. --------- Co-authored-by: Claude Opus 4.6 Original commit: aabb73a94311f409e70ccb4265910b6cba6a828f --- packages/liveblocks-server/package.json | 2 ++ tools/liveblocks-cli/Dockerfile | 38 ---------------------- tools/liveblocks-cli/Dockerfile.dev-server | 4 --- tools/liveblocks-cli/package.json | 1 + 4 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 tools/liveblocks-cli/Dockerfile delete mode 100644 tools/liveblocks-cli/Dockerfile.dev-server diff --git a/packages/liveblocks-server/package.json b/packages/liveblocks-server/package.json index a1142eb948..4d5aee6a9a 100644 --- a/packages/liveblocks-server/package.json +++ b/packages/liveblocks-server/package.json @@ -34,6 +34,8 @@ "scripts": { "dev": "tsup --watch", "build": "tsup", + "build:docker": "tsup", + "clean": "rm -rf dist/", "format": "(eslint --color --fix src/ test/ || true) && prettier --write src/ test/", "lint": "eslint --color src/ test/", "lint:package": "publint --strict && attw --pack", diff --git a/tools/liveblocks-cli/Dockerfile b/tools/liveblocks-cli/Dockerfile deleted file mode 100644 index bdbf90c175..0000000000 --- a/tools/liveblocks-cli/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -# ------------------------------------------------------------------ -# Liveblocks Dev Server — Docker Image -# -# docker build --build-arg CLI_VERSION=1.0.3 tools/liveblocks-cli/ -# ------------------------------------------------------------------ - -FROM oven/bun:1-alpine@sha256:9028ee7a60a04777190f0c3129ce49c73384d3fc918f3e5c75f5af188e431981 - -ARG CLI_VERSION=latest -ARG VERSION=dev -LABEL org.opencontainers.image.source="https://github.com/liveblocks/liveblocks" -LABEL org.opencontainers.image.version="${VERSION}" -LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later" -LABEL org.opencontainers.image.title="Liveblocks Dev Server" -LABEL org.opencontainers.image.description="Local development server for Liveblocks" - -RUN addgroup -g 1001 liveblocks && \ - adduser -u 1001 -G liveblocks -h /home/liveblocks -D liveblocks - -WORKDIR /app - -RUN bun install liveblocks@${CLI_VERSION} - -COPY scripts/docker-healthcheck.ts ./scripts/ - -RUN mkdir -p /app/.liveblocks && chown liveblocks:liveblocks /app/.liveblocks -VOLUME /app/.liveblocks - -USER liveblocks - -ENV LIVEBLOCKS_DEVSERVER_HOST=0.0.0.0 -ENV LIVEBLOCKS_DEVSERVER_PORT=1153 -EXPOSE 1153 - -HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \ - CMD ["bun", "run", "scripts/docker-healthcheck.ts"] - -ENTRYPOINT ["bun", "run", "node_modules/liveblocks/dist/index.js"] diff --git a/tools/liveblocks-cli/Dockerfile.dev-server b/tools/liveblocks-cli/Dockerfile.dev-server deleted file mode 100644 index 3f60d20733..0000000000 --- a/tools/liveblocks-cli/Dockerfile.dev-server +++ /dev/null @@ -1,4 +0,0 @@ -# Dev server image — extends liveblocks/cli with CMD ["dev", "--ci"] -ARG CLI_TAG -FROM ghcr.io/liveblocks/cli:${CLI_TAG} -CMD ["dev", "--ci"] diff --git a/tools/liveblocks-cli/package.json b/tools/liveblocks-cli/package.json index 8455f3b7f2..df1fc9c3c7 100644 --- a/tools/liveblocks-cli/package.json +++ b/tools/liveblocks-cli/package.json @@ -13,6 +13,7 @@ ], "scripts": { "build": "tsup", + "clean": "rm -rf dist/", "dev": "bun ./dist/index.js dev -v", "add-license-headers": "eslint --fix 'src/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}'", "lint": "eslint 'src/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}'", From 0d4d4c4d148a184f03447a7f6f7c35ef35be85e1 Mon Sep 17 00:00:00 2001 From: Chris Nicholas Date: Tue, 10 Mar 2026 12:42:51 +0000 Subject: [PATCH 2/2] Improve comments canvas (#3166) --- .../get-started/nextjs-comments-canvas.mdx | 36 +++++++++---------- .../src/components/CommentsCanvas.tsx | 7 ++-- .../src/components/DraggableThread.tsx | 18 +++++----- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/docs/pages/get-started/nextjs-comments-canvas.mdx b/docs/pages/get-started/nextjs-comments-canvas.mdx index 76ab518299..918430bd8e 100644 --- a/docs/pages/get-started/nextjs-comments-canvas.mdx +++ b/docs/pages/get-started/nextjs-comments-canvas.mdx @@ -163,7 +163,7 @@ components from be toggled open by clicking on the pin. ```tsx file="app/DraggableThread.tsx" - import { useMemo } from "react"; + import { useMemo, useState } from "react"; import { useEditThreadMetadata } from "@liveblocks/react/suspense"; import { FloatingThread, CommentPin } from "@liveblocks/react-ui"; import { ThreadData } from "@liveblocks/client"; @@ -176,6 +176,7 @@ components from const defaultOpen = useMemo(() => { return Number(new Date()) - Number(new Date(thread.createdAt)) <= 100; }, [thread]); + const [isOpen, setIsOpen] = useState(defaultOpen); // Enable drag const { isDragging, attributes, listeners, setNodeRef, transform } = @@ -188,31 +189,27 @@ components from const x = transform ? transform.x + thread.metadata.x : thread.metadata.x; const y = transform ? transform.y + thread.metadata.y : thread.metadata.y; - // Used to set z-index higher than other threads when dragging - const editThreadMetadata = useEditThreadMetadata(); + // Used to set z-index higher than other threads when open or dragging const maxZIndex = useMaxZIndex(); + const currentZIndex = isOpen || isDragging ? maxZIndex + 1 : thread.metadata?.zIndex || 0; return (
- editThreadMetadata({ - threadId: thread.id, - metadata: { zIndex: maxZIndex + 1 }, - }) - } style={{ position: "absolute", top: 0, left: 0, transform: `translate3d(${x}px, ${y}px, 0)`, - zIndex: thread.metadata?.zIndex || 0, + zIndex: currentZIndex, }} > { const thread = (active.data as DataRef<{ thread: ThreadData }>).current ?.thread; - if (!thread) { return; } - editThreadMetadata({ threadId: thread.id, metadata: { x: thread.metadata.x + delta.x, y: thread.metadata.y + delta.y, + zIndex: maxZIndex + 1, }, }); }, - [editThreadMetadata], + [editThreadMetadata, maxZIndex] ); return ( @@ -496,12 +494,12 @@ components from ```tsx file="app/page.tsx" highlight="6-8" import { Room } from "./Room"; - import { CollaborativeCanvas } from "./CollaborativeCanvas"; + import { CommentsCanvas } from "./CommentsCanvas"; export default function Page() { return ( - + ); } diff --git a/examples/nextjs-comments-canvas/src/components/CommentsCanvas.tsx b/examples/nextjs-comments-canvas/src/components/CommentsCanvas.tsx index 95e880f003..f9c6db6a9a 100644 --- a/examples/nextjs-comments-canvas/src/components/CommentsCanvas.tsx +++ b/examples/nextjs-comments-canvas/src/components/CommentsCanvas.tsx @@ -12,10 +12,12 @@ import { import { useCallback } from "react"; import { PlaceThreadButton } from "./PlaceThreadButton"; import { DraggableThread } from "./DraggableThread"; +import { useMaxZIndex } from "../hooks"; export function CommentsCanvas() { const { threads } = useThreads(); const editThreadMetadata = useEditThreadMetadata(); + const maxZIndex = useMaxZIndex(); // Allow click event on avatar if thread moved less than 3px const sensors = useSensors( @@ -31,7 +33,7 @@ export function CommentsCanvas() { }) ); - // On drag end, update thread metadata with new coords + // On drag end, update thread metadata with new coords and highest z-index const handleDragEnd = useCallback( ({ active, delta }: DragEndEvent) => { const thread = (active.data as DataRef<{ thread: ThreadData }>).current @@ -44,10 +46,11 @@ export function CommentsCanvas() { metadata: { x: thread.metadata.x + delta.x, y: thread.metadata.y + delta.y, + zIndex: maxZIndex + 1, }, }); }, - [editThreadMetadata] + [editThreadMetadata, maxZIndex] ); return ( diff --git a/examples/nextjs-comments-canvas/src/components/DraggableThread.tsx b/examples/nextjs-comments-canvas/src/components/DraggableThread.tsx index ba05b93868..94d7e41e96 100644 --- a/examples/nextjs-comments-canvas/src/components/DraggableThread.tsx +++ b/examples/nextjs-comments-canvas/src/components/DraggableThread.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { useEditThreadMetadata } from "@liveblocks/react/suspense"; import { FloatingThread, CommentPin } from "@liveblocks/react-ui"; import { ThreadData } from "@liveblocks/client"; @@ -11,6 +11,7 @@ export function DraggableThread({ thread }: { thread: ThreadData }) { const defaultOpen = useMemo(() => { return Number(new Date()) - Number(new Date(thread.createdAt)) <= 100; }, [thread]); + const [isOpen, setIsOpen] = useState(defaultOpen); // Enable drag const { isDragging, attributes, listeners, setNodeRef, transform } = @@ -23,31 +24,28 @@ export function DraggableThread({ thread }: { thread: ThreadData }) { const x = transform ? transform.x + thread.metadata.x : thread.metadata.x; const y = transform ? transform.y + thread.metadata.y : thread.metadata.y; - // Used to set z-index higher than other threads when dragging - const editThreadMetadata = useEditThreadMetadata(); + // Used to set z-index higher than other threads when open or dragging const maxZIndex = useMaxZIndex(); + const currentZIndex = + isOpen || isDragging ? maxZIndex + 1 : thread.metadata?.zIndex || 0; return (
- editThreadMetadata({ - threadId: thread.id, - metadata: { zIndex: maxZIndex + 1 }, - }) - } style={{ position: "absolute", top: 0, left: 0, transform: `translate3d(${x}px, ${y}px, 0)`, - zIndex: thread.metadata?.zIndex || 0, + zIndex: currentZIndex, }} >