diff --git a/.claude/skills/create-example/SKILL.md b/.claude/skills/create-example/SKILL.md new file mode 100644 index 0000000000..dbf95af451 --- /dev/null +++ b/.claude/skills/create-example/SKILL.md @@ -0,0 +1,72 @@ +--- +name: create-example +description: Creates a new example under the `/examples` directory. +--- + +This skill provides instructions on how to create a BlockNote editor example correctly. + +# Creating an example root directory + +Under the `/examples` directory, each subdirectory is a category of examples. It's name consists of a 2 digit index, followed by a dash and the category name. + +Each of these contains another set of subdirectories, where each one contains a single example. The naming of these is the same, but the category name is swapped for the example name. + +Based on the user's prompt, the most relevant category should be chosen, and a new directory for the example should be created. The index in the example directory's name should be the lowest unused one to avoid large diffs from having to reorder & rename the existing example directories. It is very unlikely that a new category directory should need to be created for the new example, but it should use the same convention. + +# Source & metadata files + +## Source files + +Any source files must be inside a `/src` directory at the root of the example directory. Within these, there must also be an `App.tsx` file, which default exports a React component. This component is responsible for rendering the entire example. + +## Metadata files + +There are two files containing metadata that must also be added at the root of the example directory: + +`.bnexample.json` + +Contains all of the example's configuration. Here's an annotated example (from `/examples/03-ui-components/13-custom-ui/.bnexample.json`): + +``` + +"playground": true, + +"docs": true, + +"author": "matthewlipski", + +"tags": [ + "Advanced", + "Inline Content", + "UI Components", + "Block Side Menu", + "Formatting Toolbar", + "Suggestion Menus", + "Slash Menu", + "Appearance & Styling" +], + +"dependencies": { + "@mui/icons-material": "^5.16.1", + "@mui/material": "^5.16.1" +}, + +"pro": true +``` + +`README.md` + +A Markdown description of the example. Made of four parts: + +1. Heading with the example name. This does not necessarily need to be the same as the example directory name and can be more verbose. +2. Description of the example, which should be no longer than a paragraph of three sentences. +3. An optional "Try me out!" callout. Should be a single sentence instructing the user how to see the changes made to the editor in the example. +4. A list of relevant docs. These are mostly internal but may also refer to e.g. dependencies used in the example. + +See `/examples/07-collaboration/01-partykit` for reference on the exact markup of these sections. + +# Generated files + +Once the source & metadata files are done, `vp run gen` should be executed from the project root directory to auto-generate additional files in the example directory, as well as the playground & docs. + +One of the generated files is `package.json` in the example directory. If the `bnexample.json` specified any dependencies, these will be included here. Therefore, `vp install` should always be executed in the project root directory after, to ensure these are installed. diff --git a/.claude/skills/debug-skill/SKILL.md b/.claude/skills/debug-skill/SKILL.md new file mode 100644 index 0000000000..cc47080e23 --- /dev/null +++ b/.claude/skills/debug-skill/SKILL.md @@ -0,0 +1,70 @@ +--- +name: debug-skill +description: Instructions for navigating and debugging BlockNote in a browser. Shows how to open specific menus & toolbars, as well select content. Should be used when prompted to fix a bug that requires inspecting the editor's appearance or rendered HTML. +--- + +# General loop + +When fixing a bug, the following feedback loop should be used. + +1. Apply a code change that you think will fix the issue. +2. Test the change in a browser environment. +3. Take screenshots to verify that the issue is fixed. +4. Repeat until the bug is indeed fixed. + +# Browser environment + +Before starting up a browser environment, you need to ensure the dev server is running. This can be done by checking if port 5173 is in use. If it isn't, running `vp run dev` at the project root will start the server. + +The Playwright CLI should be used for the browser environment. It can be used to navigate to the dev server and programmatically issue mouse clicks/keyboard inputs. If not installed, stop what you're doing and notify the user to install it. + +# Selecting an example + +After navigating to `localhost:5173`, an example must be selected. These are listed in the navbar (`mantine-AppShell-navbar` CSS class). The "Default Schema Showcase" should be selected, unless stated otherwise by the user. + +Each example will contain a BlockNote editor, and possibly additional elements like text fields or static toolbars. + +# Editor HTML structure + +Below is a list of elements that make up a BlockNote editor. This is helpful for mapping BlockNote concepts to what's actually visible in the browser. The nesting of the list items is representative of how the corresponding elements are nested in the rendered HTML. The elements are referenced by their main CSS class. + +- `bn-container`: Wrapper element for the editor. + - `bn-editor`: Root element for the BlockNote editor. + - `bn-block-group`: Root container for blocks. + - `bn-block-outer`: Wrapper element for a block. + - `bn-block`: Root element for a block. + - `bn-block-content`: Container element for all content rendered by the block itself. Also renders a `data-content-type` attribute which stores the block's type, and additional `data-*` for every non-default prop that the block has. + - `bn-inline-content`: Container element for user-editable rich text within a block. Note that not all blocks will contain this element. + - `bn-block-group`: Container for nested blocks. Note that if a block doesn't contain nested blocks, it won't have this element. + - `bn-block-column-list`: Container element for columns. + - `bn-block-column`: Column element containing blocks. + +Each element only appears once in its parent, except `bn-block-outer` and `bn-block-column-list`, which can appear multiple times. + +Each `bn-block-group` and `bn-block-column` also contain `bn-block-outer` elements. These are not listed as they can be nested to an arbitrary depth. + +Note that additional UI elements like menus and toolbars are mounted in a portal attached to the `body`. + +# Keyboard navigation + +Assume you are on a machine running macOS. You can use the following key combinations to navigate through the editor and create selections: + +- Left/Right Arrow: Moves the text cursor back/forward one character. +- Up/Down Arrow: Moves the text cursor to the previous/next block. +- Option + Left/Right Arrow: Moves the text cursor to the start/end of the current word. If already at the start/end of a word, moves it to the start/end of the previous/next one instead. +- Cmd + Left/Right Arrow: Moves the text cursor to the start/end of the line. +- Cmd + Up/Down Arrow: Moves the text cursor to the start/end of the document. + +Each of these can also be used with Shift to create/extend a selection instead of just moving the cursor. + +It is extremely important to note that these key combinations are only relevant for debugging and NOT for writing end-to-end tests. While Playwright is used for both, tests run in a Linux environment which has different bindings for keyboard navigation. + +# Opening menus & toolbars + +Here are the most often used UI elements, and how to find/open them. + +- **Formatting toolbar**: Create a selection using the keyboard and look for an element with the `bn-formatting-toolbar` CSS class. Buttons/dropdowns within it should be interacted with using the mouse instead. The `data-test` attribute will inform you what a given button or dropdown is for. Press escape to dismiss the toolbar. +- **Side menu**: Hover a block with the mouse, i.e. a `bn-block` element, and look for an element with the `bn-side-menu` CSS class. Unless specified otherwise, it contains a button to add a block ("Add block" ARIA label) and a drag handle which opens a menu on click ("Open block menu" ARIA label). Typing in the editor or moving the mouse cursor above/below it will hide the side menu, unless the drag handle menu is open. Then, it's "frozen" until dismissed by an outside click or pressing Escape. +- **Slash menu**: Type the "/" key while in a block and look for an element with the `bn-suggestion-menu` CSS class. It contains a list of items with the `bn-suggestion-menu-item` CSS class. While the menu is open, the up/down arrows navigate through items instead of moving the text cursor. Items can be triggered with a mouse click or pressing Enter while selected. Each item will convert the type of the current block to one of a given type, if it's empty. Otherwise, it will create a new block below with that type. The `bn-suggestion-menu-item-title` element's text content will indicate the new type. Pressing Escape closes the menu. +- **Link toolbar**: Hover a link in a block (anchor element within a `bn-inline-content` element), or move the text selection inside it using the arrow keys, and look for an element with the `bn-link-toolbar` CSS class. Unless specified otherwise, it contains three buttons. The first has the text, "Edit link". On click, it opens a popup to edit the link text and URL. You can locate these inputs with the "Edit title" and "Edit URL" placeholders. The other two buttons are for opening the link in a new tab ("Open in new tab" ARIA label) and deleting the link ("Remove link" ARIA label). If the toolbar was opened via mouse hover, moving the mouse off of the link or toolbar will close it after half a second. Otherwise, moving the text cursor outside the link will close the toolbar. It can also be dismissed by pressing Escape. +- **File panel**: After creating a file, image, video, or audio block, it will render a button with the text "Add file" (`bn-add-file-button` CSS class). Clicking the button will open the file panel. When the block is created using the slash menu (typically the case), the file panel will be open immediately. It always has an "Embed" tab (`data-test="embed-tab"` attribute). While this tab is selected, the file panel displays an input for the file URL ("Enter URL" placeholder) and "Embed file" button. For some examples, an "Upload" tab (`data-test="upload-tab"` attribute) will also be present. While it's selected, the file panel will display a file input (`data-test="upload-input"` attribute). After embedding/uploading a file, the block will render said file instead of displaying the "Add file" button. diff --git a/.claude/skills/playwright-cli/SKILL.md b/.claude/skills/playwright-cli/SKILL.md new file mode 100644 index 0000000000..fe30992ad9 --- /dev/null +++ b/.claude/skills/playwright-cli/SKILL.md @@ -0,0 +1,394 @@ +--- +name: playwright-cli +description: Automate browser interactions, test web pages and work with Playwright tests. +allowed-tools: Bash(playwright-cli:*) Bash(npx:*) Bash(npm:*) +--- + +# Browser Automation with playwright-cli + +## Quick start + +```bash +# open new browser +playwright-cli open +# navigate to a page +playwright-cli goto https://playwright.dev +# interact with the page using refs from the snapshot +playwright-cli click e15 +playwright-cli type "page.click" +playwright-cli press Enter +# take a screenshot (rarely used, as snapshot is more common) +playwright-cli screenshot +# close the browser +playwright-cli close +``` + +## Commands + +### Core + +```bash +playwright-cli open +# open and navigate right away +playwright-cli open https://example.com/ +playwright-cli goto https://playwright.dev +playwright-cli type "search query" +playwright-cli click e3 +playwright-cli dblclick e7 +# --submit presses Enter after filling the element +playwright-cli fill e5 "user@example.com" --submit +playwright-cli drag e2 e8 +# drop files or data onto an element (from outside the page) +playwright-cli drop e4 --path=./image.png +playwright-cli drop e4 --data="text/plain=hello world" +playwright-cli hover e4 +playwright-cli select e9 "option-value" +playwright-cli upload ./document.pdf +playwright-cli check e12 +playwright-cli uncheck e12 +playwright-cli snapshot +playwright-cli eval "document.title" +playwright-cli eval "el => el.textContent" e5 +# get element id, class, or any attribute not visible in the snapshot +playwright-cli eval "el => el.id" e5 +playwright-cli eval "el => el.getAttribute('data-testid')" e5 +playwright-cli dialog-accept +playwright-cli dialog-accept "confirmation text" +playwright-cli dialog-dismiss +playwright-cli resize 1920 1080 +playwright-cli close +``` + +### Navigation + +```bash +playwright-cli go-back +playwright-cli go-forward +playwright-cli reload +``` + +### Keyboard + +```bash +playwright-cli press Enter +playwright-cli press ArrowDown +playwright-cli keydown Shift +playwright-cli keyup Shift +``` + +### Mouse + +```bash +playwright-cli mousemove 150 300 +playwright-cli mousedown +playwright-cli mousedown right +playwright-cli mouseup +playwright-cli mouseup right +playwright-cli mousewheel 0 100 +``` + +### Save as + +Also resize large screenshots with sips (native macOS tool) if possible. + +Screenshots should be saved to `.claude/skills/playwright-cli/screenshots/` in the workspace root. + +```bash +playwright-cli screenshot +playwright-cli screenshot e5 +playwright-cli screenshot --filename=page.png +playwright-cli pdf --filename=page.pdf +``` + +### Tabs + +```bash +playwright-cli tab-list +playwright-cli tab-new +playwright-cli tab-new https://example.com/page +playwright-cli tab-close +playwright-cli tab-close 2 +playwright-cli tab-select 0 +``` + +### Storage + +```bash +playwright-cli state-save +playwright-cli state-save auth.json +playwright-cli state-load auth.json + +# Cookies +playwright-cli cookie-list +playwright-cli cookie-list --domain=example.com +playwright-cli cookie-get session_id +playwright-cli cookie-set session_id abc123 +playwright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure +playwright-cli cookie-delete session_id +playwright-cli cookie-clear + +# LocalStorage +playwright-cli localstorage-list +playwright-cli localstorage-get theme +playwright-cli localstorage-set theme dark +playwright-cli localstorage-delete theme +playwright-cli localstorage-clear + +# SessionStorage +playwright-cli sessionstorage-list +playwright-cli sessionstorage-get step +playwright-cli sessionstorage-set step 3 +playwright-cli sessionstorage-delete step +playwright-cli sessionstorage-clear +``` + +### Network + +```bash +playwright-cli route "**/*.jpg" --status=404 +playwright-cli route "https://api.example.com/**" --body='{"mock": true}' +playwright-cli route-list +playwright-cli unroute "**/*.jpg" +playwright-cli unroute +``` + +### DevTools + +```bash +playwright-cli console +playwright-cli console warning +playwright-cli requests +playwright-cli request 5 +playwright-cli run-code "async page => await page.context().grantPermissions(['geolocation'])" +playwright-cli run-code --filename=script.js +playwright-cli tracing-start +playwright-cli tracing-stop +playwright-cli video-start video.webm +playwright-cli video-chapter "Chapter Title" --description="Details" --duration=2000 +playwright-cli video-stop + +# launch the dashboard for UI review / design feedback — user annotates the page, you receive the annotated screenshot, snapshot, and notes +playwright-cli show --annotate + +# generate a Playwright locator for an element from its ref or selector +playwright-cli generate-locator e5 --raw + +# show a persistent highlight overlay for an element, optionally with a custom style +playwright-cli highlight e5 +playwright-cli highlight e5 --style="outline: 3px dashed red" +# hide a single element highlight, or all page highlights when no target is given +playwright-cli highlight e5 --hide +playwright-cli highlight --hide +``` + +## Raw output + +The global `--raw` option strips page status, generated code, and snapshot sections from the output, returning only the result value. Use it to pipe command output into other tools. Commands that don't produce output return nothing. + +```bash +playwright-cli --raw eval "JSON.stringify(performance.timing)" | jq '.loadEventEnd - .navigationStart' +playwright-cli --raw eval "JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))" > links.json +playwright-cli --raw snapshot > before.yml +playwright-cli click e5 +playwright-cli --raw snapshot > after.yml +diff before.yml after.yml +TOKEN=$(playwright-cli --raw cookie-get session_id) +playwright-cli --raw localstorage-get theme +``` + +For structured output wrapping every reply as JSON, pass --json + +```bash +playwright-cli list --json +``` + +## Open parameters + +```bash +# Use specific browser when creating session +playwright-cli open --browser=chrome +playwright-cli open --browser=firefox +playwright-cli open --browser=webkit +playwright-cli open --browser=msedge + +# Use persistent profile (by default profile is in-memory) +playwright-cli open --persistent +# Use persistent profile with custom directory +playwright-cli open --profile=/path/to/profile + +# Connect to browser via Playwright Extension +playwright-cli attach --extension=chrome + +# Connect to a running Chrome or Edge by channel name +playwright-cli attach --cdp=chrome +playwright-cli attach --cdp=msedge + +# Connect to a running browser via CDP endpoint +playwright-cli attach --cdp=http://localhost:9222 + +# Start with config file +playwright-cli open --config=my-config.json + +# Close the browser +playwright-cli close +# Detach from an attached browser (leaves the external browser running) +playwright-cli -s=msedge detach +# Delete user data for the default session +playwright-cli delete-data +``` + +## Snapshots + +After each command, playwright-cli provides a snapshot of the current browser state. + +```bash +> playwright-cli goto https://example.com +### Page +- Page URL: https://example.com/ +- Page Title: Example Domain +### Snapshot +[Snapshot](.playwright-cli/page-2026-02-14T19-22-42-679Z.yml) +``` + +You can also take a snapshot on demand using `playwright-cli snapshot` command. All the options below can be combined as needed. + +```bash +# default - save to a file with timestamp-based name +playwright-cli snapshot + +# save to file, use when snapshot is a part of the workflow result +playwright-cli snapshot --filename=after-click.yaml + +# snapshot an element instead of the whole page +playwright-cli snapshot "#main" + +# limit snapshot depth for efficiency, take a partial snapshot afterwards +playwright-cli snapshot --depth=4 +playwright-cli snapshot e34 + +# include each element's bounding box as [box=x,y,width,height] +playwright-cli snapshot --boxes +``` + +## Targeting elements + +By default, use refs from the snapshot to interact with page elements. + +```bash +# get snapshot with refs +playwright-cli snapshot + +# interact using a ref +playwright-cli click e15 +``` + +You can also use css selectors or Playwright locators. + +```bash +# css selector +playwright-cli click "#main > button.submit" + +# role locator +playwright-cli click "getByRole('button', { name: 'Submit' })" + +# test id +playwright-cli click "getByTestId('submit-button')" +``` + +## Browser Sessions + +```bash +# create new browser session named "mysession" with persistent profile +playwright-cli -s=mysession open example.com --persistent +# same with manually specified profile directory (use when requested explicitly) +playwright-cli -s=mysession open example.com --profile=/path/to/profile +playwright-cli -s=mysession click e6 +playwright-cli -s=mysession close # stop a named browser +playwright-cli -s=mysession delete-data # delete user data for persistent session + +playwright-cli list +# Close all browsers +playwright-cli close-all +# Forcefully kill all browser processes +playwright-cli kill-all +``` + +## Installation + +If global `playwright-cli` command is not available, try a local version via `npx playwright-cli`: + +```bash +npx --no-install playwright-cli --version +``` + +When local version is available, use `npx playwright-cli` in all commands. Otherwise, install `playwright-cli` as a global command: + +```bash +npm install -g @playwright/cli@latest +``` + +## Example: Form submission + +```bash +playwright-cli open https://example.com/form +playwright-cli snapshot + +playwright-cli fill e1 "user@example.com" +playwright-cli fill e2 "password123" +playwright-cli click e3 +playwright-cli snapshot +playwright-cli close +``` + +## Example: Multi-tab workflow + +```bash +playwright-cli open https://example.com +playwright-cli tab-new https://example.com/other +playwright-cli tab-list +playwright-cli tab-select 0 +playwright-cli snapshot +playwright-cli close +``` + +## Example: Debugging with DevTools + +```bash +playwright-cli open https://example.com +playwright-cli click e4 +playwright-cli fill e7 "test" +playwright-cli console +playwright-cli requests +playwright-cli close +``` + +```bash +playwright-cli open https://example.com +playwright-cli tracing-start +playwright-cli click e4 +playwright-cli fill e7 "test" +playwright-cli tracing-stop +playwright-cli close +``` + +## Example: Interactive session + +Ask the user for UI review or design feedback. The user draws boxes on the live page and types comments; you receive the annotated screenshot, the snapshot of the marked region, and the user's notes. Use this whenever the user asks for "UI review", "design feedback", or to "ask the user what they think / want / mean": + +```bash +playwright-cli open https://example.com +playwright-cli show --annotate +``` + +## Specific tasks + +- **Running and Debugging Playwright tests** [references/playwright-tests.md](references/playwright-tests.md) +- **Request mocking** [references/request-mocking.md](references/request-mocking.md) +- **Running Playwright code** [references/running-code.md](references/running-code.md) +- **Browser session management** [references/session-management.md](references/session-management.md) +- **Spec-driven testing (plan / generate / heal)** [references/spec-driven-testing.md](references/spec-driven-testing.md) +- **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md) +- **Test generation** [references/test-generation.md](references/test-generation.md) +- **Tracing** [references/tracing.md](references/tracing.md) +- **Video recording** [references/video-recording.md](references/video-recording.md) +- **Inspecting element attributes** [references/element-attributes.md](references/element-attributes.md) diff --git a/.claude/skills/playwright-cli/references/element-attributes.md b/.claude/skills/playwright-cli/references/element-attributes.md new file mode 100644 index 0000000000..4e9fa6b991 --- /dev/null +++ b/.claude/skills/playwright-cli/references/element-attributes.md @@ -0,0 +1,23 @@ +# Inspecting Element Attributes + +When the snapshot doesn't show an element's `id`, `class`, `data-*` attributes, or other DOM properties, use `eval` to inspect them. + +## Examples + +```bash +playwright-cli snapshot +# snapshot shows a button as e7 but doesn't reveal its id or data attributes + +# get the element's id +playwright-cli eval "el => el.id" e7 + +# get all CSS classes +playwright-cli eval "el => el.className" e7 + +# get a specific attribute +playwright-cli eval "el => el.getAttribute('data-testid')" e7 +playwright-cli eval "el => el.getAttribute('aria-label')" e7 + +# get a computed style property +playwright-cli eval "el => getComputedStyle(el).display" e7 +``` diff --git a/.claude/skills/playwright-cli/references/playwright-tests.md b/.claude/skills/playwright-cli/references/playwright-tests.md new file mode 100644 index 0000000000..bec2ec90e4 --- /dev/null +++ b/.claude/skills/playwright-cli/references/playwright-tests.md @@ -0,0 +1,39 @@ +# Running Playwright Tests + +To run Playwright tests, use the `npx playwright test` command, or a package manager script. To avoid opening the interactive html report, use `PLAYWRIGHT_HTML_OPEN=never` environment variable. + +```bash +# Run all tests +PLAYWRIGHT_HTML_OPEN=never npx playwright test + +# Run all tests through a custom npm script +PLAYWRIGHT_HTML_OPEN=never npm run special-test-command +``` + +# Debugging Playwright Tests + +To debug a failing Playwright test, run it with `--debug=cli` option. This command will pause the test at the start and print the debugging instructions. + +**IMPORTANT**: run the command in the background and check the output until "Debugging Instructions" is printed. Make sure to stop the command after you have finished. + +Once instructions containing a session name are printed, use `playwright-cli` to attach the session and explore the page. + +```bash +# Run the test +PLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli +# ... +# ... debugging instructions for "tw-abcdef" session ... +# ... + +# Attach to the test +playwright-cli attach tw-abcdef +``` + +Keep the test running in the background while you explore and look for a fix. +The test is paused at the start, so you should step over or pause at a particular location +where the problem is most likely to be. + +Every action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. +This code appears in the output and can be copied directly into the test. Most of the time, a specific locator or an expectation should be updated, but it could also be a bug in the app. Use your judgement. + +After fixing the test, stop the background test run. Rerun to check that test passes. diff --git a/.claude/skills/playwright-cli/references/request-mocking.md b/.claude/skills/playwright-cli/references/request-mocking.md new file mode 100644 index 0000000000..9005fda67d --- /dev/null +++ b/.claude/skills/playwright-cli/references/request-mocking.md @@ -0,0 +1,87 @@ +# Request Mocking + +Intercept, mock, modify, and block network requests. + +## CLI Route Commands + +```bash +# Mock with custom status +playwright-cli route "**/*.jpg" --status=404 + +# Mock with JSON body +playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"}]' --content-type=application/json + +# Mock with custom headers +playwright-cli route "**/api/data" --body='{"ok":true}' --header="X-Custom: value" + +# Remove headers from requests +playwright-cli route "**/*" --remove-header=cookie,authorization + +# List active routes +playwright-cli route-list + +# Remove a route or all routes +playwright-cli unroute "**/*.jpg" +playwright-cli unroute +``` + +## URL Patterns + +``` +**/api/users - Exact path match +**/api/*/details - Wildcard in path +**/*.{png,jpg,jpeg} - Match file extensions +**/search?q=* - Match query parameters +``` + +## Advanced Mocking with run-code + +For conditional responses, request body inspection, response modification, or delays: + +### Conditional Response Based on Request + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/login', route => { + const body = route.request().postDataJSON(); + if (body.username === 'admin') { + route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) }); + } else { + route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) }); + } + }); +}" +``` + +### Modify Real Response + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/user', async route => { + const response = await route.fetch(); + const json = await response.json(); + json.isPremium = true; + await route.fulfill({ response, json }); + }); +}" +``` + +### Simulate Network Failures + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/offline', route => route.abort('internetdisconnected')); +}" +# Options: connectionrefused, timedout, connectionreset, internetdisconnected +``` + +### Delayed Response + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/slow', async route => { + await new Promise(r => setTimeout(r, 3000)); + route.fulfill({ body: JSON.stringify({ data: 'loaded' }) }); + }); +}" +``` diff --git a/.claude/skills/playwright-cli/references/running-code.md b/.claude/skills/playwright-cli/references/running-code.md new file mode 100644 index 0000000000..06645ec124 --- /dev/null +++ b/.claude/skills/playwright-cli/references/running-code.md @@ -0,0 +1,240 @@ +# Running Custom Playwright Code + +Use `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands. + +## Syntax + +```bash +playwright-cli run-code "async page => { + // Your Playwright code here + // Access page.context() for browser context operations +}" +``` + +You can also load the function from a file: + +```bash +playwright-cli run-code --filename=./my-script.js +``` + +The code must be a single function expression, it is wrapped in `(...)` and evaluated. +import/export/require syntax is not supported. + +## Geolocation + +```bash +# Grant geolocation permission and set location +playwright-cli run-code "async page => { + await page.context().grantPermissions(['geolocation']); + await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 }); +}" + +# Set location to London +playwright-cli run-code "async page => { + await page.context().grantPermissions(['geolocation']); + await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 }); +}" + +# Clear geolocation override +playwright-cli run-code "async page => { + await page.context().clearPermissions(); +}" +``` + +## Permissions + +```bash +# Grant multiple permissions +playwright-cli run-code "async page => { + await page.context().grantPermissions([ + 'geolocation', + 'notifications', + 'camera', + 'microphone' + ]); +}" + +# Grant permissions for specific origin +playwright-cli run-code "async page => { + await page.context().grantPermissions(['clipboard-read'], { + origin: 'https://example.com' + }); +}" +``` + +## Media Emulation + +```bash +# Emulate dark color scheme +playwright-cli run-code "async page => { + await page.emulateMedia({ colorScheme: 'dark' }); +}" + +# Emulate light color scheme +playwright-cli run-code "async page => { + await page.emulateMedia({ colorScheme: 'light' }); +}" + +# Emulate reduced motion +playwright-cli run-code "async page => { + await page.emulateMedia({ reducedMotion: 'reduce' }); +}" + +# Emulate print media +playwright-cli run-code "async page => { + await page.emulateMedia({ media: 'print' }); +}" +``` + +## Wait Strategies + +```bash +# Wait for network idle +playwright-cli run-code "async page => { + await page.waitForLoadState('networkidle'); +}" + +# Wait for specific element +playwright-cli run-code "async page => { + await page.locator('.loading').waitFor({ state: 'hidden' }); +}" + +# Wait for function to return true +playwright-cli run-code "async page => { + await page.waitForFunction(() => window.appReady === true); +}" + +# Wait with timeout +playwright-cli run-code "async page => { + await page.locator('.result').waitFor({ timeout: 10000 }); +}" +``` + +## Frames and Iframes + +```bash +# Work with iframe +playwright-cli run-code "async page => { + const frame = page.locator('iframe#my-iframe').contentFrame(); + await frame.locator('button').click(); +}" + +# Get all frames +playwright-cli run-code "async page => { + const frames = page.frames(); + return frames.map(f => f.url()); +}" +``` + +## File Downloads + +```bash +# Handle file download +playwright-cli run-code "async page => { + const downloadPromise = page.waitForEvent('download'); + await page.getByRole('link', { name: 'Download' }).click(); + const download = await downloadPromise; + await download.saveAs('./downloaded-file.pdf'); + return download.suggestedFilename(); +}" +``` + +## Clipboard + +```bash +# Read clipboard (requires permission) +playwright-cli run-code "async page => { + await page.context().grantPermissions(['clipboard-read']); + return await page.evaluate(() => navigator.clipboard.readText()); +}" + +# Write to clipboard +playwright-cli run-code "async page => { + await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!'); +}" +``` + +## Page Information + +```bash +# Get page title +playwright-cli run-code "async page => { + return await page.title(); +}" + +# Get current URL +playwright-cli run-code "async page => { + return page.url(); +}" + +# Get page content +playwright-cli run-code "async page => { + return await page.content(); +}" + +# Get viewport size +playwright-cli run-code "async page => { + return page.viewportSize(); +}" +``` + +## JavaScript Execution + +```bash +# Execute JavaScript and return result +playwright-cli run-code "async page => { + return await page.evaluate(() => { + return { + userAgent: navigator.userAgent, + language: navigator.language, + cookiesEnabled: navigator.cookieEnabled + }; + }); +}" + +# Pass arguments to evaluate +playwright-cli run-code "async page => { + const multiplier = 5; + return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier); +}" +``` + +## Error Handling + +```bash +# Try-catch in run-code +playwright-cli run-code "async page => { + try { + await page.getByRole('button', { name: 'Submit' }).click({ timeout: 1000 }); + return 'clicked'; + } catch (e) { + return 'element not found'; + } +}" +``` + +## Complex Workflows + +```bash +# Login and save state +playwright-cli run-code "async page => { + await page.goto('https://example.com/login'); + await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com'); + await page.getByRole('textbox', { name: 'Password' }).fill('secret'); + await page.getByRole('button', { name: 'Sign in' }).click(); + await page.waitForURL('**/dashboard'); + await page.context().storageState({ path: 'auth.json' }); + return 'Login successful'; +}" + +# Scrape data from multiple pages +playwright-cli run-code "async page => { + const results = []; + for (let i = 1; i <= 3; i++) { + await page.goto(\`https://example.com/page/\${i}\`); + const items = await page.locator('.item').allTextContents(); + results.push(...items); + } + return results; +}" +``` diff --git a/.claude/skills/playwright-cli/references/session-management.md b/.claude/skills/playwright-cli/references/session-management.md new file mode 100644 index 0000000000..287e77fb7f --- /dev/null +++ b/.claude/skills/playwright-cli/references/session-management.md @@ -0,0 +1,226 @@ +# Browser Session Management + +Run multiple isolated browser sessions concurrently with state persistence. + +## Named Browser Sessions + +Use `-s` flag to isolate browser contexts: + +```bash +# Browser 1: Authentication flow +playwright-cli -s=auth open https://app.example.com/login + +# Browser 2: Public browsing (separate cookies, storage) +playwright-cli -s=public open https://example.com + +# Commands are isolated by browser session +playwright-cli -s=auth fill e1 "user@example.com" +playwright-cli -s=public snapshot +``` + +## Browser Session Isolation Properties + +Each browser session has independent: + +- Cookies +- LocalStorage / SessionStorage +- IndexedDB +- Cache +- Browsing history +- Open tabs + +## Browser Session Commands + +```bash +# List all browser sessions +playwright-cli list + +# Stop a browser session (close the browser) +playwright-cli close # stop the default browser +playwright-cli -s=mysession close # stop a named browser + +# Stop all browser sessions +playwright-cli close-all + +# Forcefully kill all daemon processes (for stale/zombie processes) +playwright-cli kill-all + +# Delete browser session user data (profile directory) +playwright-cli delete-data # delete default browser data +playwright-cli -s=mysession delete-data # delete named browser data +``` + +## Environment Variable + +Set a default browser session name via environment variable: + +```bash +export PLAYWRIGHT_CLI_SESSION="mysession" +playwright-cli open example.com # Uses "mysession" automatically +``` + +## Common Patterns + +### Concurrent Scraping + +```bash +#!/bin/bash +# Scrape multiple sites concurrently + +# Start all browsers +playwright-cli -s=site1 open https://site1.com & +playwright-cli -s=site2 open https://site2.com & +playwright-cli -s=site3 open https://site3.com & +wait + +# Take snapshots from each +playwright-cli -s=site1 snapshot +playwright-cli -s=site2 snapshot +playwright-cli -s=site3 snapshot + +# Cleanup +playwright-cli close-all +``` + +### A/B Testing Sessions + +```bash +# Test different user experiences +playwright-cli -s=variant-a open "https://app.com?variant=a" +playwright-cli -s=variant-b open "https://app.com?variant=b" + +# Compare +playwright-cli -s=variant-a screenshot +playwright-cli -s=variant-b screenshot +``` + +### Persistent Profile + +By default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk: + +```bash +# Use persistent profile (auto-generated location) +playwright-cli open https://example.com --persistent + +# Use persistent profile with custom directory +playwright-cli open https://example.com --profile=/path/to/profile +``` + +## Attaching to a Running Browser + +Use `attach` to connect to a browser that is already running, instead of launching a new one. + +### Attach by channel name + +Connect to a running Chrome or Edge instance by its channel name. The browser must have remote debugging enabled — navigate to `chrome://inspect/#remote-debugging` in the target browser and check "Allow remote debugging for this browser instance". + +```bash +# Attach to Chrome +playwright-cli attach --cdp=chrome + +# Attach to Chrome Canary +playwright-cli attach --cdp=chrome-canary + +# Attach to Microsoft Edge +playwright-cli attach --cdp=msedge + +# Attach to Edge Dev +playwright-cli attach --cdp=msedge-dev +``` + +Supported channels: `chrome`, `chrome-beta`, `chrome-dev`, `chrome-canary`, `msedge`, `msedge-beta`, `msedge-dev`, `msedge-canary`. + +When `--session` is not provided, the session is named after the channel (e.g. `--cdp=msedge` creates a session called `msedge`), so parallel attaches to Chrome and Edge don't collide on `default`. Pass `--session=` to override. + +### Attach via CDP endpoint + +Connect to a browser that exposes a Chrome DevTools Protocol endpoint: + +```bash +playwright-cli attach --cdp=http://localhost:9222 +``` + +### Attach via browser extension + +Connect to a browser with the Playwright extension installed: + +```bash +playwright-cli attach --extension +``` + +### Detach + +Tear down an attached session without affecting the external browser: + +```bash +# Detach the default attached session +playwright-cli detach + +# Detach a specific attached session +playwright-cli -s=msedge detach +``` + +`detach` only works on sessions created via `attach`. For sessions created via `open`, use `close`. + +## Default Browser Session + +When `-s` is omitted, commands use the default browser session: + +```bash +# These use the same default browser session +playwright-cli open https://example.com +playwright-cli snapshot +playwright-cli close # Stops default browser +``` + +## Browser Session Configuration + +Configure a browser session with specific settings when opening: + +```bash +# Open with config file +playwright-cli open https://example.com --config=.playwright/my-cli.json + +# Open with specific browser +playwright-cli open https://example.com --browser=firefox + +# Open in headed mode +playwright-cli open https://example.com --headed + +# Open with persistent profile +playwright-cli open https://example.com --persistent +``` + +## Best Practices + +### 1. Name Browser Sessions Semantically + +```bash +# GOOD: Clear purpose +playwright-cli -s=github-auth open https://github.com +playwright-cli -s=docs-scrape open https://docs.example.com + +# AVOID: Generic names +playwright-cli -s=s1 open https://github.com +``` + +### 2. Always Clean Up + +```bash +# Stop browsers when done +playwright-cli -s=auth close +playwright-cli -s=scrape close + +# Or stop all at once +playwright-cli close-all + +# If browsers become unresponsive or zombie processes remain +playwright-cli kill-all +``` + +### 3. Delete Stale Browser Data + +```bash +# Remove old browser data to free disk space +playwright-cli -s=oldsession delete-data +``` diff --git a/.claude/skills/playwright-cli/references/spec-driven-testing.md b/.claude/skills/playwright-cli/references/spec-driven-testing.md new file mode 100644 index 0000000000..70f05135eb --- /dev/null +++ b/.claude/skills/playwright-cli/references/spec-driven-testing.md @@ -0,0 +1,308 @@ +# Spec-driven testing (plan → generate → heal) + +End-to-end workflow for authoring and maintaining Playwright tests using `playwright-cli`. The three sections below can be used independently: + +- **Planning** — explore the app, produce a spec file describing what to test. +- **Generate** — turn a spec into Playwright test files. Update the spec if it's vague or stale. +- **Heal** — diagnose failing tests, fix the code, reconcile the spec with reality. + +All three lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics and [test-generation.md](test-generation.md) for how every `playwright-cli` action emits Playwright TypeScript. + +--- + +## 1. Planning + +Goal: produce a spec file (e.g. `specs/.plan.md`) that enumerates the scenarios to test. **Always** write the spec to a file. + +### 1.1 Prerequisite: workspace + +Check the workspace has Playwright installed before anything else: + +```bash +# Either of these confirms a workspace: +test -f playwright.config.ts || test -f playwright.config.js +npx --no-install playwright --version +``` + +If there is no Playwright install, bootstrap one and let the user pick the defaults: + +```bash +npm init playwright@latest +``` + +### 1.2 Prerequisite: seed test + +A **seed test** is a minimal test that lands the page in the state every scenario starts from: navigation to the app, any required login, feature flags, etc. Scenarios assume a fresh start _after_ the seed. `--debug=cli` pauses _inside_ this test, so the seed is where every planning and generation session begins. + +Minimum viable seed: + +```ts +// tests/seed.spec.ts +import { test } from "@playwright/test"; + +test("seed", async ({ page }) => { + await page.goto("https://example.com/"); +}); +``` + +Preferred — push navigation into a fixture so scenario tests reuse it: + +```ts +// tests/fixtures.ts +import { test as baseTest } from "@playwright/test"; +export { expect } from "@playwright/test"; + +export const test = baseTest.extend({ + page: async ({ page }, use) => { + await page.goto("https://example.com/"); + await use(page); + }, +}); +``` + +```ts +// tests/seed.spec.ts +import { test } from "./fixtures"; + +test("seed", async ({ page }) => { + // Fixture already navigates. This empty body tells agents where to start. +}); +``` + +If no seed exists, create one that at least navigates to the app. + +### 1.3 Explore the app + +Launch the app via the seed in the background and attach: + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test tests/seed.spec.ts --debug=cli +# wait for "Debugging Instructions" and the session name tw-XXXX +playwright-cli attach tw-XXXX +``` + +Resume so the seed runs, then probe the app: + +```bash +playwright-cli resume # resume so that seed test runs fully +playwright-cli snapshot # inventory of interactive elements +playwright-cli click e5 # follow a flow +playwright-cli eval "location.href" # read URL / state +playwright-cli show --annotate # ask the user to point at something +``` + +Map out: + +- Interactive surfaces (forms, buttons, lists, filters, modals). +- Primary user journeys end-to-end. +- Edge cases: empty states, validation errors, very long input, boundary values. +- Persistence: reload, local/session storage, URL fragments. +- Navigation: which controls change the URL, back/forward behaviour. + +**Important**: Do not just open the app url with playwright-cli, always go through the test to capture any custom setup done there. +**Important**: Stop the background test when done exploring. + +### 1.4 Write the spec file + +Save under `specs/.plan.md`. Use this structure: + +```markdown +# Test Plan + +## Application Overview + + + +## Test Scenarios + +### 1. + +**Seed:** `tests/seed.spec.ts` + +#### 1.1. + +**File:** `tests//.spec.ts` + +**Steps:** + +1. + - expect: + - expect: + +2. + - expect: + +#### 1.2. + +... + +### 2. + +**Seed:** `tests/seed.spec.ts` +... +``` + +Guidelines: + +- Each scenario is independent and starts from the seed's fresh state — never chain scenarios. +- Scenario names are kebab-case and match the test file name (`should-add-single-todo` → `should-add-single-todo.spec.ts`). +- Cover happy path, edge cases, validation, negative flows, persistence. +- Write steps at the user level ("Type 'Buy milk' into the input"), not the API level ("call `fill`"). +- Put observable outcomes in `- expect:` bullets; each becomes an assertion during generation. + +--- + +## 2. Generate + +Goal: take a spec file and produce Playwright test files. Optionally update the spec if it has drifted. + +### 2.1 Inputs + +- **Spec file**, e.g. `specs/basic-operations.plan.md`. +- **Target**: either a single scenario (e.g. `1.2`), a whole group (`1`), or all. +- **Seed file**, read from the `**Seed:**` line of the scenario's group. + +### 2.2 Generate one scenario + +For each target scenario, in sequence (never in parallel — scenarios share the seed session): + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli # background +playwright-cli attach tw-XXXX +# resume +``` + +**Do not** just open the app url with playwright-cli, always go through the test to capture any custom setup done there. + +Walk the scenario's `Steps:` one by one with `playwright-cli`, treating the spec as the plan and the live app as the source of truth. If a step is vague ("click the button" — which button?), references an element that no longer exists, or contradicts the app's actual behaviour, use your judgement: update the spec to match what the app really does, then keep going. Editing the spec mid-generation is expected. + +Every action prints the equivalent Playwright TypeScript (see [test-generation.md](test-generation.md)): + +```bash +playwright-cli snapshot # find refs +playwright-cli fill e3 "John Doe" # -> page.getByRole('textbox', {...}).fill(...) +playwright-cli press Enter +playwright-cli click e7 +``` + +For each `- expect:` bullet, add an explicit assertion. See [test-generation.md](test-generation.md) for details. + +Collect the generated code and write the test file at the path given in the spec: + +```ts +// spec: specs/basic-operations.plan.md +// seed: tests/seed.spec.ts +import { test, expect } from "./fixtures"; // or '@playwright/test' if no fixtures file + +test.describe("Singing in and out", () => { + test("should sign in", async ({ page }) => { + // 1. Navigate to the application + // (handled by the seed fixture) + + // 2. Type 'John Doe' into the username field + await page.getByRole("textbox", { name: "username" }).fill("John Doe"); + + // 3. Type password + await page.getByRole("textbox", { name: "password" }).fill("TestPassword"); + + // 4. Press Enter to submit + await page.getByRole("textbox", { name: "password" }).press("Enter"); + + await expect(page.getByRole("heading")).toContainText("Welcome, John Doe!"); + }); +}); +``` + +Rules: + +- **One test per file.** File path, describe name, and test name come verbatim from the spec (minus the ordinal). +- Prefix each numbered step with a `// N. ` comment before its actions. +- Use the describe group name verbatim from the spec (no `1.` ordinal). +- Import from `./fixtures` if the project has one; otherwise `@playwright/test`. +- **Important**: close the CLI session and stop the background test before moving to the next scenario. + +### 2.3 Generate multiple scenarios + +Loop 2.2 over the targeted scenarios one at a time, restarting the seed between each so every test starts from a clean page. This is safe to parallelise due to unique generated session names - just make sure each test run is stopped. + +### 2.4 Run generated tests + +After generation, run the new tests once: + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test tests//.spec.ts +``` + +Any failure goes to Section 3. + +--- + +## 3. Heal + +Goal: fix failing tests, and update the spec if the app's intended behaviour changed. + +### 3.1 Find failing tests + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test +``` + +Record the list of failing `:` entries and process them one at a time. Do not attempt parallel fixes — shared state and the single CLI session make that fragile. + +### 3.2 Debug one failure + +Run the single failing test in debug mode in the background, then attach: + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test tests//.spec.ts: --debug=cli +# wait for "Debugging Instructions" and the tw-XXXX session name +playwright-cli attach tw-XXXX +``` + +The test is paused at the start. Step forward or run to until just before the failing action or assertion, then diagnose: + +```bash +playwright-cli snapshot # did the element change / move / rename? +playwright-cli console # app-side errors? +playwright-cli network # failed request? wrong payload? +playwright-cli show --annotate # ask the user to point somewhere +``` + +Common causes: selector drift, new wrapper element, label/ARIA rename, timing (transition, async load), assertion text updated in the app, test data leaking between runs. + +Rehearse the corrected interaction with `playwright-cli` — the generated code in the output is what you paste back into the test. + +### 3.3 Apply the fix + +Edit the test file: update the locator, assertion, step order, or inputs to match the corrected behaviour. Stop the background debug run. Rerun the single test to confirm green. + +Never skip hooks or add sleeps as a fix. Never use `networkidle`. + +### 3.4 Reconcile with the spec + +Open the spec referenced by the `// spec:` header in the test file and locate the scenario that matches the test. + +- **Fix was purely technical** (locator drift, better assertion shape) and the spec's user-level behaviour still matches the app → leave the spec alone. +- **Fix changed user-visible steps, inputs, order, or expected outcomes** that the spec describes → update the spec to match reality. Keep the scenario id and file path stable; only the step / expect lines change. +- **Unclear whether the app change is intentional** (spec is stale) **or a regression** (test was right, app is wrong) → **stop and ask the user**. Provide: + - the scenario id (e.g. `2.3`), + - the spec lines that no longer match, + - the observed app behaviour (quote a snapshot excerpt or a concrete outcome). + +Only after the user answers, either update the spec (intentional change) or file/flag the test as covering a bug (regression). + +### 3.5 Iteration and giving up + +- Fix failures one at a time; rerun after each. +- If after thorough investigation you are confident the test is correct but the app is wrong _and_ the user has confirmed it's a bug: mark the test `test.fixme(...)` with a comment pointing at the user's decision or issue link. Never silently skip. + +--- + +## Cross-references + +| For... | See | +| ---------------------------------------------- | ---------------------------------------------- | +| `--debug=cli` / attach mechanics | [playwright-tests.md](playwright-tests.md) | +| How `playwright-cli` actions become TS | [test-generation.md](test-generation.md) | +| Mocking requests during exploration/generation | [request-mocking.md](request-mocking.md) | +| Managing the CLI browser session | [session-management.md](session-management.md) | diff --git a/.claude/skills/playwright-cli/references/storage-state.md b/.claude/skills/playwright-cli/references/storage-state.md new file mode 100644 index 0000000000..c856db5e40 --- /dev/null +++ b/.claude/skills/playwright-cli/references/storage-state.md @@ -0,0 +1,275 @@ +# Storage Management + +Manage cookies, localStorage, sessionStorage, and browser storage state. + +## Storage State + +Save and restore complete browser state including cookies and storage. + +### Save Storage State + +```bash +# Save to auto-generated filename (storage-state-{timestamp}.json) +playwright-cli state-save + +# Save to specific filename +playwright-cli state-save my-auth-state.json +``` + +### Restore Storage State + +```bash +# Load storage state from file +playwright-cli state-load my-auth-state.json + +# Reload page to apply cookies +playwright-cli open https://example.com +``` + +### Storage State File Format + +The saved file contains: + +```json +{ + "cookies": [ + { + "name": "session_id", + "value": "abc123", + "domain": "example.com", + "path": "/", + "expires": 1735689600, + "httpOnly": true, + "secure": true, + "sameSite": "Lax" + } + ], + "origins": [ + { + "origin": "https://example.com", + "localStorage": [ + { "name": "theme", "value": "dark" }, + { "name": "user_id", "value": "12345" } + ] + } + ] +} +``` + +## Cookies + +### List All Cookies + +```bash +playwright-cli cookie-list +``` + +### Filter Cookies by Domain + +```bash +playwright-cli cookie-list --domain=example.com +``` + +### Filter Cookies by Path + +```bash +playwright-cli cookie-list --path=/api +``` + +### Get Specific Cookie + +```bash +playwright-cli cookie-get session_id +``` + +### Set a Cookie + +```bash +# Basic cookie +playwright-cli cookie-set session abc123 + +# Cookie with options +playwright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax + +# Cookie with expiration (Unix timestamp) +playwright-cli cookie-set remember_me token123 --expires=1735689600 +``` + +### Delete a Cookie + +```bash +playwright-cli cookie-delete session_id +``` + +### Clear All Cookies + +```bash +playwright-cli cookie-clear +``` + +### Advanced: Multiple Cookies or Custom Options + +For complex scenarios like adding multiple cookies at once, use `run-code`: + +```bash +playwright-cli run-code "async page => { + await page.context().addCookies([ + { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true }, + { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' } + ]); +}" +``` + +## Local Storage + +### List All localStorage Items + +```bash +playwright-cli localstorage-list +``` + +### Get Single Value + +```bash +playwright-cli localstorage-get token +``` + +### Set Value + +```bash +playwright-cli localstorage-set theme dark +``` + +### Set JSON Value + +```bash +playwright-cli localstorage-set user_settings '{"theme":"dark","language":"en"}' +``` + +### Delete Single Item + +```bash +playwright-cli localstorage-delete token +``` + +### Clear All localStorage + +```bash +playwright-cli localstorage-clear +``` + +### Advanced: Multiple Operations + +For complex scenarios like setting multiple values at once, use `run-code`: + +```bash +playwright-cli run-code "async page => { + await page.evaluate(() => { + localStorage.setItem('token', 'jwt_abc123'); + localStorage.setItem('user_id', '12345'); + localStorage.setItem('expires_at', Date.now() + 3600000); + }); +}" +``` + +## Session Storage + +### List All sessionStorage Items + +```bash +playwright-cli sessionstorage-list +``` + +### Get Single Value + +```bash +playwright-cli sessionstorage-get form_data +``` + +### Set Value + +```bash +playwright-cli sessionstorage-set step 3 +``` + +### Delete Single Item + +```bash +playwright-cli sessionstorage-delete step +``` + +### Clear sessionStorage + +```bash +playwright-cli sessionstorage-clear +``` + +## IndexedDB + +### List Databases + +```bash +playwright-cli run-code "async page => { + return await page.evaluate(async () => { + const databases = await indexedDB.databases(); + return databases; + }); +}" +``` + +### Delete Database + +```bash +playwright-cli run-code "async page => { + await page.evaluate(() => { + indexedDB.deleteDatabase('myDatabase'); + }); +}" +``` + +## Common Patterns + +### Authentication State Reuse + +```bash +# Step 1: Login and save state +playwright-cli open https://app.example.com/login +playwright-cli snapshot +playwright-cli fill e1 "user@example.com" +playwright-cli fill e2 "password123" +playwright-cli click e3 + +# Save the authenticated state +playwright-cli state-save auth.json + +# Step 2: Later, restore state and skip login +playwright-cli state-load auth.json +playwright-cli open https://app.example.com/dashboard +# Already logged in! +``` + +### Save and Restore Roundtrip + +```bash +# Set up authentication state +playwright-cli open https://example.com +playwright-cli eval "() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }" + +# Save state to file +playwright-cli state-save my-session.json + +# ... later, in a new session ... + +# Restore state +playwright-cli state-load my-session.json +playwright-cli open https://example.com +# Cookies and localStorage are restored! +``` + +## Security Notes + +- Never commit storage state files containing auth tokens +- Add `*.auth-state.json` to `.gitignore` +- Delete state files after automation completes +- Use environment variables for sensitive data +- By default, sessions run in-memory mode which is safer for sensitive operations diff --git a/.claude/skills/playwright-cli/references/test-generation.md b/.claude/skills/playwright-cli/references/test-generation.md new file mode 100644 index 0000000000..9ce1fe5ad2 --- /dev/null +++ b/.claude/skills/playwright-cli/references/test-generation.md @@ -0,0 +1,138 @@ +# Test Generation + +Generate Playwright test code automatically as you interact with the browser. + +## How It Works + +Every action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. +This code appears in the output and can be copied directly into your test files. + +## Example Workflow + +```bash +# Start a session +playwright-cli open https://example.com/login + +# Take a snapshot to see elements +playwright-cli snapshot +# Output shows: e1 [textbox "Email"], e2 [textbox "Password"], e3 [button "Sign In"] + +# Fill form fields - generates code automatically +playwright-cli fill e1 "user@example.com" +# Ran Playwright code: +# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com'); + +playwright-cli fill e2 "password123" +# Ran Playwright code: +# await page.getByRole('textbox', { name: 'Password' }).fill('password123'); + +playwright-cli click e3 +# Ran Playwright code: +# await page.getByRole('button', { name: 'Sign In' }).click(); +``` + +## Building a Test File + +Collect the generated code into a Playwright test: + +```typescript +import { test, expect } from "@playwright/test"; + +test("login flow", async ({ page }) => { + // Generated code from playwright-cli session: + await page.goto("https://example.com/login"); + await page.getByRole("textbox", { name: "Email" }).fill("user@example.com"); + await page.getByRole("textbox", { name: "Password" }).fill("password123"); + await page.getByRole("button", { name: "Sign In" }).click(); + + // Add assertions + await expect(page).toHaveURL(/.*dashboard/); +}); +``` + +## Best Practices + +### 1. Use Semantic Locators + +The generated code uses role-based locators when possible, which are more resilient: + +```typescript +// Generated (good - semantic) +await page.getByRole("button", { name: "Submit" }).click(); + +// Avoid (fragile - CSS selectors) +await page.locator("#submit-btn").click(); +``` + +### 2. Explore Before Recording + +Take snapshots to understand the page structure before recording actions: + +```bash +playwright-cli open https://example.com +playwright-cli snapshot +# Review the element structure +playwright-cli click e5 +``` + +### 3. Add Assertions Manually + +Generated code captures actions but not assertions. Add expectations in your test using one of the recommended matchers: + +- `toBeVisible()` — element is rendered and visible +- `toHaveText(text)` — element text content matches +- `toHaveValue(value) / toBeEmpty()` — input/select value matches +- `toBeChecked() / toBeUnchecked()` — checkbox state matches +- `toMatchAriaSnapshot(snapshot)` — page (or locator) matches a partial accessibility snapshot + +Use `playwright-cli generate-locator ` to produce the locator expression for the assertion, and the snapshot/eval commands to capture the expected value. + +When asserting text content, make sure that generated locator does not contain text from the element itself. `getByTestId()` or `getByLabel()` usually work well with asserting text. When locator is text-based, prefer `toBeVisible()` instead. + +Snapshot to be matched does not have to contain all the information - only capture what's necessary for the assertion. You can use regular expressions for unstable values. + +```bash +# Get a stable locator for an element ref to use in the assertion +playwright-cli --raw generate-locator e5 +# getByRole('button', { name: 'Submit' }) + +# Capture expected text content for toHaveText +playwright-cli --raw eval "el => el.textContent" e5 + +# Capture expected input value for toHaveValue/toBeEmpty +playwright-cli --raw eval "el => el.value" e5 + +# Capture expected aria snapshot for toMatchAriaSnapshot/toBeChecked +# (whole page, or use a ref to scope to a region) +playwright-cli --raw snapshot +playwright-cli --raw snapshot e5 +``` + +```typescript +// Generated action +await page.getByRole("button", { name: "Submit" }).click(); + +// Manual assertions using the outputs above: +await expect(page.getByRole("alert", { name: "Success" })).toBeVisible(); +await expect(page.getByTestId("main-header")).toHaveText("Welcome, user"); +await expect(page.getByRole("textbox", { name: "Email" })).toHaveValue( + "user@example.com", +); +await expect( + page.getByRole("checkbox", { name: "Enable notifications" }), +).toBeChecked(); + +// toMatchAriaSnapshot on the whole page, finds a matching region +await expect(page).toMatchAriaSnapshot(` + - heading "Welcome, user" + - link /\\d+ new messages?/ + - button "Sign out" +`); + +// toMatchAriaSnapshot scoped to a region +await expect(page.getByRole("navigation")).toMatchAriaSnapshot(` + - link "Home" + - link /\\d+ new messages?/ + - link "Profile" +`); +``` diff --git a/.claude/skills/playwright-cli/references/tracing.md b/.claude/skills/playwright-cli/references/tracing.md new file mode 100644 index 0000000000..d81cdeb12b --- /dev/null +++ b/.claude/skills/playwright-cli/references/tracing.md @@ -0,0 +1,142 @@ +# Tracing + +Capture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs. + +## Basic Usage + +```bash +# Start trace recording +playwright-cli tracing-start + +# Perform actions +playwright-cli open https://example.com +playwright-cli click e1 +playwright-cli fill e2 "test" + +# Stop trace recording +playwright-cli tracing-stop +``` + +## Trace Output Files + +When you start tracing, Playwright creates a `traces/` directory with several files: + +### `trace-{timestamp}.trace` + +**Action log** - The main trace file containing: + +- Every action performed (clicks, fills, navigations) +- DOM snapshots before and after each action +- Screenshots at each step +- Timing information +- Console messages +- Source locations + +### `trace-{timestamp}.network` + +**Network log** - Complete network activity: + +- All HTTP requests and responses +- Request headers and bodies +- Response headers and bodies +- Timing (DNS, connect, TLS, TTFB, download) +- Resource sizes +- Failed requests and errors + +### `resources/` + +**Resources directory** - Cached resources: + +- Images, fonts, stylesheets, scripts +- Response bodies for replay +- Assets needed to reconstruct page state + +## What Traces Capture + +| Category | Details | +| --------------- | -------------------------------------------------- | +| **Actions** | Clicks, fills, hovers, keyboard input, navigations | +| **DOM** | Full DOM snapshot before/after each action | +| **Screenshots** | Visual state at each step | +| **Network** | All requests, responses, headers, bodies, timing | +| **Console** | All console.log, warn, error messages | +| **Timing** | Precise timing for each operation | + +## Use Cases + +### Debugging Failed Actions + +```bash +playwright-cli tracing-start +playwright-cli open https://app.example.com + +# This click fails - why? +playwright-cli click e5 + +playwright-cli tracing-stop +# Open trace to see DOM state when click was attempted +``` + +### Analyzing Performance + +```bash +playwright-cli tracing-start +playwright-cli open https://slow-site.com +playwright-cli tracing-stop + +# View network waterfall to identify slow resources +``` + +### Capturing Evidence + +```bash +# Record a complete user flow for documentation +playwright-cli tracing-start + +playwright-cli open https://app.example.com/checkout +playwright-cli fill e1 "4111111111111111" +playwright-cli fill e2 "12/25" +playwright-cli fill e3 "123" +playwright-cli click e4 + +playwright-cli tracing-stop +# Trace shows exact sequence of events +``` + +## Trace vs Video vs Screenshot + +| Feature | Trace | Video | Screenshot | +| ----------------------- | ----------- | ----------- | ---------------- | +| **Format** | .trace file | .webm video | .png/.jpeg image | +| **DOM inspection** | Yes | No | No | +| **Network details** | Yes | No | No | +| **Step-by-step replay** | Yes | Continuous | Single frame | +| **File size** | Medium | Large | Small | +| **Best for** | Debugging | Demos | Quick capture | + +## Best Practices + +### 1. Start Tracing Before the Problem + +```bash +# Trace the entire flow, not just the failing step +playwright-cli tracing-start +playwright-cli open https://example.com +# ... all steps leading to the issue ... +playwright-cli tracing-stop +``` + +### 2. Clean Up Old Traces + +Traces can consume significant disk space: + +```bash +# Remove traces older than 7 days +find .playwright-cli/traces -mtime +7 -delete +``` + +## Limitations + +- Traces add overhead to automation +- Large traces can consume significant disk space +- Some dynamic content may not replay perfectly diff --git a/.claude/skills/playwright-cli/references/video-recording.md b/.claude/skills/playwright-cli/references/video-recording.md new file mode 100644 index 0000000000..dd1f9e8bce --- /dev/null +++ b/.claude/skills/playwright-cli/references/video-recording.md @@ -0,0 +1,157 @@ +# Video Recording + +Capture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec). + +## Basic Recording + +```bash +# Open browser first +playwright-cli open + +# Start recording +playwright-cli video-start demo.webm + +# Add a chapter marker for section transitions +playwright-cli video-chapter "Getting Started" --description="Opening the homepage" --duration=2000 + +# Navigate and perform actions +playwright-cli goto https://example.com +playwright-cli snapshot +playwright-cli click e1 + +# Add another chapter +playwright-cli video-chapter "Filling Form" --description="Entering test data" --duration=2000 +playwright-cli fill e2 "test input" + +# Stop and save +playwright-cli video-stop +``` + +## Best Practices + +### 1. Use Descriptive Filenames + +```bash +# Include context in filename +playwright-cli video-start recordings/login-flow-2024-01-15.webm +playwright-cli video-start recordings/checkout-test-run-42.webm +``` + +### 2. Record entire hero scripts. + +When recording a video for the user or as a proof of work, it is best to create a code snippet and execute it with run-code. +It allows pulling appropriate pauses between the actions and annotating the video. There are new Playwright APIs for that. + +1. Perform scenario using CLI and take note of all locators and actions. You'll need those locators to request their bounding boxes for highlight. +2. Create a file with the intended script for video (below). Use pressSequentially w/ delay for nice typing, make reasonable pauses. +3. Use playwright-cli run-code --filename your-script.js + +**Important**: Overlays are `pointer-events: none` — they do not interfere with page interactions. You can safely keep sticky overlays visible while clicking, filling, or performing any actions on the page. + +```js +async (page) => { + await page.screencast.start({ + path: "video.webm", + size: { width: 1280, height: 800 }, + }); + await page.goto("https://demo.playwright.dev/todomvc"); + + // Show a chapter card — blurs the page and shows a dialog. + // Blocks until duration expires, then auto-removes. + // Use this for simple use cases, but always feel free to hand-craft your own beautiful + // overlay via await page.screencast.showOverlay(). + await page.screencast.showChapter("Adding Todo Items", { + description: "We will add several items to the todo list.", + duration: 2000, + }); + + // Perform action + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .pressSequentially("Walk the dog", { delay: 60 }); + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .press("Enter"); + await page.waitForTimeout(1000); + + // Show next chapter + await page.screencast.showChapter("Verifying Results", { + description: "Checking the item appeared in the list.", + duration: 2000, + }); + + // Add a sticky annotation that stays while you perform actions. + // Overlays are pointer-events: none, so they won't block clicks. + const annotation = await page.screencast.showOverlay(` +
+ ✓ Item added successfully +
+ `); + + // Perform more actions while the annotation is visible + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .pressSequentially("Buy groceries", { delay: 60 }); + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .press("Enter"); + await page.waitForTimeout(1500); + + // Remove the annotation when done + await annotation.dispose(); + + // You can also highlight relevant locators and provide contextual annotations. + const bounds = await page.getByText("Walk the dog").boundingBox(); + await page.screencast.showOverlay( + ` +
+
+
Check it out, it is right above this text +
+ `, + { duration: 2000 }, + ); + + await page.screencast.stop(); +}; +``` + +Embrace creativity, overlays are powerful. + +### Overlay API Summary + +| Method | Use Case | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| `page.screencast.showChapter(title, { description?, duration?, styleSheet? })` | Full-screen chapter card with blurred backdrop — ideal for section transitions | +| `page.screencast.showOverlay(html, { duration? })` | Custom HTML overlay — use for callouts, labels, highlights | +| `disposable.dispose()` | Remove a sticky overlay added without duration | +| `page.screencast.hideOverlays()` / `page.screencast.showOverlays()` | Temporarily hide/show all overlays | + +## Tracing vs Video + +| Feature | Video | Tracing | +| -------- | -------------------- | ---------------------------------------- | +| Output | WebM file | Trace file (viewable in Trace Viewer) | +| Shows | Visual recording | DOM snapshots, network, console, actions | +| Use case | Demos, documentation | Debugging, analysis | +| Size | Larger | Smaller | + +## Limitations + +- Recording adds slight overhead to automation +- Large recordings can consume significant disk space diff --git a/.claude/skills/testing-skill/SKILL.md b/.claude/skills/testing-skill/SKILL.md new file mode 100644 index 0000000000..ab70133c11 --- /dev/null +++ b/.claude/skills/testing-skill/SKILL.md @@ -0,0 +1,62 @@ +--- +name: testing-skill +description: Instructions for writing, running, and updating unit/end-to-end tests. Should be used when prompted specifically to add tests for a given feature, bug, or regression. +--- + +# Testing + +In most cases, once a feature, bug fix, or other modification has been written, it will need to have tests added, or existing tests updated. + +## Test File Locations + +### Unit Tests + +`/tests/src/unit`: Contains the bulk of unit tests, mainly relating to interoperability between BlockNote's JSON format and HTML/Markdown. Also includes some miscellaneous tests, like React rendering, selection handling, and NextJS integration. + +`/packages/core/src/api`: Contains mainly tests for getting, inserting, updating, and removing blocks, etc, under `/blockManipulation/commands`. Also includes tests for intermediary functionality between BlockNote and the underlying TipTap editor, like converting between blocks & nodes, or setting editor event handlers. + +`/packages/xl-*`: Contain tests for functionality included in a given `xl-*` package. + +### End-to-End Tests + +`tests/src/end-to-end`: Any test which interacts with the editor UI or simulates user interaction goes here. New subdirectories can be added if the functionality being tested is not covered by any of the existing ones. Important note about existing E2E tests - many are written poorly and should only loosely be used as reference. We want to avoid abstraction layers and `waitForTimeout` as much as possible. + +## When & How to Add Tests + +In general, we expect a change in code to result in failing test cases. If this does not happen, tests should be added and checked to ensure they pass with the code changes while failing without them. + +However, this may not be true when adding edge case handling or a new feature, where existing tests may all continue to pass. In this case, tests should be added as necessary to cover all of the new functionality. We should still ensure that the new tests pass with the new code changes while failing without them. + +We want to avoid adding end-to-end tests where it's possible to use unit tests instead. + +## Running & Updating Tests + +### Unit Tests + +Unit tests can be run from the root directory using `vp run test`, which will run all of them across all directories. A specific test file may be targeted by appending its name, i.e. `vp run test fileName`. Individual tests in a file may be disabled using `skip`, i.e. `it.skip("Test name", ...)` (remember to revert this once all tests pass). + +Updating tests can be done by adding the `-u` argument, i.e. `vp run test -u`. All of the other things you can do to scope which tests to target still apply. + +### End-to-End Tests + +End-to-end tests run inside a docker container. While its possible to run them outside of it, we do not have existing snapshots to compare results with, and the results sometimes differ to when they're run within Docker, so it's not worth doing. + +To run end-to-end tests, you must first build the project and run the preview. You can do this by running `vp start` from the root directory. + +You can then run the tests from the `/tests` directory using the following command: + +``` +docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.51.1-noble npx playwright test +``` + +A specific test file may be targeted by appending its name, i.e. `... npx playwright test fileName`. Individual tests in a file may be disabled using `skip`, i.e. `test.skip("Test name", ...)` (remember to revert this once all tests pass). + +Updating tests can be done by adding the `-u` argument, i.e. `... npx playwright test -u`. All of the other things you can do to scope which tests to target still apply. + +Note that running this command may result in errors or other issues, listed below along with what to do when encountered: + +- **Tests failing to navigating to preview**: project should be built and the preview started, after which the command should be run again. +- **Docker not running**: the user should be notified to launch Docker. +- **Incorrect Playwright image version**: update Playwright images and re-run the command. + +When testing a visual change, prefer writing screenshots to verify that the change is working as expected. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..87697e5b29 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Keep the e2e image build context lean and Linux-clean: never copy host +# (macOS) node_modules or build outputs — deps are installed fresh inside the +# image. What actually lands in the image is selected explicitly by the COPY +# steps in tests/Dockerfile (manifests + example apps), not by exclusions here; +# these entries just stop the heavy/irrelevant trees from bloating the context. +**/node_modules +**/dist +**/types +**/.vite +**/.vite-plus +**/*.tsbuildinfo +**/.DS_Store +.git +**/test-results +**/blob-report +**/playwright-report +tests/.vitest-attachments diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000000..bce33191f8 --- /dev/null +++ b/.env.sample @@ -0,0 +1,2 @@ +export NX_SELF_HOSTED_REMOTE_CACHE_SERVER=https://cache.nickthesick.com +export NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN=g8@ucL8em4*Z9TKXDY9OEX@!upf^Nz9 \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 983f7ae31e..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,57 +0,0 @@ -const typeScriptExtensions = [".ts", ".cts", ".mts", ".tsx"]; - -const allExtensions = [...typeScriptExtensions, ".js", ".jsx"]; - -module.exports = { - root: true, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "react-app", - "react-app/jest", - ], - parser: "@typescript-eslint/parser", - plugins: ["import", "@typescript-eslint"], - settings: { - "import/extensions": allExtensions, - "import/external-module-folders": ["node_modules", "node_modules/@types"], - "import/parsers": { - "@typescript-eslint/parser": typeScriptExtensions, - }, - "import/resolver": { - node: { - extensions: allExtensions, - }, - }, - }, - rules: { - curly: 1, - "import/no-extraneous-dependencies": [ - "error", - { - devDependencies: true, - optionalDependencies: false, - peerDependencies: false, - bundledDependencies: false, - }, - ], - // would be nice to enable these rules later, but they are too noisy right now - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/ban-ts-comment": "off", - "import/no-cycle": "error", - // doesn't work: - // "import/no-restricted-paths": [ - // "error", - // { - // zones: [ - // { - // target: "./src/**/*", - // from: "./types/**/*", - // message: "Import from this module to types is not allowed.", - // }, - // ], - // }, - // ], - }, -}; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index c0c4e4a722..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** - - -**To Reproduce** - - -**Misc** -- Node version: -- Package manager: -- Browser: -- [ ] I'm a [sponsor](https://github.com/sponsors/YousefED) and would appreciate if you could look into this sooner than later 💖 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..372e429b34 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,101 @@ +name: Bug report +description: Report a bug or broken behavior in BlockNote +labels: + - bug + - needs-triage +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug! + Please use this template to describe **broken or incorrect behavior**. + + Feature ideas or questions should go to **Discussions**. + + - type: textarea + id: problem + attributes: + label: What’s broken? + description: > + Describe the problem clearly and concisely. + What is happening that should not be happening? + placeholder: > + Example: + When editing a table cell and pressing Enter, the editor crashes and the document cannot be recovered. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What did you expect to happen? + description: > + Describe the expected or correct behavior. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: > + Provide clear steps so we can reproduce the issue. + If possible, an online reproduction (e.g. StackBlitz) is extremely helpful. + placeholder: | + 1. Create a new document + 2. Insert a table + 3. Click inside a cell + 4. Press Enter + + Optional: If you can, provide a minimal online reproduction. + You can use this starter sandbox: + https://stackblitz.com/github/TypeCellOS/BlockNote/tree/main/examples/01-basic/01-minimal?file=App.tsx + validations: + required: false + + - type: input + id: version + attributes: + label: BlockNote version + description: > + Optional — specify the version you’re using, if known. + placeholder: e.g. v0.18.2 + validations: + required: false + + - type: input + id: environment + attributes: + label: Environment + description: > + Browser, OS, framework, or other relevant environment details. + placeholder: e.g. Chrome 121, macOS 14, React 18 + validations: + required: false + + - type: textarea + id: additional + attributes: + label: Additional context + description: > + Screenshots, videos, logs, or any other context that might help. + validations: + required: false + + - type: checkboxes + id: contribute + attributes: + label: Contribution + options: + - label: "I'd be interested in contributing a fix for this issue" + required: false + + - type: checkboxes + id: sponsor + attributes: + label: Sponsor + description: > + Optional — helps us prioritize first response according to our SLA. + options: + - label: "I'm a [sponsor](https://www.blocknotejs.org/pricing) and would appreciate if you could look into this sooner than later 💖" + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..d431145bc6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false + +contact_links: + - name: Share an idea or suggest an enhancement + url: https://github.com/TypeCellOS/BlockNote/discussions/categories/ideas-enhancements + about: Share feature ideas, enhancement suggestions, or other ideas for the BlockNote project. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 11fc491ef1..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..4cd39c8b21 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,68 @@ +version: 2 +updates: + - package-ecosystem: "npm" + versioning-strategy: "lockfile-only" + directories: + - "/" + - "/packages/*" + schedule: + interval: "weekly" + labels: + - "npm dependencies" + commit-message: + prefix: "chore" + allow: + # @tiptap packages + - dependency-name: "@tiptap/core" + - dependency-name: "@tiptap/pm" + - dependency-name: "@tiptap/react" + - dependency-name: "@tiptap/extensions" + - dependency-name: "@tiptap/extension-bold" + - dependency-name: "@tiptap/extension-code" + - dependency-name: "@tiptap/extension-horizontal-rule" + - dependency-name: "@tiptap/extension-italic" + + - dependency-name: "@tiptap/extension-paragraph" + - dependency-name: "@tiptap/extension-strike" + - dependency-name: "@tiptap/extension-text" + - dependency-name: "@tiptap/extension-underline" + # prosemirror packages + - dependency-name: "prosemirror-changeset" + - dependency-name: "prosemirror-highlight" + - dependency-name: "prosemirror-model" + - dependency-name: "prosemirror-state" + - dependency-name: "prosemirror-tables" + - dependency-name: "prosemirror-transform" + - dependency-name: "prosemirror-view" + # react packages + - dependency-name: "react" + - dependency-name: "react-dom" + # yjs packages + - dependency-name: "yjs" + - dependency-name: "y-prosemirror" + ignore: + # Hono packages are used only in the demo AI server and are not part of + # the main editor/runtime surface area. + - dependency-name: "hono" + - dependency-name: "@hono/node-server" + - dependency-name: "@hono/*" + groups: + editor-dependencies: + patterns: + - "@tiptap/*" + - "prosemirror-*" + - "react" + - "react-dom" + - "yjs" + - "y-prosemirror" + cooldown: + default-days: 7 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci" + include: "scope" + cooldown: + default-days: 7 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..9870532e04 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ +# Summary + + + +## Rationale + + + +## Changes + + + +## Impact + + + +## Testing + + + +## Screenshots/Video + + + +## Checklist + +- [ ] Code follows the project's coding standards. +- [ ] Unit tests covering the new feature have been added. +- [ ] All existing tests pass. +- [ ] The documentation has been updated to reflect the new feature + +## Additional Notes + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b62b934ecb..2f8f7dfc38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,79 +6,196 @@ on: pull_request: types: [opened, synchronize, reopened, edited] +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: name: Build runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-node@v3 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: - node-version: "18.x" + fetch-depth: 100 + persist-credentials: false - - name: Cache node modules - uses: actions/cache@v3 - env: - cache-name: cache-node-modules + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - name: cache playwright - id: playwright-cache - uses: actions/cache@v3 - with: - path: ~/.cache/ms-playwright - key: pw-new-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} + node-version-file: ".node-version" + cache: true - name: Install Dependencies - run: npm install - - - name: Bootstrap packages - run: npm run bootstrap + run: vp install - name: Lint packages - run: npm run lint - env: - CI: true + run: vp lint - name: Build packages - run: npm run build - env: - CI: true + run: vp run -r build - name: Run unit tests - run: npm run test - env: - CI: true + run: vp run -r test + + - name: Run Next.js integration test (production build) + run: NEXTJS_TEST_MODE=build vp test run src/unit/nextjs/serverUtil.test.ts + working-directory: tests + + - name: Upload webpack stats artifact (editor) + uses: relative-ci/agent-upload-artifact-action@a2b5741b4f7e6a989c84ec1a3059696b23c152e5 # v2 + with: + webpackStatsFile: ./playground/dist/webpack-stats.json + artifactName: relative-ci-artifacts-editor + + - name: Soft release + id: soft-release + run: vp dlx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact + + e2e: + # Vitest Browser Mode runs in the Playwright Linux container — the same + # environment as the local Docker run — so behaviour matches local dev. + # The suite resolves every `@blocknote/*` import to its `src/` via the + # aliases in `tests/vite.config.browser.ts` (vite transpiles the package + # sources on the fly), so the packages do NOT need to be built to `dist` + # first — `vp install` + the checked-out sources are enough. + name: "E2E - ${{ matrix.browser }} (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})" + runs-on: ubuntu-latest + timeout-minutes: 30 + container: + image: mcr.microsoft.com/playwright:v1.60.0-noble + strategy: + fail-fast: false + matrix: + browser: [chromium, firefox, webkit] + shardIndex: [1, 2] + shardTotal: [2] + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + fetch-depth: 100 + persist-credentials: false + + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + cache: true + + - name: Install dependencies + run: vp install + + # No preview server: Vitest Browser Mode serves the tests + mounted example + # apps itself. `--browser` selects this matrix job's browser and + # `--shard=/` splits that browser's test files across two + # parallel machines. Each shard records a machine-readable `blob` report + # (named per browser+shard so they don't collide); the `merge-reports` job + # stitches every browser's shards into one HTML report afterwards. + - name: Run e2e tests (${{ matrix.browser }} ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) + run: HOME=/root vp test -c vite.config.browser.ts --run --browser ${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=default --reporter=blob --outputFile.blob=blob-report/blob-${{ matrix.browser }}-${{ matrix.shardIndex }}.json + working-directory: tests - - name: Run server - run: npm run start:built & npx wait-on http://localhost:3000 - env: - CI: true + - name: Upload blob report (${{ matrix.browser }} ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} + with: + name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/blob-report/ + retention-days: 1 + + - name: Upload failure artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ failure() }} + with: + name: e2e-attachments-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/.vitest-attachments/ + retention-days: 7 + + merge-reports: + # Stitch every browser+shard blob report into a single navigable HTML + # report (the Vitest equivalent of `playwright merge-reports`). Runs even + # when a shard failed, so the report always covers every browser. + name: "E2E Report" + runs-on: ubuntu-latest + needs: e2e + if: ${{ !cancelled() }} + timeout-minutes: 15 + # Same container as the e2e shards: `--mergeReports` doesn't run tests or + # launch browsers, but the HTML reporter still resolves the test files' + # browser environment, so the browser config must stay enabled (and present). + container: + image: mcr.microsoft.com/playwright:v1.60.0-noble + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + fetch-depth: 100 + persist-credentials: false - - name: Install Playwright - run: npx playwright install --with-deps + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + cache: true - - name: Run Playwright tests - working-directory: ./tests - run: npx playwright test + - name: Install dependencies + run: vp install - - uses: actions/upload-artifact@v3 - if: always() + # Gather every shard's blob into one directory (blob-report-chromium-1, …). + - name: Download blob reports + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - name: playwright-report + path: tests/blob-report + pattern: blob-report-* + merge-multiple: true + + # Re-emit a single HTML report from the merged blobs (no tests are run). + - name: Merge into HTML report + run: HOME=/root vp test -c vite.config.browser.ts --mergeReports=blob-report --reporter=html + working-directory: tests + + - name: Upload merged HTML report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} + with: + name: e2e-report path: tests/playwright-report/ - retention-days: 30 + retention-days: 7 - - name: Upload webpack stats artifact (editor) - uses: relative-ci/agent-upload-artifact-action@v1 + deploy-report: + # Publish the merged report to a GitHub Pages PR preview and comment the + # link on the PR (Vercel-style). Pages serves it over HTTP, so the report's + # data loads correctly — unlike opening the downloaded artifact over file:// + # (the @vitest/ui report fetches its data and is blocked by CORS there). + # + # Default `needs` semantics: this runs only when `merge-reports` succeeded + # (i.e. a report exists) — but regardless of whether the tests passed, so a + # red run still gets a navigable preview. Skipped for fork PRs, whose + # read-only token can't push to gh-pages or comment (they keep the artifact). + name: "E2E Report Preview" + runs-on: ubuntu-latest + needs: merge-reports + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: - webpackStatsFile: ./examples/editor/dist/webpack-stats.json - artifactName: relative-ci-artifacts-editor + persist-credentials: false + + - name: Download merged report + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: e2e-report + path: e2e-report + + # Deploys to the `gh-pages` branch under `pr-preview/pr-/` and posts a + # sticky comment with the URL. The companion `pr-preview-cleanup` workflow + # removes it when the PR closes. + - name: Deploy report to PR preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1 + with: + source-dir: e2e-report + preview-branch: gh-pages + umbrella-dir: pr-preview + action: deploy diff --git a/.github/workflows/fresh-install-tests.yml b/.github/workflows/fresh-install-tests.yml new file mode 100644 index 0000000000..a1b441678d --- /dev/null +++ b/.github/workflows/fresh-install-tests.yml @@ -0,0 +1,148 @@ +name: Fresh Install Tests + +# Periodically tests BlockNote with the latest versions of its production +# dependencies (within declared semver ranges). This catches breakage when a +# new release of a dep like @tiptap/* or prosemirror-* ships and conflicts +# with BlockNote's declared ranges — the kind of failure a user would hit when +# running `npm install @blocknote/react` in a fresh project. +# +# Only production dependencies of published (non-private) packages are updated. +# DevDependencies (vitest, vite, typescript, etc.) stay pinned to the lockfile, +# so test tooling churn doesn't cause false positives. + +on: + schedule: + - cron: "0 2 * * *" # Daily at 02:00 UTC + workflow_dispatch: # Allow manual runs + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + fresh-install-unit-tests: + name: Unit Tests (Fresh Dep Resolution) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - id: checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - id: setup_vp + uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + # Intentionally no install cache — we want fresh prod dep resolution. + cache: false + + - id: install_dependencies + name: Install dependencies + run: vp install + + - id: update_prod_deps + name: Update prod deps of published packages + # Resolves production dependencies of every published (non-private) + # workspace package to the latest version within their declared semver + # ranges. This simulates what a user gets when running + # `npm install @blocknote/react` in a fresh project. + # DevDependencies are left at their lockfile versions. + # NB: this uses pnpm directly because vp doesn't expose a `--prod`-only + # update flow; setup-vp installs pnpm on PATH so this still works. + run: | + FILTERS=$(node -e " + const fs = require('fs'); + const path = require('path'); + fs.readdirSync('packages').forEach(dir => { + try { + const pkg = JSON.parse(fs.readFileSync(path.join('packages', dir, 'package.json'), 'utf8')); + if (!pkg.private && pkg.name) process.stdout.write('--filter ' + pkg.name + ' '); + } catch {} + }); + ") + echo "Updating prod deps for: $FILTERS" + eval pnpm update --prod $FILTERS + + - id: dedupe_deps + name: Dedupe transitive dependencies + # After bumping the publishable packages' prod deps, collapse any + # duplicate transitive resolutions (e.g. @tiptap/core + @tiptap/pm) + # that would otherwise differ between the updated publishable packages + # and the un-updated examples/playground. Without this, TypeScript + # treats the two copies' exports as unrelated types and example-editor + # fails to build (TS2322 on Extension vs AnyExtension). + # Dedupe only rewrites the lockfile — it does NOT modify package.json, + # so the examples' "@blocknote/*": "latest" specs (which is what + # CodeSandbox users see) stay intact. + run: pnpm dedupe + + - id: build_packages + name: Build packages + run: vp run -r build + + - id: run_unit_tests + name: Run unit tests + run: vp run -r test + + - name: Notify Slack on workflow failure + if: ${{ failure() }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + REPOSITORY: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + RUN_ID: ${{ github.run_id }} + RUN_NUMBER: ${{ github.run_number }} + RUN_ATTEMPT: ${{ github.run_attempt }} + BRANCH: ${{ github.ref_name }} + STEPS_CHECKOUT_OUTCOME: ${{ steps.checkout.outcome }} + STEPS_SETUP_VP_OUTCOME: ${{ steps.setup_vp.outcome }} + STEPS_INSTALL_DEPENDENCIES_OUTCOME: ${{ steps.install_dependencies.outcome }} + STEPS_UPDATE_PROD_DEPS_OUTCOME: ${{ steps.update_prod_deps.outcome }} + STEPS_DEDUPE_DEPS_OUTCOME: ${{ steps.dedupe_deps.outcome }} + STEPS_BUILD_PACKAGES_OUTCOME: ${{ steps.build_packages.outcome }} + STEPS_RUN_UNIT_TESTS_OUTCOME: ${{ steps.run_unit_tests.outcome }} + run: | + if [ -z "$SLACK_WEBHOOK_URL" ]; then + echo "SLACK_WEBHOOK_URL is not configured; skipping Slack notification." + exit 0 + fi + + failed_step="Unknown step" + if [ "${STEPS_CHECKOUT_OUTCOME}" = "failure" ]; then + failed_step="Checkout repository" + elif [ "${STEPS_SETUP_VP_OUTCOME}" = "failure" ]; then + failed_step="Setup Vite+" + elif [ "${STEPS_INSTALL_DEPENDENCIES_OUTCOME}" = "failure" ]; then + failed_step="Install dependencies" + elif [ "${STEPS_UPDATE_PROD_DEPS_OUTCOME}" = "failure" ]; then + failed_step="Update prod deps of published packages" + elif [ "${STEPS_DEDUPE_DEPS_OUTCOME}" = "failure" ]; then + failed_step="Dedupe transitive dependencies" + elif [ "${STEPS_BUILD_PACKAGES_OUTCOME}" = "failure" ]; then + failed_step="Build packages" + elif [ "${STEPS_RUN_UNIT_TESTS_OUTCOME}" = "failure" ]; then + failed_step="Run unit tests" + fi + + run_url="https://github.com/${REPOSITORY}/actions/runs/${RUN_ID}" + message=$(printf '%s\n%s\n%s\n%s' \ + ":warning: Fresh Install Tests failed in *${REPOSITORY}* on branch *${BRANCH}*." \ + "*Workflow:* ${WORKFLOW}" \ + "*Run:* <${run_url}|#${RUN_NUMBER} (attempt ${RUN_ATTEMPT})>" \ + "*Failed step:* ${failed_step}") + payload=$(jq --compact-output --null-input --arg text "$message" '{text: $text}') + + curl -sS -X POST \ + --fail \ + --retry 4 \ + --retry-all-errors \ + --retry-max-time 60 \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Content-type: application/json" \ + --data "$payload" \ + "$SLACK_WEBHOOK_URL" diff --git a/.github/workflows/pr-preview-cleanup.yml b/.github/workflows/pr-preview-cleanup.yml new file mode 100644 index 0000000000..239dc2f381 --- /dev/null +++ b/.github/workflows/pr-preview-cleanup.yml @@ -0,0 +1,30 @@ +name: pr-preview-cleanup + +# Removes the GitHub Pages e2e-report preview that `build.yml`'s `deploy-report` +# job published for a PR, once that PR is closed/merged. Kept separate from +# `build.yml` so closing a PR doesn't re-run the whole build + e2e suite. +on: + pull_request: + types: [closed] + +permissions: + contents: write + pull-requests: write + +jobs: + remove-preview: + name: "Remove E2E Report Preview" + runs-on: ubuntu-latest + # Fork PRs never got a preview (read-only token), so nothing to remove. + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Remove PR preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1 + with: + preview-branch: gh-pages + umbrella-dir: pr-preview + action: remove diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000000..b39deb9957 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,33 @@ +# ./.github/workflows/publish.yml +name: Publish + +on: + push: + tags: + - v*.*.* + - v*.*.*-* + workflow_dispatch: + inputs: + version: + description: "Version tag to publish (e.g., v0.x.x-hotfix)" + required: true + type: string + +permissions: {} + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Publish disabled + # NX has been removed (it was driving `nx release publish`) and the + # replacement release tool is not yet in place. This workflow is + # intentionally a no-op so a stray tag push doesn't silently succeed + # without actually publishing anything. Once a release tool is wired + # up, restore the publish steps and remove this guard. + run: | + echo "::error::Publish workflow is disabled — release tooling is being replaced." + echo "::error::Re-enable this workflow once a release tool is wired up." + exit 1 diff --git a/.github/workflows/relative-ci.yaml b/.github/workflows/relative-ci.yaml index c2b848e202..fdee2a57f5 100644 --- a/.github/workflows/relative-ci.yaml +++ b/.github/workflows/relative-ci.yaml @@ -1,17 +1,24 @@ name: RelativeCI on: + # zizmor: ignore[dangerous-triggers] -- workflow_run is the recommended pattern + # for RelativeCI; this workflow only downloads artifacts and reports bundle stats. workflow_run: workflows: ["build"] types: - completed +permissions: + actions: read + contents: read + jobs: build: runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' steps: - name: Send bundle stats and build information to RelativeCI (editor) - uses: relative-ci/agent-action@v2 + uses: relative-ci/agent-action@fcf45416581928e8dd62eded78ce98c78e5149f8 # v3.2.3 with: artifactName: relative-ci-artifacts-editor key: ${{ secrets.RELATIVE_CI_KEY }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000..0b9ea34841 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,26 @@ +name: GitHub Actions Security Analysis with zizmor + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 diff --git a/.gitignore b/.gitignore index bc0541fef0..a22a821267 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ examples/*/types tsconfig.tsbuildinfo # testing coverage +# Vitest's attachment store (failure screenshots / diffs). Accumulates across +# runs; the HTML reporter copies what it needs into playwright-report/data, +# so the originals are unreferenced cruft. +.vitest-attachments/ # production /build @@ -29,7 +33,18 @@ yarn-error.log* .vercel test-results/ playwright-report/ +blob-report/ release /test-results/ /playwright-report/ +/blob-report/ /playwright/.cache/ +.env +*.pem +.nx/ +# Nightshift plan artifacts (keep out of version control) +.nightshift-plan +.claude/* +!.claude/skills +.playwright-cli +.claude/skills/playwright-cli/screenshots \ No newline at end of file diff --git a/.node-version b/.node-version new file mode 100644 index 0000000000..5bf4400f22 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.15.0 diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index b1215e8764..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v18.16.0 \ No newline at end of file diff --git a/.resources/release-workflow.excalidraw.svg b/.resources/release-workflow.excalidraw.svg new file mode 100644 index 0000000000..635f94e031 --- /dev/null +++ b/.resources/release-workflow.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1daVPjSNL+Pr+CYL+Oa+rIuiZiY4OzuZrmhu53N1xiYVx1MDAwYqzGV9sy18b8981cdTAwMTJgyTp8imNe8MTQINlyqSrzyefJyir997eFhcXwvuMv/rmw6N9VvUZQ63q3i7+74zd+t1x1MDAxN7RbeIpHf/fa/W41emc9XGY7vT//+KPpda/9sNPwqj65XHR6fa/RXHUwMDBi+7WgTart5lx1MDAxZkHoN3v/cj93vab/z067WVx1MDAwYrsk/pKKX1x1MDAwYsJ29/G7/Ibf9FthXHUwMDBmr/5/+PfCwn+jn3gmqLlvXFxcdTAwMGVXL+9cdTAwMWL74deNfT/o9b7p063aTvTR6E3Pt9D1q6HXumr48ak7134lidSGcTk4fI+HXHUwMDA1N1x1MDAwNFx1MDAxNFx1MDAxOFx1MDAxMDo+cVx1MDAxYtTCuvuMXHUwMDE1xFIuhk7W/eCqXHUwMDFl4lmlXHRcdTAwMDdFbfLk41x1MDAxN/+5QFx1MDAwN0d6Ybd97a+0XHUwMDFieJvYun8w3/1cdTAwMTe37cKrXl912/1WbfCesOu1elx1MDAxZK+LnVx1MDAxMb/vMmg0XHUwMDBlw/vo6jhcZth/i6nvOH1udep40afwS6/qLb/nOpxcco62O141XGJd1zBcdTAwMWHfhWthZ7NcdTAwMTaNzX/iNnVxVDfd4LT6jcbgcNCq+a7LXHUwMDE3PTr0ba3a07c9XHUwMDBmbDxq4unIX3Hbfd9dmFkrgWqgcVx1MDAxZsc2yZRIXHUwMDFm3W23XCL7xHHGQbVg4s9cdTAwMDW9VTSxMLrsJZqpXHUwMDFmj4Fr21ra/JImOGRhoX9cdTAwMTdcdTAwMGZMwkCPw35nacfQtt23ZziGrTNxfb44eN9fv+df9vHDv3rypiFXetXuxY538O3LxuaZTn3L8/d73W77dtLrNreXz+g9rVxcb8neRTXo0rsjXHUwMDAzJVxcV5/srS6F96dL/pa3bWti46Z9/TDZdZ9+i+2o36l5j+PCNFx1MDAwMOWWO7eKbbJcdTAwMTG0rtNG1mhXr+Oh/C3R4Fx1MDAxNGzkj0pcdTAwMDY2hlx1MDAwNjVCXGYhKaHYXHUwMDBlYYQ2lqo0coAl0qTPPENcdTAwMDfaXHUwMDFmocJapqzlzOThXHUwMDA3/4SNXCLYOMnHjaG3P1x1MDAwM1x1MDAwNDNUXHUwMDE5yIFcdTAwMDI8qU0hPlxihFx1MDAwNisk2FnwYYTpMsqYhSlMN7ZEZ4F4+1x1MDAxODibQbiwgueDu8RItlvhYfDgXHUwMDFhz+nQ0XWvXHUwMDE5NFxc18uhay01gqtWdEFstt9dTHZFXHUwMDE4YFxiXHUwMDFlvKFcdTAwMTnUasl4WcWLekHL725OXHUwMDEyeNvd4CpoeY2j0e33+mH7wO893kHY7fvJPvI3nr2CXHUwMDExLkd48/fdS6heVM9Pjyu/NpYqv1x1MDAwZU6aS/UpSFx1MDAwMFx1MDAwNnRuVdJcdTAwMWYjm5VcdTAwMWGPp+jBJ1x1MDAwYijJnfn8LEBcdTAwMWIhhTEmz8lcck9cdTAwMWZ8dnKjhaWG2pl8fDZcdTAwMGVw9KXe8S3oy/2ls/Pj/ZtcdTAwMDPYvDyeNKYurff36O6vk2D/y4p3sNLwf57c8Vx1MDAxMmL1V1x1MDAxYjZ3fj7Q70fy/PzI6zbY8a54W24xnlx1MDAwMyCOakWTIDE7XHUwMDA3yFx1MDAxZpVJOIAwRFx1MDAwYmqpUI5QJuwv8lxybVx0xaM8XHUwMDE3N1x1MDAxOFx1MDAwMOFcdTAwMTYpXHUwMDAwXHUwMDAzXHUwMDFi4UdcdTAwMWNcdTAwMTU+OcB40JiCXHUwMDAzIM1cdTAwMDfNKctDXHUwMDA3y9JcdTAwMDef0Vx1MDAwMYWFtpKVzlx1MDAwMDBcdTAwMTZIY6Yw3FwiXHUwMDA2sIznfS98XHUwMDBmXHUwMDE0YEzYLaBcdTAwMDCZXHUwMDFiKIdcdTAwMDNshNtnle+Vo4fK2t7xXHUwMDE19I6q99ss681+o1x1MDAxMXR6KVx1MDAwNqCFJFx1MDAwMrSSaTqPzI1cYqYgn81cdTAwMDMlSlxmu/nAh1liLCZwYqtZlX9cdTAwMTAnhkkjPy+K/Kjh0bcxXHUwMDE05Li24JmjsfxH2aUgKVwiXzz0d8N1OLqtmO3T41x1MDAwNvVbXHUwMDA3/uqVXHUwMDE3vLX8V8u6d3xQv+339/yHcK3XU3VcdTAwMWGUXHUwMDE1opnSIEtcbtH5vTdBiNZcdTAwMWFcYlx1MDAwMDdGXG5upUiJdCaIMVx1MDAxOISRn1x1MDAxYkM1y/q2YiQ6b2w6XHI48PDpqP1HcnA5eZDWXHUwMDE01bbUuTqdM1royZpKishLS1x1MDAxN+rTXHUwMDFhbyZMX+AwVuv/bv271cRQ+Vx1MDAxZcL0mMiYXHUwMDBl00U3UE6YXHUwMDFlXHJpKdhJXHUwMDA1aUHAglVI6UDLlFhHJUioUlYxtCipKI159SBeo86XiimrLXJ0XHUwMDEwSuVQb2KAXHUwMDAz8lx1MDAwMIH/akhEmE8mPuTjau4gjtpHOVx0n1x1MDAxZsRcdTAwMGJz+ILiXHUwMDEwKuRqpXu+YiCTNj2p53faQZpcIsS/LcRcdTAwMDZcdTAwMTP9Mfj9P7/nvrtSbKXulbHP+HqZyNzweuFKxLnxRvdcXCMzIFx1MDAxY3rdcFx1MDAxOVx1MDAwNzVoXVxyXHUwMDBm3tO02yRcdTAwMDBcdTAwMTKhWLXvOqDicuOGWsVd0Fx1MDAxNFx1MDAxNFisjZ2peZ1cYiCMYVx1MDAxMsUzuitHkWazXHUwMDE24rdq41s1Olx1MDAwMZlqXHUwMDE1clx1MDAwMNdcdTAwMWPgkmrBIdsoSlxmXHUwMDAyXHUwMDBiXHUwMDAztEeOtECorNm6zlpyuFT3vYxnYJOT59JcdTAwMDDmNy7at1x1MDAxM1FcdTAwMWVdu+xe337bkltcdTAwMWK791x1MDAwN3qn3vmxVp1Ux3BLXHUwMDE0gFx1MDAxNOlcdM1cblx1MDAxNyRvLrNEXHTDL61cdTAwMGbwQdBPz49+XHUwMDE0XHUwMDE0N0LlJi+FLYQ/TlGlXHUwMDFhIWfDv3xRME7DtE62WbtnVH/J7Jz/guPNze2mP6nWuLz+Unvo6dsl7V3IXHUwMDFlX1x1MDAxNvubbKk8rcE5jzngXFxaI/8uJ9FcdTAwMWFcdTAwMDbBQ1gtpGVcdTAwMTJ02vXwJEU/U1x1MDAwNlx1MDAwNavlKEgyXig00Sg2jNbpaYjZxMaHckUzudhAWMfxwciUJzaMLXI5JSmaXHUwMDE5XHUwMDA249JcdTAwMTnHlMab0Vx1MDAxYaF35Xj6XHIj9D1cYo0xoSstNHJbX47KWPd27+12pbtz5/HNL5WNm23a/zL5hKC0RDKbXHKjkqT88yVnXHUwMDAzP5RcdTAwMTcvzz9cdTAwMWLIUFx1MDAxM1xilkzmxN6tIH0wnvI3XFxcYmAyliEvXHUwMDFlT+XP7vH9WVx1MDAwNUT7un7g1Vx1MDAwM73SPdp4hXg68rpzTFx1MDAwN75unM7vvVx04rRwJFhQLTVcdTAwMDPGmFx1MDAxOJ6244pcdTAwMDBLl+XEqVx1MDAwM0W4tsYyq7TW88/afSznnqZyxzjcpTZcdTAwMWKOXHUwMDE3R1XuXHUwMDAwZVx1MDAwNj86U2L/RUP006TX0p9cdTAwMGJdbI+XVGxvXHUwMDE3qMeEx4KJu7x7KCdcXI/GtJFJQS5cdOpdKjTjXHUwMDFj0HmHfFx1MDAxYVxmXHUwMDExXGbAaODUXCJcdI8xfignaCXFXHUwMDEwgMBgOc1xbEZcdTAwMTQzyliM4C5LhG/+dPR8R1+dW1x1MDAxNlx1MDAwYq2NQefLzM+7XHUwMDBmQeHMXHUwMDFlXHUwMDAwXHUwMDE3XFxcdDZTXHUwMDEwf1x1MDAxMf9/gaRgrpk+ns5YaHzFTHAuLS04mu4vXGYl4LDFWmilXHUwMDE0XHUwMDE1IJmU2VxmXHUwMDFj+jE1SMRcdTAwMDTqM6Yo11x1MDAxOVx1MDAxM5koKzhcdTAwMWHaXHUwMDEyjaKEoa1cdTAwMDFcbneuXHUwMDA150pnWmRcYlxixTjqdjDcXHUwMDFh+mYpwdHUbFx1MDAxNDyiXHJcdTAwMTOlQOFNcoP2bGJ4cy6vgXAuJEZO5DVKmCzxoURcdTAwMWGlmZbSgkZ1k6iaXHUwMDFi4KNcdTAwMTHYUVZTXHUwMDFjY86YSlx1MDAwNOnPOZMheFxcm1x1MDAxYlx1MDAxZTUyI8CAlps0zFxin4HGQXTAMVx1MDAwNF3+nFx0wzF/c3gsNlL3ypjna2DjXHUwMDE0MIRQp1x1MDAxNVx1MDAxOGw8OqlAmMlio1wiij8uKZhcclx1MDAxNEdcdTAwMTdqXHIjNZWMcqDKWoWkSiqWaVxyI+yxJuKt8HB0heloPFx1MDAwNFwiJJfCoIrjianHSFx1MDAwMaJcdTAwMDTEIIV3XHJUuLiQzdQyXCKBSoxcdTAwMWFWMyOtkjnZXHUwMDFla13ClzlTRKlJ2VRlXCJcdTAwMWZcdFx1MDAwZddLmERcdTAwMTGSMitEXHUwMDFlXWS8MOnDKXpcdTAwMWNcYlM6IFJXYjbNXHUwMDEypZfii4V26l5cdTAwMTlcdTAwMGJ9XHJEnFx1MDAxOINcdTAwMTCCkOQqI5VBRYeoLU3iXc+AXHUwMDE45WlmQ8OpJo6VdbzaLfYyTFKdZYmaPNd6v1x1MDAxNVx1MDAxY95cdTAwMDS97dOf2lx1MDAwYq9XjsTlSXDZuvxxMElCjJHMSrVcYlx1MDAwNylBtlx1MDAwYrmJbqZcdTAwMTWRSmPkN9rV4sdwMsA/8ZlcdTAwMDcrXHUwMDAwvK1p0mBOL1mRW1x1MDAxOKdcbte2oFx1MDAxNTKOjv1cdTAwMGVcdTAwMTNhO1xiJFx1MDAwYlx1MDAwN6NyYGbo6LhcdTAwMWNYw79M2EMmXHUwMDAzXHUwMDE2tjtF6a+hVqdzXfnNLCfN5Ymzr/T8q9e/Ovy5utbZOPxS9/cncVSuXHRqY1x1MDAxY1x1MDAwMCSHXHUwMDE23GTykMNKalx0itjc1eouc410xrpqm+Ey9tlcdTAwMWP2Q1x1MDAxNbJuT+6wnFx1MDAxYc2NhuzKkmhcdTAwMTCKl5wyytySYPH+Kll3/du/gcPmtrJcdTAwMWN/vTlXW3dqr3/X037zgVx1MDAxZepcdTAwMTV/+evks8hcbpClXGbP+kZJaSuJXHUwMDEw9lx1MDAxNTeX+EguuzOpqCicSEZSTKnkuTlcdTAwMTam0lx1MDAwN1x1MDAwN35MXWJcdTAwMTaUeMV55J/7XHUwMDA3nZ1qe6m7ut9onVx1MDAxZv+4P6vUq6+wXHUwMDA2ZOR1w29cdTAwMWR2dlC/XFzV9zW//sP7erZ5XFwv4bp/m60l8kdlgigvuELizZFqoSyTKNuGk7WSYSR/zlx1MDAwM+XEeUOEtFx1MDAwNrHBPuqRLHxMt670Q6HGNDPUyKOAS23zXG5NxIi9JThQK3X5zHzeOP80vbv6rqaox8TeginqvHsoh1x1MDAwYoyGy5FT1JJcdTAwMTO3q0h6wVxu0oBC0m6AXHUwMDE0UFx1MDAwMMZcclEowIfU+ac3XHUwMDBmefPu3IlFhmCrtVx1MDAwM+M8XHUwMDEyIFxu9TdnQii3XHUwMDA0XCImXHUwMDBmL7/CdP2wfb6lwffs/b7mXHUwMDFk7/5qlU5cdTAwMTf9hEJ9OFx1MDAwYoSUnLWkXHUwMDA2de4wM3YvIYlcdTAwMDWbzPiNuEbaddwr6TTxXHUwMDE1Mlx1MDAxZPMmXHUwMDBiZlx1MDAwNFNSaC2pKy/TYLKzP4IwbZGRamDSWFfmkjHbifKeoyF1IZmBNdggN02iolx1MDAxY5/OWcRDXHQ8yZSsXHUwMDBmvU7aM9/uJ+BZ0jCCNyVcdTAwMTWzkiojhpMpXHUwMDAwmjCqoaBcdTAwMTDQ7Vx0xKzRSFx1MDAxOI1bOfPJsqbA5al28Fx1MDAwMldcYpQrwlxuOVx1MDAxNrdcdTAwMTJcZpdlp1K4S+zMtXtXp9VpLnT7rffEsMYwmjTDKr6Fclxi1sP99mazXt168E63Tpa+Nk7XvtR+ZN25YP9cdTAwMGVcdTAwMDR9V1x1MDAwMpDewlx1MDAwYoVcdTAwMTSRqKWMNoDjmKhV+NzE4+nojL78bX6ORVx1MDAxNWhcdTAwMGL523dBoufTQsoq9Eg3zfF6XHUwMDFji+uTQJ1snV57u5tnUKtcdTAwMWZddZbEW2dExIlpVi+WvodVXHUwMDA2P07Ob46vT/SE11x1MDAxZM9cdJGWKFVSxX5+701cdTAwMTCptdFEgVs15+pcdTAwMDZcdTAwMTVNVfdaQ5hbQUzR8bRgXCJb3itcdTAwMDRB2mc1Uqdoy82c6rVPXHUwMDE5VeTie5NHa+DalS7JPLkkstv0XGbW8lx1MDAwM45cZv5fdk5kauPNXHUwMDA07HhtXHUwMDFhe1x1MDAwZrF6THAsXlnHylx1MDAwZdOjwWxsqb7RVKCZXGIuXHUwMDEyXCI66jhgXHUwMDA0LIpcctRB2iiet31cdTAwMDcl0lx1MDAxNfcpVNnozDQ2q8T2XHUwMDFkkqJcXFx1MDAxMUjqmUJVNVWp/ody7v354zdDdoVcdTAwMWFI5K3UUYXh2zDn8Xq2PbhexOfLLtUvMtPodNZC4ytmonJpXHSJ0fixMJSQQMUv0FxypVsxIy3PXHUwMDE2X1mCXHUwMDExXHUwMDE5vdRKN29NXz5cdTAwMWLBOZduR2fKjEDZXHUwMDBlKtMkTSj25Fx1MDAxYtZgjZ6dXHUwMDFhV6JPucJ709zV/FxmU1x1MDAxY4rGpPAnSlx1MDAxYorGkqNfkOBQXHUwMDE3RSlV2khGczY1spHE0VZcYlx1MDAwNcJtW/5cdIr5oHgwNyhq5VxmXHUwMDE1VFx1MDAxZVx1MDAxMSpevmQ1cFx1MDAwZbb03cyQlTH79phYbKPulbHO11x1MDAwMMSJa0ApcfP6hlwiXluqkbtAYnHBXHUwMDEz+lx1MDAwMDFSRJWikY9SeGFEdHWpIJFKcW4lgp6UOShccoSBoCCwOW7ZMnsrZNyonO38Wl+j3V5Xra3/4Fx1MDAxN/fNu7MsMtZcdTAwMDKv2W7VhrFcdTAwMTFjXGbqP7fVXHUwMDE1MHBUY5gyMvfwXHUwMDE2St1uysKF3Cw4gibGuM3gLLjiXGaVt7pcdTAwMTNcZuHKXG5AhVx0LsjEePSJjkPoeFhcdTAwMDJldMFcZiBXJ1x1MDAxNlNGXHUwMDA2yKUw+s+Gj4PmTZXxXHTv+M/L83brst7cqNfMr93m2faPV8jMjLxuuLZ1uVFdXtvX4nD7tn5cdTAwMTP0b+4vS8v4XGJjbVx1MDAxY6Lmyvjk917G6XNK0im4qnSJ/uxSNql0rtSaMKFBXHUwMDAw+rw0POvxXFxcdTAwMTBA7p0uX/+cnFx1MDAxOevdR1PMzXDGrCyYXHUwMDFkL66QXHUwMDEzhmm3XHUwMDE1VtnKj1smXHUwMDEzXHUwMDBisWbI9qxsvockz5hImU7yJFx1MDAxYl1ObuebZUtcdTAwMWLfVi5Pu0bX91x1MDAxYms3P+8qtazX5k7BME5cdTAwMTlcdTAwMDHmNltWXHUwMDFhhzlVvKasJNYghUJZyalKTOFccmI1M8hcdTAwMDO1dNXtbvo1sUtXXHUwMDFjq5lcIlx1MDAxY1x1MDAxOY1cdTAwMDFcdTAwMTDKcPPpzUXefDx3rObI1Fx1MDAxMWJ1bpXbiFx1MDAwNSiKuqWTiZ1UXzxUr8C328PW8ZatLCn4Zfs/tzf3vf93ITX/LidcdKlcXFiCXHUwMDAzSfFfit5ph1NcZlx1MDAxYVx1MDAwM66k6JpWI8tiOXuh41x1MDAxYlBAINy7x1jJ3CUkn45Y5IhTlDxwiUFVstx8amHqgEfyx8jSo6rbU0fMXHUwMDE1VXf3vr6HsDomqmXWkyRbXU5cXFx1MDAxZC1cdTAwMDdGJVx1MDAwN3FcXIlcdTAwMTbouyApQitcZlx1MDAwN1VUtYRS4Z5zXHUwMDA3Qlids72Rta4qXFwpYcE9/ILnZFx1MDAwN1xy6l/U2FxiXHUwMDAxWridV+LveNeuXFxru1x1MDAxY9Cr+vLp/Fx1MDAwMljgMLD8RWJcIltcYpGgzmCVYOZcdTAwMTVcdTAwMWZcXLJ5u37Uu9r7Xv1F61x1MDAwZqZ3fPv94JxNXHUwMDE5/VDDzfTIo3Kzj1RcdTAwMTOtmNJcdTAwMTj8XHUwMDEwXWGoMrRcIlFuXCLoaoxtxlAux1aYXHUwMDE2u5R7XHRFXHUwMDA0XHUwMDE4hTRYgfuyV0lmTjW749JhXHUwMDFhXHUwMDFixiW4jHPOVuhcdTAwMTnjnSh7OVq9LCQzqm41vWTcXGKDI5JT8GqJ5MxYhYTF9fDb7cWe71x1MDAwM1x1MDAxM3AuS1x1MDAwNbFcdTAwMTI7mUpwe1rFIT1cbnTCZZVR5Vx1MDAxOM0szYFtraJccqFcdTAwMDE1lVKOc/19XHUwMDE511x1MDAxYsD02eSUXHUwMDBikYpGWd88lVO4ITTjXHUwMDBlT6icbc/YkXPYruh6XHUwMDFlztXyb1x1MDAxN0Lv6j3wrjGsJ827Mi0vqV5lpL5cdTAwMWLFvVx1MDAxOHVL6txT4YREL5R2OFx1MDAxN6lcdTAwMTRGXHUwMDBldF6FtExTw2JjXHUwMDFlkC8hSbRcdTAwMTRPcmO0wlxinnVjq92us04lKiO50vFb3rVXv76O+j4/90I0NkIolffAmeJcdTAwMDcuMOv2pJR0ti01ZuNeO+c77crh3sny7nrb2/zZv12p7+5Nx73chmJvv6RcdTAwMDc4oShf0EWYckWcdogsaeN2XHUwMDFiM1x1MDAxOFx1MDAwNjXHXHUwMDFmg1xyXHUwMDE2i7lXoUe5V8aXXoN7TUF7XHUwMDA0Ui5rqEUxh4pcdTAwMWRsdlWNdeTRlaK7XHUwMDE1plrZ563qpqRio1x1MDAxNe8wXHUwMDFm5EjCXHUwMDEwyoybmrdKZtvEidRIXHUwMDE1XHUwMDE109Hz8CC7L+crkbF8p5iAjDHmdn2zeFx1MDAwN247KVx1MDAwZTC84Fx1MDAwN7uZuH27XHUwMDFjOlx1MDAxOKNcdTAwMTJPXHUwMDA1XHUwMDFiPFxmXHUwMDEwP4537yaiU89cYv3b0bHXXHUwMDA37lx1MDAxZlOwMbR7p8KyO1mOYmNu+y0+VNVUXHUwMDEyXHUwMDE5XHUwMDAzMHYuMtbpXzSCXv09kLExNCiz2Cfd8qnJ2G9PaLvodTqHIfbsXHUwMDAwr9CQglqqXHUwMDE3XHUwMDFlj4V+J+6B6NDXds1fa3lcdTAwMTeNdC8v3lx1MDAwNP7tctZv/nFcdTAwMTm9XFysjFDE+YxcdTAwMWZcdTAwMTGFv37763+eXHJeiiJ9commit C: fixcommit B: featbranchmaintagv1.0commit A: releaseLast ReleaseNew Releasecommit D: releasepnpm run releasetagv1.1CINPMnew tagpublish \ No newline at end of file diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit new file mode 100755 index 0000000000..85fb65b4fc --- /dev/null +++ b/.vite-hooks/pre-commit @@ -0,0 +1 @@ +vp staged diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c224675018..1b6c379e0a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,5 @@ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format - "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] + "recommendations": ["VoidZero.vite-plus-extension-pack"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f46e5ae865..80f6014497 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,25 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.defaultFormatter": "oxc.oxc-vscode", "editor.formatOnSave": true, - "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.formatOnSaveMode": "file", + "editor.codeActionsOnSave": { + "source.fixAll.oxc": "explicit" }, + "oxc.fmt.configPath": "./vite.config.ts", + "npm.scriptRunner": "vp", + "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[javascriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "search.exclude": { - "packages/editor/public/types": true + "packages/editor/public/types": true, + "packages/website/docs/.vitepress": false, + "**/.*": false }, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "[xml]": { + "editor.defaultFormatter": "redhat.vscode-xml" + }, + "scm.defaultViewMode": "tree", + "search.defaultViewMode": "tree" } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..85d7918fe5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +# Project Description + +BlockNote is a block-based rich text editor for the web. It's designed as a batteries-included product that offers a solid user experience with minimal setup. However, it also offers extensibility via plugins and custom block types. + +# Issue Context + +When prompted to write a new feature, fix a bug, or make some other modification to the code, the project repository on GitHub should be scanned for issues and PRs which are relevant to the task at hand. Before writing any code, a summary of these should be given. If nothing relevant is found, the task can be started immediately. Otherwise, the user should be prompted on next steps. + +This should only be done for new conversations. If GitHub was already scanned in the same conversation, it does not need to be scanned again. + +Once the task is done and the feature is completed, bug is fixed, etc, the user should be reminded of the relevant issues and PRs found in the initial investigation. + +The GitHub CLI should be used to browse issues and PRs. + +# Common Commands + +All commands below are listed under `package.json` in the project root. See `vite.config.ts` for relevant configuration settings. + +- `vp install`: Installs dependencies. +- `vp run dev`: Starts the dev server on port 5173. +- `vp run check`: Checks for linting and formatting issues across the project and attempt resolve issues automatically. +- `vp run build`: Builds the project. +- `vp run preview`: Previews the build on port 3000. +- `vp run test`: Runs unit tests. Append with `-u` to update snapshots. Append with a file name to target only that file. +- `vp run e2e`: Runs end-to-end tests. Append with a file name to target only that file. +- `vp run e2e:updateSnaps`: Runs end-to-end tests & updates snapshots. Append with a file name to target only that file. +- `vp help`: Prints a list of all availabel commands. + +# Common Entry Points + +When writing a new feature, bug fix, or other modification, it may not be immediately clear where the code for it should be. There are a few files which are good to start looking in when this is the case: + +- `/packages/core/src/editor/BlockNoteEditor.ts`: Contains the class for the core BlockNote editor. Every editor command & event can be traced from here. +- `/packages/react/src/editor/BlockNoteView.tsx`: Contains the `BlockNoteViewEditor` component, which is the base for rendering the editor and its UI elements. Whenever the UI functionality (and often styling) needs to be changed, it will be a descendant of `BlockNoteViewEditor`. +- `/packages/mantine/src/BlockNoteView.tsx`: Contains the Mantine version of `BlockNoteView`. This can be thought of as a skin for `BlockNoteViewEditor` that uses the Mantine component library. Therefore, changes in `BlockNoteViewEditor` may also have to be propagted to it. + - The same applies for `BlockNoteView.tsx` in `/packages/ariakit` and `/packages/shadcn`, though Mantine is the defacto default version of `BlockNoteView`. + +# Additional Notes + +- Do not create git commits. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..87fed6062a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,909 @@ +## 0.51.4 (2026-06-02) + +### 🩹 Fixes + +- add explicit type annotations for callback params losing contextual typing ([#2815](https://github.com/TypeCellOS/BlockNote/pull/2815)) +- Comments emoji picker button issues (BLO-1199) ([#2769](https://github.com/TypeCellOS/BlockNote/pull/2769)) +- **core:** add editor cleanup in BlockNoteEditor.test.ts to prevent unhandled DOMObserver errors ([#2816](https://github.com/TypeCellOS/BlockNote/pull/2816)) +- **table:** prevent crash when pressing Enter in a table cell ([#2793](https://github.com/TypeCellOS/BlockNote/pull/2793)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick @nperez0111 +- Yuki Terashima @y-temp4 + +## 0.51.3 (2026-05-26) + +### 🩹 Fixes + +- table cell colors (BLO-1198) ([#2770](https://github.com/TypeCellOS/BlockNote/pull/2770)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski + +## 0.51.2 (2026-05-20) + +### 🩹 Fixes + +- color picker icons (BLO-1189) ([#2762](https://github.com/TypeCellOS/BlockNote/pull/2762)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski + +## 0.51.1 (2026-05-18) + +### 🩹 Fixes + +- backslash newlines when copying from a code block ([#2709](https://github.com/TypeCellOS/BlockNote/pull/2709)) + +### ❤️ Thank You + +- Claude Opus 4.7 (1M context) +- Nick Perez + +## 0.51.0 (2026-05-14) + +### 🚀 Features + +- Trailing block extension rewrite ([#2733](https://github.com/TypeCellOS/BlockNote/pull/2733)) +- **markdown:** replace unified.js with custom markdown parser/serializer ([#2624](https://github.com/TypeCellOS/BlockNote/pull/2624)) +- **react:** configurable portal targets for floating UI ([#2729](https://github.com/TypeCellOS/BlockNote/pull/2729), [#2692](https://github.com/TypeCellOS/BlockNote/issues/2692)) + +### 🩹 Fixes + +- Pasting plain text from VSCode (BLO-366) ([#2713](https://github.com/TypeCellOS/BlockNote/pull/2713)) +- Parse new lines in `text/plain` as line breaks (BLO-1170) ([#2712](https://github.com/TypeCellOS/BlockNote/pull/2712)) +- Code block PDF export (BLO-987) ([#2725](https://github.com/TypeCellOS/BlockNote/pull/2725)) +- Formatting toolbar opening when inserting file block with `trailingBlock: false` (BLO-860) ([#2704](https://github.com/TypeCellOS/BlockNote/pull/2704)) +- numbered list item decorations missed on initial render ([#2734](https://github.com/TypeCellOS/BlockNote/pull/2734)) +- flicker-free mobile formatting toolbar via CSS custom properties ([#2617](https://github.com/TypeCellOS/BlockNote/pull/2617), [#2616](https://github.com/TypeCellOS/BlockNote/issues/2616)) +- add `bn-thread-orphaned` CSS class to distinguish orphaned threads ([#2737](https://github.com/TypeCellOS/BlockNote/pull/2737), [#2735](https://github.com/TypeCellOS/BlockNote/issues/2735)) +- set width attribute on image and video elements in editor render ([#2740](https://github.com/TypeCellOS/BlockNote/pull/2740), [#2726](https://github.com/TypeCellOS/BlockNote/issues/2726)) +- **a11y:** use figure/figcaption for media block captions ([#2717](https://github.com/TypeCellOS/BlockNote/pull/2717)) +- **ai:** loosen serialization of blocks in columns ([#2716](https://github.com/TypeCellOS/BlockNote/pull/2716), [#2718](https://github.com/TypeCellOS/BlockNote/pull/2718)) +- **core:** trigger codeblock input rule on Enter and place cursor inside ([#2686](https://github.com/TypeCellOS/BlockNote/pull/2686)) +- **core:** preserve list item type when pasting into empty list items ([#2722](https://github.com/TypeCellOS/BlockNote/pull/2722), [#2330](https://github.com/TypeCellOS/BlockNote/issues/2330)) +- **core:** unmount editors in transformPasted tests to prevent unhandled error ([e62880b21](https://github.com/TypeCellOS/BlockNote/commit/e62880b21)) +- **drag-n-drop:** support PDF block drag & drop (BLO-893) ([#2714](https://github.com/TypeCellOS/BlockNote/pull/2714)) +- **i18:** improve french translation for empty toggle list ([#2721](https://github.com/TypeCellOS/BlockNote/pull/2721)) +- **markdown:** emit tight lists when serializing blocks to markdown ([#2715](https://github.com/TypeCellOS/BlockNote/pull/2715)) +- **markdown:** skip placeholder text for empty files ([#434](https://github.com/TypeCellOS/BlockNote/pull/434), [#2719](https://github.com/TypeCellOS/BlockNote/pull/2719)) +- **markdown:** stable round-trip for tables, captions, and audio ([#2720](https://github.com/TypeCellOS/BlockNote/pull/2720)) +- **tests:** stabilize webkit keyboard handler tests with programmatic cursor positioning ([#2746](https://github.com/TypeCellOS/BlockNote/pull/2746)) + +### ❤️ Thank You + +- Cyril G +- Manuel Raynaud @lunika +- Matthew Lipski @matthewlipski +- Movm +- Nick Perez +- Nick the Sick @nperez0111 + +## 0.50.0 (2026-05-04) + +### 🚀 Features + +- Dark mode styling for file block wrapper component (BLO-866) ([#2680](https://github.com/TypeCellOS/BlockNote/pull/2680)) +- Drag hendle menu delete button removes all other blocks in selection (BLO-1007) ([#2683](https://github.com/TypeCellOS/BlockNote/pull/2683)) +- Enter moves selection to cell below in tables (BLO-1006) ([#2685](https://github.com/TypeCellOS/BlockNote/pull/2685)) +- additional heading top padding (BLO-1008) ([#2690](https://github.com/TypeCellOS/BlockNote/pull/2690)) +- Code mark input rule edge case (BLO-938) ([#2698](https://github.com/TypeCellOS/BlockNote/pull/2698)) +- **mantine:** upgrade @mantine/core and @mantine/hooks to v9.0.2 ([#2655](https://github.com/TypeCellOS/BlockNote/pull/2655)) + +### 🩹 Fixes + +- Hardcoded strings in comment components (BLO-1033) ([#2681](https://github.com/TypeCellOS/BlockNote/pull/2681)) +- Color naming & CSS (BLO-946) ([#2684](https://github.com/TypeCellOS/BlockNote/pull/2684)) +- link HTML attributes (BLO-915) ([#2687](https://github.com/TypeCellOS/BlockNote/pull/2687)) +- guard hideMenuIfNotFrozen against undefined view state ([#2694](https://github.com/TypeCellOS/BlockNote/pull/2694), [#2699](https://github.com/TypeCellOS/BlockNote/pull/2699)) +- Clicking comment overlapping link opens link (BLO-1091) ([#2696](https://github.com/TypeCellOS/BlockNote/pull/2696)) +- prevent table row drag from moving an extra adjacent row ([#2703](https://github.com/TypeCellOS/BlockNote/pull/2703)) +- **clipboard:** use ProseMirror selection state for Shadow DOM compatibility ([#2677](https://github.com/TypeCellOS/BlockNote/pull/2677)) + +### ❤️ Thank You + +- jt_fox @LimChaeJune +- Matthew Lipski @matthewlipski +- Nick Perez +- Wieland Lindenthal +- Yousef + +## 0.49.0 (2026-04-24) + +### 🚀 Features + +- simplify links by inlining it to BlockNote ([#2623](https://github.com/TypeCellOS/BlockNote/pull/2623)) +- add Unicode quotation mark input rule for quote blocks ([#2673](https://github.com/TypeCellOS/BlockNote/pull/2673)) + +### 🩹 Fixes + +- Inserting link removes comment & add comment button click buggy ([#2620](https://github.com/TypeCellOS/BlockNote/pull/2620), [#2573](https://github.com/TypeCellOS/BlockNote/issues/2573)) +- `useEditorDOMElement` hook ([#2619](https://github.com/TypeCellOS/BlockNote/pull/2619)) +- text color was not applying to table block ([#2663](https://github.com/TypeCellOS/BlockNote/pull/2663)) +- Drag preview blocking drops when overlapping the editor (BLO-996) ([#2670](https://github.com/TypeCellOS/BlockNote/pull/2670)) +- Drag & drop of blocks without inline content opens formatting toolbar (BLO-1116) ([#2628](https://github.com/TypeCellOS/BlockNote/pull/2628), [#2603](https://github.com/TypeCellOS/BlockNote/issues/2603)) +- save file caption/name on every keystroke instead of on close ([#2575](https://github.com/TypeCellOS/BlockNote/pull/2575)) +- prevent FloatingFocusManager from resetting editor selection ([#2525](https://github.com/TypeCellOS/BlockNote/pull/2525), [#2664](https://github.com/TypeCellOS/BlockNote/pull/2664)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- miadnguyen @miadnguyen +- mianguyen +- Nick Perez +- Yousef + +## 0.48.1 (2026-04-16) + +### 🩹 Fixes + +- make CustomChange compatible with prosemirror-changeset 2.4.1 ([#2647](https://github.com/TypeCellOS/BlockNote/pull/2647)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability (CVE-2025-62718) ([c1ef3018a](https://github.com/TypeCellOS/BlockNote/commit/c1ef3018a)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability ([#2653](https://github.com/TypeCellOS/BlockNote/pull/2653)) +- **docx-exporter:** omit w:lang when no locale provided instead of defaulting to en-US ([#2651](https://github.com/TypeCellOS/BlockNote/pull/2651)) + +### ❤️ Thank You + +- Claude Opus 4.6 (1M context) +- Nick Perez +- Nick the Sick +- Stephan Meijer @StephanMeijer + +## 0.48.0 (2026-04-13) + +### 🚀 Features + +- upgrade shiki to v4 and prosemirror-highlight to v0.15.1 ([#2625](https://github.com/TypeCellOS/BlockNote/pull/2625)) +- upgrade nx to 22.6.4 and liveblocks to 3.17.0 ([#2627](https://github.com/TypeCellOS/BlockNote/pull/2627)) + +### 🩹 Fixes + +- Image block selection clears on mouse leave in Safari ([#2613](https://github.com/TypeCellOS/BlockNote/pull/2613)) +- Backspace bug when current block is empty and previous block's last child is empty ([#2610](https://github.com/TypeCellOS/BlockNote/pull/2610)) +- allow using latest @tiptap/extension-link version ([1ae8de713](https://github.com/TypeCellOS/BlockNote/commit/1ae8de713)) +- restore depth guard in getParentBlockInfo to prevent RangeError (blo-1103) ([#2585](https://github.com/TypeCellOS/BlockNote/pull/2585)) +- pin better-auth to ~1.4.x to fix docs build ([bda30458a](https://github.com/TypeCellOS/BlockNote/commit/bda30458a)) +- hide side menu on scroll instead of overflow hacks ([#2630](https://github.com/TypeCellOS/BlockNote/pull/2630), [#2043](https://github.com/TypeCellOS/BlockNote/issues/2043)) +- disable default UI when no components context is found ([#2611](https://github.com/TypeCellOS/BlockNote/pull/2611)) +- add .js extension to fast-deep-equal ESM import ([#2641](https://github.com/TypeCellOS/BlockNote/pull/2641)) +- placeholder when overflowing now wraps ([#2291](https://github.com/TypeCellOS/BlockNote/pull/2291)) +- **core:** fix unnesting blocks with siblings (BLO-1017) ([#2601](https://github.com/TypeCellOS/BlockNote/pull/2601)) +- **core:** backspace mid-text next to columnList moves block BLO-1126 ([#2629](https://github.com/TypeCellOS/BlockNote/pull/2629)) + +### 🔥 Performance + +- optimize plugin traversals for large documents BLO-1111 ([#2600](https://github.com/TypeCellOS/BlockNote/pull/2600)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Claude Opus 4.6 (1M context) +- hedi-ghodhbane @hedi-ghodhbane +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick @nperez0111 +- Yousef + +## 0.47.3 (2026-03-25) + +### 🩹 Fixes + +- **core:** preserve whitespace edge cases but collapse html formatting newlines (BLO-1065) ([#2551](https://github.com/TypeCellOS/BlockNote/pull/2551), [#2230](https://github.com/TypeCellOS/BlockNote/issues/2230)) + +### ❤️ Thank You + +- Yousef + +## 0.47.2 (2026-03-20) + +### 🩹 Fixes + +- use
/ for toggle block HTML export ([#2524](https://github.com/TypeCellOS/BlockNote/pull/2524)) +- remove @hocuspocus/provider peer dependency by inlining tiptap comment types BLO-1064 ([#2564](https://github.com/TypeCellOS/BlockNote/pull/2564)) +- **core:** slash menu fails in custom blocks after space BLO-1036 ([#2553](https://github.com/TypeCellOS/BlockNote/pull/2553)) +- **i18n:** fix typo in russian translation ([#2560](https://github.com/TypeCellOS/BlockNote/pull/2560)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Drone +- Yousef + +## 0.47.1 (2026-03-02) + +### 🩹 Fixes + +- typeerror cannot read properties of undefined ([#2522](https://github.com/TypeCellOS/BlockNote/pull/2522)) +- handle more delete key cases ([#2126](https://github.com/TypeCellOS/BlockNote/pull/2126)) +- add delay for `data-active` in collab cursors ([#2383](https://github.com/TypeCellOS/BlockNote/pull/2383)) +- disable slash menu in table content #2408 ([#2504](https://github.com/TypeCellOS/BlockNote/pull/2504), [#2408](https://github.com/TypeCellOS/BlockNote/issues/2408)) +- **ai:** selections broken due to floating-ui focus manager ([#2527](https://github.com/TypeCellOS/BlockNote/pull/2527)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Yousef + +## 0.47.0 (2026-02-23) + +### 🚀 Features + +- update suggestion menu component ([#2397](https://github.com/TypeCellOS/BlockNote/pull/2397)) +- **i18n:** add Persian (fa) localization support ([#2447](https://github.com/TypeCellOS/BlockNote/pull/2447)) +- **i18n:** add Uzbek (uz) localization support ([#2506](https://github.com/TypeCellOS/BlockNote/pull/2506)) + +### 🩹 Fixes + +- prevent nested bullet list icon rendering as emoji on iOS 18+ ([#2394](https://github.com/TypeCellOS/BlockNote/pull/2394), [#2399](https://github.com/TypeCellOS/BlockNote/pull/2399)) +- ignore drag & drop from unrelated events #1968 ([#2346](https://github.com/TypeCellOS/BlockNote/pull/2346), [#1968](https://github.com/TypeCellOS/BlockNote/issues/1968)) +- disable checkbox when editor is not editable #2406 ([#2448](https://github.com/TypeCellOS/BlockNote/pull/2448), [#2406](https://github.com/TypeCellOS/BlockNote/issues/2406)) +- Backspace/enter behaviour in empty block with children ([#2451](https://github.com/TypeCellOS/BlockNote/pull/2451)) +- handle pasting into table cells better, by collapsing their content to inline #2410 ([#2449](https://github.com/TypeCellOS/BlockNote/pull/2449), [#2410](https://github.com/TypeCellOS/BlockNote/issues/2410)) +- **accessibility:** ai combobox aria-activedescendant ([#2413](https://github.com/TypeCellOS/BlockNote/pull/2413)) +- **ai:** no more scrolling to top when opening AI menu ([#2503](https://github.com/TypeCellOS/BlockNote/pull/2503)) +- **docs:** unicode char not rendered in bug template ([f13e270be](https://github.com/TypeCellOS/BlockNote/commit/f13e270be)) + +### ❤️ Thank You + +- Cyril G @Ovgodd +- Dex Devlon @bxff +- Matthew Lipski @matthewlipski +- MDSAM05 @MDSAM05 +- Mohammad RAHMANI @Mrahmani71 +- Nick Perez +- Ogabek @OgabekYuldoshev +- Wouter Vroege +- Yousef + +## 0.46.2 (2026-01-27) + +### 🩹 Fixes + +- deep merge floatingUIOptions using nested spread operators ([#2310](https://github.com/TypeCellOS/BlockNote/pull/2310)) +- Visual differences between live editor and rendered exported HTML ([#2348](https://github.com/TypeCellOS/BlockNote/pull/2348)) +- `BlockNoteViewEditor` mismatched editable value ([#2357](https://github.com/TypeCellOS/BlockNote/pull/2357)) +- add `font-synthesis` for italic & bold in fonts that don't have them specified #2325 ([#2354](https://github.com/TypeCellOS/BlockNote/pull/2354), [#2325](https://github.com/TypeCellOS/BlockNote/issues/2325)) +- disable code block language selector when editor is not editable ([#2351](https://github.com/TypeCellOS/BlockNote/pull/2351)) +- table handles would crash ([#2384](https://github.com/TypeCellOS/BlockNote/pull/2384)) +- update CreateLinkButton to be able to toggle popover visibility ([#2316](https://github.com/TypeCellOS/BlockNote/pull/2316), [#2313](https://github.com/TypeCellOS/BlockNote/issues/2313)) +- add context,nestingLevel to toExternalHTML ([#2373](https://github.com/TypeCellOS/BlockNote/pull/2373)) +- **ai:** re-enable flipping the AIMenu when there is not enough space #2245 ([#2247](https://github.com/TypeCellOS/BlockNote/pull/2247), [#2245](https://github.com/TypeCellOS/BlockNote/issues/2245)) +- **link-toolbar:** prevent Enter from submitting during IME composition ([#2361](https://github.com/TypeCellOS/BlockNote/pull/2361)) + +### ❤️ Thank You + +- hanios123 +- Jean-Baptiste PENRATH +- Matthew Lipski @matthewlipski +- Nick Perez +- Shohei Yoshida @ysds +- Yousef + +## 0.46.1 (2026-01-10) + +This was a version bump only, there were no code changes. + +## 0.46.0 (2026-01-08) + +### 🚀 Features + +- add data-nesting-level to HTML export ([#2329](https://github.com/TypeCellOS/BlockNote/pull/2329)) +- migrate to ai sdk 6 ([#2328](https://github.com/TypeCellOS/BlockNote/pull/2328)) + +### 🩹 Fixes + +- emojipicker can sometimes fail to mount ([575b81cec](https://github.com/TypeCellOS/BlockNote/commit/575b81cec)) +- LinkToolbar Event Listener leak ([#2335](https://github.com/TypeCellOS/BlockNote/pull/2335)) +- when you convert a block into checkListItem via inputRule, it should transfer its content into checkListItem content ([#2331](https://github.com/TypeCellOS/BlockNote/pull/2331)) +- do not return focus back to menu ([484d7da36](https://github.com/TypeCellOS/BlockNote/commit/484d7da36)) +- arrow up on a checklist item should move to the element above BLO-362 ([#2306](https://github.com/TypeCellOS/BlockNote/pull/2306)) +- getPos race condition in React StrictMode ([#2311](https://github.com/TypeCellOS/BlockNote/pull/2311)) +- adjust input rules to be more tolerant to starting whitespace ([#2341](https://github.com/TypeCellOS/BlockNote/pull/2341)) +- **ai:** make sure ShowSelection works ([#2297](https://github.com/TypeCellOS/BlockNote/pull/2297)) +- **xl-email-exporter:** remove redundant sections in email export ([#2323](https://github.com/TypeCellOS/BlockNote/pull/2323)) + +### ❤️ Thank You + +- Nick Perez +- Nick the Sick @nperez0111 +- supernova @tmpluto +- Yousef + +## 0.45.0 (2025-12-17) + +### 🚀 Features + +- **ai:** expand selections to contain words ([#2304](https://github.com/TypeCellOS/BlockNote/pull/2304)) +- **extensions:** extensions can now include other extensions for grouping into one extension ([#2284](https://github.com/TypeCellOS/BlockNote/pull/2284)) + +### 🩹 Fixes + +- an invalidly specified table should not crash the editor ([#2255](https://github.com/TypeCellOS/BlockNote/pull/2255)) +- filter out invalid heading items based on the current block schema in the slash menu #2253 ([#2259](https://github.com/TypeCellOS/BlockNote/pull/2259), [#2253](https://github.com/TypeCellOS/BlockNote/issues/2253)) +- relax shiki package requirements #2279 ([#2280](https://github.com/TypeCellOS/BlockNote/pull/2280), [#2279](https://github.com/TypeCellOS/BlockNote/issues/2279)) +- filter the default tiptap extensions #2282 ([#2283](https://github.com/TypeCellOS/BlockNote/pull/2283), [#2282](https://github.com/TypeCellOS/BlockNote/issues/2282)) +- always include the cursor extension #2244 ([#2260](https://github.com/TypeCellOS/BlockNote/pull/2260), [#2244](https://github.com/TypeCellOS/BlockNote/issues/2244)) +- make `onBeforeChange` return the correct type again ([9009369b1](https://github.com/TypeCellOS/BlockNote/commit/9009369b1)) +- if there is no table block, there is no table handles to show #1055 ([#2281](https://github.com/TypeCellOS/BlockNote/pull/2281), [#1055](https://github.com/TypeCellOS/BlockNote/issues/1055)) +- pass dragHandleMenu prop to DragHandleButton ([#2254](https://github.com/TypeCellOS/BlockNote/pull/2254)) +- html diff error with whitespace ([#2230](https://github.com/TypeCellOS/BlockNote/pull/2230)) +- update regex for checklist items #2288 ([#2305](https://github.com/TypeCellOS/BlockNote/pull/2305), [#2288](https://github.com/TypeCellOS/BlockNote/issues/2288)) +- **email-exporter:** ReadableByteStreamController for safari react-email ([#2295](https://github.com/TypeCellOS/BlockNote/pull/2295)) + +### ❤️ Thank You + +- Max @maqen +- Nick Perez +- Nick the Sick @nperez0111 +- Yousef + +## 0.44.2 (2025-12-09) + +### 🩹 Fixes + +- put back `onBeforeChange` method #2221 ([#2243](https://github.com/TypeCellOS/BlockNote/pull/2243), [#2221](https://github.com/TypeCellOS/BlockNote/issues/2221)) +- Improper accessing of editor DOM element ([#2234](https://github.com/TypeCellOS/BlockNote/pull/2234)) +- make validation errors recoverable by llm ([#2054](https://github.com/TypeCellOS/BlockNote/pull/2054)) +- shadowdom support and example ([#2223](https://github.com/TypeCellOS/BlockNote/pull/2223)) +- ensure numbered list start property always present ([#2241](https://github.com/TypeCellOS/BlockNote/pull/2241), [#2242](https://github.com/TypeCellOS/BlockNote/pull/2242)) +- Suggestion menu positioning ([#2232](https://github.com/TypeCellOS/BlockNote/pull/2232)) +- conditionally access the TableHandles extension from React ([#2248](https://github.com/TypeCellOS/BlockNote/pull/2248)) +- **ai:** upgrade prosemirror-suggest-changes ([#2235](https://github.com/TypeCellOS/BlockNote/pull/2235)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- wcyat @sdip15fa +- Yousef + +## 0.44.1 (2025-12-08) + +### 🩹 Fixes + +- clearing selection was not being called when create link button is no longer rendered ([#2217](https://github.com/TypeCellOS/BlockNote/pull/2217)) +- AI menu not updating position on new line ([#2233](https://github.com/TypeCellOS/BlockNote/pull/2233)) +- UI elements not scrolling when editor DOM element is scrollable ([#2231](https://github.com/TypeCellOS/BlockNote/pull/2231)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski + +## 0.44.0 (2025-12-02) + +### 🚀 Features + +- **ai:** Abort requests ([#2213](https://github.com/TypeCellOS/BlockNote/pull/2213)) + +### ❤️ Thank You + +- Yousef + +## 0.43.0 (2025-12-01) + +### 🚀 Features + +- Major Extensions & UI Refactor ([#2143](https://github.com/TypeCellOS/BlockNote/pull/2143)) + +### 🩹 Fixes + +- allow configuring the email body's styles ([#2182](https://github.com/TypeCellOS/BlockNote/pull/2182)) +- **xl-docx-exporter:** improve OOXML interoperability ([#2206](https://github.com/TypeCellOS/BlockNote/pull/2206)) + +### ❤️ Thank You + +- Nick Perez +- Stephan Meijer @StephanMeijer + +## 0.42.3 (2025-11-19) + +### 🩹 Fixes + +- disallow access to the `domElement` or `isFocused` if the editor is unmounted ([#2187](https://github.com/TypeCellOS/BlockNote/pull/2187)) + +### ❤️ Thank You + +- Nick Perez + +## 0.42.2 (2025-11-19) + +### 🩹 Fixes + +- put back mounting system ([#2183](https://github.com/TypeCellOS/BlockNote/pull/2183)) + +### ❤️ Thank You + +- Nick Perez + +## 0.42.1 (2025-11-18) + +### 🩹 Fixes + +- do not error on invalid `backgroundColor` or `textColor` #2176 ([#2179](https://github.com/TypeCellOS/BlockNote/pull/2179), [#2176](https://github.com/TypeCellOS/BlockNote/issues/2176)) +- remove dependency array from comments re-rendering ([#2177](https://github.com/TypeCellOS/BlockNote/pull/2177)) + +### ❤️ Thank You + +- Nick Perez + +## 0.42.0 (2025-11-11) + +### 🚀 Features + +- **yjs:** expose Y.js BlockNote conversion primitives #1866 ([#2166](https://github.com/TypeCellOS/BlockNote/pull/2166), [#1866](https://github.com/TypeCellOS/BlockNote/issues/1866)) + +### 🩹 Fixes + +- Emoji picker issues ([#2092](https://github.com/TypeCellOS/BlockNote/pull/2092)) +- set a default for `blocksToFullHTML` #2100 ([#2101](https://github.com/TypeCellOS/BlockNote/pull/2101), [#2100](https://github.com/TypeCellOS/BlockNote/issues/2100)) +- correctly index blocks that have children fixes #2115 ([#2116](https://github.com/TypeCellOS/BlockNote/pull/2116), [#2115](https://github.com/TypeCellOS/BlockNote/issues/2115)) +- add more lenient parsing for code blocks, to accept newlines #2105 ([#2108](https://github.com/TypeCellOS/BlockNote/pull/2108), [#2105](https://github.com/TypeCellOS/BlockNote/issues/2105)) +- Firefox invisible text cursor after dropping blocks ([#2128](https://github.com/TypeCellOS/BlockNote/pull/2128)) +- parsing `priority` for custom inline content and styles ([#2119](https://github.com/TypeCellOS/BlockNote/pull/2119)) +- `BlockTypeSelect` item filtering based on schema ([#2112](https://github.com/TypeCellOS/BlockNote/pull/2112)) +- deleting last block in column ([#2110](https://github.com/TypeCellOS/BlockNote/pull/2110)) +- **comments:** update the styles for the cursor to be the default cursor ([#2163](https://github.com/TypeCellOS/BlockNote/pull/2163)) +- **comments:** always surface the closest mark to the current position ([#2164](https://github.com/TypeCellOS/BlockNote/pull/2164)) +- **comments:** scrolling bug when clicking comment marks ([#2165](https://github.com/TypeCellOS/BlockNote/pull/2165)) +- **react:** destroy editor instances after two ticks ([#2121](https://github.com/TypeCellOS/BlockNote/pull/2121)) +- **schema-migration:** more robust migration of background-color & text-color attributes ([#2154](https://github.com/TypeCellOS/BlockNote/pull/2154)) +- **unique-id:** do not attempt to append to y-sync plugin transactions ([#2153](https://github.com/TypeCellOS/BlockNote/pull/2153)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez + +## 0.41.1 (2025-10-09) + +This was a version bump only, there were no code changes. + +## 0.41.0 (2025-10-08) + +### 🚀 Features + +- AI menu auto scrolling ([#2039](https://github.com/TypeCellOS/BlockNote/pull/2039)) +- Shortcut to delete empty table while cells are selected ([#2052](https://github.com/TypeCellOS/BlockNote/pull/2052)) +- **divider:** add a divider block ([#2014](https://github.com/TypeCellOS/BlockNote/pull/2014)) + +### 🩹 Fixes + +- Code block language select value not updating properly ([#2050](https://github.com/TypeCellOS/BlockNote/pull/2050)) +- disable input rules for numbered headings #1789 ([#2032](https://github.com/TypeCellOS/BlockNote/pull/2032), [#1789](https://github.com/TypeCellOS/BlockNote/issues/1789)) +- video parsing and export for markdown ([#1955](https://github.com/TypeCellOS/BlockNote/pull/1955)) +- Reaction picker shown for users who can't react ([#2061](https://github.com/TypeCellOS/BlockNote/pull/2061)) +- Add Mantine dependency to individual examples ([#2070](https://github.com/TypeCellOS/BlockNote/pull/2070)) +- allow listening to `onChange` and other events before the underlying editor is initialized ([#2063](https://github.com/TypeCellOS/BlockNote/pull/2063)) +- toggle and check list item blocks ([#2071](https://github.com/TypeCellOS/BlockNote/pull/2071)) +- added missing fields to implementations in editor schema block specs ([#2046](https://github.com/TypeCellOS/BlockNote/pull/2046)) + +### ❤️ Thank You + +- Héctor Zhuang @Hector-Zhuang +- Matthew Lipski @matthewlipski +- Nick Perez + +## 0.40.0 (2025-09-30) + +### 🚀 Features + +- Mantine v8 upgrade ([#2028](https://github.com/TypeCellOS/BlockNote/pull/2028), [#2029](https://github.com/TypeCellOS/BlockNote/issues/2029)) +- Update Mantine setup ([#2033](https://github.com/TypeCellOS/BlockNote/pull/2033)) +- **ai:** SDK 5, tool calling, custom backends ([#2007](https://github.com/TypeCellOS/BlockNote/pull/2007)) +- **core:** add the ability to autofocus on the editor element ([#2018](https://github.com/TypeCellOS/BlockNote/pull/2018)) + +### 🩹 Fixes + +- Block colors menu not always showing ([#2027](https://github.com/TypeCellOS/BlockNote/pull/2027)) +- Update remianing examples to Mantine v8 ([#2031](https://github.com/TypeCellOS/BlockNote/pull/2031)) +- ShadCN example Tailwind setup ([#2042](https://github.com/TypeCellOS/BlockNote/pull/2042)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Yousef + +## 0.39.1 (2025-09-19) + +### 🩹 Fixes + +- cleanup accesses to prosemirrorView to account for tiptap 3 behavior ([#2017](https://github.com/TypeCellOS/BlockNote/pull/2017)) +- **core:** input rules can handle when a new block is empty now ([#2013](https://github.com/TypeCellOS/BlockNote/pull/2013)) + +### ❤️ Thank You + +- Nick Perez + +## 0.39.0 (2025-09-18) + +### 🚀 Features + +- move all blocks to use the custom blocks API ([#1904](https://github.com/TypeCellOS/BlockNote/pull/1904)) +- **core:** support for Tiptap V3 ([#2001](https://github.com/TypeCellOS/BlockNote/pull/2001)) + +### ❤️ Thank You + +- Nick Perez + +## 0.38.0 (2025-09-16) + +### 🚀 Features + +- Custom schemas for comment editors ([#1976](https://github.com/TypeCellOS/BlockNote/pull/1976)) + +### 🩹 Fixes + +- Suggestion menu positioning ([#1975](https://github.com/TypeCellOS/BlockNote/pull/1975)) +- doLLMRequest fails when deleting a non-existent block ([#1982](https://github.com/TypeCellOS/BlockNote/pull/1982)) +- file block resize handles not working with touch inputs ([#1981](https://github.com/TypeCellOS/BlockNote/pull/1981)) +- get pdf example working again ([a90ae4d58](https://github.com/TypeCellOS/BlockNote/commit/a90ae4d58)) +- better markdown & html paste, make methods synchronous ([#1957](https://github.com/TypeCellOS/BlockNote/pull/1957)) +- Improve setting text for custom file blocks ([#1984](https://github.com/TypeCellOS/BlockNote/pull/1984)) +- **react:** close link popover on submit in static formatting toolbar #1696 ([#1997](https://github.com/TypeCellOS/BlockNote/pull/1997), [#1696](https://github.com/TypeCellOS/BlockNote/issues/1696)) + +### ❤️ Thank You + +- dsriva03 @dsriva03 +- Héctor Zhuang @Hector-Zhuang +- Matthew Lipski @matthewlipski +- Nick the Sick + +## 0.37.0 (2025-08-29) + +### 🚀 Features + +- export `ShadCNComponentsContext` ([#1965](https://github.com/TypeCellOS/BlockNote/pull/1965)) + +### 🩹 Fixes + +- Typing in empty table cells ([#1973](https://github.com/TypeCellOS/BlockNote/pull/1973)) + +### ❤️ Thank You + +- Héctor Zhuang @Hector-Zhuang +- Matthew Lipski @matthewlipski + +## 0.36.1 (2025-08-27) + +### 🩹 Fixes + +- table column widths not being set in exported HTML ([#1947](https://github.com/TypeCellOS/BlockNote/pull/1947)) +- Minor change to formatting toolbar extension logic ([#1963](https://github.com/TypeCellOS/BlockNote/pull/1963)) +- **core:** report block moves in `getBlocksChangedByTransaction` #1924 ([#1960](https://github.com/TypeCellOS/BlockNote/pull/1960), [#1924](https://github.com/TypeCellOS/BlockNote/issues/1924)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez + +## 0.36.0 (2025-08-25) + +### 🚀 Features + +- **docx:** add locale configuration for docx export ([#1937](https://github.com/TypeCellOS/BlockNote/pull/1937)) + +### 🩹 Fixes + +- Editors in comments not inheriting theme ([#1890](https://github.com/TypeCellOS/BlockNote/pull/1890)) +- Minor drag & drop changes ([#1891](https://github.com/TypeCellOS/BlockNote/pull/1891)) +- Overflow on table blocks ([#1892](https://github.com/TypeCellOS/BlockNote/pull/1892)) +- Suggestion menu closing when clicking scroll bar ([#1899](https://github.com/TypeCellOS/BlockNote/pull/1899)) +- Table padding ([#1906](https://github.com/TypeCellOS/BlockNote/pull/1906)) +- Formatting toolbar getting wrong bounding box when updating React inline content ([#1908](https://github.com/TypeCellOS/BlockNote/pull/1908)) +- Vanilla blocks return true for editor.isEditable on initial render ([#1925](https://github.com/TypeCellOS/BlockNote/pull/1925)) +- table cell menu styling ([#1945](https://github.com/TypeCellOS/BlockNote/pull/1945)) +- Missing internationalization for toggle wrapper ([#1946](https://github.com/TypeCellOS/BlockNote/pull/1946)) +- parse image alt text for image blocks ([#1883](https://github.com/TypeCellOS/BlockNote/pull/1883)) +- initialize esm deps before copy extension uses it ([#1951](https://github.com/TypeCellOS/BlockNote/pull/1951)) +- error when dragging a block from one editor to another with multiple column extension ([#1950](https://github.com/TypeCellOS/BlockNote/pull/1950)) +- prevent infinite render loop when selecting all content ([#1956](https://github.com/TypeCellOS/BlockNote/pull/1956)) +- **core:** maintain text selection across table updates ([#1894](https://github.com/TypeCellOS/BlockNote/pull/1894)) +- **locales:** ko locale fix ([#1902](https://github.com/TypeCellOS/BlockNote/pull/1902)) +- **react:** add data attribute for correct react rendering ([#1954](https://github.com/TypeCellOS/BlockNote/pull/1954)) +- **xl-email-exporter:** better defaults, customize textStyles, output inline styles ([#1856](https://github.com/TypeCellOS/BlockNote/pull/1856)) + +### ❤️ Thank You + +- Brad Greenlee +- Cyril G @Ovgodd +- Héctor Zhuang @Hector-Zhuang +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick + +## 0.35.0 (2025-07-25) + +### 🚀 Features + +- use fumadocs for website ([#1654](https://github.com/TypeCellOS/BlockNote/pull/1654)) +- llms.mdx routes ([cea93840e](https://github.com/TypeCellOS/BlockNote/commit/cea93840e)) + +### 🩹 Fixes + +- insert file upload before block if it is closer to the top of the block ([#1857](https://github.com/TypeCellOS/BlockNote/pull/1857)) +- rename albert model ([3b0ba8d25](https://github.com/TypeCellOS/BlockNote/commit/3b0ba8d25)) +- resolve some minor drag & drop regressions ([#1862](https://github.com/TypeCellOS/BlockNote/pull/1862)) +- blockquote HTML parsing #1762 ([#1877](https://github.com/TypeCellOS/BlockNote/pull/1877), [#1762](https://github.com/TypeCellOS/BlockNote/issues/1762)) + +### ❤️ Thank You + +- Brad Greenlee +- Nick Perez +- Nick the Sick +- yousefed + +## 0.34.0 (2025-07-17) + +### 🚀 Features + +- support multi-column block in PDF, DOCX & ODT exporters ([#1781](https://github.com/TypeCellOS/BlockNote/pull/1781)) +- support react 19 ([f7b3466d3](https://github.com/TypeCellOS/BlockNote/commit/f7b3466d3)) +- disable conversion of headings to list items ([#1799](https://github.com/TypeCellOS/BlockNote/pull/1799)) +- report `moves` (indents and outdents) as changes when using `getChanges` #1757 ([#1786](https://github.com/TypeCellOS/BlockNote/pull/1786), [#1757](https://github.com/TypeCellOS/BlockNote/issues/1757)) +- allow inline content to be `draggable` ([#1818](https://github.com/TypeCellOS/BlockNote/pull/1818)) +- added type guards, types, and `editor` prop to custom inline content rendering ([#1736](https://github.com/TypeCellOS/BlockNote/pull/1736)) +- **block-change:** adds a new API for blocking changes to editor state, by filtering transactions ([#1750](https://github.com/TypeCellOS/BlockNote/pull/1750)) + +### 🩹 Fixes + +- remove lookbehind regex for browser compat ([#1827](https://github.com/TypeCellOS/BlockNote/pull/1827)) +- `ToggleWrapper` button defaulting to `submit` type ([#1823](https://github.com/TypeCellOS/BlockNote/pull/1823)) +- disable $ref in AI schemas (html format) ([#1819](https://github.com/TypeCellOS/BlockNote/pull/1819)) +- re-evaluate side-menu on scroll ([#1830](https://github.com/TypeCellOS/BlockNote/pull/1830)) +- hide table extend buttons when not editable #1848 ([#1850](https://github.com/TypeCellOS/BlockNote/pull/1850), [#1848](https://github.com/TypeCellOS/BlockNote/issues/1848)) +- resolve several drag & drop issues ([#1845](https://github.com/TypeCellOS/BlockNote/pull/1845)) + +### ❤️ Thank You + +- Arek Nawo @areknawo +- Gonçalo Basto @gbasto +- Héctor Zhuang @Hector-Zhuang +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick @nperez0111 +- Yousef + +## 0.33.0 (2025-07-03) + +### 🚀 Features + +- FloatingUI options prop for `BlockPositioner` ([#1801](https://github.com/TypeCellOS/BlockNote/pull/1801)) +- Support Google Gemini AI ([#1805](https://github.com/TypeCellOS/BlockNote/pull/1805)) + +### 🩹 Fixes + +- support multi-character suggestions ([#1734](https://github.com/TypeCellOS/BlockNote/pull/1734)) +- switch foreground color based on selected user color dynamically #1785 ([#1787](https://github.com/TypeCellOS/BlockNote/pull/1787), [#1785](https://github.com/TypeCellOS/BlockNote/issues/1785)) +- mark react package as external in email exporter ([#1807](https://github.com/TypeCellOS/BlockNote/pull/1807)) +- Duplicate `formatConversionTest` files ([#1798](https://github.com/TypeCellOS/BlockNote/pull/1798)) +- AI empty document handling ([#1810](https://github.com/TypeCellOS/BlockNote/pull/1810)) +- `bn-inline-content` class name getting duplicated ([#1794](https://github.com/TypeCellOS/BlockNote/pull/1794)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Yousef + +## 0.32.0 (2025-06-24) + +### 🚀 Features + +- toggle blocks ([#1707](https://github.com/TypeCellOS/BlockNote/pull/1707)) +- **core:** support h4, h5, and h6 ([#1634](https://github.com/TypeCellOS/BlockNote/pull/1634)) +- **xl-email-exporter:** add email exporter ([#1768](https://github.com/TypeCellOS/BlockNote/pull/1768)) + +### 🩹 Fixes + +- react 19 strict mode compatibility ([#1726](https://github.com/TypeCellOS/BlockNote/pull/1726)) +- add keys to pdf exporter ([#1739](https://github.com/TypeCellOS/BlockNote/pull/1739)) +- only listten for left click on formatting toolbar ([#1774](https://github.com/TypeCellOS/BlockNote/pull/1774)) +- prevent formatting toolbar from closing if click was from inside the editor ([#1775](https://github.com/TypeCellOS/BlockNote/pull/1775)) +- **locales:** add Hebrew translations for various components ([#1779](https://github.com/TypeCellOS/BlockNote/pull/1779)) + +### ❤️ Thank You + +- Aslam @Aslam97 +- Drew Johnson +- Jonathan Marbutt @jmarbutt +- Matthew Lipski @matthewlipski +- Nick Perez +- Samuel Bisberg +- Yousef + +## 0.31.3 (2025-06-18) + +### 🩹 Fixes + +- AI generation with empty document ([#1740](https://github.com/TypeCellOS/BlockNote/pull/1740)) +- do not send a welcome email if magic link was used on an account older than a minute ago ([db88fe4aa](https://github.com/TypeCellOS/BlockNote/commit/db88fe4aa)) +- AI system messages should always be at start of prompt ([#1741](https://github.com/TypeCellOS/BlockNote/pull/1741)) +- Selection clicking editor padding ([#1717](https://github.com/TypeCellOS/BlockNote/pull/1717)) +- preserve marks across a shift+enter #1672 ([#1743](https://github.com/TypeCellOS/BlockNote/pull/1743), [#1672](https://github.com/TypeCellOS/BlockNote/issues/1672)) +- **ai:** undo-redo after accepting/rejecting changes will undo as expected ([#1752](https://github.com/TypeCellOS/BlockNote/pull/1752)) +- **locales:** add translations for some comment strings ([#1764](https://github.com/TypeCellOS/BlockNote/pull/1764)) +- **website:** log in bug fixes ([#1742](https://github.com/TypeCellOS/BlockNote/pull/1742)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick +- Vinicius Fernandes @ViniCleFer +- Yousef + +## 0.31.2 (2025-06-05) + +### 🩹 Fixes + +- re-release ([0bc546e18](https://github.com/TypeCellOS/BlockNote/commit/0bc546e18)) +- ignore falsy values in boolean prop schema ([#1730](https://github.com/TypeCellOS/BlockNote/pull/1730)) + +### ❤️ Thank You + +- Nick Perez +- Nick the Sick + +## 0.31.1 (2025-05-23) + +### 🩹 Fixes + +- backwards-compat for `_extensions` ([#1708](https://github.com/TypeCellOS/BlockNote/pull/1708)) + +### ❤️ Thank You + +- Nick Perez + +## 0.31.0 (2025-05-20) + +### 🩹 Fixes + +- Playwright flaky keyboard handler test ([#1704](https://github.com/TypeCellOS/BlockNote/pull/1704)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski + +## 0.30.1 (2025-05-20) + +### 🩹 Fixes + +- better type-safety ([678086d4d](https://github.com/TypeCellOS/BlockNote/commit/678086d4d)) +- do not use `editor.dispatch` ([#1698](https://github.com/TypeCellOS/BlockNote/pull/1698)) +- re-added `display: flex` to blocks without inline content ([#1702](https://github.com/TypeCellOS/BlockNote/pull/1702)) +- **react:** add missing exports ([#1689](https://github.com/TypeCellOS/BlockNote/pull/1689)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick + +## 0.30.0 (2025-05-09) + +### 🚀 Features + +- expose `editor.prosemirrorState` again ([#1615](https://github.com/TypeCellOS/BlockNote/pull/1615)) +- add `undo` and `redo` methods to editor API ([#1592](https://github.com/TypeCellOS/BlockNote/pull/1592)) +- new auth & payment system ([#1617](https://github.com/TypeCellOS/BlockNote/pull/1617)) +- re-implement Y.js collaboration as BlockNote plugins ([#1638](https://github.com/TypeCellOS/BlockNote/pull/1638)) +- **file:** `previewWidth` prop now defaults to `undefined` ([#1664](https://github.com/TypeCellOS/BlockNote/pull/1664)) +- **locales:** add zh-TW i18n ([#1668](https://github.com/TypeCellOS/BlockNote/pull/1668)) + +### 🩹 Fixes + +- Formatting toolbar regression ([#1630](https://github.com/TypeCellOS/BlockNote/pull/1630)) +- provide `blockId` to `uploadFile` in UploadTab ([#1641](https://github.com/TypeCellOS/BlockNote/pull/1641)) +- do not close the menu on content/selection change ([#1644](https://github.com/TypeCellOS/BlockNote/pull/1644)) +- keep file panel open during collaboration ([#1646](https://github.com/TypeCellOS/BlockNote/pull/1646)) +- force pasting plain text into code block ([#1663](https://github.com/TypeCellOS/BlockNote/pull/1663)) +- updating HTML parsing rules to account for `prosemirror-model@1.25.1` ([#1661](https://github.com/TypeCellOS/BlockNote/pull/1661)) +- **code-block:** handle unknown languages better ([#1626](https://github.com/TypeCellOS/BlockNote/pull/1626)) +- **locales:** add slovak i18n ([#1649](https://github.com/TypeCellOS/BlockNote/pull/1649)) + +### ❤️ Thank You + +- l0st0 @l0st0 +- Lawrence Lin @linyiru +- Matthew Lipski @matthewlipski +- Nick Perez +- Quentin Nativel + +## 0.29.1 (2025-04-17) + +### 🩹 Fixes + +- try not to always use workspace version ([7af344ea9](https://github.com/TypeCellOS/BlockNote/commit/7af344ea9)) + +### ❤️ Thank You + +- Nick the Sick + +## 0.29.0 (2025-04-17) + +### 🚀 Features + +- `change` event allows getting a list of the block changed ([#1585](https://github.com/TypeCellOS/BlockNote/pull/1585)) + +### 🩹 Fixes + +- allow opening another suggestion menu if another is triggered #1473 ([#1591](https://github.com/TypeCellOS/BlockNote/pull/1591), [#1473](https://github.com/TypeCellOS/BlockNote/issues/1473)) +- add quote to schema ([aa16b15fe](https://github.com/TypeCellOS/BlockNote/commit/aa16b15fe)) +- update y-prosemirror to fix #1462 ([#1608](https://github.com/TypeCellOS/BlockNote/pull/1608), [#1462](https://github.com/TypeCellOS/BlockNote/issues/1462)) +- dispatch suggestion menu as a separate transaction ([#1614](https://github.com/TypeCellOS/BlockNote/pull/1614)) + +### ❤️ Thank You + +- Nick Perez +- Nick the Sick + +## 0.28.0 (2025-04-07) + +### 🚀 Features + +- position storage ([#1529](https://github.com/TypeCellOS/BlockNote/pull/1529)) + +### ❤️ Thank You + +- Nick Perez + +## 0.27.2 (2025-04-05) + +### 🩹 Fixes + +- minor update for publishing ([c2820fdac](https://github.com/TypeCellOS/BlockNote/commit/c2820fdac)) + +### ❤️ Thank You + +- Nick the Sick + +## 0.27.1 (2025-04-05) + +### 🚀 Features + +- **nx-cloud:** set up nx workspace ([#1586](https://github.com/TypeCellOS/BlockNote/pull/1586)) + +### 🩹 Fixes + +- update packages to use correct react versions ([ea11ebce0](https://github.com/TypeCellOS/BlockNote/commit/ea11ebce0)) + +### ❤️ Thank You + +- Nick Perez +- Nick the Sick + +## 0.27.0 (2025-04-04) + +### 🚀 Features + +- split out localization files for optimized bundle ([#1533](https://github.com/TypeCellOS/BlockNote/pull/1533)) +- remove shiki dep, add new @blocknote/code-block package for slim shiki build ([#1519](https://github.com/TypeCellOS/BlockNote/pull/1519)) +- Block quote ([#1563](https://github.com/TypeCellOS/BlockNote/pull/1563)) +- markdown pasting & custom paste handlers ([#1490](https://github.com/TypeCellOS/BlockNote/pull/1490)) + +### 🩹 Fixes + +- Backspace in empty block deletes previous block ([#1505](https://github.com/TypeCellOS/BlockNote/pull/1505)) +- Selection when clicking past end of inline content ([#1553](https://github.com/TypeCellOS/BlockNote/pull/1553)) +- better expose setting a draghandlemenu's items #1525 ([#1526](https://github.com/TypeCellOS/BlockNote/pull/1526), [#1525](https://github.com/TypeCellOS/BlockNote/issues/1525)) +- Multi-block links ([#1565](https://github.com/TypeCellOS/BlockNote/pull/1565)) +- Hard break keyboard shortcut not working in custom blocks ([#1554](https://github.com/TypeCellOS/BlockNote/pull/1554)) +- Overlapping marks in comments ([#1564](https://github.com/TypeCellOS/BlockNote/pull/1564)) +- some more sentry fixes ([#1577](https://github.com/TypeCellOS/BlockNote/pull/1577)) + +### ❤️ Thank You + +- Martinrsts @Martinrsts +- Matthew Lipski @matthewlipski +- Nick Perez + +## Previous Versions + +See [Github Releases](https://github.com/TypeCellOS/BlockNote/releases) for previous versions. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac57b6f132..a2e0099d73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,29 +3,85 @@ Directory structure: ``` -blocknote -├── packages/core - The core of the editor -├── packages/react - The main library for use in React apps -├── examples/editor - Example React app that embeds the editor -├── examples/vanilla - An advanced example if you don't want to use React or want to build your own UI components -└── tests - Playwright end to end tests +BlockNote +├── packages/core - The core of the editor, which includes all logic to get the editor running in vanilla JS. +├── packages/react - A React wrapper and UI for the editor. Requires additional components for the UI. +├── packages/ariakit - UI components for the `react` package, made with Ariakit. +├── packages/mantine - UI components for the `react` package, made with Mantine. +├── packages/shadcn - UI components for the `react` package, made with Shadcn. +├── packages/server-util - Utilities for converting BlockNote documents into static HTML for server-side rendering. +├── packages/dev-scripts - A set of tools for converting example editor setups into components for the BlockNote website. +├── examples - Example editor setups used for demos in the BlockNote website and playground. +├── docs - Code for the BlockNote website. +├── playground - A basic page where you can quickly test each of the example editor setups. +└── tests - Playwright end to end tests. ``` -An introduction into the BlockNote Prosemirror schema can be found in [packages/core/ARCHITECTURE.md](https://github.com/TypeCellOS/BlockNote/blob/main/packages/core/ARCHITECTURE.md). +An introduction into the BlockNote Prosemirror schema can be found in [packages/core/src/pm-nodes/README.md](https://github.com/TypeCellOS/BlockNote/blob/main/packages/core/src/pm-nodes/README.md). ## Running To run the project, open the command line in the project's root directory and enter the following commands: - # Install all required npm modules for lerna, and bootstrap lerna packages - npm install - npm run bootstrap +```bash +# Install all required npm modules +pnpm install - # Start the example project - npm start +# Start the example project +pnpm start +``` ## Adding packages - Add the dependency to the relevant `package.json` file (packages/xxx/package.json) -- run `npm run install-new-packages` -- Double check `package-lock.json` to make sure only the relevant packages have been affected +- Double check `pnpm-lock.yaml` to make sure only the relevant packages have been affected + +## Packages + +| Package | Size | Version | +| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| [@blocknote/core](https://github.com/TypeCellOS/BlockNote/tree/main/packages/core) | | | +| [@blocknote/react](https://github.com/TypeCellOS/BlockNote/tree/main/packages/react) | | | +| [@blocknote/ariakit](https://github.com/TypeCellOS/BlockNote/tree/main/packages/ariakit) | | | +| [@blocknote/mantine](https://github.com/TypeCellOS/BlockNote/tree/main/packages/mantine) | | | +| [@blocknote/shadcn](https://github.com/TypeCellOS/BlockNote/tree/main/packages/shadcn) | | | +| [@blocknote/server-util](https://github.com/TypeCellOS/BlockNote/tree/main/packages/server-util) | | | + +## Releasing + +This diagram illustrates the release workflow for the BlockNote monorepo. + +![Release Workflow](./.resources/release-workflow.excalidraw.svg) + +Essentially, when the maintainers have decided to release a new version of BlockNote, they will: + +1. Check that the `main` branch is in a releasable state: + - CI status of main branch is green + - Builds are passing +2. Bump the package versions using the `pnpm run deploy` command. This command will: + 1. Based on semantic versioning, determine the next version number. + 2. Apply the new version number to all publishable packages within the monorepo. + 3. Generate a changelog for the new version. + 4. Commit the changes to the `main` branch. + 5. Create a new git tag for the new version. + 6. Push the changes to the `origin` remote. + 7. Create a new GitHub Release with the same name as the new version. + 8. Trigger a release workflow. + +The release workflow will: + +1. Checkout the `main` branch. +2. Install the dependencies. +3. Build the project. +4. Login to npm. +5. Publish the packages to npm. + +### Publishing a new package + +From time to time, you may need to publish a new package to npm. To do this, you cannot just deploy the package to npm, you need to: + +1. Run `nx release version --dry-run` and check that the version number is correct for the package. + - Once this is done, you can run `nx release version` to actually apply the version bump locally (staged to your local git repo). +2. Run `nx release changelog --from --dry-run` and check that the changelog is correct for the package. + - Once this is done, you can run the same command without the `--dry-run` flag to actually apply the changelog, commit & push the changes to the `main` branch. +3. The release workflow will automatically publish the package to npm. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a612ad9813..0000000000 --- a/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000..35a742ce7e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,5 @@ +BlockNote is 100% Open Source Software. + +Source code in this repository is covered by the "Mozilla Public License Version 2.0" (MPL-2.0) license, except for the XL packages. The MPL-2.0 license allows you to use BlockNote in commercial (and closed-source) applications. If you make changes to the BlockNote source files, you're expected to publish these changes so the wider community can benefit as well. + +The XL packages (source code in the `packages/xl-*` directories and published in NPM as `@blocknote/xl-*`) are licensed under the "GNU General Public License Version 3" (GPL-3.0). Additionally, a commercial license is available. See our website (https://www.blocknotejs.org/pricing) for more information and the commercial license terms. \ No newline at end of file diff --git a/README.md b/README.md index bdb653434e..f391efb3f3 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,42 @@

- TypeCell + TypeCell

Welcome to BlockNote! The open source Block-Based -rich text editor. Easily add a modern text editing experience to your app. -

- -

-Discord Matrix +React rich text editor. Easily add a modern text editing experience to your app.

Homepage - - - Introduction - - + - Documentation + - + Quickstart + - + Examples

# Live demo -Play with the editor @ [https://blocknote-main.vercel.app/](https://blocknote-main.vercel.app/). - -(Source in [examples/editor](/examples/editor)) +See our homepage @ [https://www.blocknotejs.org](https://www.blocknotejs.org/) or browse the [examples](https://www.blocknotejs.org/examples). # Example code (React) [![npm version](https://badge.fury.io/js/%40blocknote%2Freact.svg)](https://badge.fury.io/js/%40blocknote%2Freact) ```typescript -import { BlockNoteView, useBlockNote } from "@blocknote/react"; -import "@blocknote/core/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/core/fonts/inter.css"; +import "@blocknote/mantine/style.css"; function App() { - const editor = useBlockNote({ - onEditorContentChange: (editor) => { - // Log the document to console on every update - console.log(editor.getJSON()); - }, - }); + const editor = useCreateBlockNote(); return ; } @@ -51,8 +44,6 @@ function App() { `@blocknote/react` comes with a fully styled UI that makes it an instant, polished editor ready to use in your app. -If you prefer to create your own UI components (menus), or don't want to use React, you can use `@blocknote/core` (_advanced_, [see docs](https://www.blocknotejs.org/docs/vanilla-js)). - # Features BlockNote comes with a number of features and components to make it easy to embed a high-quality block-based editor in your app: @@ -87,28 +78,19 @@ BlockNote comes with a number of features and components to make it easy to embe # Feedback 🙋‍♂️🙋‍♀️ -We'd love to hear your thoughts and see your experiments, so [come and say hi on Discord](https://discord.gg/Qc2QTTH5dF) or [Matrix](https://matrix.to/#/#typecell-space:matrix.org). +We'd love to hear your thoughts and see your experiments, so [come and say hi on Discord](https://discord.gg/Qc2QTTH5dF). # Contributing 🙌 -See [CONTRIBUTING.md](CONTRIBUTING.md) for more info and guidance on how to run the project (TLDR: just use `npm start`). - -Directory structure: - -``` -blocknote -├── packages/core - The core of the editor -├── packages/react - The main library for use in React apps -├── examples/editor - Example React app that embeds the editor -├── examples/vanilla - An advanced example if you don't want to use React or want to build your own UI components -└── tests - Playwright end to end tests -``` +See [CONTRIBUTING.md](CONTRIBUTING.md) for more info and guidance on how to run the project (TLDR: just use `pnpm start`). The codebase is automatically tested using Vitest and Playwright. # License 📃 -BlockNote is licensed under the [MPL 2.0 license](https://fossa.com/blog/open-source-software-licenses-101-mozilla-public-license-2-0/), which allows you to use BlockNote in commercial (and closed-source) applications. If you make changes to the BlockNote source files, you're expected to publish these changes so the wider community can benefit as well. +BlockNote is 100% Open Source Software. The majority of BlockNote is licensed under the [MPL-2.0 license](LICENSE-MPL.txt), which allows you to use BlockNote in commercial (and closed-source) applications. If you make changes to the BlockNote source files, you're expected to publish these changes so the wider community can benefit as well. [Learn more](https://fossa.com/blog/open-source-software-licenses-101-mozilla-public-license-2-0/). + +The XL packages (source code in the `packages/xl-*` directories and published in NPM as `@blocknote/xl-*`) are licensed under the GPL-3.0. If you cannot comply with this license and want to use the XL libraries, you'll need a commercial license. Refer to [our website](https://www.blocknotejs.org/pricing) for more information. # Credits ❤️ @@ -121,3 +103,5 @@ BlockNote is built as part of [TypeCell](https://www.typecell.org). TypeCell is Hosting and deployments powered by Vercel: NLNet + +This project is tested with BrowserStack diff --git a/docs/.env.local.example b/docs/.env.local.example new file mode 100644 index 0000000000..a2dba36b4b --- /dev/null +++ b/docs/.env.local.example @@ -0,0 +1,35 @@ +AUTH_SECRET= # Linux: `openssl rand -hex 32` or go to https://generate-secret.vercel.app/32 + +# Better Auth Deployed URL +BETTER_AUTH_URL=http://localhost:3000 + +# ======= OPTIONAL ======= + +# # Polar Sandbox is used in dev mode: https://sandbox.polar.sh/ +# # You may need to delete your user in their dashboard if you get a "cannot attach new external ID error" +# POLAR_ACCESS_TOKEN= +# POLAR_WEBHOOK_SECRET= + +# # In production, we use postgres +# POSTGRES_URL= + +# # Email +# SMTP_HOST= +# SMTP_USER= +# SMTP_PASS= +# SMTP_PORT= +# # Insecure if false, secure if any other value +# SMTP_SECURE=false + +# # For GitHub Signin method +# AUTH_GITHUB_ID= +# AUTH_GITHUB_SECRET= + +# # The SENTRY_AUTH_TOKEN variable is picked up by the Sentry Build Plugin. +# # It's used for authentication when uploading source maps. +# SENTRY_AUTH_TOKEN= + +NEXT_PUBLIC_BLOCKNOTE_AI_SERVER_API_KEY= +NEXT_PUBLIC_BLOCKNOTE_AI_SERVER_BASE_URL= + +TURNSTILE_SECRET_KEY= \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..c12953bff8 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,32 @@ +# deps +/node_modules + +# generated content +.source + +# test & build +/coverage +/.next/ +/out/ +/build +*.tsbuildinfo + +# misc +.DS_Store +*.pem +/.pnp +.pnp.js +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# others +.env*.local +.vercel +next-env.d.ts +# Sentry Config File +.env.sentry-build-plugin + +/content/examples/*/* +/components/example/generated/ +sqlite.db diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..b8e4860113 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,105 @@ +# Website Development + +This is the code for the [BlockNote documentation website](https://www.blocknotejs.org). If you're looking to work on BlockNote itself, check the [`packages`](/packages/) folder. + +To get started with development of the website, you can follow these steps: + +1. Initialize the DB + +If you haven't already, you can initialize the database with the following command: + +```bash +cd docs && pnpm run init-db +``` + +This will initialize an SQLite database at `./docs/sqlite.db`. + +2. Setup environment variables + +Copy the `.env.example` file to `.env.local` and set the environment variables. + +```bash +cp .env.example .env.local +``` + +If you want to test logging in, or payments see more information below [in the environment variables section](#environment-variables). + +3. Start the development server from within the `./docs` directory. + +```bash +pnpm run dev +``` + +This will start the development server on port 3000. + +## Environment Variables + +### Logging in + +To test logging in, you can set the following environment variables: + +```bash +AUTH_SECRET=test +# Github OAuth optionally +AUTH_GITHUB_ID=test +AUTH_GITHUB_SECRET=test +``` + +Note: the GITHUB_ID and GITHUB_SECRET are optional, but if you want to test logging in with Github you'll need to set them. For local development, you'll need to set the callback URL to `http://localhost:3000/api/auth/callback/github` + +### Payments + +To test payments, you can set the following environment variables: + +```bash +POLAR_ACCESS_TOKEN=test +POLAR_WEBHOOK_SECRET=test +``` + +For testing payments, you'll need access to the polar sandbox which needs to be configured to point a webhook to your local server. This can be configured at: + +You'll need something like [ngrok](https://ngrok.com/) to expose your local server to the internet. + +```bash +ngrok http http://localhost:3000 +``` + +You'll need the webhook to point to ngrok like so: + +``` +https://0000-00-00-000-00.ngrok-free.app/api/auth/polar/webhooks +``` + +With this webhook pointing to your local server, you should be able to test payments. + +### Email sending + +Note, this is not required, if email sending is not configured, the app will log the email it would send to the console. Often this is more convenient for development. + +To test email sending, you can set the following environment variables: + +```bash +SMTP_HOST= +SMTP_USER= +SMTP_PASS= +SMTP_PORT= +SMTP_SECURE=false +``` + +When configured, you'll be able to send emails to the email address you've configured. + +To setup with protonmail, you'll need to go to and create a new SMTP submission token. + +You'll need to set the following environment variables: + +```bash +SMTP_HOST=smtp.protonmail.com +SMTP_USER=my.email@protonmail.com +SMTP_PASS=my-smtp-token +SMTP_PORT=587 +SMTP_SECURE=false +``` + +# Contributing + +To submit your changes, open a pull request to the [BlockNote GitHub repo](https://github.com/TypeCellOS/BlockNote). Pull requests will automatically be deployed to a preview environment. diff --git a/docs/app/(auth)/layout.tsx b/docs/app/(auth)/layout.tsx new file mode 100644 index 0000000000..715e32a774 --- /dev/null +++ b/docs/app/(auth)/layout.tsx @@ -0,0 +1,10 @@ +import { HomeLayout } from "@/components/fumadocs/layout/home"; +import { baseOptions } from "@/lib/layout.shared"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + +
{children}
+
+ ); +} diff --git a/docs/app/(auth)/signin/page.tsx b/docs/app/(auth)/signin/page.tsx new file mode 100644 index 0000000000..b21d007847 --- /dev/null +++ b/docs/app/(auth)/signin/page.tsx @@ -0,0 +1,18 @@ +import AuthenticationPage from "@/components/AuthenticationPage"; +import { getFullMetadata } from "@/lib/getFullMetadata"; +import { Suspense } from "react"; + +export const metadata = getFullMetadata({ + title: "Sign In", + path: "/signin", +}); + +// Suspense + imported AuthenticationPage because AuthenticationPage is a client component +// https://nextjs.org/docs/app/api-reference/functions/use-search-params#static-rendering +export default function Page() { + return ( + + + + ); +} diff --git a/docs/app/(auth)/signin/password/page.tsx b/docs/app/(auth)/signin/password/page.tsx new file mode 100644 index 0000000000..d998c4db48 --- /dev/null +++ b/docs/app/(auth)/signin/password/page.tsx @@ -0,0 +1,17 @@ +import AuthenticationPage from "@/components/AuthenticationPage"; +import { Metadata } from "next"; +import { Suspense } from "react"; + +export const metadata: Metadata = { + title: "Password Login", +}; + +// Suspense + imported AuthenticationPage because AuthenticationPage is a client component +// https://nextjs.org/docs/app/api-reference/functions/use-search-params#static-rendering +export default function Page() { + return ( + + + + ); +} diff --git a/docs/app/(auth)/signup/page.tsx b/docs/app/(auth)/signup/page.tsx new file mode 100644 index 0000000000..04aceaa07a --- /dev/null +++ b/docs/app/(auth)/signup/page.tsx @@ -0,0 +1,18 @@ +import AuthenticationPage from "@/components/AuthenticationPage"; +import { getFullMetadata } from "@/lib/getFullMetadata"; +import { Suspense } from "react"; + +export const metadata = getFullMetadata({ + title: "Sign Up", + path: "/signup", +}); + +// Suspense + imported AuthenticationPage because AuthenticationPage is a client component +// https://nextjs.org/docs/app/api-reference/functions/use-search-params#static-rendering +export default function Page() { + return ( + + + + ); +} diff --git a/docs/app/(home)/_components/BlockCatalog.tsx b/docs/app/(home)/_components/BlockCatalog.tsx new file mode 100644 index 0000000000..6d1c86e36e --- /dev/null +++ b/docs/app/(home)/_components/BlockCatalog.tsx @@ -0,0 +1,108 @@ +"use client"; +import { + AudioWaveform, + ChevronRight, + Code2, + FileText, + Heading, + Image, + List, + ListOrdered, + ListTodo, + Minus, + Pilcrow, + Puzzle, + Quote, + Table, + Video, +} from "lucide-react"; +import React from "react"; + +const BlockCatalogItem: React.FC<{ name: string; icon: React.ReactNode }> = ({ + name, + icon, +}) => ( +
+
+
+ {icon} +
+ + {name} + +
+); + +export const BlockCatalog: React.FC = () => { + return ( +
+ {/* Subtle decorative elements */} +
+
+
+
+
+ +
+
+
+ 🧩 +
+

+ Build anything, block by block. +

+

+ Every BlockNote document is a collection of blocks—headings, lists, + images, and more. Use the built-in blocks, customize them to fit + your needs, or create entirely new ones. +

+
+ +
+ } + /> + } + /> + } /> + } + /> + } + /> + } + /> + } /> + } /> + } + /> + } /> + } /> + } /> + } + /> + } + /> + } + /> +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/DigitalCommons.tsx b/docs/app/(home)/_components/DigitalCommons.tsx new file mode 100644 index 0000000000..6bd11d7b06 --- /dev/null +++ b/docs/app/(home)/_components/DigitalCommons.tsx @@ -0,0 +1,127 @@ +"use client"; +import Link from "next/link"; +import React, { useRef, useState } from "react"; + +export const DigitalCommons: React.FC = () => { + const videoRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); + + const handlePlayPause = () => { + if (videoRef.current) { + if (isPlaying) { + videoRef.current.pause(); + } else { + videoRef.current.play(); + } + setIsPlaying(!isPlaying); + } + }; + + return ( +
+ {/* Warm gradient overlay to harmonize with cream hero */} +
+ {/* Top edge gradient for smoother transition */} +
+ +
+ {/* Asymmetric layout: content + video (vertically centered) */} +
+ {/* Left: Editorial content */} +
+ {/* Eyebrow with EU flag only */} +
+ 🇪🇺 + + Digital Commons + +
+ + {/* Headline - editorial style */} +

+ Three nations choose
+ + open source + {" "} + to power +
+ their digital future. +

+ + {/* Short punchy copy */} +

+ France, Germany, and the Netherlands partner to build{" "} + + Docs + + , a collaborative writing tool for thousands of public servants.{" "} + BlockNote is the engine. +

+ + {/* Compelling social proof - simpler */} +

+ "Building Digital Commons means better tools, data + sovereignty, and shared progress." +

+ + {/* CTA */} + + Partner with us + + +
+ + {/* Right: Video - vertically centered */} +
+ {/* Glow effect */} +
+ +
+ + + {/* Play button overlay */} + {!isPlaying && ( + + )} +
+
+
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FAQ.tsx b/docs/app/(home)/_components/FAQ.tsx new file mode 100644 index 0000000000..5fbe0bda40 --- /dev/null +++ b/docs/app/(home)/_components/FAQ.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +const faqs = [ + { + question: "Isn't it easier to use a Headless editor framework?", + answer: + "There are a number of really powerful headless text editor frameworks available. In fact, BlockNote is built on Prosemirror and TipTap. However, even when using a headless library, it takes several months and requires deep expertise to build a fully-featured editor with a polished UI that your users expect.", + }, + { + question: "Is BlockNote ready for production use?", + answer: + "BlockNote is used by dozens of companies in production, ranging from startups to large enterprises and public institutions. Also, we didn't reinvent the wheel. The core editor is built on top of Prosemirror - a battle tested framework that powers software from Atlassian, Gitlab, the New York Times, and many others.", + }, + { + question: "Can I add my own extensions to BlockNote?", + answer: + "BlockNote comes with lot of functionality out-of-the-box, but we understand that every use case is different. You can easily customize the built-in UI Components, or create your own custom Blocks, Inline Content, and Styles. If you want to go even further, you can extend the core editor with additional Prosemirror or TipTap plugins.", + }, + { + question: "Is BlockNote really free?", + answer: + "100% of BlockNote is open source. We offer consultancy, support services and commercial licenses for specific XL packages to help sustain BlockNote. Explore our pricing page for more details.", + }, +]; + +export const FAQ: React.FC = () => { + return ( +
+
+
+

+ Questions? +

+
+ +
+ {faqs.map((faq, index) => ( +
+

+ {faq.question} +

+

+ {faq.answer} +

+
+ ))} +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FeatureAI.tsx b/docs/app/(home)/_components/FeatureAI.tsx new file mode 100644 index 0000000000..6f87e80e6b --- /dev/null +++ b/docs/app/(home)/_components/FeatureAI.tsx @@ -0,0 +1,63 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureAI: React.FC = () => { + const [activeTab, setActiveTab] = useState<"toolbar" | "models" | "human">( + "toolbar", + ); + + const content: Record = { + toolbar: { + type: "video", + src: "/video/ai-select.mp4", + className: "px-4", + }, + models: { + type: "image", + src: "/img/screenshots/home/any_model.png", + alt: "Bring Any Model", + }, + human: { + type: "image", + src: "/img/screenshots/home/human_in_the_loop.png", + alt: "Human in the Loop", + }, + }; + + const tabs = [ + { + id: "toolbar", + icon: , + label: "AI in the Editor", + description: + "Context-aware completions and edits directly in the document.", + }, + { + id: "models", + icon: 🔌, + label: "Bring Any Model", + description: "Connect OpenAI, Anthropic, or your own endpoints.", + }, + { + id: "human", + icon: 🤝, + label: "Human in the Loop", + description: "Users accept, reject, or refine AI suggestions.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={true} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureCollab.tsx b/docs/app/(home)/_components/FeatureCollab.tsx new file mode 100644 index 0000000000..3553d8255f --- /dev/null +++ b/docs/app/(home)/_components/FeatureCollab.tsx @@ -0,0 +1,65 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureCollab: React.FC<{ + code: { realtime: string }; +}> = ({ code }) => { + const [activeTab, setActiveTab] = useState< + "realtime" | "comments" | "suggestions" + >("realtime"); + + const content: Record = { + realtime: { + type: "code", + file: "CollaborativeEditor.tsx", + code: code.realtime, + }, + comments: { + type: "image", + src: "/img/screenshots/home/comments.png", + alt: "Comments", + }, + suggestions: { + type: "image", + src: "/img/screenshots/home/versioning.png", + alt: "Versioning", + }, + }; + + const tabs = [ + { + id: "realtime", + icon: 👯, + label: "Real-Time Sync", + description: "Yjs-powered with automatic conflict resolution.", + }, + { + id: "comments", + icon: 💬, + label: "Comments", + description: "Inline threads and mentions keep conversations in context.", + }, + { + id: "suggestions", + icon: 📝, + label: "Suggestions & Versioning (coming soon)", + description: + "Track changes, accept or reject edits. Full document history.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={false} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureDX.tsx b/docs/app/(home)/_components/FeatureDX.tsx new file mode 100644 index 0000000000..a98353e0e1 --- /dev/null +++ b/docs/app/(home)/_components/FeatureDX.tsx @@ -0,0 +1,64 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureDX: React.FC<{ + code: { theming: string; extend: string }; +}> = ({ code }) => { + const [activeTab, setActiveTab] = useState<"types" | "theming" | "extend">( + "types", + ); + + const content: Record = { + types: { + type: "image", + src: "/img/screenshots/home/code-typescript-support.png", + alt: "Type-Safe Schema", + }, + theming: { + type: "code", + file: "Editor.tsx", + code: code.theming, + }, + extend: { + type: "code", + file: "CustomBlock.tsx", + code: code.extend, + }, + }; + + const tabs = [ + { + id: "types", + icon: 📐, + label: "Type-Safe", + description: "Full autocompletion and type inference for custom schemas.", + }, + { + id: "theming", + icon: 🎨, + label: "Bring your Design System", + description: "Works with Mantine, shadcn/ui, or go headless.", + }, + { + id: "extend", + icon: 🔧, + label: "Extend Everything", + description: "Create custom blocks, inline content, menus and more.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={true} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureSection.tsx b/docs/app/(home)/_components/FeatureSection.tsx new file mode 100644 index 0000000000..c7a9973005 --- /dev/null +++ b/docs/app/(home)/_components/FeatureSection.tsx @@ -0,0 +1,101 @@ +import React from "react"; + +interface FeatureTab { + id: string; + icon: React.ReactNode; + label: string; + description: string; +} + +interface FeatureSectionProps { + title: string; + description: string; + tabs: FeatureTab[]; + activeTabId: string; + onTabChange: (id: string) => void; + // The content to display on the right side (Visual or Code) + children: React.ReactNode; + // Optional: Swap order for visual variety (Left/Right) + reverse?: boolean; +} + +export const FeatureSection: React.FC = ({ + title, + description, + tabs, + activeTabId, + onTabChange, + children, + reverse = false, +}) => { + return ( +
+ {/* Left Text & Tabs */} +
+

+ {title} +

+

+ {description} +

+ +
+ {tabs.map((tab) => { + const isActive = activeTabId === tab.id; + // Dynamic styles based on active state could be passed or handled here + // For simplicity, we'll use a generic active style or specific color logic if needed. + // But CodePlayground had specific colors (purple, amber, blue). + // Let's rely on the parent or use a generic active style here for now, + // or we can add a 'color' prop to FeatureTab if we want distinct colors per tab. + + return ( + + ); + })} +
+
+ + {/* Right Visual */} +
+ {/*
*/} +
+ {children} +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FeatureUX.tsx b/docs/app/(home)/_components/FeatureUX.tsx new file mode 100644 index 0000000000..3bfe2d00ff --- /dev/null +++ b/docs/app/(home)/_components/FeatureUX.tsx @@ -0,0 +1,60 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; +export const FeatureUX: React.FC = () => { + const [activeTab, setActiveTab] = useState<"components" | "ai" | "blocks">( + "components", + ); + + const content: Record = { + components: { + type: "video", + src: "/video/batteries-included.mp4", + }, + ai: { + type: "video", + src: "/video/ai-select.mp4", + className: "px-4", + }, + blocks: { + type: "video", + src: "/video/dragdrop.mp4", + }, + }; + + const tabs = [ + { + id: "components", + icon: 🔋, + label: "Ready to Use", + description: + "Slash menus, formatting toolbars, and drag handles work instantly.", + }, + { + id: "ai", + icon: , + label: "AI Assistance", + description: "Write and redact content with AI.", + }, + { + id: "blocks", + icon: 🧱, + label: "Block-Based", + description: "Drag, drop, and nest content blocks.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={false} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FrameworkPill.tsx b/docs/app/(home)/_components/FrameworkPill.tsx new file mode 100644 index 0000000000..82e6b90216 --- /dev/null +++ b/docs/app/(home)/_components/FrameworkPill.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +export const FrameworkPill: React.FC<{ + name: string; + color: string; + icon?: React.ReactNode; +}> = ({ name, color, icon }) => ( +
+ {icon ||
} + {name} +
+); diff --git a/docs/app/(home)/_components/Hero.tsx b/docs/app/(home)/_components/Hero.tsx new file mode 100644 index 0000000000..2bb0dc5b52 --- /dev/null +++ b/docs/app/(home)/_components/Hero.tsx @@ -0,0 +1,69 @@ +import Link from "next/link"; +import React from "react"; +import { HeroVideo } from "./HeroVideo"; +import { TextLoop } from "./TextLoop"; + +export const Hero: React.FC = () => { + const BADGES = [ + { icon: "⭐️", text: "100k+ weekly installs" }, + { icon: "🛡️", text: "100% Open source & self-hostable" }, + { icon: "✨", text: "AI Ready" }, + ]; + + return ( +
+ {/* Hero Section */} +
+ {/* Passive Neural Background */} +
+ {/* Badge */} + + {BADGES.map((badge, index) => ( +
+ + {badge.icon} + {badge.text} + +
+ ))} +
+ +

+ Build a Notion-style{" "} + editor in minutes. +

+

+ The AI-native, open source rich + text editor for React. Add a{" "} + fully customizable modern block-based editing + experience to your product that users will love. +

+ +
+ + View Demo + + → + + + + Documentation + +
+
+
+ +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/HeroVideo.tsx b/docs/app/(home)/_components/HeroVideo.tsx new file mode 100644 index 0000000000..a36cccbe73 --- /dev/null +++ b/docs/app/(home)/_components/HeroVideo.tsx @@ -0,0 +1,77 @@ +"use client"; +import Link from "next/link"; +import React from "react"; + +export const HeroVideo: React.FC = () => { + return ( + <> + +
+ {/* Editor Placeholder */} + {/* Editor Preview */} + +
+
+ {/* Browser Chrome */} +