[pull] main from tldraw:main#604
Merged
Merged
Conversation
…8905) Adjusting a theme slider in the `custom-theme` example passed a brand-new `themes` object to `<Tldraw>` on every change. `useLocalStore` treats `themes` as part of the store options, so each new reference re-ran `createTLStore` and reloaded from persistence — the whole board visibly flashed on every slider drag. This reworks the example to apply theme changes through the programmatic API instead of the prop: - The `themes` prop is now a stable, module-level object passed once to `<Tldraw>`. It registers the custom `pink` color and custom fonts at store creation and seeds the starting values. Because its reference never changes, the store is never recreated. - Slider adjustments are applied at runtime with `editor.updateTheme({ ...editor.getTheme('default')!, fontSize, lineHeight, strokeWidth })`, spreading the current theme so custom colors and fonts are preserved. Shapes re-render reactively without reloading the canvas. No SDK changes — the fix is entirely in the example. ### Change type - [x] `bugfix` ### Test plan 1. Run `yarn dev` and open http://localhost:5420/custom-theme. 2. Adjust any of the sliders (font size, line height, stroke width). 3. The shapes re-render with the new theme values without the canvas flashing or reloading. - [ ] Unit tests - [ ] End to end tests ### Release notes - Update the `custom-theme` example to apply runtime theme changes via `editor.updateTheme()` so adjusting theme display values no longer reloads the canvas.
…ns (#9279) In order to correct one-off bad machine translations without waiting for a full re-translation cycle, this PR adds an `i18n-fix-strings` script that patches specific (key, locale) translations directly in Lokalise. The automated i18n pipeline (`i18n-upload-strings` / `i18n-download-strings`) occasionally returns a wrong translation — a word translated in the wrong sense, or scrambled placeholders/tags — and there was no targeted way to correct just those entries. The script is data-driven (a `FIXES` table of key → English source → per-locale corrected value), dry-runs by default, and only writes when passed `--apply`. It refuses any fix that would drop a placeholder or HTML tag present in the English source, and normalizes locale codes so `ko_KR` matches `ko-kr`. It targets the dotcom Lokalise project via the existing `LOKALISE_PROJECT_ID` / `LOKALISE_API_TOKEN` env vars. Usage: ```bash yarn i18n-fix-strings # dry run — prints the before/after diff per locale yarn i18n-fix-strings --apply # write the corrections to Lokalise ``` The script currently carries one correction: French "Clear search" was `Recherche propre` ("clean search", adjective) instead of the verb `Effacer la recherche`. This has already been applied to Lokalise; it ships in the table as documentation and a re-runnable record. ### Change type - [x] `other` ### Test plan 1. Run `yarn i18n-fix-strings` (dry run) with valid `LOKALISE_PROJECT_ID` / `LOKALISE_API_TOKEN` — confirm it prints the before/after diff and writes nothing. 2. Run with `--apply` — confirm the targeted translation is updated in Lokalise and a re-run reports "already correct". ### Code changes | Section | LOC change | | -------------- | ---------- | | Config/tooling | +195 / -0 |
## What Fixes a cluster of "My files" bugs in the workspaces model that all trace to one root cause. A shared file you open as a guest is mirrored into your home group — this is the intended **guest file** feature (the file shows in "My files" with a guest badge so you can find it again). But if that file is owned by a workspace you're a **member of**, it would show in "My files" yet open into that workspace — because the active workspace is derived from the file's `owningGroupId`. So clicking it (or switching to "My files", which opens the top file) bounced you into the workspace, and you could never get back to your home. This happens when: - you open a workspace's shared file as a guest, then later **join** that workspace, or - a workspace's **welcome file** is mirrored into home during the create-workspace race. It explains the reported symptoms: shared files and a "Welcome to your workspace" file showing in "My files", and clicking them teleporting the user into another workspace with no way back. ## Preview https://pr-9270-preview-deploy.tldraw.com/ ## Fixes 1. **Teleport / "can't get back to My files" — read-side filter (`getWorkspaceFilesSorted`).** For the home workspace, the sidebar now lists only files it owns, legacy files, and genuine guest files (owned by workspaces you're **not** a member of). A file owned by a workspace you **are** a member of is no longer listed in home, so it can't bounce you out. Guest-file mirroring (`onEnterFile`) is unchanged — guest files still appear as before. Filtering on read also fixes already-affected users on deploy, before any data cleanup. 2. **File-count limit consistency (`canCreateNewFile`).** It now counts only files the workspace actually owns (`owningGroupId === workspaceId`), not guest files or hidden/mislinked rows — matching the server's `assertNotMaxFiles`. Prevents "max files reached" firing on files the user can't see or remove. 3. **Empty home stays switchable (`createFile`).** Authorizes the home workspace via `getRole`/`can('addFiles')` instead of a raw `group_user` lookup. The home workspace (`id === userId`) is implicitly owned and may have no `group_user` row (e.g. before it syncs), so creating a file there could be rejected — leaving an empty home impossible to switch back to. This matches how every other workspace op already authorizes. ## How to verify On the preview (https://pr-9270-preview-deploy.tldraw.com/), with two accounts: 1. **Account A:** create a workspace, add a file inside it, then copy that **file's share link** and the **workspace invite link**. 2. **Account B (incognito):** open the **file share link** → the file appears in "My files" with a guest badge. 3. **Account B:** open the **workspace invite link** and accept it (you're now a member of the workspace). 4. **Account B:** click **"My files"** → you stay in "My files". Before the fix, step 4 bounced you into the workspace (you couldn't get back to "My files"). ## Testing - New scenario test — `opening a guest file does not trap the member outside their home after they join its workspace` (`sharing-live.scenario.spec.ts`). It fails on the unfixed code (the member is teleported into the workspace) and passes here. - Unit tests in `mutators.test.ts` cover `createFile` home authorization and `onEnterFile` guest / member / legacy mirroring. - `yarn typecheck`, lint, and the full dotcom e2e suite pass.
In order to stop burning GitHub Actions minutes when the dotcom dev stack fails to start, this PR reduces the Playwright `webServer` startup timeout in CI from `840_000`ms (14 min) to `180_000`ms (3 min). That timeout is how long Playwright waits for `http://localhost:3000` to respond before any test runs; on a stuck or broken stack it previously sat idle for ~14 minutes before failing. The dotcom server should boot well within 3 minutes, so this aborts a hung run quickly instead. The old value was derived to cover the dev stack's internal per-stage readiness budgets in `zero-cache/dev-env.ts` (~13 min worst case). Those internal budgets are left unchanged: they gate individual stages (Postgres, migrations, Zero cache, sync worker) and govern the local dev experience, and a genuinely failing stage already throws and exits quickly. The 3-min cap now bounds the whole `yarn dev-app` command in CI. Trade-off: if a CI cold start ever legitimately needs more than 3 minutes, the e2e job will fail at that mark — the fix then is to bump this value to a measured number, not restore 14 minutes. ### Change type - [x] `improvement` ### Test plan - A healthy CI e2e run should start tests exactly as before. - A run where the dev server never comes up should now abort at ~3 min instead of ~14 min. - [ ] Unit tests - [x] End to end tests ### Code changes | Section | LOC change | | -------------- | ---------- | | Config/tooling | +5 / -2 |
) In order to keep nested lists from collapsing into a single column so quickly, this PR halves the default per-level indentation on rich-text lists. Closes #9041. Closes #8712 List indentation is set on `.tl-rich-text ul/ol` in `packages/editor/editor.css`, a single rule shared by all rich text (note shapes, text shapes, geo labels), so the reduction applies everywhere lists render. The base `padding-left` drops from `3.25ch` to `1.625ch`. The ordered-list variants that add room for 2- and 3-digit numbers keep their `+1ch` / `+2ch` digit-width increments, so numbered lists stay aligned: `4.25ch` → `2.625ch` and `5.25ch` → `3.625ch`. ### Change type - [x] `improvement` ### Test plan 1. Run `yarn dev` and create a note shape. 2. Type a bulleted list and press Tab to nest several levels; confirm each level indents about half as much as before and bullets stay readable. 3. Repeat with an ordered list, including lists long enough to reach 10+ and 100+ items, and confirm numbers remain aligned. 4. Spot-check a text shape and a geo shape label to confirm rich-text lists still render correctly. ### Release notes - Reduce the default indentation on rich-text lists so nested lists take up less horizontal space. --------- Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
) In order to tighten up the tldraw.com UI, this PR makes a batch of focused refinements to the dotcom app shell: it standardizes the SVG icons to use \`currentColor\`, slims down the custom theme token set, and reworks the invite, sign-in, and workspace settings dialogs along with sidebar and menu spacing. Highlights: - **Icons** — \`edit\`, \`search\`, \`settings\` icons now use \`currentColor\` instead of hard-coded fills so they inherit theme text color; add an \`avatar\` icon. - **Theme tokens** — strip exported \`tla\` theme variants down to the keys that don't resolve through shared \`tl-color-*\` tokens, removing ~hundreds of redundant per-theme overrides in \`ui-themes.ts\`. - **Invite dialog** — retitle to "Join workspace", move the workspace name into a description line, drop the logo image and the redundant "No thanks" button, and rename the primary action to "Accept invitation". - **Sign-in dialog** — simplify the create-account copy. - **Workspace settings dialog** — layout and structure cleanup. - **Sidebar & menus** — spacing and CSS adjustments across the sidebar, top bar, share menu, buttons, and dialogs. Relates to #9082. ### Change type - [x] `improvement` ### Test plan 1. Run \`yarn dev-app\` and open tldraw.com locally. 2. Open the invite flow and confirm the dialog shows "Join workspace" with the workspace name and a single "Accept invitation" button. 3. Open the sign-in dialog and confirm the create-account copy. 4. Open workspace settings and confirm the dialog layout. 5. Switch between custom themes (e.g. solarized) in light and dark and confirm colors still apply correctly. 6. Confirm the edit, search, and settings icons follow the current text color. ### Release notes - Polish the tldraw.com invite, sign-in, and workspace settings dialogs, sidebar, and theming. ### Code changes | Section | LOC change | | -------------- | ----------- | | Apps | +351 / -854 | | Config/tooling | +4 / -1 | --------- Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com>
#9278) Closes #8328 The fix in #8334 surfaced a rather unpleasant visual issue that occur once the user tabulate and press enter: the "back to canvas" stays visible during the animate. This PR fixes that by forcing the button to hide once the "focus" button is pressed. ### Change type - [ ] `bugfix` - [x] `improvement` - [ ] `feature` - [ ] `api` - [ ] `other` ### Test plan 1. Create a shape 2. Go far far, really far (on the left, on the right, where you want, you do you) 3. Press tab 4. Press enter => The "back to content" button is not visible during the animation - [ ] Unit tests - [ ] End to end tests ### Release notes - Small visual improvement with the "focus canvas" a11y behaviour, we hide the "back to canvas" button during the animation that pan back to the first created shape
In order to stop common English words and phrases from being rendered as broken mermaid diagrams on tldraw.com, this PR tightens the mermaid detection heuristic in `simpleMermaidStringTest`. Pasting text like "timeline", "kanban-board", "info", or "graph paper" previously matched a mermaid diagram keyword as a bare prefix and was routed to the mermaid renderer instead of being inserted as a plain text shape (closes #8481). The detection regex matched diagram keywords with no word boundary and no structural validation, so: 1. **No word boundary** — "kanban-board" matched because "kanban" is a prefix. 2. **No structural validation** — a single word like "timeline", or a phrase like "graph paper", matched even though a real diagram has structure beyond the bare keyword. This PR addresses both: - Adds a trailing `(?![\w-])` word-boundary lookahead so keywords glued to more text no longer match: "kanban-board", "gantt-chart", "information", "graphql". - Requires evidence of an actual diagram for unfenced text — either a recognized variant suffix (e.g. `sankey-beta`, `stateDiagram-v2`) or a line break followed by real content. This rejects single-line prose such as "graph paper is nice" or "pie in the sky" while still accepting `graph LR\n A --> B`. - Keeps explicit ` ```mermaid ` fences permissive: a bare keyword inside a fence the user has labelled as mermaid is still detected, since intent is clear. ### Change type - [x] `bugfix` ### Test plan 1. Go to tldraw.com. 2. Copy a common word or phrase ("timeline", "kanban-board", "info", "graph paper") and paste it onto the canvas — it should appear as a plain text shape, not a diagram. 3. Paste a real diagram (e.g. `timeline` followed by ` title History`, or a ` ```mermaid ` fenced diagram) — it should still render as a mermaid diagram. - [x] Unit tests ### Release notes - Fix mermaid detection on tldraw.com so common words and phrases like "timeline", "kanban-board", "info", and "graph paper" paste as plain text instead of being rendered as broken diagrams. ### Code changes | Section | LOC change | | ------- | ---------- | | Apps | +27 / -2 | | Tests | +67 / -9 |
In order to make the line "bend" threshold feel consistent at every zoom level, this PR divides `MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES` by the current zoom level so the 2px minimum distance between shift-clicked line handles is measured in screen space rather than page space. Closes #7661. Previously the threshold was compared directly against page-space distances. At low zoom 2 page pixels was effectively invisible (very easy to trigger), and at high zoom it felt large (hard to add points close together). This mirrors the zoom-aware drag detection in `Editor.ts`. ### Change type - [x] `bugfix` ### Test plan 1. Open the line tool with tool lock enabled. 2. Zoom in (e.g. 400%), draw a line, then shift-click a few screen pixels past the end handle — a new point should be added. 3. Zoom out (e.g. 25%), draw a line, then shift-click within ~2 screen pixels of the end handle — the click should merge into the existing handle instead of adding a point. - [x] Unit tests - [ ] End to end tests ### Release notes - Fix the line tool's minimum bend distance so it's measured in screen pixels, keeping shift-click behavior consistent across zoom levels. ### Code changes | Section | LOC change | | --------- | ---------- | | Core code | +7 / -2 | | Tests | +41 / -0 |
This PR updates the i18n strings. ### Change type - [x] `other` Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com> Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com>
…9292) In order to make file and workspace deletion less ambiguous, this PR includes the name of the item being deleted in the confirmation dialogs. Previously the dialogs read generic copy like "Are you sure you want to delete this file?", which gave no confirmation of which file or workspace was about to be deleted. The file delete/forget dialog now reads "Are you sure you want to delete {fileName}?" (and the "forget" variant for non-owners), and the delete-workspace confirmation reads "Are you sure you want to delete {workspaceName}? This action cannot be undone." Unnamed workspaces fall back to the existing name placeholder. This matches the existing pattern already used when removing a workspace member. ### Change type - [x] `improvement` ### Test plan 1. Open a file's menu and click Delete (or Forget as a non-owner); confirm the dialog names the file. 2. Open a workspace's settings and click Delete workspace; confirm the dialog names the workspace. ### Release notes - Show the file or workspace name in delete confirmation dialogs on tldraw.com.
In order to let our published packages depend on modern ESM-only modules without breaking CommonJS consumers, this PR drops support for the now-EOL Node 20 and requires Node `>=22.12.0` — a version where `require()` of an ES module works natively. Calling `require()` on an ES module (instead of throwing `ERR_REQUIRE_ESM`) is unflagged as of Node 22.12.0. Node 20 reached EOL in April 2026, so rather than carry the awkward `^20.19.0 || >=22.12.0` range we just require `>=22.12.0`. With that floor, an ESM-only dependency in a `dist-cjs` build just works for consumers on a supported Node. ## Changes - Set the root `engines.node` to `>=22.12.0`. - Add a yarn constraint (`enforceNodeEngineOnPackages` in `yarn.config.cjs`) that applies the same range to every `packages/*` workspace, applied with `yarn constraints --fix`. (CI's `yarn constraints` keeps it consistent going forward.) - Update the AGENTS.md setup note to match. ## Why per-package, not just the root The root `package.json` is `private` and is **never published** — its `engines` only constrains the monorepo dev environment; a consumer never sees it. When someone runs `npm install @tldraw/editor`, npm reads **that package's own** published `engines` and warns on a mismatch. There's no "monorepo root" in what they install (just a single package tarball), and npm has no notion of inheriting `engines` from a workspace root. | Where `engines` lives | Who it affects | | --- | --- | | root `package.json` (private) | tldraw developers building the monorepo | | each published package | consumers who install that package | So to actually signal the requirement to consumers, the field has to be on the packages they install. Each is independently installable (someone might pull in just `@tldraw/store`), so each declares its own; the yarn constraint keeps them in sync. Note this is a new pattern for the repo — until now `engines` lived only at the root. ## Notes - **`engines` is advisory** — npm only *warns* on an unmet engine, it doesn't hard-fail the install. So this won't break anyone's build; it signals the supported range. - Alternative/complement: #9050 solves the same ESM-only-in-CJS problem at the build level (inlining ESM-only deps into the CJS output). The `@tldraw/mermaid` lazy-import that previously rode along here is now its own PR: #9100. ### Change type - [x] `improvement` ### Test plan 1. `yarn constraints` and `yarn check-packages` pass (engines applied consistently to every `packages/*`). 2. Published packages now declare `engines.node: ">=22.12.0"`. ### Release notes - Drop support for the EOL Node 20. The minimum supported Node is now `>=22.12.0` — the version where `require()` of an ES module is supported natively, which lets tldraw safely depend on modern ESM-only packages. ### Code changes | Section | LOC change | | -------------- | ---------- | | Documentation | +1 / -1 | | Config/tooling | +71 / -2 |
In order to keep the keyboard shortcuts dialog in sync with the shortcuts the editor actually handles, this PR adds the shortcuts that had a keybinding but were missing from the dialog. This follows up on #7399 (copy as PNG), which added the shortcut but never listed it, and covers the other gaps noted in #9087 (flatten, toggle locked, and more). Added rows: - Tools: insert embed (`cmd+i`), highlight tool (`shift+d`) - Edit: copy as PNG (`cmd+shift+c`), print (`cmd+p`) - Transform: frame selection (`cmd+alt+g`), flatten (`shift+f`), toggle locked (`shift+l`), distribute horizontally (`alt+shift+h`), distribute vertically (`alt+shift+v`) A few shortcuts are intentionally left out and are now documented in the new test's `NOT_IN_SHORTCUTS_DIALOG` list: cursor-targeted zoom/paste variants and rotation (duplicates of rows already shown or covered by the accessibility group), style-picking shortcuts, geo-tool re-select, page navigation (no dialog labels), and cursor chat (only shown with collaboration UI enabled). A test now fails if a shortcut gains a keybinding without being added to the dialog or to that intentional-omission list, so the dialog can't silently drift again. Closes #9087 ### Change type - [x] `improvement` ### Test plan 1. Open the editor and press the keyboard shortcuts shortcut (`cmd+alt+/`). 2. Confirm the new rows appear: insert embed, highlight, copy as PNG, print, frame selection, flatten, toggle locked, distribute horizontally/vertically. - [x] Unit tests ### Code changes | Section | LOC change | | --------- | ---------- | | Core code | +9 / -0 | | Tests | +59 / -0 | ### Release notes - Add the missing keyboard shortcuts (copy as PNG, print, insert embed, highlight tool, frame selection, flatten, toggle locked, distribute horizontally/vertically) to the keyboard shortcuts dialog.
In order to fix sticky note attribution text rendering larger and blurrier in PNG/SVG exports than on the live canvas, this PR renders the attribution as HTML inside a `<foreignObject>` instead of as a native SVG `<text>` element. Closes #8941. The note body text was already exported through `RichTextSVG`, which wraps HTML in a `<foreignObject>` so the rasterized output uses the exact same layout and font metrics as the live DOM. The attribution was the only element exported as a native SVG `<text>`, which the browser's SVG text engine lays out and antialiases with different metrics — that mismatch was the degradation. Rendering it through a `foreignObject` with the existing `tl-note__attribution` class makes the export match the DOM. Because the `foreignObject` now applies the real `.tl-note__attribution` CSS (`max-width: 60%; overflow: hidden; text-overflow: ellipsis`), ellipsis truncation happens natively and identically to the canvas, so the manual `truncateAttributionForSvg` text-measurement helper is no longer needed and has been removed. ### Change type - [x] `bugfix` ### Test plan 1. Open a sticky note and type some text so the attribution (first name) appears in the bottom-right. 2. Copy the note as PNG (or export to PNG/SVG) and compare the pasted result against the original. 3. Confirm the attribution text is crisp and the same size/weight as on canvas, at both `scale = 1` and a resized note (`scale ≠ 1`). 4. Use a long attribution name and confirm it truncates with an ellipsis the same way as on the canvas. - [ ] Unit tests - [ ] End to end tests ### Code changes | Section | LOC change | | --------- | ---------- | | Core code | +21 / -38 | ### Release notes - Fix sticky note attribution text rendering larger and blurrier than the canvas when a note is exported or copied as a PNG.
…OS input zoom (#9308) In order to clean up a few rough edges on tldraw.com, this PR bundles three small dotcom fixes: - **Theme tokens** — standardize text colors on `--tl-color-text-0` across the sidebar, top panel, menus, dialogs, buttons, and admin page. Several rules referenced `--tl-color-text` and `--tl-color-text-1`, which led to inconsistent text contrast. - **Auth dialog** — narrow the auth dialog from 450px to 360px and drop a redundant mobile `.authBody` override so the layout reads better. - **iOS input zoom** — bump form fields to 16px on iOS only (gated behind `@supports (-webkit-touch-callout: none)`) so Safari no longer zooms in when a text field is focused. ### Change type - [x] `bugfix` ### Test plan 1. On iOS Safari, focus the sidebar search input (and other text fields) and confirm the page no longer zooms in. 2. Open the auth dialog and confirm it renders at the narrower width on desktop and mobile. 3. Check the sidebar, top panel, menus, dialogs, and admin page for consistent text color in light and dark themes. - [ ] Unit tests - [ ] End to end tests ### Release notes - Fix iOS Safari zooming in when focusing text fields on tldraw.com. - Standardize text colors and narrow the auth dialog. ### Code changes | Section | LOC change | | ------- | ---------- | | Apps | +76 / -69 |
In order to make pasting URLs feel responsive even when bookmark metadata is slow to fetch, this PR creates the bookmark shape immediately as a placeholder and hydrates it with metadata in the background. The placeholder shows a loading spinner overlay (similar to image loading) so the loading state is visible at the paste location, and once metadata resolves the asset is patched in. If the fetch fails, the bookmark gracefully falls back to a URL-only card. <img width="2272" height="1396" alt="Kapture 2026-06-18 at 12 28 53" src="https://github.com/user-attachments/assets/f0b9516c-59b9-4cec-a9a8-755c0be0693d" /> Closes #8653 ### Change type - [x] `feature` ### Test plan 1. Run `yarn dev`, open the examples app, and paste a URL onto the canvas — a bookmark placeholder should appear immediately at the paste location. 2. Throttle the network in DevTools and paste again — the placeholder with its loading spinner should be visible while metadata loads, then resolve into the full bookmark preview. 3. Paste a recognized embed URL (e.g. a YouTube link) — it should still create an embed shape, not a bookmark placeholder. 4. Paste a URL that fails to unfurl — the bookmark should remain visible as a URL-only card rather than disappearing. 5. Paste a URL, then immediately undo — the placeholder should disappear cleanly without leaving an extra "metadata loaded" undo step. - [x] Unit tests - [ ] End to end tests ### Release notes - Pasted bookmarks now show an immediate loading placeholder at the paste location, with a loading spinner, instead of waiting for metadata before appearing. ### API changes - BookmarkShapeUtil now uses `getGeometry` to explicitly define its geometry. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 : )