From 99cddb0dee76d471db0daac7de008f4c39e6ffda Mon Sep 17 00:00:00 2001 From: Akshay Agrawal Date: Mon, 23 Jun 2025 15:49:16 -0700 Subject: [PATCH 01/26] fix: CLI print for non UTF-8 terminals (#5397) Replace common unicode characters with ASCII on UnicodeEncodeError encountered while printing to console. Should fix failing CLI test on CI. --- marimo/_cli/print.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/marimo/_cli/print.py b/marimo/_cli/print.py index 4d18b9fb6b7..57c33931e80 100644 --- a/marimo/_cli/print.py +++ b/marimo/_cli/print.py @@ -76,13 +76,32 @@ def muted(text: str) -> str: return "\033[37;2m" + text + "\033[0m" if _USE_COLOR else text -def echo(*args: Any, **kwargs: Any) -> None: - if GLOBAL_SETTINGS.QUIET: - return - +def _echo_or_print(*args: Any, **kwargs: Any) -> None: try: import click click.echo(*args, **kwargs) except ModuleNotFoundError: print(*args, **kwargs) # noqa: T201 + + +def echo(*args: Any, **kwargs: Any) -> None: + if GLOBAL_SETTINGS.QUIET: + return + + try: + _echo_or_print(*args, **kwargs) # noqa: T201 + except UnicodeEncodeError: + # Handle non-UTF-8 terminals (such as CP-1252, Windows) by replacing + # common Unicode characters with ASCII equivalents for non-UTF-8 + # terminals. + ascii_args = [] + for arg in args: + if isinstance(arg, str): + ascii_arg = arg.replace("→", "->").replace("←", "<-") + ascii_arg = ascii_arg.replace("✓", "v").replace("✗", "x") + ascii_arg = ascii_arg.replace("•", "*").replace("…", "...") + ascii_args.append(ascii_arg) + else: + ascii_args.append(arg) + _echo_or_print(*ascii_args, **kwargs) From b7e308a476c3c15275f39959d369f99127633009 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Mon, 23 Jun 2025 18:58:32 -0400 Subject: [PATCH 02/26] chore(frontend): Add `invariant` debugger statement for development (#5414) I do this often during development and find it really useful for debugging invariant failures. Thought I'd share this either to show other folks or check it into the repo so other developers can use it too. The conditional `debugger` statement to the `invariant()` utility only runs in development. When an invariant fails, it automatically triggers a breakpoint in the browser before throwing the error. This immediately allows you to step into where some assertion failed. The nice property is that the entire `if (import.meta.env.DEV)` block gets stripped out in production builds (e.g., `vite build`) since the bundler resolves to `if (false) {}`. --- frontend/src/utils/assertNever.ts | 3 ++- frontend/src/utils/invariant.ts | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/assertNever.ts b/frontend/src/utils/assertNever.ts index 9900b48a801..009e3f0b79e 100644 --- a/frontend/src/utils/assertNever.ts +++ b/frontend/src/utils/assertNever.ts @@ -1,11 +1,12 @@ /* Copyright 2024 Marimo. All rights reserved. */ +import { invariant } from "./invariant"; import { Logger } from "./Logger"; /** * Type-safe exhaustiveness check for discriminated unions. */ export function assertNever(x: never): never { - throw new Error(`Unexpected object: ${x}`); + invariant(false, `Unexpected object: ${x}`); } /** diff --git a/frontend/src/utils/invariant.ts b/frontend/src/utils/invariant.ts index ffad8f04f79..f21365eef26 100644 --- a/frontend/src/utils/invariant.ts +++ b/frontend/src/utils/invariant.ts @@ -27,6 +27,11 @@ export function invariant( msg: string, ): asserts expression { if (!expression) { + if (import.meta.env.DEV) { + // Triggers a breakpoint in development; stripped out in production builds. + // biome-ignore lint/suspicious/noDebugger: code block is stripped out in production builds + debugger; // eslint-disable-line no-debugger + } throw new Error(msg); } } From 0a9ee16a67dcc44b07e78ceeefa659123d901d5e Mon Sep 17 00:00:00 2001 From: Srihari Thyagarajan <57552973+Haleshot@users.noreply.github.com> Date: Tue, 24 Jun 2025 05:15:21 +0530 Subject: [PATCH 03/26] [Windows] Fix `Copy Path` and `Copy Relative Path` returning identical results. (#5399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Fix #5395 ## 🔍 Description of Changes Both copy path options in the file explorer's (View File sidebar panel) context menu were returning the same absolute path on Windows systems. The relative path should show only the portion relative to the workspace root, not the full/absolute path. Fixed by replacing the slash logic with a new `withTrailingDelimiter` method that uses the existing `PathBuilder.deliminator` to append the correct path separator accordingly. Added tests covering both Unix and Windows path scenarios. **Before**: Both options returned `D:\full\path\to\file.py` **After**: Copy Path returns `D:\full\path\to\file.py`, Copy Relative Path returns `subfolder\file.py` ## Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [x] I have added tests for the changes made. - [x] I have run the code and verified that it works as expected. ## 📜 Reviewers @akshayka OR @Light2Dark --------- Signed-off-by: Srihari Thyagarajan Co-authored-by: Shahmir Varqha --- .../__tests__/requesting-tree.test.ts | 57 +++++++++++++++++++ .../editor/file-tree/requesting-tree.tsx | 10 ++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/editor/file-tree/__tests__/requesting-tree.test.ts b/frontend/src/components/editor/file-tree/__tests__/requesting-tree.test.ts index 38173b2dc46..c2d65976244 100644 --- a/frontend/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +++ b/frontend/src/components/editor/file-tree/__tests__/requesting-tree.test.ts @@ -1,6 +1,7 @@ /* Copyright 2024 Marimo. All rights reserved. */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { toast } from "@/components/ui/use-toast"; +import type { FilePath } from "@/utils/paths"; import { RequestingTree } from "../requesting-tree"; const sendListFiles = vi.fn(); @@ -261,4 +262,60 @@ describe("RequestingTree", () => { expect(mockOnChange).toHaveBeenCalled(); }); }); + + describe("relativeFromRoot", () => { + test("should return relative path for Unix paths", async () => { + const tree = new RequestingTree({ + listFiles: sendListFiles, + createFileOrFolder: sendCreateFileOrFolder, + deleteFileOrFolder: sendDeleteFileOrFolder, + renameFileOrFolder: sendRenameFileOrFolder, + }); + + sendListFiles.mockResolvedValue({ + files: [], + root: "/home/user/project", + }); + + await tree.initialize(vi.fn()); + + const relativePath = tree.relativeFromRoot( + "/home/user/project/src/file.py" as FilePath, + ); + expect(relativePath).toBe("src/file.py"); + }); + + test("should return relative path for Windows paths", async () => { + const tree = new RequestingTree({ + listFiles: sendListFiles, + createFileOrFolder: sendCreateFileOrFolder, + deleteFileOrFolder: sendDeleteFileOrFolder, + renameFileOrFolder: sendRenameFileOrFolder, + }); + + sendListFiles.mockResolvedValue({ + files: [], + root: "C:\\Users\\test\\project", + }); + + await tree.initialize(vi.fn()); + + const relativePath = tree.relativeFromRoot( + "C:\\Users\\test\\project\\src\\file.py" as FilePath, + ); + expect(relativePath).toBe("src\\file.py"); + }); + + test("should return original path when not under root", async () => { + const relativePath = requestingTree.relativeFromRoot( + "/other/path/file.py" as FilePath, + ); + expect(relativePath).toBe("/other/path/file.py"); + }); + + test("should handle root path exactly", async () => { + const relativePath = requestingTree.relativeFromRoot("/root" as FilePath); + expect(relativePath).toBe("/root"); + }); + }); }); diff --git a/frontend/src/components/editor/file-tree/requesting-tree.tsx b/frontend/src/components/editor/file-tree/requesting-tree.tsx index 36267255b4c..76812ed07db 100644 --- a/frontend/src/components/editor/file-tree/requesting-tree.tsx +++ b/frontend/src/components/editor/file-tree/requesting-tree.tsx @@ -203,7 +203,11 @@ export class RequestingTree { }; public relativeFromRoot = (path: FilePath): FilePath => { - const root = withTrailingSlash(this.rootPath); + // Add a trailing delimiter to the root path if it doesn't have one + const root = this.rootPath.endsWith(this.path.deliminator) + ? this.rootPath + : `${this.rootPath}${this.path.deliminator}`; + if (path.startsWith(root)) { return path.slice(root.length) as FilePath; } @@ -224,7 +228,3 @@ export class RequestingTree { return response; }; } - -function withTrailingSlash(path: string): string { - return path.endsWith("/") ? path : `${path}/`; -} From f2ca0c610169e5b63e57e77a55d09b1bf54251ad Mon Sep 17 00:00:00 2001 From: Shahmir Varqha Date: Tue, 24 Jun 2025 10:53:28 +0800 Subject: [PATCH 04/26] chore: fix api spec (#5422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary There's an outdated API spec, causing CI to fail. This was added recently in #5409 , and should be safe (not surfaced in frontend) ## 🔍 Description of Changes ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [ ] I have added tests for the changes made. - [x] I have run the code and verified that it works as expected. ## 📜 Reviewers --- openapi/api.yaml | 71 +++++++++++++++------------------------------- openapi/src/api.ts | 33 +++++++-------------- 2 files changed, 34 insertions(+), 70 deletions(-) diff --git a/openapi/api.yaml b/openapi/api.yaml index 68ba347d855..ac4cd5ef81f 100644 --- a/openapi/api.yaml +++ b/openapi/api.yaml @@ -258,54 +258,29 @@ components: type: object - properties: toolInvocation: - oneOf: - - properties: - args: - additionalProperties: {} - type: object - state: - enum: - - call - - partial-call - type: string - step: - type: integer - toolCallId: - type: string - toolName: - type: string - required: - - state - - toolCallId - - toolName - - step - - args - type: object - - properties: - args: - additionalProperties: {} - type: object - result: {} - state: - enum: - - call - - partial-call - - result - type: string - step: - type: integer - toolCallId: - type: string - toolName: - type: string - required: - - state - - result - - toolCallId - - toolName - - step - - args - type: object + properties: + args: + additionalProperties: {} + type: object + result: {} + state: + enum: + - result + type: string + step: + type: integer + toolCallId: + type: string + toolName: + type: string + required: + - state + - result + - toolCallId + - toolName + - step + - args + type: object type: enum: - tool-invocation diff --git a/openapi/src/api.ts b/openapi/src/api.ts index 10750c5a21b..f44f3b68c28 100644 --- a/openapi/src/api.ts +++ b/openapi/src/api.ts @@ -2648,28 +2648,17 @@ export interface components { type: "reasoning"; } | { - toolInvocation: - | { - args: { - [key: string]: unknown; - }; - /** @enum {string} */ - state: "call" | "partial-call"; - step: number; - toolCallId: string; - toolName: string; - } - | { - args: { - [key: string]: unknown; - }; - result: unknown; - /** @enum {string} */ - state: "call" | "partial-call" | "result"; - step: number; - toolCallId: string; - toolName: string; - }; + toolInvocation: { + args: { + [key: string]: unknown; + }; + result: unknown; + /** @enum {string} */ + state: "result"; + step: number; + toolCallId: string; + toolName: string; + }; /** @enum {string} */ type: "tool-invocation"; } From 53fbef6b760d1c36302af73a9a7334b59ec10d3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:08:15 +0800 Subject: [PATCH 05/26] chore(deps): update testing dependencies (#5383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@playwright/test](https://playwright.dev) ([source](https://redirect.github.com/microsoft/playwright)) | [`^1.53.0` -> `^1.53.1`](https://renovatebot.com/diffs/npm/@playwright%2ftest/1.53.0/1.53.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@playwright%2ftest/1.53.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@playwright%2ftest/1.53.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@playwright%2ftest/1.53.0/1.53.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@playwright%2ftest/1.53.0/1.53.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [@testing-library/react](https://redirect.github.com/testing-library/react-testing-library) | [`^16.2.0` -> `^16.3.0`](https://renovatebot.com/diffs/npm/@testing-library%2freact/16.2.0/16.3.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@testing-library%2freact/16.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@testing-library%2freact/16.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@testing-library%2freact/16.2.0/16.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@testing-library%2freact/16.2.0/16.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
microsoft/playwright (@​playwright/test) ### [`v1.53.1`](https://redirect.github.com/microsoft/playwright/compare/v1.53.0...3c60cb476613ee50c040b488d96f390be3985a89) [Compare Source](https://redirect.github.com/microsoft/playwright/compare/v1.53.0...v1.53.1)
testing-library/react-testing-library (@​testing-library/react) ### [`v16.3.0`](https://redirect.github.com/testing-library/react-testing-library/releases/tag/v16.3.0) [Compare Source](https://redirect.github.com/testing-library/react-testing-library/compare/v16.2.0...v16.3.0) ##### Features - add bernardobelchior as a contributor for code, and doc ([#​1391](https://redirect.github.com/testing-library/react-testing-library/issues/1391)) ([9fc6a75](https://redirect.github.com/testing-library/react-testing-library/commit/9fc6a75d74bb8e03a48d3339efde4dd83cd5328b))
--- ### Configuration 📅 **Schedule**: Branch creation - "on the 21st day of the month" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/marimo-team/marimo). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- frontend/package.json | 4 ++-- frontend/pnpm-lock.yaml | 36 ++++++++++++++++++------------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 55251ee0744..ec0185ab6ee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -189,7 +189,7 @@ "@biomejs/biome": "2.0.0-beta.6", "@codecov/vite-plugin": "^1.9.1", "@csstools/postcss-light-dark-function": "^2.0.8", - "@playwright/test": "^1.53.0", + "@playwright/test": "^1.53.1", "@storybook/addon-docs": "^8.6.14", "@storybook/addon-essentials": "^8.6.14", "@storybook/addon-interactions": "^8.6.14", @@ -199,7 +199,7 @@ "@storybook/react-vite": "^8.6.14", "@swc-jotai/react-refresh": "^0.3.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.2.0", + "@testing-library/react": "^16.3.0", "@types/katex": "^0.16.7", "@types/lodash-es": "^4.17.12", "@types/node": "^24.0.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index e9ba0f9f6e1..cecc167ec35 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -464,8 +464,8 @@ importers: specifier: ^2.0.8 version: 2.0.8(postcss@8.5.4) '@playwright/test': - specifier: ^1.53.0 - version: 1.53.0 + specifier: ^1.53.1 + version: 1.53.1 '@storybook/addon-docs': specifier: ^8.6.14 version: 8.6.14(@types/react@19.1.7)(storybook@8.6.14) @@ -494,8 +494,8 @@ importers: specifier: ^6.6.3 version: 6.6.3 '@testing-library/react': - specifier: ^16.2.0 - version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/katex': specifier: ^0.16.7 version: 0.16.7 @@ -1843,8 +1843,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.53.0': - resolution: {integrity: sha512-15hjKreZDcp7t6TL/7jkAo6Df5STZN09jGiv5dbP9A6vMVncXRqE7/B2SncsyOwrkZRBH2i6/TPOL8BVmm3c7w==} + '@playwright/test@1.53.1': + resolution: {integrity: sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==} engines: {node: '>=18'} hasBin: true @@ -3506,8 +3506,8 @@ packages: resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - '@testing-library/react@16.2.0': - resolution: {integrity: sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==} + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 @@ -7086,13 +7086,13 @@ packages: pkg-types@1.2.1: resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} - playwright-core@1.53.0: - resolution: {integrity: sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw==} + playwright-core@1.53.1: + resolution: {integrity: sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==} engines: {node: '>=18'} hasBin: true - playwright@1.53.0: - resolution: {integrity: sha512-ghGNnIEYZC4E+YtclRn4/p6oYbdPiASELBIYkBXfaTVKreQUYbMUYQDwS12a8F0/HtIjr/CkGjtwABeFPGcS4Q==} + playwright@1.53.1: + resolution: {integrity: sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==} engines: {node: '>=18'} hasBin: true @@ -10668,9 +10668,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.53.0': + '@playwright/test@1.53.1': dependencies: - playwright: 1.53.0 + playwright: 1.53.1 '@plotly/d3-sankey-circular@0.33.1': dependencies: @@ -12899,7 +12899,7 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.6 '@testing-library/dom': 10.4.0 @@ -17093,11 +17093,11 @@ snapshots: mlly: 1.7.2 pathe: 1.1.2 - playwright-core@1.53.0: {} + playwright-core@1.53.1: {} - playwright@1.53.0: + playwright@1.53.1: dependencies: - playwright-core: 1.53.0 + playwright-core: 1.53.1 optionalDependencies: fsevents: 2.3.2 From ff59d6766d71f4837de34562deae308950fb519f Mon Sep 17 00:00:00 2001 From: Akshay Agrawal Date: Mon, 23 Jun 2025 20:22:23 -0700 Subject: [PATCH 06/26] fix: marimo development preview (#5403) This PR fixes `marimo development preview` for previewing static HTML using local assets. Previously the generated HTML didn't include outputs (ie the notebook wasn't run), and also got stuck on attempting to connect to the frontend ("Connecting..." spinner) * actually generate the notebook session and outputs * for static notebooks, initialize the connection as open --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: dylan --- frontend/src/core/network/connection.ts | 2 +- .../src/core/websocket/StaticWebsocket.ts | 15 ++++- marimo/_cli/development/commands.py | 62 ++++++++++++++----- 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/frontend/src/core/network/connection.ts b/frontend/src/core/network/connection.ts index ea5255ff194..67f58b5b246 100644 --- a/frontend/src/core/network/connection.ts +++ b/frontend/src/core/network/connection.ts @@ -5,7 +5,7 @@ import { type ConnectionStatus, WebSocketState } from "../websocket/types"; /** * Atom for storing the connection status. - * Initialized to CONNECTING. + * Initialized to CONNECTING for normal mode, OPEN for static mode. */ export const connectionAtom = atom({ state: WebSocketState.CONNECTING, diff --git a/frontend/src/core/websocket/StaticWebsocket.ts b/frontend/src/core/websocket/StaticWebsocket.ts index d4120366e6d..981a23e2970 100644 --- a/frontend/src/core/websocket/StaticWebsocket.ts +++ b/frontend/src/core/websocket/StaticWebsocket.ts @@ -17,9 +17,20 @@ export class StaticWebsocket implements IReconnectingWebSocket { onmessage = null; onopen = null; - addEventListener(type: unknown, callback: unknown, options?: unknown): void { - // Noop + addEventListener( + type: string, + callback: EventListener, + _options?: unknown, + ): void { + // Normally this would be a no-op in a mock, but we simulate a synthetic "open" event + // to mimic the WebSocket transitioning from CONNECTING to OPEN state. + if (type === "open") { + queueMicrotask(() => { + callback(new Event("open")); + }); + } } + removeEventListener( type: unknown, callback: unknown, diff --git a/marimo/_cli/development/commands.py b/marimo/_cli/development/commands.py index 4d44e355488..1290f829a1d 100644 --- a/marimo/_cli/development/commands.py +++ b/marimo/_cli/development/commands.py @@ -11,7 +11,10 @@ import click from marimo._cli.print import orange -from marimo._convert.converters import MarimoConvert +from marimo._server.session.serialize import ( + serialize_notebook, + serialize_session_view, +) from marimo._utils.code import hash_code if TYPE_CHECKING: @@ -533,13 +536,8 @@ def preview(file_path: Path, port: int, host: str, headless: bool) -> None: from starlette.routing import Route from starlette.staticfiles import StaticFiles - from marimo import __version__ from marimo._ast.app_config import _AppConfig from marimo._config.config import DEFAULT_CONFIG - from marimo._schemas.session import ( - NotebookSessionMetadata, - NotebookSessionV1, - ) from marimo._server.templates.templates import static_notebook_template from marimo._server.tokens import SkewProtectionToken from marimo._utils.paths import marimo_package_path @@ -548,16 +546,41 @@ def preview(file_path: Path, port: int, host: str, headless: bool) -> None: from starlette.requests import Request try: - # Convert to notebook format - notebook_snapshot = MarimoConvert.from_py( - file_path.read_text(encoding="utf-8") - ).to_notebook_v1() - - # Create empty session snapshot since we're not running the code - session_snapshot = NotebookSessionV1( - version="1", - metadata=NotebookSessionMetadata(marimo_version=__version__), - cells=[], + # Run the notebook to get actual outputs + click.echo(f"Running notebook {file_path.name}...") + from marimo._server.export import run_app_until_completion + from marimo._server.file_router import AppFileRouter + from marimo._server.utils import asyncio_run + from marimo._utils.marimo_path import MarimoPath + + # Create file manager for the notebook + file_router = AppFileRouter.from_filename(MarimoPath(file_path)) + file_key = file_router.get_unique_file_key() + assert file_key is not None + file_manager = file_router.get_file_manager(file_key) + + # Run the notebook to completion and get session view + session_view, did_error = asyncio_run( + run_app_until_completion( + file_manager, + cli_args={}, + argv=None, + ) + ) + if did_error: + click.echo( + "Warning: Some cells had errors during execution", err=True + ) + + # Create session snapshot from the executed session + session_snapshot = serialize_session_view( + session_view, + cell_ids=list(file_manager.app.cell_manager.cell_ids()), + ) + + # Get notebook snapshot from file manager + notebook_snapshot = serialize_notebook( + session_view, file_manager.app.cell_manager ) # Get the static assets directory @@ -609,6 +632,13 @@ async def serve_html(request: Request) -> HTMLResponse: name="assets", ) + # Mount other static files (favicon, icons, manifest, etc.) + app.mount( + "/", + StaticFiles(directory=static_root, html=False), + name="static", + ) + url = f"http://{host}:{port}" click.echo(f"Serving preview at {url}") click.echo("Press Ctrl+C to stop the server") From 2517a6666bdfb785f633b3273d72d6e235bf17f5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:34:17 +0800 Subject: [PATCH 07/26] [pre-commit.ci] pre-commit autoupdate (#5425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.13 → v0.12.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.13...v0.12.0) - [github.com/biomejs/pre-commit: v2.0.0-beta.5 → v2.0.2](https://github.com/biomejs/pre-commit/compare/v2.0.0-beta.5...v2.0.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f2b1b0cc20..a016cd7aa24 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: pass_filenames: false - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.13 + rev: v0.12.0 hooks: # Run the linter - id: ruff @@ -40,7 +40,7 @@ repos: - id: ruff-format - repo: https://github.com/biomejs/pre-commit - rev: v2.0.0-beta.5 + rev: v2.0.2 hooks: - id: biome-check args: [--config-path, biome.jsonc, --diagnostic-level, warn] From 92d82315cb8824d3430381db47eece5a8dab4f5e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:55:59 +0800 Subject: [PATCH 08/26] chore(deps): update build tools (#5418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@vitejs/plugin-react](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme) ([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)) | [`^4.5.0` -> `^4.5.2`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/4.5.0/4.5.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@vitejs%2fplugin-react/4.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@vitejs%2fplugin-react/4.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@vitejs%2fplugin-react/4.5.0/4.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitejs%2fplugin-react/4.5.0/4.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [@vitejs/plugin-react-swc](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc#readme) ([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react-swc)) | [`^3.10.0` -> `^3.10.2`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react-swc/3.10.0/3.10.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@vitejs%2fplugin-react-swc/3.10.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@vitejs%2fplugin-react-swc/3.10.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@vitejs%2fplugin-react-swc/3.10.0/3.10.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitejs%2fplugin-react-swc/3.10.0/3.10.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [postcss](https://postcss.org/) ([source](https://redirect.github.com/postcss/postcss)) | [`^8.5.4` -> `^8.5.6`](https://renovatebot.com/diffs/npm/postcss/8.5.4/8.5.6) | [![age](https://developer.mend.io/api/mc/badges/age/npm/postcss/8.5.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/postcss/8.5.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/postcss/8.5.4/8.5.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/postcss/8.5.4/8.5.6?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [vitest](https://redirect.github.com/vitest-dev/vitest) ([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`^3.1.4` -> `^3.2.4`](https://renovatebot.com/diffs/npm/vitest/3.1.4/3.2.4) | [![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/vitest/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/vitest/3.1.4/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/3.1.4/3.2.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
vitejs/vite-plugin-react (@​vitejs/plugin-react) ### [`v4.5.2`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#452-2025-06-10) [Compare Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/2f3205265904ff7770021700689a0d6fe17b1f03...bfb45addb83ebae8feebdb75be2e07ce27e916cb) ##### Suggest `@vitejs/plugin-react-oxc` if rolldown-vite is detected [#​491](https://redirect.github.com/vitejs/vite-plugin-react/pull/491) Emit a log which recommends `@vitejs/plugin-react-oxc` when `rolldown-vite` is detected to improve performance and use Oxc under the hood. The warning can be disabled by setting `disableOxcRecommendation: false` in the plugin options. ##### Use `optimizeDeps.rollupOptions` instead of `optimizeDeps.esbuildOptions` for rolldown-vite [#​489](https://redirect.github.com/vitejs/vite-plugin-react/pull/489) This suppresses the warning about `optimizeDeps.esbuildOptions` being deprecated in rolldown-vite. ##### Add Vite 7-beta to peerDependencies range [#​497](https://redirect.github.com/vitejs/vite-plugin-react/pull/497) React plugins are compatible with Vite 7, this removes the warning when testing the beta. ### [`v4.5.1`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#451-2025-06-03) [Compare Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/476e705375ef618458918580beb63f43799d12e4...2f3205265904ff7770021700689a0d6fe17b1f03) ##### Add explicit semicolon in preambleCode [#​485](https://redirect.github.com/vitejs/vite-plugin-react/pull/485) This fixes an edge case when using HTML minifiers that strips line breaks aggressively.
vitejs/vite-plugin-react (@​vitejs/plugin-react-swc) ### [`v3.10.2`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react-swc/CHANGELOG.md#3102-2025-06-10) [Compare Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/8ce7183265c43f88623655a9cfdcec5282068f9b...32d49ecf9b15e3070c7abe5a176252a3fe542e5c) ##### Suggest `@vitejs/plugin-react-oxc` if rolldown-vite is detected [#​491](https://redirect.github.com/vitejs/vite-plugin-react/pull/491) Emit a log which recommends `@vitejs/plugin-react-oxc` when `rolldown-vite` is detected to improve performance and use Oxc under the hood. The warning can be disabled by setting `disableOxcRecommendation: false` in the plugin options. ##### Use `optimizeDeps.rollupOptions` instead of `optimizeDeps.esbuildOptions` for rolldown-vite [#​489](https://redirect.github.com/vitejs/vite-plugin-react/pull/489) This suppresses the warning about `optimizeDeps.esbuildOptions` being deprecated in rolldown-vite. ##### Add Vite 7-beta to peerDependencies range [#​497](https://redirect.github.com/vitejs/vite-plugin-react/pull/497) React plugins are compatible with Vite 7, this removes the warning when testing the beta. ### [`v3.10.1`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react-swc/CHANGELOG.md#3101-2025-06-03) [Compare Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/dcadcfc2841c0bedfe44279c556835c350dfa5fa...8ce7183265c43f88623655a9cfdcec5282068f9b) ##### Add explicit semicolon in preambleCode [#​485](https://redirect.github.com/vitejs/vite-plugin-react/pull/485) This fixes an edge case when using HTML minifiers that strips line breaks aggressively.
postcss/postcss (postcss) ### [`v8.5.6`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#856) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.5...8.5.6) - Fixed `ContainerWithChildren` type discriminating (by [@​Goodwine](https://redirect.github.com/Goodwine)). ### [`v8.5.5`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#855) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.4...8.5.5) - Fixed `package.json`→`exports` compatibility with some tools (by [@​JounQin](https://redirect.github.com/JounQin)).
vitest-dev/vitest (vitest) ### [`v3.2.4`](https://redirect.github.com/vitest-dev/vitest/releases/tag/v3.2.4) [Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.3...v3.2.4) #####    🐞 Bug Fixes - Use correct path for optimisation of strip-literal  -  by [@​mrginglymus](https://redirect.github.com/mrginglymus) in [https://github.com/vitest-dev/vitest/issues/8139](https://redirect.github.com/vitest-dev/vitest/issues/8139) [(44940)](https://redirect.github.com/vitest-dev/vitest/commit/44940d9dd) - Print uint and buffer as a simple string  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8141](https://redirect.github.com/vitest-dev/vitest/issues/8141) [(b86bf)](https://redirect.github.com/vitest-dev/vitest/commit/b86bf0d99) - **browser**: - Show a helpful error when spying on an export  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8178](https://redirect.github.com/vitest-dev/vitest/issues/8178) [(56007)](https://redirect.github.com/vitest-dev/vitest/commit/5600772c2) - **cli**: - `vitest run --watch` should be watch-mode  -  by [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/8128](https://redirect.github.com/vitest-dev/vitest/issues/8128) [(657e8)](https://redirect.github.com/vitest-dev/vitest/commit/657e83f9f) - Use absolute path environment on Windows  -  by [@​colinaaa](https://redirect.github.com/colinaaa) in [https://github.com/vitest-dev/vitest/issues/8105](https://redirect.github.com/vitest-dev/vitest/issues/8105) [(85dc0)](https://redirect.github.com/vitest-dev/vitest/commit/85dc0195f) - Throw error when `--shard x/` exceeds count of test files  -  by [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/8112](https://redirect.github.com/vitest-dev/vitest/issues/8112) [(8a18c)](https://redirect.github.com/vitest-dev/vitest/commit/8a18c8e20) - **coverage**: - Ignore SCSS in browser mode  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8161](https://redirect.github.com/vitest-dev/vitest/issues/8161) [(0c3be)](https://redirect.github.com/vitest-dev/vitest/commit/0c3be6f63) - **deps**: - Update all non-major dependencies  -  in [https://github.com/vitest-dev/vitest/issues/8123](https://redirect.github.com/vitest-dev/vitest/issues/8123) [(93f32)](https://redirect.github.com/vitest-dev/vitest/commit/93f3200e4) - **expect**: - Handle async errors in expect.soft  -  by [@​lzl0304](https://redirect.github.com/lzl0304) in [https://github.com/vitest-dev/vitest/issues/8145](https://redirect.github.com/vitest-dev/vitest/issues/8145) [(68699)](https://redirect.github.com/vitest-dev/vitest/commit/686996912) - **pool**: - Auto-adjust `minWorkers` when only `maxWorkers` specified  -  by [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/8110](https://redirect.github.com/vitest-dev/vitest/issues/8110) [(14dc0)](https://redirect.github.com/vitest-dev/vitest/commit/14dc0724f) - **reporter**: - `task.meta` should be available in custom reporter's errors  -  by [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/8115](https://redirect.github.com/vitest-dev/vitest/issues/8115) [(27df6)](https://redirect.github.com/vitest-dev/vitest/commit/27df68a0e) - **runner**: - Preserve handler wrapping on extend  -  by [@​pengooseDev](https://redirect.github.com/pengooseDev) in [https://github.com/vitest-dev/vitest/issues/8153](https://redirect.github.com/vitest-dev/vitest/issues/8153) [(a9281)](https://redirect.github.com/vitest-dev/vitest/commit/a92812b70) - **ui**: - Ensure ui config option works correctly  -  by [@​lzl0304](https://redirect.github.com/lzl0304) in [https://github.com/vitest-dev/vitest/issues/8147](https://redirect.github.com/vitest-dev/vitest/issues/8147) [(42eeb)](https://redirect.github.com/vitest-dev/vitest/commit/42eeb2ee6) #####     [View changes on GitHub](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.3...v3.2.4) ### [`v3.2.3`](https://redirect.github.com/vitest-dev/vitest/releases/tag/v3.2.3) [Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.2...v3.2.3) #####    🚀 Features - **browser**: Use base url instead of **vitest**  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8126](https://redirect.github.com/vitest-dev/vitest/issues/8126) [(1d8eb)](https://redirect.github.com/vitest-dev/vitest/commit/1d8ebf9ae) - **ui**: Show test annotations and metadata in the test report tab  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8093](https://redirect.github.com/vitest-dev/vitest/issues/8093) [(c69be)](https://redirect.github.com/vitest-dev/vitest/commit/c69be1fc1) #####    🐞 Bug Fixes - Rerun tests when project's setup file is changed  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8097](https://redirect.github.com/vitest-dev/vitest/issues/8097) [(0f335)](https://redirect.github.com/vitest-dev/vitest/commit/0f3350667) - Revert `expect.any` return type  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8129](https://redirect.github.com/vitest-dev/vitest/issues/8129) [(47514)](https://redirect.github.com/vitest-dev/vitest/commit/4751436d5) - Run only the name plugin last, not all config plugins  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8130](https://redirect.github.com/vitest-dev/vitest/issues/8130) [(83862)](https://redirect.github.com/vitest-dev/vitest/commit/83862d46e) - **pool**: - Throw if user's tests use `process.send()`  -  by [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/8125](https://redirect.github.com/vitest-dev/vitest/issues/8125) [(dfe81)](https://redirect.github.com/vitest-dev/vitest/commit/dfe81a67a) - **runner**: - Fast sequential task updates missing  -  by [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/8121](https://redirect.github.com/vitest-dev/vitest/issues/8121) [(7bd11)](https://redirect.github.com/vitest-dev/vitest/commit/7bd11a9b3) - Comments between fixture destructures  -  by [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/8127](https://redirect.github.com/vitest-dev/vitest/issues/8127) [(dc469)](https://redirect.github.com/vitest-dev/vitest/commit/dc469f260) - **vite-node**: - Unable to handle errors where sourcemap mapping empty  -  by [@​blake-newman](https://redirect.github.com/blake-newman) and [@​hi-ogawa](https://redirect.github.com/hi-ogawa) in [https://github.com/vitest-dev/vitest/issues/8071](https://redirect.github.com/vitest-dev/vitest/issues/8071) [(8aa25)](https://redirect.github.com/vitest-dev/vitest/commit/8aa252121) #####     [View changes on GitHub](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.2...v3.2.3) ### [`v3.2.2`](https://redirect.github.com/vitest-dev/vitest/releases/tag/v3.2.2) [Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.1...v3.2.2) #####    🚀 Features - Support rolldown-vite  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) and [@​hi-ogawa](https://redirect.github.com/hi-ogawa) in [https://github.com/vitest-dev/vitest/issues/7509](https://redirect.github.com/vitest-dev/vitest/issues/7509) [(c8d62)](https://redirect.github.com/vitest-dev/vitest/commit/c8d6264bf) #####    🐞 Bug Fixes - **browser**: - Calculate prepare time from `createTesters` call on the main thread  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8101](https://redirect.github.com/vitest-dev/vitest/issues/8101) [(142c7)](https://redirect.github.com/vitest-dev/vitest/commit/142c735e1) - Optimize build output and always prebundle vitest  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) [(00a39)](https://redirect.github.com/vitest-dev/vitest/commit/00a391656) - Make custom locators available in `vitest-browser-*` packages  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8103](https://redirect.github.com/vitest-dev/vitest/issues/8103) [(247ef)](https://redirect.github.com/vitest-dev/vitest/commit/247ef5822) - **expect**: - Ensure we can always self `toEqual`  -  by [@​dubzzz](https://redirect.github.com/dubzzz) in [https://github.com/vitest-dev/vitest/issues/8094](https://redirect.github.com/vitest-dev/vitest/issues/8094) [(02ec8)](https://redirect.github.com/vitest-dev/vitest/commit/02ec89203) - **reporter**: - Allow `dot` reporter to work in non interactive terminals  -  by [@​bstephen1](https://redirect.github.com/bstephen1) and [@​AriPerkkio](https://redirect.github.com/AriPerkkio) in [https://github.com/vitest-dev/vitest/issues/7994](https://redirect.github.com/vitest-dev/vitest/issues/7994) [(6db9f)](https://redirect.github.com/vitest-dev/vitest/commit/6db9f5207) #####     [View changes on GitHub](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.1...v3.2.2) ### [`v3.2.1`](https://redirect.github.com/vitest-dev/vitest/releases/tag/v3.2.1) [Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.0...v3.2.1) #####    🐞 Bug Fixes - Use sha1 instead of md5 for hashing  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) [(e4c73)](https://redirect.github.com/vitest-dev/vitest/commit/e4c73503e) - **expect**: - Fix chai import in dts  -  by [@​hi-ogawa](https://redirect.github.com/hi-ogawa) in [https://github.com/vitest-dev/vitest/issues/8077](https://redirect.github.com/vitest-dev/vitest/issues/8077) [(a7593)](https://redirect.github.com/vitest-dev/vitest/commit/a759347ff) - Export `DeeplyAllowMatchers`  -  by [@​sheremet-va](https://redirect.github.com/sheremet-va) in [https://github.com/vitest-dev/vitest/issues/8078](https://redirect.github.com/vitest-dev/vitest/issues/8078) [(30ab4)](https://redirect.github.com/vitest-dev/vitest/commit/30ab42c0f) #####     [View changes on GitHub](https://redirect.github.com/vitest-dev/vitest/compare/v3.2.0...v3.2.1) ### [`v3.2.0`](https://redirect.github.com/vitest-dev/vitest/compare/v3.1.4...v3.2.0) [Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v3.1.4...v3.2.0)
--- ### Configuration 📅 **Schedule**: Branch creation - "on the 23rd day of the month" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/marimo-team/marimo). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- frontend/package.json | 8 +- frontend/pnpm-lock.yaml | 661 +++++++++++++++++++++------------------- 2 files changed, 354 insertions(+), 315 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index ec0185ab6ee..8fb15abbe1d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -209,8 +209,8 @@ "@types/timestring": "^6.0.5", "@typescript-eslint/eslint-plugin": "^7.15.0", "@typescript-eslint/parser": "^7.15.0", - "@vitejs/plugin-react": "^4.5.0", - "@vitejs/plugin-react-swc": "^3.10.0", + "@vitejs/plugin-react": "^4.5.2", + "@vitejs/plugin-react-swc": "^3.10.2", "autoprefixer": "^10.4.21", "babel-plugin-react-compiler": "19.1.0-rc.1", "blob-polyfill": "^7.0.20220408", @@ -226,7 +226,7 @@ "jsdom": "^24.1.3", "msw": "^2.10.2", "npm-run-all2": "^6.2.6", - "postcss": "^8.5.4", + "postcss": "^8.5.6", "postcss-plugin-namespace": "^0.0.3", "react": "^19.1.0", "react-compiler-runtime": "19.1.0-rc.1", @@ -241,7 +241,7 @@ "vite-plugin-top-level-await": "^1.5.0", "vite-plugin-wasm": "^3.4.1", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.1.4" + "vitest": "^3.2.4" }, "packageManager": "pnpm@10.10.0", "pnpm": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index cecc167ec35..290ed34f526 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -284,7 +284,7 @@ importers: version: 0.21.2(vega@5.33.0) cssnano: specifier: ^7.0.6 - version: 7.0.6(postcss@8.5.4) + version: 7.0.6(postcss@8.5.6) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -462,7 +462,7 @@ importers: version: 1.9.1(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) '@csstools/postcss-light-dark-function': specifier: ^2.0.8 - version: 2.0.8(postcss@8.5.4) + version: 2.0.8(postcss@8.5.6) '@playwright/test': specifier: ^1.53.1 version: 1.53.1 @@ -524,14 +524,14 @@ importers: specifier: ^7.15.0 version: 7.15.0(eslint@8.57.0)(typescript@5.8.3) '@vitejs/plugin-react': - specifier: ^4.5.0 - version: 4.5.0(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) + specifier: ^4.5.2 + version: 4.6.0(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) '@vitejs/plugin-react-swc': - specifier: ^3.10.0 - version: 3.10.0(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) + specifier: ^3.10.2 + version: 3.10.2(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) autoprefixer: specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.4) + version: 10.4.21(postcss@8.5.6) babel-plugin-react-compiler: specifier: 19.1.0-rc.1 version: 19.1.0-rc.1 @@ -564,7 +564,7 @@ importers: version: 54.0.0(eslint@8.57.0) eslint-plugin-vitest: specifier: ^0.4.1 - version: 0.4.1(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0)) + version: 0.4.1(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0)) jsdom: specifier: ^24.1.3 version: 24.1.3 @@ -575,8 +575,8 @@ importers: specifier: ^6.2.6 version: 6.2.6 postcss: - specifier: ^8.5.4 - version: 8.5.4 + specifier: ^8.5.6 + version: 8.5.6 postcss-plugin-namespace: specifier: ^0.0.3 version: 0.0.3 @@ -620,8 +620,8 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.3)(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0) packages: @@ -760,8 +760,8 @@ packages: resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.26.5': - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} '@babel/helper-replace-supers@7.25.9': @@ -842,14 +842,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -3098,8 +3098,11 @@ packages: '@codemirror/state': 6.x.x '@codemirror/view': 6.x.x - '@rolldown/pluginutils@1.0.0-beta.9': - resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} + '@rolldown/pluginutils@1.0.0-beta.11': + resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==} + + '@rolldown/pluginutils@1.0.0-beta.19': + resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==} '@rollup/plugin-virtual@3.0.2': resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==} @@ -3400,68 +3403,68 @@ packages: '@swc-jotai/react-refresh@0.3.0': resolution: {integrity: sha512-WIWesycqFWqFRlfMa/NYON7AX6zTtSwK7z+nVRgdlk2r5iIv2/BDTeRgg3on+YvYlKR7IipioRVswyPOTq/ZKA==} - '@swc/core-darwin-arm64@1.11.29': - resolution: {integrity: sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==} + '@swc/core-darwin-arm64@1.12.5': + resolution: {integrity: sha512-3WF+naP/qkt5flrTfJr+p07b522JcixKvIivM7FgvllA6LjJxf+pheoILrTS8IwrNAK/XtHfKWYcGY+3eaA4mA==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.11.29': - resolution: {integrity: sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==} + '@swc/core-darwin-x64@1.12.5': + resolution: {integrity: sha512-GCcD3dft8YN7unTBcW02Fx41jXp2MNQHCjx5ceWSEYOGvn7vBSUp7k7LkfTxGN5Ftxb9a1mxhPq8r4rD2u/aPw==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.11.29': - resolution: {integrity: sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==} + '@swc/core-linux-arm-gnueabihf@1.12.5': + resolution: {integrity: sha512-jWlzP/Y4+wbE/EJM+WGIDQsklLFV3g5LmbYTBgrY4+5nb517P31mkBzf5y2knfNWPrL7HzNu0578j3Zi2E6Iig==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.11.29': - resolution: {integrity: sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==} + '@swc/core-linux-arm64-gnu@1.12.5': + resolution: {integrity: sha512-GkzgIUz+2r6J6Tn3hb7/4ByaWHRrRZt4vuN9BLAd+y65m2Bt0vlEpPtWhrB/TVe4hEkFR+W5PDETLEbUT4i0tQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.11.29': - resolution: {integrity: sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==} + '@swc/core-linux-arm64-musl@1.12.5': + resolution: {integrity: sha512-g0AJ7QmZPj3Uw+C5pDa48LAUG7JBgQmB0mN5cW+s2mjaFKT0mTSxYALtx/MDZwJExDPo0yJV8kSbFO1tvFPyhg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.11.29': - resolution: {integrity: sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==} + '@swc/core-linux-x64-gnu@1.12.5': + resolution: {integrity: sha512-PeYoSziNy+iNiBHPtAsO84bzBne/mbCsG5ijYkAhS1GVsDgohClorUvRXXhcUZoX2gr8TfSI9WLHo30K+DKiHg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.11.29': - resolution: {integrity: sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==} + '@swc/core-linux-x64-musl@1.12.5': + resolution: {integrity: sha512-EJrfCCIyuV5LLmYgKtIMwtgsnjVesdFe0IgQzEKs9OfB6cL6g7WO9conn8BkGX8jphVa7jChKxShDGkreWWDzA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.11.29': - resolution: {integrity: sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==} + '@swc/core-win32-arm64-msvc@1.12.5': + resolution: {integrity: sha512-FnwT7fxkJJMgsfiDoZKEVGyCzrPFbzpflFAAoTCUCu3MaHw6mW55o/MAAfofvJ1iIcEpec4o93OilsmKtpyO5Q==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.11.29': - resolution: {integrity: sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==} + '@swc/core-win32-ia32-msvc@1.12.5': + resolution: {integrity: sha512-jW6l4KFt9mIXSpGseE6BQOEFmbIeXeShDuWgldEJXKeXf/uPs8wrqv80XBIUwVpK0ZbmJwPQ0waGVj8UM3th2Q==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.11.29': - resolution: {integrity: sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==} + '@swc/core-win32-x64-msvc@1.12.5': + resolution: {integrity: sha512-AZszwuEjlz1tSNLQRm3T5OZJ5eebxjJlDQnnzXJmg0B7DJMRoaAe1HTLOmejxjFK6yWr7fh+pSeCw2PgQLxgqA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.11.29': - resolution: {integrity: sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==} + '@swc/core@1.12.5': + resolution: {integrity: sha512-KxA0PHHIuUBmQ/Oi+xFpVzILj2Oo37sTtftCbyowQlyx5YOknEOw1kLpas5hMcpznXgFyAWbpK71xQps4INPgA==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -3475,8 +3478,8 @@ packages: '@swc/helpers@0.5.1': resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} - '@swc/types@0.1.21': - resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==} + '@swc/types@0.1.23': + resolution: {integrity: sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==} '@tailwindcss/typography@0.5.16': resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} @@ -3570,6 +3573,9 @@ packages: '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/clone@0.1.30': resolution: {integrity: sha512-vcxBr+ybljeSiasmdke1cQ9ICxoEwaBgM1OQ/P5h4MPj/kRyLcDl5L8PrftlbyV1kBbJIs3M3x1A1+rcWd4mEA==} @@ -3672,6 +3678,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/diff-match-patch@1.0.36': resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} @@ -3954,28 +3963,28 @@ packages: '@codemirror/state': ^6 '@codemirror/view': ^6 - '@vitejs/plugin-react-swc@3.10.0': - resolution: {integrity: sha512-ZmkdHw3wo/o/Rk05YsXZs/DJAfY2CdQ5DUAjoWji+PEr+hYADdGMCGgEAILbiKj+CjspBTuTACBcWDrmC8AUfw==} + '@vitejs/plugin-react-swc@3.10.2': + resolution: {integrity: sha512-xD3Rdvrt5LgANug7WekBn1KhcvLn1H3jNBfJRL3reeOIua/WnZOEV5qi5qIBq5T8R0jUDmRtxuvk4bPhzGHDWw==} peerDependencies: - vite: ^4 || ^5 || ^6 + vite: ^4 || ^5 || ^6 || ^7.0.0-beta.0 - '@vitejs/plugin-react@4.5.0': - resolution: {integrity: sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==} + '@vitejs/plugin-react@4.6.0': + resolution: {integrity: sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 '@vitest/expect@2.0.5': resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} - '@vitest/expect@3.1.4': - resolution: {integrity: sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.1.4': - resolution: {integrity: sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true @@ -3988,20 +3997,20 @@ packages: '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - '@vitest/pretty-format@3.1.4': - resolution: {integrity: sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@3.1.4': - resolution: {integrity: sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@3.1.4': - resolution: {integrity: sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} '@vitest/spy@2.0.5': resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - '@vitest/spy@3.1.4': - resolution: {integrity: sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} '@vitest/utils@2.0.5': resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} @@ -4009,8 +4018,8 @@ packages: '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} - '@vitest/utils@3.1.4': - resolution: {integrity: sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -6270,6 +6279,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -6483,8 +6495,8 @@ packages: loro-crdt@1.5.4: resolution: {integrity: sha512-yqtu2/JhuVTbGLCQT29P8nyUsMtetMpdqINJzxfIC9ZKsIXob3mhIFCo/BcsA7gga2Ck+BcOvOhC+OBQvSj8xg==} - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -7370,8 +7382,8 @@ packages: resolution: {integrity: sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==} engines: {node: '>=6.0.0'} - postcss@8.5.4: - resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} potpack@1.0.2: @@ -8119,6 +8131,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + strongly-connected-components@1.0.1: resolution: {integrity: sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==} @@ -8316,12 +8331,12 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyglobby@0.2.13: - resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} tinyqueue@2.0.3: @@ -8342,6 +8357,10 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + to-float32@1.1.0: resolution: {integrity: sha512-keDnAusn/vc+R3iEiSDw8TOF7gPiTLdK1ArvWtYbJQiVfmRg6i/CAvbKq3uIS0vWroAC7ZecN3DjQKw3aSklUg==} @@ -8805,8 +8824,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@3.1.4: - resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -8868,16 +8887,16 @@ packages: yaml: optional: true - vitest@3.1.4: - resolution: {integrity: sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.1.4 - '@vitest/ui': 3.1.4 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -9372,7 +9391,7 @@ snapshots: dependencies: '@babel/types': 7.27.6 - '@babel/helper-plugin-utils@7.26.5': {} + '@babel/helper-plugin-utils@7.27.1': {} '@babel/helper-replace-supers@7.25.9(@babel/core@7.27.4)': dependencies: @@ -9416,7 +9435,7 @@ snapshots: dependencies: '@babel/core': 7.27.4 '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.27.4) - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -9424,7 +9443,7 @@ snapshots: dependencies: '@babel/core': 7.27.4 '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.27.4) - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.27.4) transitivePeerDependencies: - supports-color @@ -9432,23 +9451,23 @@ snapshots: '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4) - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-simple-access': 7.25.9 transitivePeerDependencies: - supports-color @@ -9457,26 +9476,26 @@ snapshots: dependencies: '@babel/core': 7.27.4 '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.27.4) - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.27.4)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.27.4)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.27.4) - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.27.4) transitivePeerDependencies: @@ -9485,7 +9504,7 @@ snapshots: '@babel/preset-typescript@7.25.9(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.27.4) '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.27.4) @@ -9879,26 +9898,26 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 - '@csstools/postcss-light-dark-function@2.0.8(postcss@8.5.4)': + '@csstools/postcss-light-dark-function@2.0.8(postcss@8.5.6)': dependencies: '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 - '@csstools/postcss-progressive-custom-properties': 4.0.1(postcss@8.5.4) - '@csstools/utilities': 2.0.0(postcss@8.5.4) - postcss: 8.5.4 + '@csstools/postcss-progressive-custom-properties': 4.0.1(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 - '@csstools/postcss-progressive-custom-properties@4.0.1(postcss@8.5.4)': + '@csstools/postcss-progressive-custom-properties@4.0.1(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': dependencies: postcss-selector-parser: 7.1.0 - '@csstools/utilities@2.0.0(postcss@8.5.4)': + '@csstools/utilities@2.0.0(postcss@8.5.6)': dependencies: - postcss: 8.5.4 + postcss: 8.5.6 '@dagrejs/dagre@1.1.4': dependencies: @@ -12494,7 +12513,9 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.37.2 - '@rolldown/pluginutils@1.0.0-beta.9': {} + '@rolldown/pluginutils@1.0.0-beta.11': {} + + '@rolldown/pluginutils@1.0.0-beta.19': {} '@rollup/plugin-virtual@3.0.2(rollup@4.39.0)': optionalDependencies: @@ -12796,51 +12817,51 @@ snapshots: '@swc-jotai/react-refresh@0.3.0': {} - '@swc/core-darwin-arm64@1.11.29': + '@swc/core-darwin-arm64@1.12.5': optional: true - '@swc/core-darwin-x64@1.11.29': + '@swc/core-darwin-x64@1.12.5': optional: true - '@swc/core-linux-arm-gnueabihf@1.11.29': + '@swc/core-linux-arm-gnueabihf@1.12.5': optional: true - '@swc/core-linux-arm64-gnu@1.11.29': + '@swc/core-linux-arm64-gnu@1.12.5': optional: true - '@swc/core-linux-arm64-musl@1.11.29': + '@swc/core-linux-arm64-musl@1.12.5': optional: true - '@swc/core-linux-x64-gnu@1.11.29': + '@swc/core-linux-x64-gnu@1.12.5': optional: true - '@swc/core-linux-x64-musl@1.11.29': + '@swc/core-linux-x64-musl@1.12.5': optional: true - '@swc/core-win32-arm64-msvc@1.11.29': + '@swc/core-win32-arm64-msvc@1.12.5': optional: true - '@swc/core-win32-ia32-msvc@1.11.29': + '@swc/core-win32-ia32-msvc@1.12.5': optional: true - '@swc/core-win32-x64-msvc@1.11.29': + '@swc/core-win32-x64-msvc@1.12.5': optional: true - '@swc/core@1.11.29': + '@swc/core@1.12.5': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.21 + '@swc/types': 0.1.23 optionalDependencies: - '@swc/core-darwin-arm64': 1.11.29 - '@swc/core-darwin-x64': 1.11.29 - '@swc/core-linux-arm-gnueabihf': 1.11.29 - '@swc/core-linux-arm64-gnu': 1.11.29 - '@swc/core-linux-arm64-musl': 1.11.29 - '@swc/core-linux-x64-gnu': 1.11.29 - '@swc/core-linux-x64-musl': 1.11.29 - '@swc/core-win32-arm64-msvc': 1.11.29 - '@swc/core-win32-ia32-msvc': 1.11.29 - '@swc/core-win32-x64-msvc': 1.11.29 + '@swc/core-darwin-arm64': 1.12.5 + '@swc/core-darwin-x64': 1.12.5 + '@swc/core-linux-arm-gnueabihf': 1.12.5 + '@swc/core-linux-arm64-gnu': 1.12.5 + '@swc/core-linux-arm64-musl': 1.12.5 + '@swc/core-linux-x64-gnu': 1.12.5 + '@swc/core-linux-x64-musl': 1.12.5 + '@swc/core-win32-arm64-msvc': 1.12.5 + '@swc/core-win32-ia32-msvc': 1.12.5 + '@swc/core-win32-x64-msvc': 1.12.5 '@swc/counter@0.1.3': {} @@ -12848,7 +12869,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@swc/types@0.1.21': + '@swc/types@0.1.23': dependencies: '@swc/counter': 0.1.3 @@ -12983,6 +13004,10 @@ snapshots: dependencies: '@babel/types': 7.27.6 + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + '@types/clone@0.1.30': {} '@types/cookie@0.6.0': {} @@ -13108,6 +13133,8 @@ snapshots: dependencies: '@types/ms': 0.7.34 + '@types/deep-eql@4.0.2': {} + '@types/diff-match-patch@1.0.36': {} '@types/doctrine@0.0.9': {} @@ -13451,20 +13478,20 @@ snapshots: '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) '@connectrpc/connect-web': 1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0)) - '@vitejs/plugin-react-swc@3.10.0(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0))': + '@vitejs/plugin-react-swc@3.10.2(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0))': dependencies: - '@rolldown/pluginutils': 1.0.0-beta.9 - '@swc/core': 1.11.29 + '@rolldown/pluginutils': 1.0.0-beta.11 + '@swc/core': 1.12.5 vite: 6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.5.0(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0))': + '@vitejs/plugin-react@4.6.0(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0))': dependencies: '@babel/core': 7.27.4 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.27.4) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.27.4) - '@rolldown/pluginutils': 1.0.0-beta.9 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.4) + '@rolldown/pluginutils': 1.0.0-beta.19 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 vite: 6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0) @@ -13478,16 +13505,17 @@ snapshots: chai: 5.2.0 tinyrainbow: 1.2.0 - '@vitest/expect@3.1.4': + '@vitest/expect@3.2.4': dependencies: - '@vitest/spy': 3.1.4 - '@vitest/utils': 3.1.4 + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.4(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0))': + '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0))': dependencies: - '@vitest/spy': 3.1.4 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: @@ -13502,18 +13530,19 @@ snapshots: dependencies: tinyrainbow: 1.2.0 - '@vitest/pretty-format@3.1.4': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.1.4': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 3.1.4 + '@vitest/utils': 3.2.4 pathe: 2.0.3 + strip-literal: 3.0.0 - '@vitest/snapshot@3.1.4': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 3.1.4 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 @@ -13521,27 +13550,27 @@ snapshots: dependencies: tinyspy: 3.0.2 - '@vitest/spy@3.1.4': + '@vitest/spy@3.2.4': dependencies: - tinyspy: 3.0.2 + tinyspy: 4.0.3 '@vitest/utils@2.0.5': dependencies: '@vitest/pretty-format': 2.0.5 estree-walker: 3.0.3 - loupe: 3.1.3 + loupe: 3.1.4 tinyrainbow: 1.2.0 '@vitest/utils@2.1.9': dependencies: '@vitest/pretty-format': 2.1.9 - loupe: 3.1.3 + loupe: 3.1.4 tinyrainbow: 1.2.0 - '@vitest/utils@3.1.4': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 3.1.4 - loupe: 3.1.3 + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 tinyrainbow: 2.0.0 '@webassemblyjs/ast@1.14.1': @@ -13848,14 +13877,14 @@ snapshots: attr-accept@2.2.5: {} - autoprefixer@10.4.21(postcss@8.5.4): + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-lite: 1.0.30001722 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -14006,7 +14035,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.3 + loupe: 3.1.4 pathval: 2.0.0 chalk@3.0.0: @@ -14276,9 +14305,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-declaration-sorter@7.2.0(postcss@8.5.4): + css-declaration-sorter@7.2.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 css-font-size-keywords@1.0.0: {} @@ -14306,12 +14335,12 @@ snapshots: css-loader@7.1.2(webpack@5.96.1(esbuild@0.25.5)): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.4) - postcss-modules-local-by-default: 4.1.0(postcss@8.5.4) - postcss-modules-scope: 3.2.1(postcss@8.5.4) - postcss-modules-values: 4.0.0(postcss@8.5.4) + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.1.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: @@ -14350,49 +14379,49 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-default@7.0.6(postcss@8.5.4): + cssnano-preset-default@7.0.6(postcss@8.5.6): dependencies: browserslist: 4.25.0 - css-declaration-sorter: 7.2.0(postcss@8.5.4) - cssnano-utils: 5.0.0(postcss@8.5.4) - postcss: 8.5.4 - postcss-calc: 10.0.2(postcss@8.5.4) - postcss-colormin: 7.0.2(postcss@8.5.4) - postcss-convert-values: 7.0.4(postcss@8.5.4) - postcss-discard-comments: 7.0.3(postcss@8.5.4) - postcss-discard-duplicates: 7.0.1(postcss@8.5.4) - postcss-discard-empty: 7.0.0(postcss@8.5.4) - postcss-discard-overridden: 7.0.0(postcss@8.5.4) - postcss-merge-longhand: 7.0.4(postcss@8.5.4) - postcss-merge-rules: 7.0.4(postcss@8.5.4) - postcss-minify-font-values: 7.0.0(postcss@8.5.4) - postcss-minify-gradients: 7.0.0(postcss@8.5.4) - postcss-minify-params: 7.0.2(postcss@8.5.4) - postcss-minify-selectors: 7.0.4(postcss@8.5.4) - postcss-normalize-charset: 7.0.0(postcss@8.5.4) - postcss-normalize-display-values: 7.0.0(postcss@8.5.4) - postcss-normalize-positions: 7.0.0(postcss@8.5.4) - postcss-normalize-repeat-style: 7.0.0(postcss@8.5.4) - postcss-normalize-string: 7.0.0(postcss@8.5.4) - postcss-normalize-timing-functions: 7.0.0(postcss@8.5.4) - postcss-normalize-unicode: 7.0.2(postcss@8.5.4) - postcss-normalize-url: 7.0.0(postcss@8.5.4) - postcss-normalize-whitespace: 7.0.0(postcss@8.5.4) - postcss-ordered-values: 7.0.1(postcss@8.5.4) - postcss-reduce-initial: 7.0.2(postcss@8.5.4) - postcss-reduce-transforms: 7.0.0(postcss@8.5.4) - postcss-svgo: 7.0.1(postcss@8.5.4) - postcss-unique-selectors: 7.0.3(postcss@8.5.4) - - cssnano-utils@5.0.0(postcss@8.5.4): - dependencies: - postcss: 8.5.4 - - cssnano@7.0.6(postcss@8.5.4): - dependencies: - cssnano-preset-default: 7.0.6(postcss@8.5.4) + css-declaration-sorter: 7.2.0(postcss@8.5.6) + cssnano-utils: 5.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-calc: 10.0.2(postcss@8.5.6) + postcss-colormin: 7.0.2(postcss@8.5.6) + postcss-convert-values: 7.0.4(postcss@8.5.6) + postcss-discard-comments: 7.0.3(postcss@8.5.6) + postcss-discard-duplicates: 7.0.1(postcss@8.5.6) + postcss-discard-empty: 7.0.0(postcss@8.5.6) + postcss-discard-overridden: 7.0.0(postcss@8.5.6) + postcss-merge-longhand: 7.0.4(postcss@8.5.6) + postcss-merge-rules: 7.0.4(postcss@8.5.6) + postcss-minify-font-values: 7.0.0(postcss@8.5.6) + postcss-minify-gradients: 7.0.0(postcss@8.5.6) + postcss-minify-params: 7.0.2(postcss@8.5.6) + postcss-minify-selectors: 7.0.4(postcss@8.5.6) + postcss-normalize-charset: 7.0.0(postcss@8.5.6) + postcss-normalize-display-values: 7.0.0(postcss@8.5.6) + postcss-normalize-positions: 7.0.0(postcss@8.5.6) + postcss-normalize-repeat-style: 7.0.0(postcss@8.5.6) + postcss-normalize-string: 7.0.0(postcss@8.5.6) + postcss-normalize-timing-functions: 7.0.0(postcss@8.5.6) + postcss-normalize-unicode: 7.0.2(postcss@8.5.6) + postcss-normalize-url: 7.0.0(postcss@8.5.6) + postcss-normalize-whitespace: 7.0.0(postcss@8.5.6) + postcss-ordered-values: 7.0.1(postcss@8.5.6) + postcss-reduce-initial: 7.0.2(postcss@8.5.6) + postcss-reduce-transforms: 7.0.0(postcss@8.5.6) + postcss-svgo: 7.0.1(postcss@8.5.6) + postcss-unique-selectors: 7.0.3(postcss@8.5.6) + + cssnano-utils@5.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + cssnano@7.0.6(postcss@8.5.6): + dependencies: + cssnano-preset-default: 7.0.6(postcss@8.5.6) lilconfig: 3.1.3 - postcss: 8.5.4 + postcss: 8.5.6 csso@5.0.5: dependencies: @@ -15134,13 +15163,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-vitest@0.4.1(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0)): + eslint-plugin-vitest@0.4.1(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0)): dependencies: '@typescript-eslint/utils': 7.15.0(eslint@8.57.0)(typescript@5.8.3) eslint: 8.57.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.15.0(@typescript-eslint/parser@7.15.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3) - vitest: 3.1.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0) transitivePeerDependencies: - supports-color - typescript @@ -15810,9 +15839,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.4): + icss-utils@5.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 ieee754@1.2.1: {} @@ -16087,6 +16116,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -16305,7 +16336,7 @@ snapshots: loro-crdt@1.5.4: {} - loupe@3.1.3: {} + loupe@3.1.4: {} lru-cache@10.4.3: {} @@ -17180,198 +17211,198 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-calc@10.0.2(postcss@8.5.4): + postcss-calc@10.0.2(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-colormin@7.0.2(postcss@8.5.4): + postcss-colormin@7.0.2(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-convert-values@7.0.4(postcss@8.5.4): + postcss-convert-values@7.0.4(postcss@8.5.6): dependencies: browserslist: 4.25.0 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-discard-comments@7.0.3(postcss@8.5.4): + postcss-discard-comments@7.0.3(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-discard-duplicates@7.0.1(postcss@8.5.4): + postcss-discard-duplicates@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-discard-empty@7.0.0(postcss@8.5.4): + postcss-discard-empty@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-discard-overridden@7.0.0(postcss@8.5.4): + postcss-discard-overridden@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-import@15.1.0(postcss@8.5.4): + postcss-import@15.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.10 - postcss-js@4.0.1(postcss@8.5.4): + postcss-js@4.0.1(postcss@8.5.6): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.4 + postcss: 8.5.6 - postcss-load-config@4.0.2(postcss@8.5.4): + postcss-load-config@4.0.2(postcss@8.5.6): dependencies: lilconfig: 3.1.3 yaml: 2.7.0 optionalDependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-merge-longhand@7.0.4(postcss@8.5.4): + postcss-merge-longhand@7.0.4(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - stylehacks: 7.0.4(postcss@8.5.4) + stylehacks: 7.0.4(postcss@8.5.6) - postcss-merge-rules@7.0.4(postcss@8.5.4): + postcss-merge-rules@7.0.4(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-api: 3.0.0 - cssnano-utils: 5.0.0(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 5.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-minify-font-values@7.0.0(postcss@8.5.4): + postcss-minify-font-values@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-gradients@7.0.0(postcss@8.5.4): + postcss-minify-gradients@7.0.0(postcss@8.5.6): dependencies: colord: 2.9.3 - cssnano-utils: 5.0.0(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 5.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-params@7.0.2(postcss@8.5.4): + postcss-minify-params@7.0.2(postcss@8.5.6): dependencies: browserslist: 4.25.0 - cssnano-utils: 5.0.0(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 5.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-minify-selectors@7.0.4(postcss@8.5.4): + postcss-minify-selectors@7.0.4(postcss@8.5.6): dependencies: cssesc: 3.0.0 - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-modules-extract-imports@3.1.0(postcss@8.5.4): + postcss-modules-extract-imports@3.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-modules-local-by-default@4.1.0(postcss@8.5.4): + postcss-modules-local-by-default@4.1.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.4): + postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-modules-values@4.0.0(postcss@8.5.4): + postcss-modules-values@4.0.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 - postcss-nested@6.2.0(postcss@8.5.4): + postcss-nested@6.2.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-normalize-charset@7.0.0(postcss@8.5.4): + postcss-normalize-charset@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-normalize-display-values@7.0.0(postcss@8.5.4): + postcss-normalize-display-values@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-positions@7.0.0(postcss@8.5.4): + postcss-normalize-positions@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@7.0.0(postcss@8.5.4): + postcss-normalize-repeat-style@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-string@7.0.0(postcss@8.5.4): + postcss-normalize-string@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@7.0.0(postcss@8.5.4): + postcss-normalize-timing-functions@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@7.0.2(postcss@8.5.4): + postcss-normalize-unicode@7.0.2(postcss@8.5.6): dependencies: browserslist: 4.25.0 - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-url@7.0.0(postcss@8.5.4): + postcss-normalize-url@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@7.0.0(postcss@8.5.4): + postcss-normalize-whitespace@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-ordered-values@7.0.1(postcss@8.5.4): + postcss-ordered-values@7.0.1(postcss@8.5.6): dependencies: - cssnano-utils: 5.0.0(postcss@8.5.4) - postcss: 8.5.4 + cssnano-utils: 5.0.0(postcss@8.5.6) + postcss: 8.5.6 postcss-value-parser: 4.2.0 postcss-plugin-namespace@0.0.3: dependencies: postcss: 7.0.39 - postcss-reduce-initial@7.0.2(postcss@8.5.4): + postcss-reduce-initial@7.0.2(postcss@8.5.6): dependencies: browserslist: 4.25.0 caniuse-api: 3.0.0 - postcss: 8.5.4 + postcss: 8.5.6 - postcss-reduce-transforms@7.0.0(postcss@8.5.4): + postcss-reduce-transforms@7.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 postcss-resolve-nested-selector@0.1.6: {} - postcss-safe-parser@7.0.1(postcss@8.5.4): + postcss-safe-parser@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser@6.0.10: dependencies: @@ -17388,15 +17419,15 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-svgo@7.0.1(postcss@8.5.4): + postcss-svgo@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 svgo: 3.3.2 - postcss-unique-selectors@7.0.3(postcss@8.5.4): + postcss-unique-selectors@7.0.3(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 postcss-value-parser@4.2.0: {} @@ -17406,7 +17437,7 @@ snapshots: picocolors: 0.2.1 source-map: 0.6.1 - postcss@8.5.4: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -18440,6 +18471,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + strongly-connected-components@1.0.1: {} style-loader@4.0.0(webpack@5.96.1(esbuild@0.25.5)): @@ -18456,10 +18491,10 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - stylehacks@7.0.4(postcss@8.5.4): + stylehacks@7.0.4(postcss@8.5.6): dependencies: browserslist: 4.25.0 - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 stylelint-config-recommended@14.0.1(stylelint@16.19.1(typescript@5.8.3)): @@ -18500,9 +18535,9 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.4 + postcss: 8.5.6 postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 7.0.1(postcss@8.5.4) + postcss-safe-parser: 7.0.1(postcss@8.5.6) postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 @@ -18630,11 +18665,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.4 - postcss-import: 15.1.0(postcss@8.5.4) - postcss-js: 4.0.1(postcss@8.5.4) - postcss-load-config: 4.0.2(postcss@8.5.4) - postcss-nested: 6.2.0(postcss@8.5.4) + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.0.1(postcss@8.5.6) + postcss-load-config: 4.0.2(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.10 sucrase: 3.35.0 @@ -18708,12 +18743,12 @@ snapshots: tinyexec@0.3.2: {} - tinyglobby@0.2.13: + tinyglobby@0.2.14: dependencies: fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 - tinypool@1.0.2: {} + tinypool@1.1.1: {} tinyqueue@2.0.3: {} @@ -18725,6 +18760,8 @@ snapshots: tinyspy@3.0.2: {} + tinyspy@4.0.3: {} + to-float32@1.1.0: {} to-px@1.0.1: @@ -19383,7 +19420,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@3.1.4(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0): + vite-node@3.2.4(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0): dependencies: cac: 6.7.14 debug: 4.4.1 @@ -19407,7 +19444,7 @@ snapshots: vite-plugin-top-level-await@1.5.0(rollup@4.39.0)(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)): dependencies: '@rollup/plugin-virtual': 3.0.2(rollup@4.39.0) - '@swc/core': 1.11.29 + '@swc/core': 1.12.5 uuid: 10.0.0 vite: 6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0) transitivePeerDependencies: @@ -19434,9 +19471,9 @@ snapshots: esbuild: 0.25.5 fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 - postcss: 8.5.4 + postcss: 8.5.6 rollup: 4.39.0 - tinyglobby: 0.2.13 + tinyglobby: 0.2.14 optionalDependencies: '@types/node': 24.0.3 fsevents: 2.3.3 @@ -19445,28 +19482,30 @@ snapshots: terser: 5.43.1 yaml: 2.7.0 - vitest@3.1.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@1.21.7)(jsdom@24.1.3)(less@4.2.0)(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(terser@5.43.1)(yaml@2.7.0): dependencies: - '@vitest/expect': 3.1.4 - '@vitest/mocker': 3.1.4(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) - '@vitest/pretty-format': 3.1.4 - '@vitest/runner': 3.1.4 - '@vitest/snapshot': 3.1.4 - '@vitest/spy': 3.1.4 - '@vitest/utils': 3.1.4 + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.0.3)(typescript@5.8.3))(vite@6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 debug: 4.4.1 expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 + picomatch: 4.0.2 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.13 - tinypool: 1.0.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 tinyrainbow: 2.0.0 vite: 6.3.2(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0) - vite-node: 3.1.4(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0) + vite-node: 3.2.4(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.0)(terser@5.43.1)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 From 7f97b04bce7e19f17f64a15d4126600c2ebbc7b3 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Tue, 24 Jun 2025 01:36:05 -0400 Subject: [PATCH 09/26] feat(frontend): Highlight reactive references (#5415) One of my favorite details in ObservableHQ notebooks is the subtle visual cue for dependencies between cells (bold/underline). This PR brings a similar experience to marimo. We already have all the information we need (I think), in the front end from the dependency graph viewer work. When editing a cell, any references to variables defined in other cells are highlighted as dependencies. These highlights update as the user edits or as the global variable state changes. | Light mode | Dark mode | |---------------------------------------------------------------------------|---------------------------------------------------------------------------| | ![Light mode](https://github.com/user-attachments/assets/cc90a703-38db-4ee6-8665-0d98ddcc6c87) | ![Dark mode](https://github.com/user-attachments/assets/376f0efc-b575-45fc-9d7f-d459cfcaade8) | --- .../app-config/user-config-form.tsx | 28 + .../editor/__tests__/data-attributes.test.tsx | 1 + .../editor/actions/useConfigActions.tsx | 28 +- .../editor/cell/code/cell-editor.tsx | 1 + .../src/components/icons/loading-ellipsis.tsx | 2 +- .../core/codemirror/__tests__/setup.test.ts | 1 + frontend/src/core/codemirror/cm.ts | 9 + .../codemirror/completion/Autocompleter.ts | 39 +- .../src/core/codemirror/completion/hints.ts | 8 + .../codemirror/go-to-definition/commands.ts | 8 + .../codemirror/go-to-definition/underline.ts | 41 +- .../__tests__/analyzer.test.ts | 1067 +++++++++++++++++ .../reactive-references/analyzer.ts | 498 ++++++++ .../reactive-references/extension.ts | 154 +++ frontend/src/core/codemirror/theme/dark.ts | 75 +- frontend/src/core/codemirror/theme/light.ts | 88 +- .../config/__tests__/config-schema.test.ts | 2 + frontend/src/core/config/config-schema.ts | 2 + frontend/src/core/runtime/runtime.ts | 2 +- marimo/_config/config.py | 3 + openapi/api.yaml | 4 +- openapi/src/api.ts | 1 + 22 files changed, 1988 insertions(+), 74 deletions(-) create mode 100644 frontend/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts create mode 100644 frontend/src/core/codemirror/reactive-references/analyzer.ts create mode 100644 frontend/src/core/codemirror/reactive-references/extension.ts diff --git a/frontend/src/components/app-config/user-config-form.tsx b/frontend/src/components/app-config/user-config-form.tsx index 1376ea85c7c..394b0eac45d 100644 --- a/frontend/src/components/app-config/user-config-form.tsx +++ b/frontend/src/components/app-config/user-config-form.tsx @@ -659,6 +659,34 @@ export const UserConfigForm: React.FC = () => { )} /> + ( +
+ + Reference highlighting + + + + + + + + + Visually emphasizes variables in a cell that are defined + elsewhere in the notebook. + +
+ )} + /> { default_table_page_size: 10, default_width: "normal", theme: "light", + reference_highlighting: false, }, keymap: { preset: "default" }, completion: { diff --git a/frontend/src/components/editor/actions/useConfigActions.tsx b/frontend/src/components/editor/actions/useConfigActions.tsx index 8596f2af5a9..acc0c2195bd 100644 --- a/frontend/src/components/editor/actions/useConfigActions.tsx +++ b/frontend/src/components/editor/actions/useConfigActions.tsx @@ -51,7 +51,7 @@ export function useConfigActions() { }, }, { - label: "Config > Switch keymap to VIM", + label: "Config > Switch keymap to VIM", hidden: config.keymap.preset === "vim", handle: () => { handleUserConfig({ @@ -103,6 +103,32 @@ export function useConfigActions() { }, hidden: config.completion.copilot === "github", }, + { + label: "Config > Disable reference highlighting", + hidden: !config.display.reference_highlighting, + handle: () => { + handleUserConfig({ + ...config, + display: { + ...config.display, + reference_highlighting: false, + }, + }); + }, + }, + { + label: "Config > Enable reference highlighting", + hidden: config.display.reference_highlighting, + handle: () => { + handleUserConfig({ + ...config, + display: { + ...config.display, + reference_highlighting: true, + }, + }); + }, + }, ]; return actions.filter((a) => !a.hidden); diff --git a/frontend/src/components/editor/cell/code/cell-editor.tsx b/frontend/src/components/editor/cell/code/cell-editor.tsx index 94b9f7cb2d9..3c83f1e99bb 100644 --- a/frontend/src/components/editor/cell/code/cell-editor.tsx +++ b/frontend/src/components/editor/cell/code/cell-editor.tsx @@ -176,6 +176,7 @@ const CellEditorInternal = ({ theme, hotkeys: new OverridingHotkeyProvider(userConfig.keymap.overrides ?? {}), diagnosticsConfig: userConfig.diagnostics, + displayConfig: userConfig.display, }); extensions.push( diff --git a/frontend/src/components/icons/loading-ellipsis.tsx b/frontend/src/components/icons/loading-ellipsis.tsx index ac41ca18fc0..d4cae21cc15 100644 --- a/frontend/src/components/icons/loading-ellipsis.tsx +++ b/frontend/src/components/icons/loading-ellipsis.tsx @@ -1,4 +1,4 @@ -/* Copyright 2025 Marimo. All rights reserved. */ +/* Copyright 2024 Marimo. All rights reserved. */ import clsx from "clsx"; import { Circle } from "lucide-react"; diff --git a/frontend/src/core/codemirror/__tests__/setup.test.ts b/frontend/src/core/codemirror/__tests__/setup.test.ts index dc39adca3bc..ceed7f3e04b 100644 --- a/frontend/src/core/codemirror/__tests__/setup.test.ts +++ b/frontend/src/core/codemirror/__tests__/setup.test.ts @@ -66,6 +66,7 @@ function getOpts() { diagnosticsConfig: {}, hotkeys: new OverridingHotkeyProvider({}), theme: "light", + displayConfig: { reference_highlighting: false }, } as const; } diff --git a/frontend/src/core/codemirror/cm.ts b/frontend/src/core/codemirror/cm.ts index 017dd2e0d6e..2138fd4e99c 100644 --- a/frontend/src/core/codemirror/cm.ts +++ b/frontend/src/core/codemirror/cm.ts @@ -42,6 +42,7 @@ import type { CellId } from "../cells/ids"; import type { CompletionConfig, DiagnosticsConfig, + DisplayConfig, KeymapConfig, LSPConfig, } from "../config/config-schema"; @@ -65,6 +66,7 @@ import { getCurrentLanguageAdapter } from "./language/commands"; import { adaptiveLanguageConfiguration } from "./language/extension"; import { dndBundle } from "./misc/dnd"; import { pasteBundle } from "./misc/paste"; +import { reactiveReferencesBundle } from "./reactive-references/extension"; import { dynamicReadonly } from "./readonly/extension"; import { darkTheme } from "./theme/dark"; import { lightTheme } from "./theme/light"; @@ -80,6 +82,7 @@ export interface CodeMirrorSetupOpts { hotkeys: HotkeyProvider; lspConfig: LSPConfig; diagnosticsConfig: DiagnosticsConfig; + displayConfig: Pick; } function getPlaceholderType(opts: CodeMirrorSetupOpts) { @@ -100,6 +103,7 @@ export const setupCodeMirror = (opts: CodeMirrorSetupOpts): Extension[] => { completionConfig, lspConfig, diagnosticsConfig, + displayConfig, } = opts; const placeholderType = getPlaceholderType(opts); @@ -139,6 +143,11 @@ export const setupCodeMirror = (opts: CodeMirrorSetupOpts): Extension[] => { : [], // Readonly extension dynamicReadonly(store), + // Reactive references highlighting + reactiveReferencesBundle( + cellId, + displayConfig.reference_highlighting ?? false, + ), ]; }; diff --git a/frontend/src/core/codemirror/completion/Autocompleter.ts b/frontend/src/core/codemirror/completion/Autocompleter.ts index c295f78ff48..3890907d57f 100644 --- a/frontend/src/core/codemirror/completion/Autocompleter.ts +++ b/frontend/src/core/codemirror/completion/Autocompleter.ts @@ -4,6 +4,7 @@ import type { Tooltip } from "@codemirror/view"; import { DeferredRequestRegistry } from "@/core/network/DeferredRequestRegistry"; import { sendCodeCompletionRequest } from "@/core/network/requests"; import type { CodeCompletionRequest } from "@/core/network/types"; +import { isPlatformMac } from "../../hotkeys/shortcuts"; import type { CompletionOption, CompletionResultMessage, @@ -12,6 +13,7 @@ import "../../../components/editor/documentation.css"; function constructCompletionInfoNode( innerHtml?: string | null, + showGoToDefinitionHint = false, ): HTMLElement | null { if (!innerHtml) { return null; @@ -19,10 +21,40 @@ function constructCompletionInfoNode( const container = document.createElement("span"); container.classList.add("mo-cm-tooltip"); container.classList.add("docs-documentation"); + if (showGoToDefinitionHint) { + container.classList.add("cm-tooltip-section"); + } container.style.display = "flex"; container.style.flexDirection = "column"; container.style.gap = ".8rem"; container.innerHTML = innerHtml; + + if (showGoToDefinitionHint) { + const instructionDiv = document.createElement("div"); + instructionDiv.classList.add( + "text-xs", + "text-muted-foreground", + "font-medium", + "pt-1", + "-mt-2", + "border-t", + "border-border", + ); + + // Create kbd element for the key symbol + const kbd = document.createElement("kbd"); + kbd.className = + "ml-1 rounded-md bg-muted/40 px-2 text-[0.75rem] font-prose center border border-foreground/20 text-muted-foreground inline whitespace-nowrap"; + kbd.textContent = isPlatformMac() ? "⌘" : "Ctrl"; + + // Add the instruction text + instructionDiv.append(document.createTextNode("Jump to defining cell ")); + instructionDiv.append(kbd); + instructionDiv.append(document.createTextNode(" + Click")); + + container.append(instructionDiv); + } + return container; } @@ -79,12 +111,14 @@ export const Autocompleter = { limitToType, excludeTypes, exactName, + showGoToDefinitionHint = false, }: { position: number; message: CompletionResultMessage; limitToType?: "tooltip"; excludeTypes?: string[]; exactName?: string; + showGoToDefinitionHint?: boolean; }): (Tooltip & { html?: string | null }) | undefined { const firstOption = getFirstOption(message.options, exactName); if (!firstOption) { @@ -92,7 +126,10 @@ export const Autocompleter = { } const from = position - message.prefix_length; - const dom = constructCompletionInfoNode(firstOption.completion_info); + const dom = constructCompletionInfoNode( + firstOption.completion_info, + showGoToDefinitionHint, + ); if (!dom) { return; } diff --git a/frontend/src/core/codemirror/completion/hints.ts b/frontend/src/core/codemirror/completion/hints.ts index 3878098e8ef..2a779a72b77 100644 --- a/frontend/src/core/codemirror/completion/hints.ts +++ b/frontend/src/core/codemirror/completion/hints.ts @@ -10,6 +10,7 @@ import type { LSPConfig } from "@/core/config/config-schema"; import { documentationAtom } from "@/core/documentation/state"; import { store } from "@/core/state/jotai"; import { Logger } from "@/utils/Logger"; +import { reactiveReferencesField } from "../reactive-references/extension"; import { AUTOCOMPLETER, Autocompleter } from "./Autocompleter"; export function hintTooltip(lspConfig: LSPConfig) { @@ -46,6 +47,12 @@ async function requestDocumentation( const { startToken, endToken } = getPositionAtWordBounds(view.state.doc, pos); + // Check if this position is on a reactive variable + const isReactiveVariable = + view.state + .field(reactiveReferencesField, false) + ?.ranges.some((range) => pos >= range.from && pos <= range.to) ?? false; + const result = await AUTOCOMPLETER.request({ document: view.state.doc.slice(0, endToken).toString(), // convert Text to string cellId: cellId, @@ -60,6 +67,7 @@ async function requestDocumentation( message: result, exactName: fullWord, excludeTypes: excludeTypes, + showGoToDefinitionHint: isReactiveVariable, }); return tooltip ?? null; } diff --git a/frontend/src/core/codemirror/go-to-definition/commands.ts b/frontend/src/core/codemirror/go-to-definition/commands.ts index 878ede847db..dd4eff74a8e 100644 --- a/frontend/src/core/codemirror/go-to-definition/commands.ts +++ b/frontend/src/core/codemirror/go-to-definition/commands.ts @@ -44,6 +44,14 @@ export function goToVariableDefinition( return false; } // Stop traversal if found + // Skip function/lambda bodies entirely + if ( + node.name === "LambdaExpression" || + node.name === "FunctionDefinition" + ) { + return false; + } + // Check if the node is an identifier and matches the variable name if ( node.name === "VariableName" && diff --git a/frontend/src/core/codemirror/go-to-definition/underline.ts b/frontend/src/core/codemirror/go-to-definition/underline.ts index 73d348d4ccd..bc7cfd9b35d 100644 --- a/frontend/src/core/codemirror/go-to-definition/underline.ts +++ b/frontend/src/core/codemirror/go-to-definition/underline.ts @@ -10,12 +10,17 @@ import { type ViewUpdate, } from "@codemirror/view"; import type { TreeCursor } from "@lezer/common"; +import { + reactiveHoverDecoration, + reactiveReferencesField, +} from "../reactive-references/extension"; -// Decoration +// Decorations const underlineDecoration = Decoration.mark({ class: "underline" }); // State Effects const addUnderline = StateEffect.define<{ from: number; to: number }>(); +const addReactiveHover = StateEffect.define<{ from: number; to: number }>(); const removeUnderlines = StateEffect.define(); // Underline Field @@ -30,6 +35,12 @@ export const underlineField = StateField.define({ newUnderlines = underlines.update({ add: [underlineDecoration.range(effect.value.from, effect.value.to)], }); + } else if (effect.is(addReactiveHover)) { + newUnderlines = underlines.update({ + add: [ + reactiveHoverDecoration.range(effect.value.from, effect.value.to), + ], + }); } else if (effect.is(removeUnderlines)) { newUnderlines = Decoration.none; } @@ -61,7 +72,7 @@ class MetaUnderlineVariablePlugin { window.addEventListener("mouseleave", this.windowBlur); } - update(update: ViewUpdate) { + update(_update: ViewUpdate) { // We cannot add any transactions here (e.g. clearing underlines), // otherwise CM fails with // "Calls to EditorView.update are not allowed while an update is in progress" @@ -129,6 +140,32 @@ class MetaUnderlineVariablePlugin { return; } + // First, check if this position is a reactive variable (high-confidence navigable) + // Use cached analysis from reactive variables StateField for fast lookup + const reactiveState = this.view.state.field(reactiveReferencesField, false); + const reactiveRange = reactiveState?.ranges.find( + (range) => pos >= range.from && pos <= range.to, + ); + + if (reactiveRange) { + // This is a reactive variable - add subtle hover enhancement + const { from, to } = reactiveRange; + if ( + this.hoveredRange && + this.hoveredRange.from === from && + this.hoveredRange.to === to + ) { + return; + } + // Clear existing decorations + this.clearUnderline(); + // Add subtle hover enhancement for reactive variables + this.hoveredRange = { from, to, position: pos }; + this.view.dispatch({ effects: addReactiveHover.of(this.hoveredRange) }); + return; + } + + // Fallback: Use existing basic AST check for other variables const tree = syntaxTree(this.view.state); const cursor: TreeCursor = tree.cursorAt(pos); diff --git a/frontend/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts b/frontend/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts new file mode 100644 index 00000000000..f159785d055 --- /dev/null +++ b/frontend/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts @@ -0,0 +1,1067 @@ +/* Copyright 2024 Marimo. All rights reserved. */ + +import { python } from "@codemirror/lang-python"; +import { EditorState } from "@codemirror/state"; +import { describe, expect, test } from "vitest"; +import type { CellId } from "@/core/cells/ids"; +import type { VariableName, Variables } from "@/core/variables/types"; +import { findReactiveVariables, type ReactiveVariableRange } from "../analyzer"; + +describe("findReactiveVariables - Lexical Scoping", () => { + test("should highlight global variable but not function parameter", () => { + expect( + runHighlight( + ["a", "b"], + ` +def foo(a): + return a + b +`, + ), + ).toMatchInlineSnapshot(` + " + def foo(a): + return a + b + ^ + " + `); + }); + + test("should highlight both variables when no shadowing", () => { + expect( + runHighlight( + ["a", "b"], + ` +def foo(x): + return a + b +`, + ), + ).toMatchInlineSnapshot(` + " + def foo(x): + return a + b + ^ ^ + " + `); + }); + + test("should handle lambda parameters", () => { + expect( + runHighlight(["a", "b"], "result = lambda a: a + b"), + ).toMatchInlineSnapshot(` + " + result = lambda a: a + b + ^ + " + `); + }); + + test("should handle comprehension variables", () => { + expect( + runHighlight(["a", "data"], "result = [a for a in data]"), + ).toMatchInlineSnapshot(` + " + result = [a for a in data] + ^^^^ + " + `); + }); + + test("should handle nested scopes", () => { + expect( + runHighlight( + ["a", "b", "c"], + ` +def outer(a): + def inner(b): + return a + b + c + return inner +`, + ), + ).toMatchInlineSnapshot(` + " + def outer(a): + def inner(b): + return a + b + c + ^ + return inner + " + `); + }); + + test("should return no highlight for syntax errors", () => { + expect( + runHighlight( + ["a", "b"], + ` +def foo(a: + return a + b +`, + ), + ).toMatchInlineSnapshot(` + " + def foo(a: + return a + b + " + `); + }); + + test("should handle multiple parameters", () => { + expect( + runHighlight( + ["a", "b", "c", "d"], + ` +def foo(a, b, c): + return a + b + c + d +`, + ), + ).toMatchInlineSnapshot(` + " + def foo(a, b, c): + return a + b + c + d + ^ + " + `); + }); + + test("should handle recursive function", () => { + expect( + runHighlight( + ["n", "base"], + ` +def factorial(n): + if n <= 1: + return base + return n * factorial(n - 1) +`, + ), + ).toMatchInlineSnapshot(` + " + def factorial(n): + if n <= 1: + return base + ^^^^ + return n * factorial(n - 1) + " + `); + }); + + test("function param vs global", () => { + expect( + runHighlight(["a", "b"], "def foo(a): return a + b"), + ).toMatchInlineSnapshot(` + " + def foo(a): return a + b + ^ + " + `); + }); + + test("lambda param vs global", () => { + expect( + runHighlight(["x", "b"], "func = lambda x: x + b"), + ).toMatchInlineSnapshot(` + " + func = lambda x: x + b + ^ + " + `); + }); + + test("lambda with multiple params", () => { + expect( + runHighlight(["x", "y", "z"], "f = lambda x, y: x + y + z"), + ).toMatchInlineSnapshot(` + " + f = lambda x, y: x + y + z + ^ + " + `); + }); + + test("comprehension shadows global", () => { + expect(runHighlight(["a"], "[a for a in range(5)]")).toMatchInlineSnapshot(` + " + [a for a in range(5)] + " + `); + }); + + test("nested comprehension", () => { + expect( + runHighlight(["a", "b"], "[(a + b) for a, b in [(1,2), (3,4)]]"), + ).toMatchInlineSnapshot(` + " + [(a + b) for a, b in [(1,2), (3,4)]] + " + `); + }); + + test("dict comprehension using global", () => { + expect( + runHighlight( + ["k", "v", "offset"], + `{k: v + offset for k, v in [("a", 1), ("b", 2)]}`, + ), + ).toMatchInlineSnapshot(` + " + {k: v + offset for k, v in [("a", 1), ("b", 2)]} + ^^^^^^ + " + `); + }); + + test("generator expression", () => { + expect( + runHighlight( + ["x", "threshold", "global_list"], + "(x + threshold for x in global_list)", + ), + ).toMatchInlineSnapshot(` + " + (x + threshold for x in global_list) + ^^^^^^^^^ ^^^^^^^^^^^ + " + `); + }); + + test("class body using globals", () => { + expect( + runHighlight(["a", "b"], "class MyClass:\n value = a + b"), + ).toMatchInlineSnapshot(` + " + class MyClass: + value = a + b + ^ ^ + " + `); + }); + + test("decorator using global", () => { + expect( + runHighlight(["logger"], "@logger\ndef decorated(): pass"), + ).toMatchInlineSnapshot(` + " + @logger + ^^^^^^ + def decorated(): pass + " + `); + }); + + test("try/except block using global", () => { + expect( + runHighlight( + ["e", "logger"], + `try:\n 1 / 0\nexcept Exception as e:\n print("Error", logger)`, + ), + ).toMatchInlineSnapshot(` + " + try: + 1 / 0 + except Exception as e: + print("Error", logger) + ^^^^^^ + " + `); + }); + + test("with statement using global", () => { + expect( + runHighlight(["path", "f"], "with open(path) as f:\n print(f.read())"), + ).toMatchInlineSnapshot(` + " + with open(path) as f: + ^^^^ + print(f.read()) + " + `); + }); + + test("function reusing global name as param", () => { + expect( + runHighlight(["logger"], "def shadow_logger(logger): return logger + 1"), + ).toMatchInlineSnapshot(` + " + def shadow_logger(logger): return logger + 1 + " + `); + }); + + test("shadowed global in inner function", () => { + expect( + runHighlight( + ["a", "b", "x", "z"], + ` +def outer(): + z = 10 + x = 20 + def inner(): + a = 2 + return a + b + z # highlight: b + return inner() + `, + ), + ).toMatchInlineSnapshot(` + " + def outer(): + z = 10 + x = 20 + def inner(): + a = 2 + return a + b + z # highlight: b + ^ + return inner() + + " + `); + }); + + test("multiple assignment", () => { + expect( + runHighlight(["x", "y", "z", "a"], "x = y = z + a"), + ).toMatchInlineSnapshot(` + " + x = y = z + a + ^ ^ + " + `); + }); + + test("nested lambdas", () => { + expect( + runHighlight( + ["x", "y", "b"], + "nested_lambda = lambda x: (lambda y: x + y + b)", + ), + ).toMatchInlineSnapshot(` + " + nested_lambda = lambda x: (lambda y: x + y + b) + ^ + " + `); + }); + + test("function using builtin and global", () => { + expect( + runHighlight(["offset", "len"], "def use_len(x): return len(x) + offset"), + ).toMatchInlineSnapshot(` + " + def use_len(x): return len(x) + offset + ^^^ ^^^^^^ + " + `); + }); + + test("global in return", () => { + expect( + runHighlight(["config"], "def get_config(): return config"), + ).toMatchInlineSnapshot(` + " + def get_config(): return config + ^^^^^^ + " + `); + }); + + test("shadowed global in inner function 2", () => { + expect( + runHighlight( + ["a", "b", "x", "z"], + ` +def outer2(): + z = 10 + x = 20 + def inner(): + a = 2 + return a + b + z # b should be highlighted + return inner() +`, + ), + ).toMatchInlineSnapshot(` + " + def outer2(): + z = 10 + x = 20 + def inner(): + a = 2 + return a + b + z # b should be highlighted + ^ + return inner() + " + `); + }); + + test("global used inside class", () => { + expect( + runHighlight( + ["config"], + ` +class Configurable: + value = config +`, + ), + ).toMatchInlineSnapshot(` + " + class Configurable: + value = config + ^^^^^^ + " + `); + }); + + test("comprehension shadows global 2", () => { + expect( + runHighlight(["i"], "squares = [i**2 for i in range(10)]"), + ).toMatchInlineSnapshot(` + " + squares = [i**2 for i in range(10)] + " + `); + }); + + test("comprehension with global in condition", () => { + expect( + runHighlight(["x", "z"], "filtered = [x for x in [] if x > z]"), + ).toMatchInlineSnapshot(` + " + filtered = [x for x in [] if x > z] + ^ + " + `); + }); + + test("dict comprehension with local destructuring", () => { + expect( + runHighlight(["k", "v"], `kv_map = {k: v for (k, v) in [("a", "b")]}`), + ).toMatchInlineSnapshot(` + " + kv_map = {k: v for (k, v) in [("a", "b")]} + " + `); + }); + + test("lambda inside function accessing global", () => { + expect( + runHighlight( + ["x", "g"], + ` +def make_adder(x): + return lambda y: x + y + g +`, + ), + ).toMatchInlineSnapshot(` + " + def make_adder(x): + return lambda y: x + y + g + ^ + " + `); + }); + + test("rebinding in list comprehension", () => { + expect( + runHighlight(["x"], "rebinding = [x for x in range(5)]"), + ).toMatchInlineSnapshot(` + " + rebinding = [x for x in range(5)] + " + `); + }); + + test("global used inside nested def in comprehension", () => { + expect( + runHighlight( + ["x", "z"], + "nested_comp = [lambda: x + z for x in range(5)]", + ), + ).toMatchInlineSnapshot(` + " + nested_comp = [lambda: x + z for x in range(5)] + ^ + " + `); + }); + + test("async function using global", () => { + expect( + runHighlight( + ["client", "x"], + ` +async def fetch(): + return await client.get(x) +`, + ), + ).toMatchInlineSnapshot(` + " + async def fetch(): + return await client.get(x) + ^^^^^^ ^ + " + `); + }); + + test("class method using global", () => { + expect( + runHighlight( + ["x"], + ` +class Thing: + def compute(self): + return self.factor * x +`, + ), + ).toMatchInlineSnapshot(` + " + class Thing: + def compute(self): + return self.factor * x + ^ + " + `); + }); + + test("function with multiple local scopes", () => { + expect( + runHighlight( + ["external", "y", "x"], + ` +def complex(x): + if x > 0: + y = x * 2 + else: + y = external + return y +`, + ), + ).toMatchInlineSnapshot(` + " + def complex(x): + if x > 0: + y = x * 2 + else: + y = external + ^^^^^^^^ + return y + " + `); + }); + + test("nested import with alias", () => { + expect( + runHighlight( + ["mo", "x", "polars"], + ` +def nested_import(): + import marimo as mo + import polars + print(mo, x, polars)`, + ), + ).toMatchInlineSnapshot(` + " + def nested_import(): + import marimo as mo + import polars + print(mo, x, polars) + ^ + " + `); + }); + + test("from-import with shadowing", () => { + expect( + runHighlight( + ["my_sin", "x", "my_func"], + ` +def inner(): + from math import sin as my_sin + return my_sin(x) + my_func(x)`, + ), + ).toMatchInlineSnapshot(` + " + def inner(): + from math import sin as my_sin + return my_sin(x) + my_func(x) + ^ ^^^^^^^ ^ + " + `); + }); + + test("shadowed global via assignment", () => { + expect( + runHighlight( + ["polars", "x"], + ` +def myfunc(): + polars = "not a module" + return x + polars`, + ), + ).toMatchInlineSnapshot(` + " + def myfunc(): + polars = "not a module" + return x + polars + ^ + " + `); + }); + + test("multiple imports and local names", () => { + expect( + runHighlight( + ["np", "pd", "x", "y"], + ` +def analyze(): + import numpy as np + import pandas as pd + result = x + y + np.array([1, 2])`, + ), + ).toMatchInlineSnapshot(` + " + def analyze(): + import numpy as np + import pandas as pd + result = x + y + np.array([1, 2]) + ^ ^ + " + `); + }); + + test("import shadowed by parameter", () => { + expect( + runHighlight( + ["polars", "x"], + ` +def run(polars): + return polars + x`, + ), + ).toMatchInlineSnapshot(` + " + def run(polars): + return polars + x + ^ + " + `); + }); + + test("mixed comprehension and outer globals", () => { + expect( + runHighlight(["y", "z"], "values = [y + z for y in range(5)]"), + ).toMatchInlineSnapshot(` + " + values = [y + z for y in range(5)] + ^ + " + `); + }); + + test("lambda inside function with outer global", () => { + expect( + runHighlight( + ["x", "a", "offset"], + ` +def wrapper(): + return lambda x: x + a + offset`, + ), + ).toMatchInlineSnapshot(` + " + def wrapper(): + return lambda x: x + a + offset + ^ ^^^^^^ + " + `); + }); + + test("with statement inside function with global", () => { + expect( + runHighlight( + ["path", "a"], + ` +def func(): + with open(path) as a: + print(a.read())`, + ), + ).toMatchInlineSnapshot(` + " + def func(): + with open(path) as a: + ^^^^ + print(a.read()) + " + `); + }); + + test("redeclaration at top-level", () => { + // These variables are redeclared locally, so they should not be highlighted + // even though marimo may reject the redeclaration at runtime. + expect( + runHighlight( + ["a", "b", "f", "i", "y"], + ` +a = 10 +b = 20 + +with open("/test.txt") as f: + print(f.read()) + +for i in range(10): + print(i) + +print(y)`, + ), + ).toMatchInlineSnapshot(` + " + a = 10 + b = 20 + + with open("/test.txt") as f: + print(f.read()) + + for i in range(10): + print(i) + + print(y) + ^ + " + `); + }); + + test("shadowed global method in inner function", () => { + // These variables are redeclared locally, so they should not be highlighted + // even though marimo may reject the redeclaration at runtime. + expect( + runHighlight( + ["z", "x", "a", "b", "inner"], + ` +def outer2(): + z = 10 + x = 20 + + def inner(): + a = 2 + return a + b + z # b should be highlighted + + return inner() +`, + ), + ).toMatchInlineSnapshot(` + " + def outer2(): + z = 10 + x = 20 + + def inner(): + a = 2 + return a + b + z # b should be highlighted + ^ + + return inner() + " + `); + }); + + // Pathological test cases for edge cases + test("global statement overrides local scoping", () => { + expect( + runHighlight( + ["x", "y"], + ` +def outer(): + x = 1 + def inner(): + global x # This refers to global x, not outer's x + return x + y + return inner() +`, + ), + ).toMatchInlineSnapshot(` + " + def outer(): + x = 1 + def inner(): + global x # This refers to global x, not outer's x + return x + y + ^ + return inner() + " + `); + }); + + test("nonlocal statement accesses enclosing scope", () => { + expect( + runHighlight( + ["z", "global_var"], + ` +def outer(): + z = 10 + def inner(): + nonlocal z # This refers to outer's z, not global z + return z + global_var + return inner() +`, + ), + ).toMatchInlineSnapshot(` + " + def outer(): + z = 10 + def inner(): + nonlocal z # This refers to outer's z, not global z + return z + global_var + ^^^^^^^^^^ + return inner() + " + `); + }); + + test("star unpacking in assignment", () => { + expect( + runHighlight( + ["values", "a", "b", "c"], + ` +def func(): + a, *b, c = values + return a + len(b) + c +`, + ), + ).toMatchInlineSnapshot(` + " + def func(): + a, *b, c = values + ^^^^^^ + return a + len(b) + c + " + `); + }); + + test("nested tuple unpacking", () => { + expect( + runHighlight( + ["nested_data", "x", "y", "z"], + ` +def func(): + (x, (y, z)) = nested_data + return x + y + z +`, + ), + ).toMatchInlineSnapshot(` + " + def func(): + (x, (y, z)) = nested_data + ^^^^^^^^^^^ + return x + y + z + " + `); + }); + + test("walrus operator in comprehension", () => { + expect( + runHighlight( + ["data", "threshold", "process"], + ` +result = [y for x in data if (y := process(x)) > threshold] +`, + ), + ).toMatchInlineSnapshot(` + " + result = [y for x in data if (y := process(x)) > threshold] + ^^^^ ^^^^^^^ ^^^^^^^^^ + " + `); + }); + + test("exception variable scoping", () => { + expect( + runHighlight( + ["e", "logger", "risky_operation"], + ` +def func(): + try: + risky_operation() + except Exception as e: # e is local to except block + return str(e) + logger +`, + ), + ).toMatchInlineSnapshot(` + " + def func(): + try: + risky_operation() + ^^^^^^^^^^^^^^^ + except Exception as e: # e is local to except block + return str(e) + logger + ^^^^^^ + " + `); + }); + + test("star import potential shadowing", () => { + expect( + runHighlight( + ["x", "y", "unknown_func"], + ` +def func(): + from math import * # Could import anything, including x + return x + y + unknown_func() +`, + ), + ).toMatchInlineSnapshot(` + " + def func(): + from math import * # Could import anything, including x + return x + y + unknown_func() + ^ ^ ^^^^^^^^^^^^ + " + `); + }); + + test("class variable vs instance variable", () => { + expect( + runHighlight( + ["class_global", "instance_global"], + ` +class MyClass: + class_var = class_global # Should highlight class_global + + def __init__(self): + self.instance_var = instance_global # Should highlight instance_global + + def method(self): + return self.class_var + self.instance_var +`, + ), + ).toMatchInlineSnapshot(` + " + class MyClass: + class_var = class_global # Should highlight class_global + ^^^^^^^^^^^^ + + def __init__(self): + self.instance_var = instance_global # Should highlight instance_global + ^^^^^^^^^^^^^^^ + + def method(self): + return self.class_var + self.instance_var + " + `); + }); + + test("nested class with outer scope access", () => { + expect( + runHighlight( + ["outer_var", "global_var"], + ` +def outer_func(): + outer_var = 1 + + class InnerClass: + # Classes can't access enclosing function scope directly + value = global_var # Should highlight global_var, not outer_var + value2 = outer_var + + def method(self): + return self.value + global_var + + return InnerClass() +`, + ), + ).toMatchInlineSnapshot(` + " + def outer_func(): + outer_var = 1 + + class InnerClass: + # Classes can't access enclosing function scope directly + value = global_var # Should highlight global_var, not outer_var + ^^^^^^^^^^ + value2 = outer_var + + def method(self): + return self.value + global_var + ^^^^^^^^^^ + + return InnerClass() + " + `); + }); +}); + +/** + * Convenient helper for testing reactive variable highlighting + */ +function runHighlight(variableNames: string[], code: string): string { + const variables: Variables = {}; + for (const name of variableNames) { + variables[name as VariableName] = { + name: name as VariableName, + declaredBy: ["other-cell" as CellId], + usedBy: [], + value: "test-value", + dataType: "str", + }; + } + const ranges = findReactiveVariables({ + cellId: "current-cell" as CellId, + state: EditorState.create({ + doc: code, + extensions: [python()], + }), + variables, + }); + return formatCodeWithHighlights(code, ranges); +} + +/** + * Helper function to format code with highlighted ranges for snapshot testing + * Simple format with carets pointing to reactive variables + */ +function formatCodeWithHighlights( + code: string, + ranges: ReactiveVariableRange[], +): string { + const lines = code.split("\n"); + const result: string[] = []; + + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex]; + + // Start position of this line in the original code + const lineStart = + lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0); + const lineEnd = lineStart + line.length; + + // Intersecting ranges + const lineRanges = ranges.filter( + (range) => range.from < lineEnd && range.to > lineStart, + ); + + result.push(line); + + if (lineRanges.length > 0) { + // Create underline + const underline = Array.from({ length: line.length }).fill(" "); + + for (const range of lineRanges) { + const startInLine = Math.max(0, range.from - lineStart); + const endInLine = Math.min(line.length, range.to - lineStart); + + for (let i = startInLine; i < endInLine; i++) { + underline[i] = "^"; + } + } + + // Add the underline if it has any markers + if (underline.includes("^")) { + result.push(underline.join("").trimEnd()); + } + } + } + + // Ensure result starts and ends with empty lines for nicer snapshot view + const out = [...result]; + if (out[0] !== "") { + out.unshift(""); + } + if (out[out.length - 1] !== "") { + out.push(""); + } + return out.join("\n"); +} diff --git a/frontend/src/core/codemirror/reactive-references/analyzer.ts b/frontend/src/core/codemirror/reactive-references/analyzer.ts new file mode 100644 index 00000000000..81168b99232 --- /dev/null +++ b/frontend/src/core/codemirror/reactive-references/analyzer.ts @@ -0,0 +1,498 @@ +/* Copyright 2024 Marimo. All rights reserved. */ + +import { syntaxTree } from "@codemirror/language"; +import type { EditorState } from "@codemirror/state"; +import type { SyntaxNode, Tree, TreeCursor } from "@lezer/common"; +import type { CellId } from "@/core/cells/ids"; +import type { VariableName, Variables } from "@/core/variables/types"; + +export interface ReactiveVariableRange { + from: number; + to: number; + variableName: string; +} + +/** + * Analyzes the given editor state to find variable names that represent + * reactive dependencies from other cells (similar to ObservableHQ's approach). + */ +export function findReactiveVariables(options: { + state: EditorState; + cellId: CellId; + variables: Variables; +}): ReactiveVariableRange[] { + const tree = syntaxTree(options.state); + + if (!tree) { + // No AST available yet - this can happen during initial editor setup + // or when the language parser hasn't processed the code + return []; + } + + if (hasSyntaxErrors(tree)) { + return []; + } + + // Collect variable names that are: + // - Not from in the current cell + // - Not from a "setup" cell + // - Not of type "module" + const allVariableNames = new Set( + Object.keys(options.variables).filter((name) => { + const variable = options.variables[name as VariableName]; + return ( + variable.dataType !== "module" && + !variable.declaredBy.includes("setup" as CellId) && + !variable.declaredBy.includes(options.cellId) + ); + }), + ); + + if (allVariableNames.size === 0) { + return []; + } + + const ranges: ReactiveVariableRange[] = []; + + // Map from node position to declared variables in that scope + const allDeclarations = new Map>(); + // Map from node position to scope type + const scopeTypes = new Map(); + + // First pass: all variable declarations in their respective scopes + function collectDeclarations(node: SyntaxNode | Tree, scopeStack: number[]) { + const cursor = node.cursor(); + const nodeName = cursor.name; + const nodeStart = cursor.from; + + const isNewScope = [ + "FunctionDefinition", + "LambdaExpression", + "ArrayComprehensionExpression", + "SetComprehension", + "DictionaryComprehensionExpression", + "ComprehensionExpression", + "ClassDefinition", + ].includes(nodeName); + + let currentScopeStack = scopeStack; + if (isNewScope) { + currentScopeStack = [...scopeStack, nodeStart]; + allDeclarations.set(nodeStart, new Set()); + scopeTypes.set(nodeStart, nodeName); + } + + switch (nodeName) { + case "FunctionDefinition": { + const subCursor = node.cursor(); + subCursor.firstChild(); + do { + if (subCursor.name === "VariableName") { + const functionName = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + // Add function name to the parent scope (not the function's own scope) + const parentScope = scopeStack[scopeStack.length - 1] ?? -1; + if (!allDeclarations.has(parentScope)) { + allDeclarations.set(parentScope, new Set()); + } + allDeclarations.get(parentScope)?.add(functionName); + break; // Function name is the first VariableName, so we can break here + } + } while (subCursor.nextSibling()); + + // Function params + const paramCursor = node.cursor(); + paramCursor.firstChild(); + do { + if (paramCursor.name === "ParamList") { + const paramListCursor = paramCursor.node.cursor(); + paramListCursor.firstChild(); + do { + if (paramListCursor.name === "VariableName") { + const paramName = options.state.doc.sliceString( + paramListCursor.from, + paramListCursor.to, + ); + allDeclarations.get(nodeStart)?.add(paramName); + } + } while (paramListCursor.nextSibling()); + } + } while (paramCursor.nextSibling()); + + break; + } + case "LambdaExpression": { + // Lambda params + const subCursor = node.cursor(); + subCursor.firstChild(); + do { + if (subCursor.name === "ParamList") { + const paramCursor = subCursor.node.cursor(); + paramCursor.firstChild(); + do { + if (paramCursor.name === "VariableName") { + const paramName = options.state.doc.sliceString( + paramCursor.from, + paramCursor.to, + ); + allDeclarations.get(nodeStart)?.add(paramName); + } + } while (paramCursor.nextSibling()); + } + } while (subCursor.nextSibling()); + + break; + } + case "ArrayComprehensionExpression": + case "DictionaryComprehensionExpression": + case "SetComprehension": + case "ComprehensionExpression": { + // Domprehension variables - look for VariableName or TupleExpression after 'for' + const subCursor = node.cursor(); + subCursor.firstChild(); + let foundFor = false; + do { + if (subCursor.name === "for") { + foundFor = true; + } else if (foundFor && subCursor.name === "VariableName") { + const varName = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + allDeclarations.get(nodeStart)?.add(varName); + } else if (foundFor && subCursor.name === "TupleExpression") { + // Handle tuple destructuring like (k, v) + const tupleCursor = subCursor.node.cursor(); + tupleCursor.firstChild(); + do { + if (tupleCursor.name === "VariableName") { + const varName = options.state.doc.sliceString( + tupleCursor.from, + tupleCursor.to, + ); + allDeclarations.get(nodeStart)?.add(varName); + } + } while (tupleCursor.nextSibling()); + } else if (foundFor && subCursor.name === "in") { + foundFor = false; // Stop collecting variables after 'in' + } + } while (subCursor.nextSibling()); + + break; + } + case "ClassDefinition": { + const subCursor = node.cursor(); + subCursor.firstChild(); + do { + if (subCursor.name === "VariableName") { + const className = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + // Add class name to the parent scope (not the class's own scope) + const parentScope = scopeStack[scopeStack.length - 1] ?? -1; + if (!allDeclarations.has(parentScope)) { + allDeclarations.set(parentScope, new Set()); + } + allDeclarations.get(parentScope)?.add(className); + break; // Class name is the first VariableName, so we can break here + } + } while (subCursor.nextSibling()); + + break; + } + case "AssignStatement": { + // Assignments - capture all variables being assigned to (variables that come before the last AssignOp) + const subCursor = node.cursor(); + + // First pass: all AssignOp positions to know where assignment targets end + const assignOpPositions: number[] = []; + subCursor.firstChild(); + do { + if (subCursor.name === "AssignOp") { + assignOpPositions.push(subCursor.from); + } + } while (subCursor.nextSibling()); + + // Second pass: all VariableNames and TupleExpressions that come before the last AssignOp + const lastAssignOpPosition = + assignOpPositions[assignOpPositions.length - 1]; + + const secondPassCursor = node.cursor(); + secondPassCursor.firstChild(); + const currentScope = + currentScopeStack[currentScopeStack.length - 1] ?? -1; + + do { + if (secondPassCursor.from < lastAssignOpPosition) { + extractAssignmentTargets(secondPassCursor, { + currentScope, + state: options.state, + allDeclarations, + scopeTypes, + }); + } + } while (secondPassCursor.nextSibling()); + + break; + } + case "ForStatement": { + // For loop variables + const subCursor = node.cursor(); + subCursor.firstChild(); + let foundFor = false; + do { + if (subCursor.name === "for") { + foundFor = true; + } else if (foundFor && subCursor.name === "VariableName") { + const varName = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + // Add to the current innermost scope (or global if no scopes) + const currentScope = + currentScopeStack[currentScopeStack.length - 1] ?? -1; + if (!allDeclarations.has(currentScope)) { + allDeclarations.set(currentScope, new Set()); + } + allDeclarations.get(currentScope)?.add(varName); + } else if (foundFor && subCursor.name === "in") { + foundFor = false; // Stop collecting variables after 'in' + } + } while (subCursor.nextSibling()); + + break; + } + case "ImportStatement": { + // Handle import x + const subCursor = node.cursor(); + subCursor.firstChild(); + do { + if (subCursor.name === "VariableName") { + const varName = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + + const currentScope = + currentScopeStack[currentScopeStack.length - 1] ?? -1; + if (!allDeclarations.has(currentScope)) { + allDeclarations.set(currentScope, new Set()); + } + allDeclarations.get(currentScope)?.add(varName); + } + } while (subCursor.nextSibling()); + + break; + } + case "ImportFromStatement": { + // Handle from x import y as z + const subCursor = node.cursor(); + subCursor.firstChild(); + let foundImport = false; + do { + if (subCursor.name === "import") { + foundImport = true; + } else if (foundImport && subCursor.name === "VariableName") { + const varName = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + // Add to the current innermost scope + const currentScope = + currentScopeStack[currentScopeStack.length - 1] ?? -1; + if (!allDeclarations.has(currentScope)) { + allDeclarations.set(currentScope, new Set()); + } + allDeclarations.get(currentScope)?.add(varName); + } + } while (subCursor.nextSibling()); + + break; + } + case "TryStatement": { + // Exception variable binding - look for 'as' followed by VariableName + const subCursor = node.cursor(); + subCursor.firstChild(); + let foundAs = false; + do { + if (subCursor.name === "as") { + foundAs = true; + } else if (foundAs && subCursor.name === "VariableName") { + const varName = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + const currentScope = + currentScopeStack[currentScopeStack.length - 1] ?? -1; + if (!allDeclarations.has(currentScope)) { + allDeclarations.set(currentScope, new Set()); + } + allDeclarations.get(currentScope)?.add(varName); + foundAs = false; + } + } while (subCursor.nextSibling()); + + break; + } + case "WithStatement": { + const subCursor = node.cursor(); + subCursor.firstChild(); + let foundAs = false; + do { + if (subCursor.name === "as") { + foundAs = true; + } else if (foundAs && subCursor.name === "VariableName") { + const varName = options.state.doc.sliceString( + subCursor.from, + subCursor.to, + ); + const currentScope = + currentScopeStack[currentScopeStack.length - 1] ?? -1; + if (!allDeclarations.has(currentScope)) { + allDeclarations.set(currentScope, new Set()); + } + allDeclarations.get(currentScope)?.add(varName); + foundAs = false; + } + } while (subCursor.nextSibling()); + + break; + } + // No default + } + + if (cursor.firstChild()) { + do { + collectDeclarations(cursor.node, currentScopeStack); + } while (cursor.nextSibling()); + } + } + + // Second pass: find variable usages and check if they should be highlighted + function findUsages(node: SyntaxNode | Tree, scopeStack: number[]) { + const cursor = node.cursor(); + const nodeName = cursor.name; + const nodeStart = cursor.from; + + const isNewScope = [ + "FunctionDefinition", + "LambdaExpression", + "ArrayComprehensionExpression", + "SetComprehension", + "DictionaryComprehensionExpression", + "ComprehensionExpression", + "ClassDefinition", + ].includes(nodeName); + + let currentScopeStack = scopeStack; + if (isNewScope) { + currentScopeStack = [...scopeStack, nodeStart]; + } + + if (nodeName === "VariableName") { + const varName = options.state.doc.sliceString(cursor.from, cursor.to); + + if (allVariableNames.has(varName)) { + let isDeclaredLocally = false; + for (const scope of currentScopeStack) { + if (allDeclarations.get(scope)?.has(varName)) { + isDeclaredLocally = true; + break; + } + } + if (allDeclarations.get(-1)?.has(varName)) { + isDeclaredLocally = true; + } + + if (!isDeclaredLocally) { + ranges.push({ + from: cursor.from, + to: cursor.to, + variableName: varName, + }); + } + } + } + + if (cursor.firstChild()) { + do { + findUsages(cursor.node, currentScopeStack); + } while (cursor.nextSibling()); + } + } + + collectDeclarations(tree, []); + findUsages(tree, []); + + return ranges; +} + +/** + * Checks if the syntax tree contains any syntax errors. + * If there are errors, we shouldn't show reactive variable highlighting. + */ +function hasSyntaxErrors(tree: Tree): boolean { + const cursor = tree.cursor(); + do { + // Lezer uses "⚠" as the error node name for syntax errors + if (cursor.name === "⚠" || cursor.type.isError) { + return true; + } + } while (cursor.next()); + return false; +} + +/** + * Helper function to extract variable names from assignment targets (including tuples) + */ +function extractAssignmentTargets( + cursor: TreeCursor, + options: { + currentScope: number; + state: EditorState; + allDeclarations: Map>; + scopeTypes: Map; + }, +) { + switch (cursor.name) { + case "VariableName": { + const varName = options.state.doc.sliceString(cursor.from, cursor.to); + const isInClassScope = + options.currentScope !== -1 && + options.scopeTypes.get(options.currentScope) === "ClassDefinition"; + + if (!isInClassScope) { + if (!options.allDeclarations.has(options.currentScope)) { + options.allDeclarations.set(options.currentScope, new Set()); + } + options.allDeclarations.get(options.currentScope)?.add(varName); + } + + break; + } + case "TupleExpression": { + // Handle tuple unpacking like (x, (y, z)) = ... + const tupleCursor = cursor.node.cursor(); + tupleCursor.firstChild(); + do { + extractAssignmentTargets(tupleCursor, options); + } while (tupleCursor.nextSibling()); + + break; + } + case "ArrayExpression": { + // Handle list unpacking like [a, b, c] = ... + const arrayCursor = cursor.node.cursor(); + arrayCursor.firstChild(); + do { + extractAssignmentTargets(arrayCursor, options); + } while (arrayCursor.nextSibling()); + + break; + } + // No default + } +} diff --git a/frontend/src/core/codemirror/reactive-references/extension.ts b/frontend/src/core/codemirror/reactive-references/extension.ts new file mode 100644 index 00000000000..ee0f22f702a --- /dev/null +++ b/frontend/src/core/codemirror/reactive-references/extension.ts @@ -0,0 +1,154 @@ +/* Copyright 2024 Marimo. All rights reserved. */ + +import { StateEffect, StateField } from "@codemirror/state"; +import { + Decoration, + type DecorationSet, + EditorView, + ViewPlugin, + type ViewUpdate, +} from "@codemirror/view"; +import { type DebouncedFunc, debounce } from "lodash-es"; + +import type { CellId } from "@/core/cells/ids"; +import { store } from "@/core/state/jotai"; +import { variablesAtom } from "@/core/variables/state"; +import { Logger } from "@/utils/Logger"; + +import { findReactiveVariables, type ReactiveVariableRange } from "./analyzer"; + +const reactiveVariableDecoration = Decoration.mark({ + class: "mo-cm-reactive-reference", +}); + +export const reactiveHoverDecoration = Decoration.mark({ + class: "mo-cm-reactive-reference-hover", +}); + +const updateReactiveVariables = StateEffect.define(); + +/** + * Enhanced state that stores both visual decorations and analysis ranges + * for efficient access by other extensions (e.g., goto definition) + */ +interface ReactiveReferencesState { + decorations: DecorationSet; + ranges: ReactiveVariableRange[]; +} + +/** + * Plugin that manages highlighting marimo's reactive variables + */ +class ReactiveReferencesPlugin { + private view: EditorView; + private cellId: CellId; + private variablesUnsubscribe: () => void; + + // Delay (in ms) before highlighting reactive variables after user changes or store updates + private readonly highlightDebounceMs = 300; + + // Debounced function to trigger highlighting + private readonly scheduleHighlighting: DebouncedFunc<() => void>; + + constructor(view: EditorView, cellId: CellId) { + this.view = view; + this.cellId = cellId; + + this.scheduleHighlighting = debounce(() => { + this.runHighlighting(); + }, this.highlightDebounceMs); + + // React to variable store changes + this.variablesUnsubscribe = store.sub(variablesAtom, () => { + this.scheduleHighlighting(); + }); + + // Initial run + this.scheduleHighlighting(); + } + + update(update: ViewUpdate) { + if (update.docChanged || update.focusChanged) { + this.scheduleHighlighting(); + } + } + + destroy() { + this.scheduleHighlighting.cancel(); + this.variablesUnsubscribe(); + } + + private runHighlighting() { + const ranges = findReactiveVariables({ + state: this.view.state, + cellId: this.cellId, + variables: store.get(variablesAtom), + }); + + if (ranges.length > 0) { + Logger.debug( + `Found ${ranges.length} reactive variables in cell ${this.cellId}`, + ); + } + + // Defer dispatch to avoid triggering during an editor update cycle + queueMicrotask(() => { + this.view.dispatch({ + effects: updateReactiveVariables.of(ranges), + }); + }); + } +} + +/** + * Creates the reactive variables extension + */ +/** + * StateField that stores both decorations and analysis ranges + */ +export const reactiveReferencesField = + StateField.define({ + create() { + return { + decorations: Decoration.none, + ranges: [], + }; + }, + update(state, tr) { + let newState = { + decorations: state.decorations.map(tr.changes), + ranges: state.ranges, + }; + + for (const effect of tr.effects) { + if (effect.is(updateReactiveVariables)) { + // Update both decorations and cached ranges + newState = { + decorations: Decoration.set( + effect.value.map((range) => + reactiveVariableDecoration.range(range.from, range.to), + ), + ), + ranges: effect.value, + }; + } + } + return newState; + }, + provide: (f) => + EditorView.decorations.from(f, (state) => state.decorations), + }); + +function reactiveReferencesExtension(cellId: CellId) { + return [ + reactiveReferencesField, + ViewPlugin.define((view) => new ReactiveReferencesPlugin(view, cellId)), + ]; +} + +/** + * Bundle function to conditionally include reactive references highlighting + */ +export function reactiveReferencesBundle(cellId: CellId, enabled: boolean) { + return enabled ? reactiveReferencesExtension(cellId) : []; +} diff --git a/frontend/src/core/codemirror/theme/dark.ts b/frontend/src/core/codemirror/theme/dark.ts index 3a8fdce6c88..4ad4fc6f70c 100644 --- a/frontend/src/core/codemirror/theme/dark.ts +++ b/frontend/src/core/codemirror/theme/dark.ts @@ -1,35 +1,50 @@ /* Copyright 2024 Marimo. All rights reserved. */ +import { EditorView } from "@codemirror/view"; import { tags as t } from "@lezer/highlight"; import { createTheme } from "thememirror"; -export const darkTheme = createTheme({ - variant: "dark", - settings: { - background: "#282c34", - foreground: "#abb2bf", - caret: "#528bff", - selection: "#3E4451", - lineHighlight: "#2c313c", - gutterBackground: "var(--color-background)", - gutterForeground: "var(--gray-10)", - }, - styles: [ - { tag: t.comment, color: "#5c6370" }, - { tag: t.variableName, color: "#abb2bf" }, - { tag: [t.string, t.special(t.brace)], color: "#98c379" }, - { tag: t.number, color: "#d19a66" }, - { tag: t.bool, color: "#d19a66" }, - { tag: t.null, color: "#d19a66" }, - { tag: t.keyword, color: "#c678dd", fontWeight: 500 }, - { tag: t.className, color: "#61afef" }, - { tag: t.definition(t.typeName), color: "#61afef" }, - { tag: t.typeName, color: "#56b6c2" }, - { tag: t.angleBracket, color: "#abb2bf" }, - { tag: t.tagName, color: "#e06c75" }, - { tag: t.attributeName, color: "#d19a66" }, - { tag: t.operator, color: "#56b6c2", fontWeight: 500 }, - { tag: [t.function(t.variableName)], color: "#61afef" }, - { tag: [t.propertyName], color: "#e5c07b" }, - ], -}); +export const darkTheme = [ + createTheme({ + variant: "dark", + settings: { + background: "#282c34", + foreground: "#abb2bf", + caret: "#528bff", + selection: "#3E4451", + lineHighlight: "#2c313c", + gutterBackground: "var(--color-background)", + gutterForeground: "var(--gray-10)", + }, + styles: [ + { tag: t.comment, color: "#5c6370" }, + { tag: t.variableName, color: "#abb2bf" }, + { tag: [t.string, t.special(t.brace)], color: "#98c379" }, + { tag: t.number, color: "#d19a66" }, + { tag: t.bool, color: "#d19a66" }, + { tag: t.null, color: "#d19a66" }, + { tag: t.keyword, color: "#c678dd", fontWeight: 500 }, + { tag: t.className, color: "#61afef" }, + { tag: t.definition(t.typeName), color: "#61afef" }, + { tag: t.typeName, color: "#56b6c2" }, + { tag: t.angleBracket, color: "#abb2bf" }, + { tag: t.tagName, color: "#e06c75" }, + { tag: t.attributeName, color: "#d19a66" }, + { tag: t.operator, color: "#56b6c2", fontWeight: 500 }, + { tag: [t.function(t.variableName)], color: "#61afef" }, + { tag: [t.propertyName], color: "#e5c07b" }, + ], + }), + EditorView.theme({ + ".mo-cm-reactive-reference": { + fontWeight: "400", + color: "#2a7aa5", + borderBottom: "2px solid #bad3de", + }, + ".mo-cm-reactive-reference-hover": { + cursor: "pointer", + borderBottomWidth: "3px", + borderBottomColor: "#4a90a5", + }, + }), +]; diff --git a/frontend/src/core/codemirror/theme/light.ts b/frontend/src/core/codemirror/theme/light.ts index 740d8b881d6..6ae02724365 100644 --- a/frontend/src/core/codemirror/theme/light.ts +++ b/frontend/src/core/codemirror/theme/light.ts @@ -1,44 +1,58 @@ /* Copyright 2024 Marimo. All rights reserved. */ +import { EditorView } from "@codemirror/view"; import { tags as t } from "@lezer/highlight"; import { createTheme } from "thememirror"; -export const lightTheme = createTheme({ - variant: "light", - settings: { - background: "#ffffff", - foreground: "#000000", - caret: "#000000", - selection: "#d7d4f0", - lineHighlight: "#cceeff44", - gutterBackground: "var(--color-background)", - gutterForeground: "var(--gray-10)", - }, - styles: [ - // Default codemirror light theme - { tag: t.comment, color: "#708090" }, - { tag: t.variableName, color: "#000000" }, - { tag: [t.string, t.special(t.brace)], color: "#a11" }, - { tag: t.number, color: "#164" }, - { tag: t.bool, color: "#219" }, - { tag: t.null, color: "#219" }, - { tag: t.keyword, color: "#708", fontWeight: 500 }, - // { tag: t.operator, color: '#000' }, - { tag: t.className, color: "#00f" }, - { tag: t.definition(t.typeName), color: "#00f" }, - { tag: t.typeName, color: "#085" }, - { tag: t.angleBracket, color: "#000000" }, - { tag: t.tagName, color: "#170" }, - { tag: t.attributeName, color: "#00c" }, - // Adjustments - { tag: t.operator, color: "#a2f", fontWeight: 500 }, - { - tag: [t.function(t.variableName)], - color: "#00c", +export const lightTheme = [ + createTheme({ + variant: "light", + settings: { + background: "#ffffff", + foreground: "#000000", + caret: "#000000", + selection: "#d7d4f0", + lineHighlight: "#cceeff44", + gutterBackground: "var(--color-background)", + gutterForeground: "var(--gray-10)", }, - { - tag: [t.propertyName], - color: "#05a", + styles: [ + // Default codemirror light theme + { tag: t.comment, color: "#708090" }, + { tag: t.variableName, color: "#000000" }, + { tag: [t.string, t.special(t.brace)], color: "#a11" }, + { tag: t.number, color: "#164" }, + { tag: t.bool, color: "#219" }, + { tag: t.null, color: "#219" }, + { tag: t.keyword, color: "#708", fontWeight: 500 }, + // { tag: t.operator, color: '#000' }, + { tag: t.className, color: "#00f" }, + { tag: t.definition(t.typeName), color: "#00f" }, + { tag: t.typeName, color: "#085" }, + { tag: t.angleBracket, color: "#000000" }, + { tag: t.tagName, color: "#170" }, + { tag: t.attributeName, color: "#00c" }, + // Adjustments + { tag: t.operator, color: "#a2f", fontWeight: 500 }, + { + tag: [t.function(t.variableName)], + color: "#00c", + }, + { + tag: [t.propertyName], + color: "#05a", + }, + ], + }), + EditorView.theme({ + ".mo-cm-reactive-reference": { + fontWeight: "400", + color: "#005f87", + borderBottom: "2px solid #bad3de", }, - ], -}); + ".mo-cm-reactive-reference-hover": { + cursor: "pointer", + borderBottomWidth: "3px", + }, + }), +]; diff --git a/frontend/src/core/config/__tests__/config-schema.test.ts b/frontend/src/core/config/__tests__/config-schema.test.ts index 382b28e1275..5afcf2f629e 100644 --- a/frontend/src/core/config/__tests__/config-schema.test.ts +++ b/frontend/src/core/config/__tests__/config-schema.test.ts @@ -57,6 +57,7 @@ test("default UserConfig - empty", () => { "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", + "reference_highlighting": false, "theme": "light", }, "experimental": {}, @@ -113,6 +114,7 @@ test("default UserConfig - one level", () => { "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", + "reference_highlighting": false, "theme": "light", }, "experimental": {}, diff --git a/frontend/src/core/config/config-schema.ts b/frontend/src/core/config/config-schema.ts index 75d6fc7e8f6..85668926c45 100644 --- a/frontend/src/core/config/config-schema.ts +++ b/frontend/src/core/config/config-schema.ts @@ -111,6 +111,7 @@ export const UserConfigSchema = z } return width; }), + reference_highlighting: z.boolean().default(false), }) .passthrough() .default({}), @@ -186,6 +187,7 @@ export type CompletionConfig = UserConfig["completion"]; export type KeymapConfig = UserConfig["keymap"]; export type LSPConfig = UserConfig["language_servers"]; export type DiagnosticsConfig = UserConfig["diagnostics"]; +export type DisplayConfig = UserConfig["display"]; export const AppTitleSchema = z.string(); export const SqlOutputSchema = z.enum(VALID_SQL_OUTPUT_FORMATS).default("auto"); diff --git a/frontend/src/core/runtime/runtime.ts b/frontend/src/core/runtime/runtime.ts index 644328c96ec..715c0a20888 100644 --- a/frontend/src/core/runtime/runtime.ts +++ b/frontend/src/core/runtime/runtime.ts @@ -41,7 +41,7 @@ export class RuntimeManager { formatHttpURL( path?: string, searchParams?: URLSearchParams, - restrictToKnownQueryParams: boolean = true, + restrictToKnownQueryParams = true, ): URL { if (!path) { path = ""; diff --git a/marimo/_config/config.py b/marimo/_config/config.py index e03c226dd80..9d9584551e7 100644 --- a/marimo/_config/config.py +++ b/marimo/_config/config.py @@ -167,6 +167,7 @@ class DisplayConfig(TypedDict): - `dataframes`: `"rich"` or `"plain"` - `custom_css`: list of paths to custom CSS files - `default_table_page_size`: default number of rows to display in tables + - `reference_highlighting`: if `True`, highlight reactive variable references """ theme: Theme @@ -176,6 +177,7 @@ class DisplayConfig(TypedDict): dataframes: Literal["rich", "plain"] custom_css: NotRequired[list[str]] default_table_page_size: int + reference_highlighting: NotRequired[bool] @mddoc @@ -432,6 +434,7 @@ class PartialMarimoConfig(TypedDict, total=False): "default_width": "medium", "dataframes": "rich", "default_table_page_size": 10, + "reference_highlighting": False, }, "formatting": {"line_length": 79}, "keymap": {"preset": "default", "overrides": {}}, diff --git a/openapi/api.yaml b/openapi/api.yaml index ac4cd5ef81f..4d72cee2d96 100644 --- a/openapi/api.yaml +++ b/openapi/api.yaml @@ -1567,6 +1567,8 @@ components: - full - columns type: string + reference_highlighting: + type: boolean theme: enum: - light @@ -2692,7 +2694,7 @@ components: type: object info: title: marimo API - version: 0.14.0 + version: 0.14.7 openapi: 3.1.0 paths: /@file/{filename_and_length}: diff --git a/openapi/src/api.ts b/openapi/src/api.ts index f44f3b68c28..7a880d55978 100644 --- a/openapi/src/api.ts +++ b/openapi/src/api.ts @@ -3191,6 +3191,7 @@ export interface components { default_table_page_size: number; /** @enum {string} */ default_width: "normal" | "compact" | "medium" | "full" | "columns"; + reference_highlighting?: boolean; /** @enum {string} */ theme: "light" | "dark" | "system"; }; From e3326a72f251a9d6482360c943470fd07b47e4cb Mon Sep 17 00:00:00 2001 From: Akshay Agrawal Date: Mon, 23 Jun 2025 23:04:25 -0700 Subject: [PATCH 10/26] fix: Update HTML snapshots for server templates (#5429) To account for config change with reactive variable highlighting (I merged #5415 without updating) --- tests/_server/templates/snapshots/export1.txt | 2 +- tests/_server/templates/snapshots/export2.txt | 2 +- tests/_server/templates/snapshots/export3.txt | 2 +- tests/_server/templates/snapshots/export4.txt | 2 +- tests/_server/templates/snapshots/export5.txt | 2 +- tests/_server/templates/snapshots/export6.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/_server/templates/snapshots/export1.txt b/tests/_server/templates/snapshots/export1.txt index 1db573dcd04..112be92a8c6 100644 --- a/tests/_server/templates/snapshots/export1.txt +++ b/tests/_server/templates/snapshots/export1.txt @@ -59,7 +59,7 @@ "mode": "read", "version": "0.0.0", "serverToken": "token", - "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, + "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "reference_highlighting": false, "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, "configOverrides": {"formatting": {"line_length": 100}}, "appConfig": {"sql_output": "auto", "width": "compact"}, "view": {"showAppCode": true}, diff --git a/tests/_server/templates/snapshots/export2.txt b/tests/_server/templates/snapshots/export2.txt index fbaea1dc71b..620daa8664b 100644 --- a/tests/_server/templates/snapshots/export2.txt +++ b/tests/_server/templates/snapshots/export2.txt @@ -59,7 +59,7 @@ "mode": "read", "version": "0.0.0", "serverToken": "token", - "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, + "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "reference_highlighting": false, "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, "configOverrides": {"formatting": {"line_length": 100}}, "appConfig": {"sql_output": "auto", "width": "compact"}, "view": {"showAppCode": true}, diff --git a/tests/_server/templates/snapshots/export3.txt b/tests/_server/templates/snapshots/export3.txt index ce2a10e2e1d..80ced017575 100644 --- a/tests/_server/templates/snapshots/export3.txt +++ b/tests/_server/templates/snapshots/export3.txt @@ -59,7 +59,7 @@ "mode": "read", "version": "0.0.0", "serverToken": "token", - "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, + "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "reference_highlighting": false, "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, "configOverrides": {"formatting": {"line_length": 100}}, "appConfig": {"sql_output": "auto", "width": "compact"}, "view": {"showAppCode": true}, diff --git a/tests/_server/templates/snapshots/export4.txt b/tests/_server/templates/snapshots/export4.txt index 586a359c2f7..53f379bd08c 100644 --- a/tests/_server/templates/snapshots/export4.txt +++ b/tests/_server/templates/snapshots/export4.txt @@ -59,7 +59,7 @@ "mode": "read", "version": "0.0.0", "serverToken": "token", - "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, + "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "reference_highlighting": false, "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, "configOverrides": {"formatting": {"line_length": 100}}, "appConfig": {"css_file": "custom.css", "sql_output": "auto", "width": "compact"}, "view": {"showAppCode": true}, diff --git a/tests/_server/templates/snapshots/export5.txt b/tests/_server/templates/snapshots/export5.txt index b085d380eaf..973996339d5 100644 --- a/tests/_server/templates/snapshots/export5.txt +++ b/tests/_server/templates/snapshots/export5.txt @@ -59,7 +59,7 @@ "mode": "read", "version": "0.0.0", "serverToken": "token", - "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, + "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "reference_highlighting": false, "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, "configOverrides": {"formatting": {"line_length": 100}}, "appConfig": {"app_title": "My App", "html_head_file": "head.html", "sql_output": "auto", "width": "compact"}, "view": {"showAppCode": true}, diff --git a/tests/_server/templates/snapshots/export6.txt b/tests/_server/templates/snapshots/export6.txt index 46afed2927b..ee5a11ab0ec 100644 --- a/tests/_server/templates/snapshots/export6.txt +++ b/tests/_server/templates/snapshots/export6.txt @@ -59,7 +59,7 @@ "mode": "read", "version": "0.0.0", "serverToken": "token", - "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "custom_css": ["custom1.css", "custom2.css"], "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, + "config": {"completion": {"activate_on_typing": true, "copilot": false}, "display": {"cell_output": "above", "code_editor_font_size": 14, "custom_css": ["custom1.css", "custom2.css"], "dataframes": "rich", "default_table_page_size": 10, "default_width": "medium", "reference_highlighting": false, "theme": "light"}, "formatting": {"line_length": 79}, "keymap": {"overrides": {}, "preset": "default"}, "language_servers": {"pylsp": {"enable_flake8": false, "enable_mypy": true, "enable_pydocstyle": false, "enable_pyflakes": false, "enable_pylint": false, "enable_ruff": true, "enabled": true}}, "package_management": {"manager": "pixi"}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "default_sql_output": "auto", "on_cell_change": "autorun", "output_max_bytes": 8000000, "reactive_tests": true, "std_stream_max_bytes": 1000000, "watcher_on_save": "lazy"}, "save": {"autosave": "after_delay", "autosave_delay": 1000, "format_on_save": false}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}, "configOverrides": {"formatting": {"line_length": 100}}, "appConfig": {"sql_output": "auto", "width": "compact"}, "view": {"showAppCode": true}, From cce318f62ebd83b236dbd406a9a5eed5ae653343 Mon Sep 17 00:00:00 2001 From: Shahmir Varqha Date: Tue, 24 Jun 2025 23:17:10 +0800 Subject: [PATCH 11/26] fix: set cell editor height and overflow to hidden when hidden (#5391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Fixes #5367 . Targets the inner div and corrects the css. This issue only affects safari (the parent height doesn't seem to be respected). ## 🔍 Description of Changes ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [ ] I have added tests for the changes made. - [x] I have run the code and verified that it works as expected. ## 📜 Reviewers @akshayka --- frontend/src/components/editor/cell/code/cell-editor.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/editor/cell/code/cell-editor.tsx b/frontend/src/components/editor/cell/code/cell-editor.tsx index 3c83f1e99bb..3b074007d26 100644 --- a/frontend/src/components/editor/cell/code/cell-editor.tsx +++ b/frontend/src/components/editor/cell/code/cell-editor.tsx @@ -373,7 +373,8 @@ const CellEditorInternal = ({ if (isMarkdown && hidden && hasOutput) { editorClassName = "h-0 overflow-hidden"; } else if (hidden) { - editorClassName = "opacity-20 h-8 overflow-hidden"; + editorClassName = + "opacity-20 h-8 [&>div.cm-editor]:h-full [&>div.cm-editor]:overflow-hidden"; } return ( From 80a17194c84711eeca2bd9264ad6c09d07b44675 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Tue, 24 Jun 2025 12:08:54 -0400 Subject: [PATCH 12/26] chore(frontend): Simplify and tighten TypeScript configuration (#5413) This PR refactors our `tsconfig.json` to use a simplified setup based on Vite's default config (`npx create vite`). Many previously listed options are already covered by `strict` setting, so I've removed the redundant ones for clarity. JavaScript support has also been explicitly disabled to enforce a stricter TypeScript-only codebase. --- .../datasources/__tests__/utils.test.ts | 6 +- .../components/datasources/datasources.tsx | 3 +- frontend/src/components/datasources/utils.ts | 6 +- .../src/components/editor/RecoveryButton.tsx | 2 +- .../ai/__tests__/completion-utils.test.ts | 6 +- .../components/slides/slides-component.tsx | 6 - frontend/src/components/slides/slides.css | 7 + .../language/__tests__/extension.test.ts | 2 +- .../codemirror/language/__tests__/sql.test.ts | 3 +- .../core/codemirror/language/languages/sql.ts | 3 +- .../core/codemirror/language/panel/sql.tsx | 6 +- .../datasets/__tests__/all-tables.test.ts | 2 +- .../datasets/__tests__/data-source.test.ts | 3 +- .../core/datasets/data-source-connections.ts | 14 +- frontend/src/core/datasets/engines.ts | 10 ++ .../src/core/websocket/useMarimoWebSocket.tsx | 6 +- frontend/tsconfig.json | 135 ++++-------------- 17 files changed, 72 insertions(+), 148 deletions(-) create mode 100644 frontend/src/core/datasets/engines.ts diff --git a/frontend/src/components/datasources/__tests__/utils.test.ts b/frontend/src/components/datasources/__tests__/utils.test.ts index b3be5889abf..423f119f40e 100644 --- a/frontend/src/components/datasources/__tests__/utils.test.ts +++ b/frontend/src/components/datasources/__tests__/utils.test.ts @@ -1,10 +1,8 @@ /* Copyright 2024 Marimo. All rights reserved. */ import { describe, expect, it } from "vitest"; -import { - DUCKDB_ENGINE, - type SQLTableContext, -} from "@/core/datasets/data-source-connections"; +import type { SQLTableContext } from "@/core/datasets/data-source-connections"; +import { DUCKDB_ENGINE } from "@/core/datasets/engines"; import type { DataTable, DataTableColumn } from "@/core/kernel/messages"; import { sqlCode } from "../utils"; diff --git a/frontend/src/components/datasources/datasources.tsx b/frontend/src/components/datasources/datasources.tsx index 56e1b88e187..c969297e3e8 100644 --- a/frontend/src/components/datasources/datasources.tsx +++ b/frontend/src/components/datasources/datasources.tsx @@ -29,12 +29,11 @@ import { cellIdsAtom, useCellActions } from "@/core/cells/cells"; import { useLastFocusedCellId } from "@/core/cells/focus"; import { autoInstantiateAtom } from "@/core/config/config"; import { - DUCKDB_ENGINE, dataConnectionsMapAtom, - INTERNAL_SQL_ENGINES, type SQLTableContext, useDataSourceActions, } from "@/core/datasets/data-source-connections"; +import { DUCKDB_ENGINE, INTERNAL_SQL_ENGINES } from "@/core/datasets/engines"; import { PreviewSQLTable, PreviewSQLTableList, diff --git a/frontend/src/components/datasources/utils.ts b/frontend/src/components/datasources/utils.ts index 27e34afaf9a..ef4d9f19094 100644 --- a/frontend/src/components/datasources/utils.ts +++ b/frontend/src/components/datasources/utils.ts @@ -1,8 +1,6 @@ /* Copyright 2024 Marimo. All rights reserved. */ -import { - DUCKDB_ENGINE, - type SQLTableContext, -} from "@/core/datasets/data-source-connections"; +import type { SQLTableContext } from "@/core/datasets/data-source-connections"; +import { DUCKDB_ENGINE } from "@/core/datasets/engines"; import type { DataTable, DataType } from "@/core/kernel/messages"; import type { ColumnHeaderStatsKey } from "../data-table/types"; diff --git a/frontend/src/components/editor/RecoveryButton.tsx b/frontend/src/components/editor/RecoveryButton.tsx index 1e3b006147f..f8c4a06ee6c 100644 --- a/frontend/src/components/editor/RecoveryButton.tsx +++ b/frontend/src/components/editor/RecoveryButton.tsx @@ -1,10 +1,10 @@ /* Copyright 2024 Marimo. All rights reserved. */ import type { Notebook } from "@marimo-team/marimo-api"; -import { Button } from "components/ui/button"; import { SaveIcon } from "lucide-react"; import type { JSX } from "react"; import { Button as EditorButton } from "@/components/editor/inputs/Inputs"; +import { Button } from "@/components/ui/button"; import { getNotebook } from "@/core/cells/cells"; import { notebookCells } from "@/core/cells/utils"; import { getMarimoVersion } from "@/core/meta/globals"; diff --git a/frontend/src/components/editor/ai/__tests__/completion-utils.test.ts b/frontend/src/components/editor/ai/__tests__/completion-utils.test.ts index 61d1473df37..7ef076d47ce 100644 --- a/frontend/src/components/editor/ai/__tests__/completion-utils.test.ts +++ b/frontend/src/components/editor/ai/__tests__/completion-utils.test.ts @@ -1,10 +1,8 @@ /* Copyright 2024 Marimo. All rights reserved. */ import { beforeEach, describe, expect, it, type Mock, vi } from "vitest"; import { getCodes } from "@/core/codemirror/copilot/getCodes"; -import { - DUCKDB_ENGINE, - dataSourceConnectionsAtom, -} from "@/core/datasets/data-source-connections"; +import { dataSourceConnectionsAtom } from "@/core/datasets/data-source-connections"; +import { DUCKDB_ENGINE } from "@/core/datasets/engines"; import { datasetsAtom } from "@/core/datasets/state"; import type { DatasetsState } from "@/core/datasets/types"; import { store } from "@/core/state/jotai"; diff --git a/frontend/src/components/slides/slides-component.tsx b/frontend/src/components/slides/slides-component.tsx index 71861300a88..a74c4dd9f45 100644 --- a/frontend/src/components/slides/slides-component.tsx +++ b/frontend/src/components/slides/slides-component.tsx @@ -14,12 +14,6 @@ import { useEventListener } from "@/hooks/useEventListener"; import { cn } from "@/utils/cn"; import "./slides.css"; -import "swiper/css"; -import "swiper/css/virtual"; -import "swiper/css/keyboard"; -import "swiper/css/navigation"; -import "swiper/css/pagination"; -import "swiper/css/scrollbar"; interface SlidesComponentProps { className?: string; diff --git a/frontend/src/components/slides/slides.css b/frontend/src/components/slides/slides.css index 52e20e2f256..bc54dcfeb56 100644 --- a/frontend/src/components/slides/slides.css +++ b/frontend/src/components/slides/slides.css @@ -1,3 +1,10 @@ +@import "swiper/css"; +@import "swiper/css/virtual"; +@import "swiper/css/keyboard"; +@import "swiper/css/navigation"; +@import "swiper/css/pagination"; +@import "swiper/css/scrollbar"; + .mo-slides-theme { --swiper-theme-color: var(--primary); --swiper-pagination-color: var(--swiper-theme-color); diff --git a/frontend/src/core/codemirror/language/__tests__/extension.test.ts b/frontend/src/core/codemirror/language/__tests__/extension.test.ts index 4f90c110b17..a2b4e36694a 100644 --- a/frontend/src/core/codemirror/language/__tests__/extension.test.ts +++ b/frontend/src/core/codemirror/language/__tests__/extension.test.ts @@ -4,7 +4,7 @@ import { EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { describe, expect, it } from "vitest"; import type { CellId } from "@/core/cells/ids"; -import { DUCKDB_ENGINE } from "@/core/datasets/data-source-connections"; +import { DUCKDB_ENGINE } from "@/core/datasets/engines"; import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys"; import { cellConfigExtension } from "../../config/extension"; import { diff --git a/frontend/src/core/codemirror/language/__tests__/sql.test.ts b/frontend/src/core/codemirror/language/__tests__/sql.test.ts index 25245b0bbe4..faf3f3e0c2b 100644 --- a/frontend/src/core/codemirror/language/__tests__/sql.test.ts +++ b/frontend/src/core/codemirror/language/__tests__/sql.test.ts @@ -3,11 +3,10 @@ import { PostgreSQL } from "@codemirror/lang-sql"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { - type ConnectionName, - DUCKDB_ENGINE, dataSourceConnectionsAtom, setLatestEngineSelected, } from "@/core/datasets/data-source-connections"; +import { type ConnectionName, DUCKDB_ENGINE } from "@/core/datasets/engines"; import { datasetsAtom } from "@/core/datasets/state"; import type { DatasetsState } from "@/core/datasets/types"; import type { DataSourceConnection } from "@/core/kernel/messages"; diff --git a/frontend/src/core/codemirror/language/languages/sql.ts b/frontend/src/core/codemirror/language/languages/sql.ts index 8d2967b4f52..6cc50fc6796 100644 --- a/frontend/src/core/codemirror/language/languages/sql.ts +++ b/frontend/src/core/codemirror/language/languages/sql.ts @@ -23,12 +23,11 @@ import { parser } from "@lezer/python"; import dedent from "string-dedent"; import { isSchemaless } from "@/components/datasources/utils"; import { - type ConnectionName, - DUCKDB_ENGINE, dataConnectionsMapAtom, dataSourceConnectionsAtom, setLatestEngineSelected, } from "@/core/datasets/data-source-connections"; +import { type ConnectionName, DUCKDB_ENGINE } from "@/core/datasets/engines"; import { datasetTablesAtom } from "@/core/datasets/state"; import type { DataSourceConnection } from "@/core/kernel/messages"; import { store } from "@/core/state/jotai"; diff --git a/frontend/src/core/codemirror/language/panel/sql.tsx b/frontend/src/core/codemirror/language/panel/sql.tsx index 4297ebde16c..78b97dc9066 100644 --- a/frontend/src/core/codemirror/language/panel/sql.tsx +++ b/frontend/src/core/codemirror/language/panel/sql.tsx @@ -15,11 +15,13 @@ import { SelectValue, } from "@/components/ui/select"; import { - type ConnectionName, dataConnectionsMapAtom, - INTERNAL_SQL_ENGINES, setLatestEngineSelected, } from "@/core/datasets/data-source-connections"; +import { + type ConnectionName, + INTERNAL_SQL_ENGINES, +} from "@/core/datasets/engines"; import type { DataSourceConnection } from "@/core/kernel/messages"; import { useNonce } from "@/hooks/useNonce"; diff --git a/frontend/src/core/datasets/__tests__/all-tables.test.ts b/frontend/src/core/datasets/__tests__/all-tables.test.ts index 82b02c336d3..10b16ad02b1 100644 --- a/frontend/src/core/datasets/__tests__/all-tables.test.ts +++ b/frontend/src/core/datasets/__tests__/all-tables.test.ts @@ -4,9 +4,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { store } from "@/core/state/jotai"; import { allTablesAtom, - DUCKDB_ENGINE, dataSourceConnectionsAtom, } from "../data-source-connections"; +import { DUCKDB_ENGINE } from "../engines"; import { datasetsAtom } from "../state"; import type { DatasetsState } from "../types"; diff --git a/frontend/src/core/datasets/__tests__/data-source.test.ts b/frontend/src/core/datasets/__tests__/data-source.test.ts index 69534adbd0b..ae37beb1193 100644 --- a/frontend/src/core/datasets/__tests__/data-source.test.ts +++ b/frontend/src/core/datasets/__tests__/data-source.test.ts @@ -3,13 +3,12 @@ import { beforeEach, describe, expect, it } from "vitest"; import type { DataTable } from "@/core/kernel/messages"; import type { VariableName } from "@/core/variables/types"; import { - type ConnectionName, type DataSourceConnection, type DataSourceState, exportedForTesting, - INTERNAL_SQL_ENGINES, type SQLTableContext, } from "../data-source-connections"; +import { type ConnectionName, INTERNAL_SQL_ENGINES } from "../engines"; const { reducer, initialState } = exportedForTesting; diff --git a/frontend/src/core/datasets/data-source-connections.ts b/frontend/src/core/datasets/data-source-connections.ts index df08d5a347a..e9ea8de93fc 100644 --- a/frontend/src/core/datasets/data-source-connections.ts +++ b/frontend/src/core/datasets/data-source-connections.ts @@ -4,23 +4,19 @@ import { atom } from "jotai"; import { isSchemaless } from "@/components/datasources/utils"; import { createReducerAndAtoms } from "@/utils/createReducer"; import { Logger } from "@/utils/Logger"; -import type { TypedString } from "@/utils/typed"; import type { DataSourceConnection as DataSourceConnectionType, DataTable, } from "../kernel/messages"; import { store } from "../state/jotai"; import type { VariableName } from "../variables/types"; +import { + type ConnectionName, + DUCKDB_ENGINE, + INTERNAL_SQL_ENGINES, +} from "./engines"; import { datasetTablesAtom } from "./state"; -export type ConnectionName = TypedString<"ConnectionName">; - -// DuckDB engine is treated as the default engine -// As it doesn't require passing an engine variable to the backend -// Keep this in sync with the backend name -export const DUCKDB_ENGINE = "__marimo_duckdb" as ConnectionName; -export const INTERNAL_SQL_ENGINES = new Set([DUCKDB_ENGINE]); - const initialConnections: ConnectionsMap = new Map([ [ DUCKDB_ENGINE, diff --git a/frontend/src/core/datasets/engines.ts b/frontend/src/core/datasets/engines.ts new file mode 100644 index 00000000000..978b5f74f06 --- /dev/null +++ b/frontend/src/core/datasets/engines.ts @@ -0,0 +1,10 @@ +/* Copyright 2024 Marimo. All rights reserved. */ +import type { TypedString } from "@/utils/typed"; + +export type ConnectionName = TypedString<"ConnectionName">; + +// DuckDB engine is treated as the default engine +// As it doesn't require passing an engine variable to the backend +// Keep this in sync with the backend name +export const DUCKDB_ENGINE = "__marimo_duckdb" as ConnectionName; +export const INTERNAL_SQL_ENGINES = new Set([DUCKDB_ENGINE]); diff --git a/frontend/src/core/websocket/useMarimoWebSocket.tsx b/frontend/src/core/websocket/useMarimoWebSocket.tsx index 6ee049aa250..afdd807f965 100644 --- a/frontend/src/core/websocket/useMarimoWebSocket.tsx +++ b/frontend/src/core/websocket/useMarimoWebSocket.tsx @@ -27,10 +27,8 @@ import { focusAndScrollCellOutputIntoView } from "../cells/scrollCellIntoView"; import type { CellData } from "../cells/types"; import { capabilitiesAtom } from "../config/capabilities"; import { useSetAppConfig } from "../config/config"; -import { - type ConnectionName, - useDataSourceActions, -} from "../datasets/data-source-connections"; +import { useDataSourceActions } from "../datasets/data-source-connections"; +import type { ConnectionName } from "../datasets/engines"; import { PreviewSQLTable, PreviewSQLTableList, diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 8c419c750e7..b07381e5ec4 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,113 +1,40 @@ { + /* Visit https://aka.ms/tsconfig to read more about this file */ "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "ES2019" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - "lib": [ - "ESNext", - "DOM" - ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, - "jsx": "react-jsx" /* Specify what JSX code is generated. */, - "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "ESNext" /* Specify what module code is generated. */, - "rootDir": "src" /* Specify the root folder within your source files. */, - "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, - "baseUrl": "src" /* Specify the base directory to resolve non-relative module names. */, - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2019", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "esModuleInterop": true, "types": [ "vite/client", "@testing-library/jest-dom" - ] /* Specify type package names to be included without being referenced in a source file. */, - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "build" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, - "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */, - "strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */, - "strictBindCallApply": true /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */, - "strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */, - "noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */, - "useUnknownInCatchVariables": true /* Default catch clause variables as 'unknown' instead of 'any'. */, - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, - // "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */, - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */, - "noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */, - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */, - // "noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type. */, - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */, + ], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "noImplicitOverride": true, + + /* Style */ "paths": { - "@/*": ["./*"] - } + "@/*": ["./src/*"] + }, + "forceConsistentCasingInFileNames": true, + "useDefineForClassFields": true, + "experimentalDecorators": true }, "include": ["src"] } + From 189667475406709ec37571463e016ffda0078e04 Mon Sep 17 00:00:00 2001 From: Dylan Madisetti Date: Tue, 24 Jun 2025 14:58:29 -0700 Subject: [PATCH 13/26] dev: Use islands as local asset server (#5436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary quarto `0.14.7` is broken. I'm having trouble confirming this is the issue by replicating it locally, since I can't replicate the breakage. My dev served assets seem to work in general. But `being islands` != `being wasm` as is, and I think some of the logic around sockets gets lost (0.14.6 works, so this may be the offending PR: #5403) @akshayka --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- frontend/islands/vite.config.mts | 1 - frontend/package.json | 1 + frontend/src/core/dom/htmlUtils.ts | 5 +++++ frontend/src/core/islands/utils.ts | 2 +- frontend/src/core/websocket/StaticWebsocket.ts | 4 +++- 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/islands/vite.config.mts b/frontend/islands/vite.config.mts index 00f521e049a..6bdb5b529a6 100644 --- a/frontend/islands/vite.config.mts +++ b/frontend/islands/vite.config.mts @@ -40,7 +40,6 @@ export default defineConfig({ "process.env": { NODE_ENV: JSON.stringify(process.env.NODE_ENV), }, - "import.meta.env.VITE_MARIMO_ISLANDS": JSON.stringify(true), // Precedence: VITE_MARIMO_VERSION > package.json version > "latest" "import.meta.env.VITE_MARIMO_VERSION": process.env.VITE_MARIMO_VERSION ? JSON.stringify(process.env.VITE_MARIMO_VERSION) diff --git a/frontend/package.json b/frontend/package.json index 8fb15abbe1d..cfb83c66f9b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -172,6 +172,7 @@ "lint:stylelint": "stylelint src/**/*.css --fix", "format": "biome format --write .", "preview": "vite preview", + "dev:quarto": "VITE_MARIMO_ISLANDS=true vite", "dev:islands": "cross-env VITE_MARIMO_ISLANDS=true vite --config islands/vite.config.mts", "build:islands": "cross-env VITE_MARIMO_ISLANDS=true vite --config islands/vite.config.mts build", "preview:islands": "cross-env VITE_MARIMO_VERSION='0.4.6' VITE_MARIMO_ISLANDS=true vite --config islands/vite.config.mts build", diff --git a/frontend/src/core/dom/htmlUtils.ts b/frontend/src/core/dom/htmlUtils.ts index 43bc216fab0..dbb92c2da91 100644 --- a/frontend/src/core/dom/htmlUtils.ts +++ b/frontend/src/core/dom/htmlUtils.ts @@ -3,6 +3,7 @@ import { assertExists } from "@/utils/assertExists"; import { jsonParseWithSpecialChar } from "@/utils/json/json-parser"; import { Objects } from "@/utils/objects"; import { UIElementId } from "../cells/ids"; +import { isIslands } from "../islands/utils"; import { PyodideRouter } from "../wasm/router"; import { isWasm } from "../wasm/utils"; import type { UIElementRegistry } from "./uiregistry"; @@ -50,6 +51,10 @@ export function serializeInitialValue(value: unknown) { } export function getFilenameFromDOM() { + // If running in Islands, just return the window title. + if (isIslands()) { + return document.title || null; + } // If we are running in WASM, we can get the filename from the URL if (isWasm()) { const filename = PyodideRouter.getFilename(); diff --git a/frontend/src/core/islands/utils.ts b/frontend/src/core/islands/utils.ts index 831bada8c29..2dfc72ee5c5 100644 --- a/frontend/src/core/islands/utils.ts +++ b/frontend/src/core/islands/utils.ts @@ -1,5 +1,5 @@ /* Copyright 2024 Marimo. All rights reserved. */ export function isIslands() { - return import.meta.env.VITE_MARIMO_ISLANDS === true; + return import.meta.env.VITE_MARIMO_ISLANDS === "true"; } diff --git a/frontend/src/core/websocket/StaticWebsocket.ts b/frontend/src/core/websocket/StaticWebsocket.ts index 981a23e2970..c838c3f26a5 100644 --- a/frontend/src/core/websocket/StaticWebsocket.ts +++ b/frontend/src/core/websocket/StaticWebsocket.ts @@ -1,4 +1,6 @@ /* Copyright 2024 Marimo. All rights reserved. */ + +import { isIslands } from "@/core/islands/utils"; import type { IReconnectingWebSocket } from "./types"; export class StaticWebsocket implements IReconnectingWebSocket { @@ -24,7 +26,7 @@ export class StaticWebsocket implements IReconnectingWebSocket { ): void { // Normally this would be a no-op in a mock, but we simulate a synthetic "open" event // to mimic the WebSocket transitioning from CONNECTING to OPEN state. - if (type === "open") { + if (type === "open" && !isIslands()) { queueMicrotask(() => { callback(new Event("open")); }); From 8c4735b9732e44859620b40fc0e2843c33e41ed3 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Tue, 24 Jun 2025 18:30:13 -0400 Subject: [PATCH 14/26] chore(frontend/lsp): Replace "as `any`" type assertion with a type guard (#5437) Adds a custom type guard for the protected `notify` method on `LanguageServerClient`s. Removes the need for a type assertion and ignoring eslint rule. --- frontend/src/core/codemirror/lsp/notebook-lsp.ts | 12 +++++++----- frontend/src/core/codemirror/lsp/types.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/frontend/src/core/codemirror/lsp/notebook-lsp.ts b/frontend/src/core/codemirror/lsp/notebook-lsp.ts index 5644cbf22db..a9d279f39c3 100644 --- a/frontend/src/core/codemirror/lsp/notebook-lsp.ts +++ b/frontend/src/core/codemirror/lsp/notebook-lsp.ts @@ -8,7 +8,11 @@ import { Logger } from "@/utils/Logger"; import { LRUCache } from "@/utils/lru"; import { getTopologicalCodes } from "../copilot/getCodes"; import { createNotebookLens } from "./lens"; -import { CellDocumentUri, type ILanguageServerClient } from "./types"; +import { + CellDocumentUri, + type ILanguageServerClient, + isNotifyingClient, +} from "./types"; import { getLSPDocument } from "./utils"; export class NotebookLanguageServerClient implements ILanguageServerClient { @@ -42,12 +46,10 @@ export class NotebookLanguageServerClient implements ILanguageServerClient { // Handle configuration after initialization this.initializePromise.then(() => { invariant( - "notify" in this.client, + isNotifyingClient(this.client), "notify is not a method on the client", ); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (this.client as any).notify("workspace/didChangeConfiguration", { + this.client.notify("workspace/didChangeConfiguration", { settings: initialSettings, }); }); diff --git a/frontend/src/core/codemirror/lsp/types.ts b/frontend/src/core/codemirror/lsp/types.ts index 9f7e374b044..f4c46fc8f7c 100644 --- a/frontend/src/core/codemirror/lsp/types.ts +++ b/frontend/src/core/codemirror/lsp/types.ts @@ -25,3 +25,18 @@ export const CellDocumentUri = { return uri.slice(this.PREFIX.length) as CellId; }, }; + +/** + * Notify is a @protected method on `LanguageServerClient`, + * hiding public use with TypeScript. + */ +export function isNotifyingClient( + client: ILanguageServerClient, +): client is ILanguageServerClient & { + notify: ( + kind: string, + options: { settings: Record }, + ) => void; +} { + return "notify" in client; +} From 180127a1be167ff3f9d09ad50d2189c771de3512 Mon Sep 17 00:00:00 2001 From: Akshay Agrawal Date: Tue, 24 Jun 2025 16:45:59 -0700 Subject: [PATCH 15/26] docs: call out package support among wasm limitations (#5439) It came up in https://github.com/marimo-team/marimo/pull/5343 that our documentation on package limitations for WASM could be better. --- docs/guides/apps.md | 2 +- docs/guides/exporting.md | 5 ++++- docs/guides/wasm.md | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/guides/apps.md b/docs/guides/apps.md index 9c559b797ec..0ce22635a34 100644 --- a/docs/guides/apps.md +++ b/docs/guides/apps.md @@ -62,7 +62,7 @@ can reconstruct your layout. If you prefer a slideshow-like experience, you can use the slides layout. Enable the slides layout in the app preview, via the same dropdown as above. !!! info "See slides layout in action" - Check out this [example notebook](https://marimo.io/p/@gvarnavides/stem-probes) that runs in slides mode, powered by our [Community Cloud](./publishing/community_cloud/). + Check out this [example notebook](https://marimo.io/p/@gvarnavides/stem-probes) that runs in slides mode, powered by our [Community Cloud](publishing/community_cloud/index.md). Unlike the grid layout, the slides are much less customizable: diff --git a/docs/guides/exporting.md b/docs/guides/exporting.md index 3234bd2c994..ff050c7913b 100644 --- a/docs/guides/exporting.md +++ b/docs/guides/exporting.md @@ -168,7 +168,7 @@ You can also use other tools that work with Jupyter notebooks: ## Export to WASM-powered HTML -Export your notebook to a self-contained HTML file that runs using WebAssembly: +Export your notebook to a self-contained HTML file that runs using [WebAssembly](wasm.md): ```bash # export as readonly, with code locked @@ -187,6 +187,9 @@ Options: - `--watch/--no-watch`: Watch the notebook for changes and automatically export - `--include-cloudflare`: Write configuration files necessary for deploying to Cloudflare +Note that WebAssembly notebooks have [limitations](wasm.md#limitations); in particular, +[many but not all packages work](wasm.md#packages). + !!! note "Note" The exported file must be served over HTTP to function correctly - it diff --git a/docs/guides/wasm.md b/docs/guides/wasm.md index 3fbeb0dffd1..c131badb0c8 100644 --- a/docs/guides/wasm.md +++ b/docs/guides/wasm.md @@ -138,6 +138,15 @@ amount of data, and also lets you sync notebooks (and their data) from GitHub. While WASM notebooks let you share marimo notebooks seamlessly, they have some limitations. +**Packages.** Many but not all packages are supported. All packages with pure +Python wheels on PyPI are supported, as well as additional packages like NumPy, +SciPy, scikit-learn, duckdb, polars, and more. For a full list of supported +packages, see [Pyodide's documentation on supported +packages.](https://pyodide.org/en/stable/usage/packages-in-pyodide.html) + +If you want a package to be supported, consider [filing an +issue](https://github.com/pyodide/pyodide/issues/new?assignees=&labels=new+package+request&projects=&template=package_request.md&title=). + **PDB.** PDB is not currently supported. **Threading and multi-processing.** WASM notebooks do not support multithreading From e044d49aa9314c0291ccea89f3394a906febd13d Mon Sep 17 00:00:00 2001 From: Shahmir Varqha Date: Wed, 25 Jun 2025 11:31:56 +0800 Subject: [PATCH 16/26] improvement: install packages directly (#5434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Just a refactor to install packages with one-click rather than opening the sidebar to install them. ## 🔍 Description of Changes ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [ ] I have added tests for the changes made. - [x] I have run the code and verified that it works as expected. ## 📜 Reviewers @manzt --- .../column-explorer-panel/column-explorer.tsx | 12 +- .../__tests__/install-package-button.test.tsx | 18 +-- .../components/datasources/column-preview.tsx | 28 ++-- .../datasources/install-package-button.tsx | 23 +--- .../editor/chrome/panels/packages-panel.tsx | 121 +++--------------- .../src/core/packages/toast-components.tsx | 88 +++++++++++++ .../src/core/packages/useInstallPackage.ts | 45 +++++++ 7 files changed, 185 insertions(+), 150 deletions(-) create mode 100644 frontend/src/core/packages/toast-components.tsx create mode 100644 frontend/src/core/packages/useInstallPackage.ts diff --git a/frontend/src/components/data-table/column-explorer-panel/column-explorer.tsx b/frontend/src/components/data-table/column-explorer-panel/column-explorer.tsx index 0096888d48a..53ca5c65932 100644 --- a/frontend/src/components/data-table/column-explorer-panel/column-explorer.tsx +++ b/frontend/src/components/data-table/column-explorer-panel/column-explorer.tsx @@ -78,7 +78,7 @@ export const ColumnExplorerPanel = ({ className="h-3 w-3 ml-1 mt-0.5" /> - + { const { theme } = useTheme(); - const { data, error, isPending } = useAsyncData(async () => { + const { + data, + error, + isPending, + refetch: refetchPreview, + } = useAsyncData(async () => { const response = await previewColumn({ column: columnName }); return response; }, []); @@ -196,7 +201,8 @@ const ColumnPreview = ({ } = data; const errorState = - previewError && renderPreviewError(previewError, missing_packages); + previewError && + renderPreviewError(previewError, missing_packages, refetchPreview); const previewStats = stats && renderStats(stats, dataType); diff --git a/frontend/src/components/datasources/__tests__/install-package-button.test.tsx b/frontend/src/components/datasources/__tests__/install-package-button.test.tsx index 34b7a75dffd..4ce2368b074 100644 --- a/frontend/src/components/datasources/__tests__/install-package-button.test.tsx +++ b/frontend/src/components/datasources/__tests__/install-package-button.test.tsx @@ -1,6 +1,6 @@ /* Copyright 2024 Marimo. All rights reserved. */ -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { InstallPackageButton } from "../install-package-button"; @@ -11,12 +11,6 @@ vi.mock("@/components/editor/chrome/state", () => ({ }), })); -const mockSetPackagesToInstall = vi.fn(); -vi.mock("jotai", () => ({ - atom: () => ({}), - useSetAtom: () => mockSetPackagesToInstall, -})); - vi.mock("@/components/editor/chrome/panels/packages-state", () => ({ packagesToInstallAtom: {}, })); @@ -65,14 +59,4 @@ describe("InstallPackageButton", () => { ); expect(screen.getByText("Install altair")).toBeInTheDocument(); }); - - it("should open the packages panel when clicked", () => { - render(); - - fireEvent.click(screen.getByText("Install altair")); - - expect(mockSetPackagesToInstall).toHaveBeenCalledWith("altair"); - - expect(mockOpenApplication).toHaveBeenCalledWith("packages"); - }); }); diff --git a/frontend/src/components/datasources/column-preview.tsx b/frontend/src/components/datasources/column-preview.tsx index e9752800130..835e0bb85fe 100644 --- a/frontend/src/components/datasources/column-preview.tsx +++ b/frontend/src/components/datasources/column-preview.tsx @@ -41,6 +41,18 @@ export const DatasetColumnPreview: React.FC<{ }> = ({ table, column, preview, onAddColumnChart, sqlTableContext }) => { const { theme } = useTheme(); + const previewColumn = () => { + previewDatasetColumn({ + source: table.source, + tableName: table.name, + columnName: column.name, + sourceType: table.source_type, + fullyQualifiedTableName: sqlTableContext + ? `${sqlTableContext.database}.${sqlTableContext.schema}.${table.name}` + : table.name, + }); + }; + useOnMount(() => { if (preview) { return; @@ -51,15 +63,7 @@ export const DatasetColumnPreview: React.FC<{ return; } - previewDatasetColumn({ - source: table.source, - tableName: table.name, - columnName: column.name, - sourceType: table.source_type, - fullyQualifiedTableName: sqlTableContext - ? `${sqlTableContext.database}.${sqlTableContext.schema}.${table.name}` - : table.name, - }); + previewColumn(); }); if (table.source_type === "connection") { @@ -93,7 +97,7 @@ export const DatasetColumnPreview: React.FC<{ const error = preview.error && - renderPreviewError(preview.error, preview.missing_packages); + renderPreviewError(preview.error, preview.missing_packages, previewColumn); const stats = preview.stats && renderStats(preview.stats, column.type); @@ -137,15 +141,17 @@ export const DatasetColumnPreview: React.FC<{ export function renderPreviewError( error: string, missing_packages?: string[] | null, + refetchPreview?: () => void, ) { return ( -
+
{error} {missing_packages && ( )}
diff --git a/frontend/src/components/datasources/install-package-button.tsx b/frontend/src/components/datasources/install-package-button.tsx index a32d46bc753..df6f4a74fa6 100644 --- a/frontend/src/components/datasources/install-package-button.tsx +++ b/frontend/src/components/datasources/install-package-button.tsx @@ -1,16 +1,15 @@ /* Copyright 2024 Marimo. All rights reserved. */ -import { useSetAtom } from "jotai"; import React from "react"; -import { packagesToInstallAtom } from "@/components/editor/chrome/panels/packages-state"; -import { useChromeActions } from "@/components/editor/chrome/state"; import { Button } from "@/components/ui/button"; +import { useInstallPackages } from "@/core/packages/useInstallPackage"; import { cn } from "@/utils/cn"; interface InstallPackageButtonProps { packages: string[] | undefined; showMaxPackages?: number; className?: string; + onInstall?: () => void; } /** @@ -21,29 +20,21 @@ export const InstallPackageButton: React.FC = ({ packages, showMaxPackages, className, + onInstall, }) => { - const chromeActions = useChromeActions(); - const setPackagesToInstall = useSetAtom(packagesToInstallAtom); + const { handleInstallPackages } = useInstallPackages(); if (!packages || packages.length === 0) { return null; } - const handleClick = () => { - const packagesString = packages.join(", "); - - // Set the packages to install - setPackagesToInstall(packagesString); - - // Open the packages panel - chromeActions.openApplication("packages"); - }; - return ( ); @@ -80,7 +90,9 @@ export const NotebookMenuDropdown: React.FC = () => { return ( - {button} + + {button} + {actions.map((action) => { if (action.hidden) { diff --git a/frontend/src/components/editor/controls/shutdown-button.tsx b/frontend/src/components/editor/controls/shutdown-button.tsx index c68e8364f22..0f270da6be1 100644 --- a/frontend/src/components/editor/controls/shutdown-button.tsx +++ b/frontend/src/components/editor/controls/shutdown-button.tsx @@ -8,7 +8,17 @@ import { AlertDialogDestructiveAction } from "../../ui/alert-dialog"; import { Tooltip } from "../../ui/tooltip"; import { Button } from "../inputs/Inputs"; -export const ShutdownButton: React.FC<{ description: string }> = (props) => { +interface Props { + description: string; + disabled?: boolean; + tooltip?: string; +} + +export const ShutdownButton: React.FC = ({ + description, + disabled = false, + tooltip = "Shutdown", +}) => { const { openConfirm, closeModal } = useImperativeModal(); const handleShutdown = () => { sendShutdown(); @@ -23,19 +33,20 @@ export const ShutdownButton: React.FC<{ description: string }> = (props) => { } return ( - +