[pull] main from tldraw:main#580
Merged
Merged
Conversation
…numbers (#9076) In order to stop two concurrent PRs from each picking the same zero-cache migration number and silently colliding on `main`, this PR adds a check that validates the migration filenames in `apps/dotcom/zero-cache/migrations/`. Migration numbers are chosen by hand and the filenames differ, so git merges two same-numbered migrations cleanly and `main` ends up with a duplicate. The check throws on duplicate or non-contiguous numbers and runs as a vitest test under `yarn test-ci`. Because CI checks out the post-merge tree (both `pull_request` and the `merge_group` queue), the collision is caught when the second PR is merged rather than after it lands. Closes #9075. The existing duplicate `027` on `main` is grandfathered in via `GRANDFATHERED_DUPLICATE_NUMBERS` so the check passes today; new migrations must use a unique, contiguous number. ### Change type - [x] `other` (CI/tooling, test) ### Test plan 1. From `apps/dotcom/zero-cache`, run `yarn test run` — passes against the real migrations directory. 2. Create a colliding file: `cp migrations/031_group_id_indexes.sql migrations/031_dupe.sql`, rerun — fails with "Duplicate migration number 031". Remove it. 3. Rename a file to create a gap: `mv migrations/031_*.sql migrations/033_*.sql`, rerun — fails with "missing 032". Revert. - [x] Unit tests - [ ] End to end tests ### Code changes | Section | LOC change | | -------------- | ---------- | | Tests | +43 / -0 | | Apps | +64 / -0 | | Config/tooling | +12 / -0 |
…#9064) In order to keep the sidebar anchored to the group you're working in, this PR changes file deletion to stay within the current group. Previously, deleting a file from a group navigated to the top of "My files", which scrolled the sidebar to the top and opened an unrelated personal file. Now we navigate to the top of the deleted file's group, falling back to "My files" only when the group has no remaining files. Closes #8974. ### How it works The sidebar has no direct "scroll to group" behavior — scrolling is a side effect of which file is active. Each file link calls `scrollIntoView({ block: 'nearest' })` when it becomes the active file (the one in the URL). Previously, deleting a group file navigated to the top of "My files", so that link became active and scrolled the sidebar up and out of the group. Navigating to the group's top remaining file instead makes a file *inside the group* active, so the sidebar reveals it in place and stays anchored to the group. A couple of details for reviewers unfamiliar with this code: - `block: 'nearest'` means "top of current group" is really "reveal the group's top file" with minimal scrolling — it matches the rest of the sidebar's active-file navigation rather than pinning the group header to the top of the viewport. - `ensureFileVisibleInSidebar` is load-bearing: it expands the group so the active link exists in the DOM before we navigate. Without it the scroll would no-op when the group is collapsed (for example, deleting from the editor's file menu while the sidebar group is closed). ### Change type - [x] `bugfix` ### Test plan 1. Enable groups and create a group with two or more files. 2. Open one of the files in the group. 3. Delete a file from the group. 4. The sidebar should stay scrolled to the current group and open the top remaining file in that group, rather than jumping up to "My files". 5. Delete the last file in the group — navigation should fall back to "My files" (creating a new file if there are none). - [ ] Unit tests - [x] End to end tests ### Release notes - Fix the sidebar jumping to "My files" when deleting a file from a group; it now stays in the current group. ### Code changes | Section | LOC change | | ------- | ---------- | | Tests | +45 / -0 | | Apps | +19 / -7 |
…pabase (#9033) In order to stop Zero from doing a full reset of its server replica and every connected client on every Supabase schema change, this PR adopts Zero's manual schema-change hook. tldraw.com runs Zero with upstream Postgres on Supabase. Zero detects schema changes via Postgres event triggers, but Supabase doesn't fire them for `ALTER PUBLICATION`. An undetected schema change makes Zero do a full reset of its server replica and every connected client — increasingly expensive as the DB grows. - `migrate.ts` calls `zero_0.update_schemas()` once after all migrations apply, in the same transaction, so schema changes update the replica in place. Guarded so it's a no-op before Zero has booted (fresh DB). Uses `to_regprocedure` to resolve the no-arg overload (`zero_0` has two `update_schemas` overloads; `to_regproc` returns NULL for the ambiguous name and would silently skip the call). - A drift check fails the migration loudly if the shard schema is ever not `zero_0`, so the hardcoded name can't silently stop matching. - `032_enable_ddl_detection.sql` sets `ddlDetection = true` (guarded) as a safety-net; it's already `true` on staging. Verified on staging: shard schema is `zero_0`, the no-arg overload exists, and `ddlDetection` is already `true`. This migration is numbered `032` (the next free number on `main`). #9029 also currently uses `032` for `032_rename_group_admin_role_to_member.sql`; whichever PR lands second will need to bump its migration number to avoid the collision. Closes #9031 ### Change type - [x] `other` ### Test plan 1. Deploy to staging and apply a migration that contains DDL. 2. Confirm Zero updates its replica in place — no full reset, clients don't re-download all state (check Zero logs). 3. Confirm `SELECT "ddlDetection" FROM zero_0."shardConfig";` is `true` and `update_schemas()` ran without error.
Splits the vitest 4 upgrade out of the Dependabot group bump (#8998), where it was bundled with low-risk patches. vitest 3 → 4 is a major bump that needs real test changes, so it gets its own PR. ### What changed - Bump `vitest`, `@vitest/ui`, `@vitest/coverage-v8` from `^3.2.4` to `^4.1.7`. (4.1.8 exists but is blocked by the repo's 7-day `npmMinimalAgeGate`; `^4.1.7` is the newest version that passes the gate, and caret will pick up 4.1.8 once it ages in.) ### vitest 4 breakages fixed - **Constructor mocks** — vitest 4 won't let an arrow-function or `mockReturnValue` mock be called with `new`. Converted the `Range`, `FontFace`, and `ClipboardItem` global mocks to function-based mocks (`TextManager`, `FontManager`, `clipboardCallbacks` tests). - **`vi.restoreAllMocks()`** now clears a factory mock's implementation, so `devFreeze`'s dev-mode block explicitly mocks `isDev → true`. - **Web storage** — vitest 4's jsdom no longer exposes `localStorage`/`sessionStorage`. Added an in-memory polyfill to the shared vitest setup (`internal/config/vitest/setup.ts`) so storage-touching code works (fixes `LocalIndexedDb`). - **JSX transform** — vite 8 transforms with oxc and ignores the deprecated `esbuild.jsx` option. The dotcom client keeps `jsx: preserve` in its tsconfig for the app build, so its vitest config now uses `@vitejs/plugin-react`. ### Verification All package and app test suites pass locally under vitest 4 (editor, tldraw, store, sync-core, utils, dotcom/client, etc.). Note: `apps/dotcom/sync-worker`'s `replicatorMigrations` snapshot differs locally only because node 26's bundled SQLite emits an extra `sqlite_stat4` table; CI runs node 24, which matches the committed snapshot, so it's left unchanged. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use shift + Q as a shortcut to select the style form a shape and have it be applied to the next shape. Have the same base behavior that #8917 initially introduced to allow you to capture the style of a shape on hover, but rather than apply it directly to an existing shape if you have one selected it should just apply the style to the next shape. ### API changes - added a `shift+q` shortcut to copy styles form a hovered shape in order to apply it for the next shape ### Change type - [ ] `bugfix` - [ ] `improvement` - [x] `feature` - [ ] `api` - [ ] `other` ### Test plan <breathing intensifies> 1. Create a shape using any colour 2. Create an other shape using any other colour 3. Hover first shape 4. press `shift+q` 5. create an other shape The shape created should now have the same style than the first shape - [ ] Unit tests - [ ] End to end tests ### Release notes - Added new shortcuts in order to copy styles from a selected shape. `shift+q` will copy the styles of a hovered shape and set it for the next shape to be created
In order to pick up Zero 1.6's deployment and reliability fixes, this PR bumps `@rocicorp/zero` from `1.5.0` to `1.6.1` across the dotcom client, sync-worker, zero-cache, and `dotcom-shared`. Zero 1.6 ships with no breaking changes versus 1.5. The Fly/Docker deploy templates read the version from `zero-cache/package.json` via `internal/scripts/deploy-dotcom.ts`, so the bump propagates to the deployed zero-cache image automatically. ### Change type - [x] `improvement` ### Test plan - [ ] Unit tests - [ ] End to end tests ### Release notes - Bump Zero to 1.6.1.
Closes #9024 In order to stop the default Google Maps embed from reading `process.env.NEXT_PUBLIC_GC_API_KEY` inside the SDK (#9024), this PR lets consumers pass per-embed config through `EmbedShapeUtil.configure`. Config is keyed by embed type, so each definition only receives its own values and no key leaks across embeds. ```ts EmbedShapeUtil.configure({ embedConfig: { google_maps: { apiKey: '...' } }, }) ``` tldraw.com now configures the embed util explicitly (reading the vite-inlined key in app code) instead of relying on the SDK reading the env var. ### Change type - [x] `api` ### Test plan 1. Configure `EmbedShapeUtil` with a `google_maps` api key and paste a Google Maps URL — the embed renders with the key. 2. Without config, the maps embed URL has no key (no env fallback in the SDK). - [x] Unit tests ### API changes - Added `embedConfig` option to `EmbedShapeOptions`. - Added `GoogleMapsEmbedConfig` and `DefaultEmbedConfig` types. - `EmbedDefinition`/`CustomEmbedDefinition` are now generic over a `Config`; `toEmbedUrl(url, config?)` takes an optional per-embed config. - `getEmbedInfo`/`matchUrl` accept an optional `embedConfig` argument. ### Release notes - Add `embedConfig` to `EmbedShapeUtil` so embeds like Google Maps can be given an API key via `EmbedShapeUtil.configure({ embedConfig: { google_maps: { apiKey } } })`. The default Google Maps embed no longer reads `process.env.NEXT_PUBLIC_GC_API_KEY`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )