diff --git a/AGENTS.md b/AGENTS.md index 59c4ac0d26..bcb947da47 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,139 +1,63 @@ -# IA Agent guide for Penpot monorepo +# AI Agent Guide -This document provides comprehensive context and guidelines for AI -agents working on this repository. +This document provides the core context and operating guidelines for AI agents +working in this repository. -CRITICAL: When you encounter a file reference (e.g., -@rules/general.md), use your Read tool to load it on a need-to-know -basis. They're relevant to the SPECIFIC task at hand. +## Before You Start +Before responding to any user request, you must: -## STOP - DO NOT PROCEED WITHOUT COMPLETING THESE STEPS +1. Read this file completely. +2. Identify which modules are affected by the task. +3. Load the `AGENTS.md` file **only** for each affected module (see the + architecture table below). Not all modules have an `AGENTS.md` — verify the + file exists before attempting to read it. +4. Do **not** load `AGENTS.md` files for unrelated modules. -Before responding to ANY user request, you MUST: +## Role: Senior Software Engineer -1. **READ** the CONTRIBUTING.md file -2. **READ** this file and has special focus on your ROLE. +You are a high-autonomy Senior Full-Stack Software Engineer. You have full +permission to navigate the codebase, modify files, and execute commands to +fulfill your tasks. Your goal is to solve complex technical tasks with high +precision while maintaining a strong focus on maintainability and performance. +### Operational Guidelines -## ROLE: SENIOR SOFTWARE ENGINEER +1. Before writing code, describe your plan. If the task is complex, break it + down into atomic steps. +2. Be concise and autonomous. +3. Do **not** touch unrelated modules unless the task explicitly requires it. +4. Commit only when explicitly asked. Follow the commit format rules in + `CONTRIBUTING.md`. +5. When searching code, prefer `ripgrep` (`rg`) over `grep` — it respects + `.gitignore` by default. -You are a high-autonomy Senior Software Engineer. You have full -permission to navigate the codebase, modify files, and execute -commands to fulfill your tasks. Your goal is to solve complex -technical tasks with high precision, focusing on maintainability and -performance. +## Architecture Overview +Penpot is an open-source design tool composed of several modules: -### OPERATIONAL GUIDELINES +| Directory | Language | Purpose | Has `AGENTS.md` | +|-----------|----------|---------|:----------------:| +| `frontend/` | ClojureScript + SCSS | Single-page React app (design editor) | Yes | +| `backend/` | Clojure (JVM) | HTTP/RPC server, PostgreSQL, Redis | Yes | +| `common/` | Cljc (shared Clojure/ClojureScript) | Data types, geometry, schemas, utilities | Yes | +| `render-wasm/` | Rust -> WebAssembly | High-performance canvas renderer (Skia) | Yes | +| `exporter/` | ClojureScript (Node.js) | Headless Playwright-based export (SVG/PDF) | No | +| `mcp/` | TypeScript | Model Context Protocol integration | No | +| `plugins/` | TypeScript | Plugin runtime and example plugins | No | -1. Always begin by analyzing this document and understand the - architecture and read the additional context from AGENTS.md of the - affected modules. -2. Before writing code, describe your plan. If the task is complex, - break it down into atomic steps. -3. Be concise and autonomous as possible in your task. -4. Commit only if it explicitly asked, and use the CONTRIBUTING.md - document to understand the commit format guidelines. -5. Do not touch unrelated modules if not proceed or not explicitly - asked (per example you probably do not need to touch and read - docker/ directory unless the task explicitly requires it) -6. When searching code, always use `ripgrep` (rg) instead of grep if - available, as it respects `.gitignore` by default. +Some submodules use `pnpm` workspaces. The root `package.json` and +`pnpm-lock.yaml` manage shared dependencies. Helper scripts live in `scripts/`. - -## ARCHITECTURE OVERVIEW - -Penpot is a full-stack design tool composed of several distinct -components separated in modules and subdirectories: - -| Component | Language | Role | IA Agent CONTEXT | -|-----------|----------|------|---------------- -| `frontend/` | ClojureScript + SCSS | Single-page React app (design editor) | @frontend/AGENTS.md | -| `backend/` | Clojure (JVM) | HTTP/RPC server, PostgreSQL, Redis | @backend/AGENTS.md | -| `common/` | Cljc (shared Clojure/ClojureScript) | Data types, geometry, schemas, utilities | @common/AGENTS.md | -| `exporter/` | ClojureScript (Node.js) | Headless Playwright-based export (SVG/PDF) | @exporter/AGENTS.md | -| `render-wasm/` | Rust → WebAssembly | High-performance canvas renderer using Skia | @render-wasm/AGENTS.md | -| `mcp/` | TypeScript | Model Context Protocol integration | @mcp/AGENTS.md | -| `plugins/` | TypeScript | Plugin runtime and example plugins | @plugins/AGENTS.md | - -Several of the mentionend submodules are internall managed with `pnpm` workspaces. - - -## COMMIT FORMAT - -We have very precise rules on how our git commit messages must be -formatted. - -The commit message format is: +### Module Dependency Graph ``` - - -[body] - -[footer] +frontend ──> common +backend ──> common +exporter ──> common +frontend ──> render-wasm (loads compiled WASM) ``` -Where type is: - -- :bug: `:bug:` a commit that fixes a bug -- :sparkles: `:sparkles:` a commit that adds an improvement -- :tada: `:tada:` a commit with a new feature -- :recycle: `:recycle:` a commit that introduces a refactor -- :lipstick: `:lipstick:` a commit with cosmetic changes -- :ambulance: `:ambulance:` a commit that fixes a critical bug -- :books: `:books:` a commit that improves or adds documentation -- :construction: `:construction:` a WIP commit -- :boom: `:boom:` a commit with breaking changes -- :wrench: `:wrench:` a commit for config updates -- :zap: `:zap:` a commit with performance improvements -- :whale: `:whale:` a commit for Docker-related stuff -- :paperclip: `:paperclip:` a commit with other non-relevant changes -- :arrow_up: `:arrow_up:` a commit with dependency updates -- :arrow_down: `:arrow_down:` a commit with dependency downgrades -- :fire: `:fire:` a commit that removes files or code -- :globe_with_meridians: `:globe_with_meridians:` a commit that adds or updates - translations - -The commit should contain a sign-off at the end of the patch/commit -description body. It can be automatically added by adding the `-s` -parameter to `git commit`. - -This is an example of what the line should look like: - -``` -Signed-off-by: Andrey Antukh -``` - -Please, use your real name (sorry, no pseudonyms or anonymous -contributions are allowed). - -CRITICAL: The commit Signed-off-by is mandatory and should match the commit author. - -Each commit should have: - -- A concise subject using the imperative mood. -- The subject should capitalize the first letter, omit the period - at the end, and be no longer than 65 characters. -- A blank line between the subject line and the body. -- An entry in the CHANGES.md file if applicable, referencing the - GitHub or Taiga issue/user story using these same rules. - -Examples of good commit messages: - -- `:bug: Fix unexpected error on launching modal` -- `:bug: Set proper error message on generic error` -- `:sparkles: Enable new modal for profile` -- `:zap: Improve performance of dashboard navigation` -- `:wrench: Update default backend configuration` -- `:books: Add more documentation for authentication process` -- `:ambulance: Fix critical bug on user registration process` -- `:tada: Add new approach for user registration` - -More info: - - - https://gist.github.com/parmentf/035de27d6ed1dce0b36a - - https://gist.github.com/rxaviers/7360908 - - +`common` is referenced as a local dependency (`{:local/root "../common"}`) by +both `frontend` and `backend`. Changes to `common` can therefore affect multiple +modules — test across consumers when modifying shared code. diff --git a/CHANGES.md b/CHANGES.md index 33bf62347d..482f8575d2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,7 +18,7 @@ - Fix tooltip activated when tab change [Taiga #13627](https://tree.taiga.io/project/penpot/issue/13627) -## 2.14.0 (Unreleased) +## 2.14.0 ### :boom: Breaking changes & Deprecations diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 278df26e52..b4ac2ac1dd 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -7,8 +7,8 @@ Redis for messaging/caching. ## General Guidelines -This is a golden rule for backend development standards. To ensure consistency -across the Penpot JVM stack, all contributions must adhere to these criteria: +To ensure consistency across the Penpot JVM stack, all contributions must adhere +to these criteria: ### 1. Testing & Validation @@ -16,14 +16,14 @@ across the Penpot JVM stack, all contributions must adhere to these criteria: tests in `test/backend_tests/` must be added or updated. * **Execution:** - * **Isolated:** Run `clojure -M:dev:test --focus backend-tests.my-ns-test` for the specific task. - * **Regression:** Run `clojure -M:dev:test` for ensure the suite passes without regressions in related functional areas. + * **Isolated:** Run `clojure -M:dev:test --focus backend-tests.my-ns-test` for the specific test namespace. + * **Regression:** Run `clojure -M:dev:test` to ensure the suite passes without regressions in related functional areas. ### 2. Code Quality & Formatting * **Linting:** All code must pass `clj-kondo` checks (run `pnpm run lint:clj`) * **Formatting:** All the code must pass the formatting check (run `pnpm run - check-fmt`). Use the `pnpm run fmt` fix the formatting issues. Avoid "dirty" + check-fmt`). Use `pnpm run fmt` to fix formatting issues. Avoid "dirty" diffs caused by unrelated whitespace changes. * **Type Hinting:** Use explicit JVM type hints (e.g., `^String`, `^long`) in performance-critical paths to avoid reflection overhead. @@ -40,18 +40,18 @@ namespaces structure: - `app.db.*` – Database layer - `app.tasks.*` – Background job tasks - `app.main` – Integrant system setup and entrypoint -- `app.loggers` – Internal loggers (auditlog, mattermost, etc) (do not be confused with `app.common.loggin`) +- `app.loggers` – Internal loggers (auditlog, mattermost, etc.) (not to be confused with `app.common.logging`) ### RPC -The PRC methods are implement in a some kind of multimethod structure using -`app.util.serivices` namespace. The main RPC methods are collected under +The RPC methods are implemented using a multimethod-like structure via the +`app.util.services` namespace. The main RPC methods are collected under `app.rpc.commands` namespace and exposed under `/api/rpc/command/`. -The RPC method accepts POST and GET requests indistinctly and uses `Accept` -header for negotiate the response encoding (which can be transit, the defaut or -plain json). It also accepts transit (defaut) or json as input, which should be -indicated using `Content-Type` header. +The RPC method accepts POST and GET requests indistinctly and uses the `Accept` +header to negotiate the response encoding (which can be Transit — the default — +or plain JSON). It also accepts Transit (default) or JSON as input, which should +be indicated using the `Content-Type` header. The main convention is: use `get-` prefix on RPC name when we want READ operation. @@ -107,7 +107,7 @@ are config maps with `::ig/ref` for dependencies. Components implement (db/insert! conn :table row))) ``` -Almost all methods on `app.db` namespace accepts `pool`, `conn` or +Almost all methods in the `app.db` namespace accept `pool`, `conn`, or `cfg` as params. Migrations live in `src/app/migrations/` as numbered SQL files. They run automatically on startup. @@ -116,7 +116,7 @@ Migrations live in `src/app/migrations/` as numbered SQL files. They run automat ### Error Handling The exception helpers are defined on Common module, and are available under -`app.commin.exceptions` namespace. +`app.common.exceptions` namespace. Example of raising an exception: @@ -132,10 +132,11 @@ Common types: `:not-found`, `:validation`, `:authorization`, `:conflict`, `:inte ### Performance Macros (`app.common.data.macros`) -Always prefer these macros over their `clojure.core` equivalents — they compile to faster JavaScript: +Always prefer these macros over their `clojure.core` equivalents — they provide +optimized implementations: ```clojure -(dm/select-keys m [:a :b]) ;; ~6x faster than core/select-keys +(dm/select-keys m [:a :b]) ;; faster than core/select-keys (dm/get-in obj [:a :b :c]) ;; faster than core/get-in (dm/str "a" "b" "c") ;; string concatenation ``` diff --git a/common/AGENTS.md b/common/AGENTS.md index 996a7f4953..2659b83939 100644 --- a/common/AGENTS.md +++ b/common/AGENTS.md @@ -1,14 +1,13 @@ # Penpot Common – Agent Instructions -A shared module with code written in Clojure, ClojureScript and -JavaScript. Contains multplatform code that can be used and executed -from frontend, backend or exporter modules. It uses clojure reader -conditionals for specify platform specific implementation. +A shared module with code written in Clojure, ClojureScript, and +JavaScript. Contains multiplatform code that can be used and executed +from the frontend, backend, or exporter modules. It uses Clojure reader +conditionals to specify platform-specific implementations. ## General Guidelines -This is a golden rule for common module development. To ensure -consistency across the penpot stack, all contributions must adhere to +To ensure consistency across the Penpot stack, all contributions must adhere to these criteria: ### 1. Testing & Validation @@ -16,11 +15,11 @@ these criteria: If code is added or modified in `src/`, corresponding tests in `test/common_tests/` must be added or updated. -* **Environment:** Tests should run in a JS (nodejs) and JVM + * **Environment:** Tests should run in both JS (Node.js) and JVM environments. * **Location:** Place tests in the `test/common_tests/` directory, following the namespace structure of the source code (e.g., `app.common.colors` -> `common-tests.colors-test`). -* **Execution:** The tests should be executed on both: JS (nodejs) and JVM environments +* **Execution:** Tests should be executed on both JS (Node.js) and JVM environments: * **Isolated:** * JS: To run a focused ClojureScript unit test: edit the `test/common_tests/runner.cljs` to narrow the test suite, then @@ -37,8 +36,8 @@ If code is added or modified in `src/`, corresponding tests in * **Formatting:** All code changes must pass the formatting check * Run `pnpm run check-fmt:clj` for CLJ/CLJS/CLJC * Run `pnpm run check-fmt:js` for JS - * Use the `pnpm run fmt` fix all the formatting issues (`pnpm run - fmt:clj` or `pnpm run fmt:js` for isolated formatting fix) + * Use `pnpm run fmt` to fix all formatting issues (`pnpm run + fmt:clj` or `pnpm run fmt:js` for isolated formatting fix). ## Code Conventions @@ -50,16 +49,16 @@ namespaces structure: - `app.common.types.*` – Shared data types for shapes, files, pages using Malli schemas - `app.common.schema` – Malli abstraction layer, exposes the most used functions from malli - `app.common.geom.*` – Geometry and shape transformation helpers -- `app.common.data` – Generic helpers used around all application -- `app.common.math` – Generic math helpers used around all aplication +- `app.common.data` – Generic helpers used across the entire application +- `app.common.math` – Generic math helpers used across the entire application - `app.common.json` – Generic JSON encoding/decoding helpers - `app.common.data.macros` – Performance macros used everywhere ### Reader Conditionals -We use reader conditionals to target for differentiate an -implementation depending on the target platform where code should run: +We use reader conditionals to differentiate implementations depending on the +target platform where the code runs: ```clojure #?(:clj (import java.util.UUID) diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index b6f63794cc..b4ad811522 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -1,13 +1,12 @@ # Penpot Frontend – Agent Instructions -ClojureScript based frontend application that uses React, RxJS as main +ClojureScript-based frontend application that uses React and RxJS as its main architectural pieces. - ## General Guidelines -This is a golden rule for frontend development standards. To ensure consistency -across the penpot stack, all contributions must adhere to these criteria: +To ensure consistency across the Penpot stack, all contributions must adhere to +these criteria: ### 1. Testing & Validation @@ -22,7 +21,7 @@ If code is added or modified in `src/`, corresponding tests in running backend. Test are developed using cljs.test. * **Mocks & Stubs:** * Use proper mocks for any side-effecting functions (e.g., API calls, storage access). - * Avoid testing through the UI (DOM), we have e2e tests for that/ + * Avoid testing through the UI (DOM); we have e2e tests for that. * Use `with-redefs` or similar ClojureScript mocking utilities to isolate the logic under test. * **No Flakiness:** Tests must be deterministic. Do not use `setTimeout` or real network calls. Use synchronous mocks for asynchronous workflows where @@ -34,15 +33,15 @@ If code is added or modified in `src/`, corresponding tests in * **Isolated:** To run a focused ClojureScript unit test: edit the `test/frontend_tests/runner.cljs` to narrow the test suite, then `pnpm run test`. - * **Regression:** Run `pnpm run test` without modifications on the runner (preferred) + * **Regression:** To run `pnpm run test` without modifications on the runner (preferred) #### Integration Tests (Playwright) Integration tests are developed under `frontend/playwright` directory, we use -mocks for remove communication with backend. +mocks for remote communication with the backend. -You should not add, modify or run the integration tests unless it exlicitly asked for. +You should not add, modify or run the integration tests unless explicitly asked. ``` @@ -50,7 +49,7 @@ pnpm run test:e2e # Playwright e2e tests pnpm run test:e2e --grep "pattern" # Single e2e test by pattern ``` -Ensure everything installed before executing tests with `./scripts/setup` script. +Ensure everything is installed before executing tests with the `./scripts/setup` script. ### 2. Code Quality & Formatting @@ -68,8 +67,8 @@ Ensure everything installed before executing tests with `./scripts/setup` script ### 3. Implementation Rules -* **Logic vs. View:** If logic is embedded in an UI component, extract it into a - function in the same namespace if is only used locally or look for a helper +* **Logic vs. View:** If logic is embedded in a UI component, extract it into a + function in the same namespace if it is only used locally, or look for a helper namespace to make it unit-testable. @@ -113,7 +112,7 @@ State is a single atom managed by a Potok store. Events implement protocols ``` The state is located under `app.main.store` namespace where we have -the `emit!` function responsible of emiting events. +the `emit!` function responsible for emitting events. Example: @@ -128,15 +127,14 @@ Example: (st/emit! (my-event))) ``` -On `app.main.refs` we have reactive references which lookup into the main state -for just inner data or precalculated data. That references are very usefull but -should be used with care because, per example if we have complex operation, this -operation will be executed on each state change, and sometimes is better to have -simple references and use react `use-memo` for more granular memoization. +On `app.main.refs` we have reactive references which look up the main state +for inner data or precalculated data. These references are very useful but +should be used with care because, for example, if we have a complex operation, +this operation will be executed on each state change. Sometimes it is better to +have simple references and use React `use-memo` for more granular memoization. -Prefer helpers from `app.util.dom` instead of using direct dom calls, if no helper is -available, prefer adding a new helper for handling it and the use the -new helper. +Prefer helpers from `app.util.dom` instead of using direct DOM calls. If no +helper is available, prefer adding a new helper and then using it. ### UI Components (React & Rumext: mf/defc) @@ -175,19 +173,20 @@ lifecycle management. These are analogous to standard React hooks: ``` The `mf/use-state` in difference with React.useState, returns an atom-like -object, where you can use `swap!` or `reset!` for to perform an update and -`deref` for get the current value. +object, where you can use `swap!` or `reset!` to perform an update and +`deref` to get the current value. -You also has `mf/deref` hook (which does not follow the `use-` naming pattern) -and it's purpose is watch (subscribe to changes) on atom or derived atom (from -okulary) and get the current value. Is mainly used for subscribe to lenses -defined in `app.main.refs` or (private lenses defined in namespaces). +You also have the `mf/deref` hook (which does not follow the `use-` naming +pattern) and its purpose is to watch (subscribe to changes on) an atom or +derived atom (from okulary) and get the current value. It is mainly used to +subscribe to lenses defined in `app.main.refs` or private lenses defined in +namespaces. Rumext also comes with improved syntax macros as alternative to `mf/use-effect` and `mf/use-memo` functions. Examples: -Example for `mf/with-memo` macro: +Example for `mf/with-effect` macro: ```clj ;; Using functions @@ -221,7 +220,7 @@ Example for `mf/with-memo` macro: (filterv #(= team-id (:team-id %))))) ``` -Prefer using the macros for it syntax simplicity. +Prefer using the macros for their syntax simplicity. #### 4. Component Usage (Hiccup Syntax) @@ -282,22 +281,22 @@ CSS modules pattern): - If a value isn't in the DS, use the `px2rem(n)` mixin: `@use "ds/_utils.scss" as *; padding: px2rem(23);`. - Do **not** create new SCSS variables for one-off values. -- Use physical directions with logical ones to support RTL/LTR naturally. - - ❌ `margin-left`, `padding-right`, `left`, `right`. - - ✅ `margin-inline-start`, `padding-inline-end`, `inset-inline-start`. -- Always use the `use-typography` mixin from `ds/typography.scss`. - - ✅ `@include t.use-typography("title-small");` +- Use physical directions with logical ones to support RTL/LTR naturally: + - Avoid: `margin-left`, `padding-right`, `left`, `right`. + - Prefer: `margin-inline-start`, `padding-inline-end`, `inset-inline-start`. +- Always use the `use-typography` mixin from `ds/typography.scss`: + - Example: `@include t.use-typography("title-small");` - Use `$br-*` for radius and `$b-*` for thickness from `ds/_borders.scss`. - Use only tokens from `ds/colors.scss`. Do **NOT** use `design-tokens.scss` or legacy color variables. -- Use mixins only those defined in`ds/mixins.scss`. Avoid legacy mixins like +- Use mixins only from `ds/mixins.scss`. Avoid legacy mixins like `@include flexCenter;`. Write standard CSS (flex/grid) instead. - Use the `@use` instead of `@import`. If you go to refactor existing SCSS file, try to replace all `@import` with `@use`. Example: `@use "ds/_sizes.scss" as *;` (Use `as *` to expose variables directly). - Avoid deep selector nesting or high-specificity (IDs). Flatten selectors: - - ❌ `.card { .title { ... } }` - - ✅ `.card-title { ... }` + - Avoid: `.card { .title { ... } }` + - Prefer: `.card-title { ... }` - Leverage component-level CSS variables for state changes (hover/focus) instead of rewriting properties. @@ -324,5 +323,5 @@ Always prefer these macros over their `clojure.core` equivalents — they compil ### Configuration `src/app/config.clj` reads globally defined variables and exposes precomputed -configuration vars ready to be used from other parts of the application +configuration values ready to be used from other parts of the application. diff --git a/frontend/packages/draft-js/index.js b/frontend/packages/draft-js/index.js index 23bd20af01..84b7190537 100644 --- a/frontend/packages/draft-js/index.js +++ b/frontend/packages/draft-js/index.js @@ -366,12 +366,19 @@ export function getInlineStyle(state, blockKey, offset) { const NEWLINE_REGEX = /\r\n?|\n/g; function splitTextIntoTextBlocks(text) { + if (text == null) { + return []; + } return text.split(NEWLINE_REGEX); } export function insertText(state, text, attrs, inlineStyles) { const blocks = splitTextIntoTextBlocks(text); + if (blocks.length === 0) { + return state; + } + const character = CharacterMetadata.create({style: OrderedSet(inlineStyles)}); let blockArray = DraftPasteProcessor.processText( diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 8c1b8021a0..4c3e60f7d4 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -295,6 +295,22 @@ (def default-paste-from-blob (create-paste-from-blob false)) +(defn- clipboard-permission-error? + "Check if the given error is a clipboard permission error + (NotAllowedError DOMException)." + [cause] + (and (instance? js/DOMException cause) + (= (.-name cause) "NotAllowedError"))) + +(defn- on-clipboard-permission-error + [cause] + (if (clipboard-permission-error? cause) + (rx/of (ntf/show {:content (tr "errors.clipboard-permission-denied") + :type :toast + :level :warning + :timeout 5000})) + (rx/throw cause))) + (defn paste-from-clipboard "Perform a `paste` operation using the Clipboard API." [] @@ -303,7 +319,8 @@ (watch [_ _ _] (->> (clipboard/from-navigator default-options) (rx/mapcat default-paste-from-blob) - (rx/take 1))))) + (rx/take 1) + (rx/catch on-clipboard-permission-error))))) (defn paste-from-event "Perform a `paste` operation from user emmited event." @@ -483,11 +500,20 @@ (-> entry t/decode-str paste-transit-props)) (on-error [cause] - (let [data (ex-data cause)] - (if (:not-implemented data) - (rx/of (ntf/warn (tr "errors.clipboard-not-implemented"))) - (js/console.error "Clipboard error:" cause)) - (rx/empty)))] + (cond + (clipboard-permission-error? cause) + (rx/of (ntf/show {:content (tr "errors.clipboard-permission-denied") + :type :toast + :level :warning + :timeout 5000})) + + (:not-implemented (ex-data cause)) + (rx/of (ntf/warn (tr "errors.clipboard-not-implemented"))) + + :else + (do + (js/console.error "Clipboard error:" cause) + (rx/empty))))] (->> (clipboard/from-navigator default-options) (rx/mapcat #(.text %)) diff --git a/frontend/src/app/main/data/workspace/path/changes.cljs b/frontend/src/app/main/data/workspace/path/changes.cljs index c2bc293da3..8eb73b3663 100644 --- a/frontend/src/app/main/data/workspace/path/changes.cljs +++ b/frontend/src/app/main/data/workspace/path/changes.cljs @@ -69,7 +69,7 @@ content (if (and (not preserve-move-to) (= (-> content last :command) :move-to)) (path/content (take (dec (count content)) content)) - content)] + (path/content content))] (st/set-content state content))) ptk/WatchEvent diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 135df9ecb3..616cd17b98 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -221,12 +221,13 @@ handle-pasted-text (fn [text _ _] - (let [current-block-styles (ted/get-editor-current-block-data state) - inline-styles (ted/get-editor-current-inline-styles state) - style (merge current-block-styles inline-styles) - state (-> (ted/insert-text state text style) - (handle-change))] - (st/emit! (dwt/update-editor-state shape state))) + (when (seq text) + (let [current-block-styles (ted/get-editor-current-block-data state) + inline-styles (ted/get-editor-current-inline-styles state) + style (merge current-block-styles inline-styles) + state (-> (ted/insert-text state text style) + (handle-change))] + (st/emit! (dwt/update-editor-state shape state)))) "handled")] (mf/use-layout-effect on-mount) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index ce2a8ae403..120da5dced 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -205,10 +205,10 @@ detach-value (mf/use-fn - (mf/deps on-detach index) + (mf/deps on-detach index color) (fn [_] (when on-detach - (on-detach index)))) + (on-detach index color)))) handle-select (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs index ec5770eabb..7cb0956d11 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs @@ -80,7 +80,7 @@ on-color-detach (mf/use-fn (mf/deps index on-color-detach) - (fn [color] + (fn [_ color] (on-color-detach index color))) on-remove diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 0e9d15635d..aa46be4497 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -170,8 +170,17 @@ [^js node name] (let [name (str/camel name)] (loop [current node] - (if (or (nil? current) (obj/in? (.-dataset current) name)) + (cond + (nil? current) + nil + + (not= (.-nodeType current) js/Node.ELEMENT_NODE) + (recur (.-parentElement current)) + + (obj/in? (.-dataset current) name) current + + :else (recur (.-parentElement current)))))) (defn get-parent-with-selector diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 4e52ddd5ee..1fd7de80e5 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1328,6 +1328,10 @@ msgstr "Character limit exceeded" msgid "errors.clipboard-not-implemented" msgstr "Your browser cannot do this operation" +#: src/app/main/data/workspace/clipboard.cljs +msgid "errors.clipboard-permission-denied" +msgstr "Clipboard access denied. Please allow clipboard permissions in your browser to paste content" + #: src/app/main/errors.cljs:235 msgid "errors.comment-error" msgstr "There was an error with the comment" diff --git a/opencode.json b/opencode.json index 0ab56b6192..6376bc70e5 100644 --- a/opencode.json +++ b/opencode.json @@ -1,4 +1,4 @@ { "$schema": "https://opencode.ai/config.json", - "instructions": ["CONTRIBUTING.md", "AGENTS.md"] + "instructions": ["AGENTS.md"] } diff --git a/package.json b/package.json index 3580392cf5..687d4def63 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,9 @@ "fmt": "./scripts/fmt" }, "devDependencies": { - "@github/copilot": "^1.0.2", + "@github/copilot": "^1.0.11", "@types/node": "^20.12.7", - "esbuild": "^0.25.9" + "esbuild": "^0.25.9", + "opencode-ai": "^1.3.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bec7b49e31..4d683348b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,17 @@ importers: .: devDependencies: '@github/copilot': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.0.11 + version: 1.0.11 '@types/node': specifier: ^20.12.7 version: 20.19.37 esbuild: specifier: ^0.25.9 version: 0.25.12 + opencode-ai: + specifier: ^1.3.0 + version: 1.3.0 packages: @@ -176,44 +179,44 @@ packages: cpu: [x64] os: [win32] - '@github/copilot-darwin-arm64@1.0.2': - resolution: {integrity: sha512-dYoeaTidsphRXyMjvAgpjEbBV41ipICnXURrLFEiATcjC4IY6x2BqPOocrExBYW/Tz2VZvDw51iIZaf6GXrTmw==} + '@github/copilot-darwin-arm64@1.0.11': + resolution: {integrity: sha512-wdKimjtbsVeXqMqQSnGpGBPFEYHljxXNuWeH8EIJTNRgFpAsimcivsFgql3Twq4YOp0AxfsH36icG4IEen30mA==} cpu: [arm64] os: [darwin] hasBin: true - '@github/copilot-darwin-x64@1.0.2': - resolution: {integrity: sha512-8+Z9dYigEfXf0wHl9c2tgFn8Cr6v4RAY8xTgHMI9mZInjQyxVeBXCxbE2VgzUtDUD3a705Ka2d8ZOz05aYtGsg==} + '@github/copilot-darwin-x64@1.0.11': + resolution: {integrity: sha512-VeuPv8rzBVGBB8uDwMEhcHBpldoKaq26yZ5YQm+G9Ka5QIF+1DMah8ZNRMVsTeNKkb1ji9G8vcuCsaPbnG3fKg==} cpu: [x64] os: [darwin] hasBin: true - '@github/copilot-linux-arm64@1.0.2': - resolution: {integrity: sha512-ik0Y5aTXOFRPLFrNjZJdtfzkozYqYeJjVXGBAH3Pp1nFZRu/pxJnrnQ1HrqO/LEgQVbJzAjQmWEfMbXdQIxE4Q==} + '@github/copilot-linux-arm64@1.0.11': + resolution: {integrity: sha512-/d8p6RlFYKj1Va2hekFIcYNMHWagcEkaxgcllUNXSyQLnmEtXUkaWtz62VKGWE+n/UMkEwCB6vI2xEwPTlUNBQ==} cpu: [arm64] os: [linux] hasBin: true - '@github/copilot-linux-x64@1.0.2': - resolution: {integrity: sha512-mHSPZjH4nU9rwbfwLxYJ7CQ90jK/Qu1v2CmvBCUPfmuGdVwrpGPHB5FrB+f+b0NEXjmemDWstk2zG53F7ppHfw==} + '@github/copilot-linux-x64@1.0.11': + resolution: {integrity: sha512-UujTRO3xkPFC1CybchBbCnaTEAG6JrH0etIst07JvfekMWgvRxbiCHQPpDPSzBCPiBcGu0gba0/IT+vUCORuIw==} cpu: [x64] os: [linux] hasBin: true - '@github/copilot-win32-arm64@1.0.2': - resolution: {integrity: sha512-tLW2CY/vg0fYLp8EuiFhWIHBVzbFCDDpohxT/F/XyMAdTVSZLnopCcxQHv2BOu0CVGrYjlf7YOIwPfAKYml1FA==} + '@github/copilot-win32-arm64@1.0.11': + resolution: {integrity: sha512-EOW8HUM+EmnHEZEa+iUMl4pP1+2eZUk2XCbynYiMehwX9sidc4BxEHp2RuxADSzFPTieQEWzgjQmHWrtet8pQg==} cpu: [arm64] os: [win32] hasBin: true - '@github/copilot-win32-x64@1.0.2': - resolution: {integrity: sha512-cFlc3xMkKKFRIYR00EEJ2XlYAemeh5EZHsGA8Ir2G0AH+DOevJbomdP1yyCC5gaK/7IyPkHX3sGie5sER2yPvQ==} + '@github/copilot-win32-x64@1.0.11': + resolution: {integrity: sha512-fKGkSNamzs3h9AbmswNvPYJBORCb2Y8CbusijU3C7fT3ohvqnHJwKo5iHhJXLOKZNOpFZgq9YKha410u9sIs6Q==} cpu: [x64] os: [win32] hasBin: true - '@github/copilot@1.0.2': - resolution: {integrity: sha512-716SIZMYftldVcJay2uZOzsa9ROGGb2Mh2HnxbDxoisFsWNNgZlQXlV7A+PYoGsnAo2Zk/8e1i5SPTscGf2oww==} + '@github/copilot@1.0.11': + resolution: {integrity: sha512-cptVopko/tNKEXyBP174yBjHQBEwg6CqaKN2S0M3J+5LEB8u31bLL75ioOPd+5vubqBrA0liyTdcHeZ8UTRbmg==} hasBin: true '@types/node@20.19.37': @@ -224,6 +227,70 @@ packages: engines: {node: '>=18'} hasBin: true + opencode-ai@1.3.0: + resolution: {integrity: sha512-il/dC3B55m5mZV2u72emfPqkZBTzrlZwqGI4Ds5Ld6kt2LTUzBZtKB8sOfy7Bmw2qIel0hLZdoKc8wxLjaXQDw==} + hasBin: true + + opencode-darwin-arm64@1.3.0: + resolution: {integrity: sha512-OB+yl/BZkjQhnjjFc+KT57iqhPlXNq3E0oIcHHlGiG63L2LTY3zfi9OhzaoemL+or2CWnpCITUe91yTAddiSEQ==} + cpu: [arm64] + os: [darwin] + + opencode-darwin-x64-baseline@1.3.0: + resolution: {integrity: sha512-Th5yiWOSDeEcjnKWhR8b267Uf8r+jwLFhv30JK4x07Zdmu3Jjjr6TdMvjLgEOv3PWmHf/1yYz22Xachb+QST0A==} + cpu: [x64] + os: [darwin] + + opencode-darwin-x64@1.3.0: + resolution: {integrity: sha512-jivDUpmhzkT7WZp7pXVSb9fdnEVuhKBsnve/9fIkI/UFHxomiZ2NIaNRbHxG26PYT9a1IR4D5QvXBq623g2Mnw==} + cpu: [x64] + os: [darwin] + + opencode-linux-arm64-musl@1.3.0: + resolution: {integrity: sha512-EmXBHyRSzWCnD/KDpaSi8ldgjOa+1t5c5tRASyL/lnbinsrZekxub3lI+oxRvKJXESKdgq9EP4gkp6t2fqGsFw==} + cpu: [arm64] + os: [linux] + + opencode-linux-arm64@1.3.0: + resolution: {integrity: sha512-rWEEKo4oqgJ/zk670ywg6uhEPwbUIQCwYCeh+xJ3IlgPltQNiIjqUbzbRqAmEfI1Uj9DCdbZ2TUtHayRv8umKw==} + cpu: [arm64] + os: [linux] + + opencode-linux-x64-baseline-musl@1.3.0: + resolution: {integrity: sha512-sb7LyPlf+5/t4pQ3whcHPVlb7R7SRY0Bgjgy55amEs3xRuKnC3BfSoj8CAoY50M/yVAbOj0haoxu4LFixljwNw==} + cpu: [x64] + os: [linux] + + opencode-linux-x64-baseline@1.3.0: + resolution: {integrity: sha512-STZtcgGgeRlaFCmkk+mNm+01d02JCzCPvP9kWwNpRF6FBGTcFZ97MxEoGvk+7mEqMueImVQZOR21NiYN6anQhw==} + cpu: [x64] + os: [linux] + + opencode-linux-x64-musl@1.3.0: + resolution: {integrity: sha512-Jc/EbYgqmT2J2WLPm7EQWBYfSqetWTrI4Ipc4KFrSB/LbM/7lfXkjpemjQaYNlDTVkvPXaUPFJUpisH64xZ+4g==} + cpu: [x64] + os: [linux] + + opencode-linux-x64@1.3.0: + resolution: {integrity: sha512-U9aS0wl0uBDxXncqSYhYBDDQP2ZwiTiuJSLM6MgtFJTbUXuTZZCKmQ8p7C5/+Nxpl4sY5xK+ZaCJcS3k3WGN3g==} + cpu: [x64] + os: [linux] + + opencode-windows-arm64@1.3.0: + resolution: {integrity: sha512-3iWo9lOctaWQ+8QHRKszINPTLjLtb0ztzedlvdY5HAiot9MUK/G5MHeskutxQ7sMvTACiAp02ey+Ml/f/jyf7Q==} + cpu: [arm64] + os: [win32] + + opencode-windows-x64-baseline@1.3.0: + resolution: {integrity: sha512-pYuY+9LqPLB/GrlZQr67Cl8RlV6vcay4fW8L3TjabwJOinFMDX9OpNo+DkdKJW7YtPtHD78cXaNDEV8tv9Nx2A==} + cpu: [x64] + os: [win32] + + opencode-windows-x64@1.3.0: + resolution: {integrity: sha512-iFd/6GwfM3jlI2tOb3f12m5ddDY8Ug2HiUU1xmxWJvDnbDBdftlHrzD5twlbIHnKoGvohepX8iWk+A/UN2cXKQ==} + cpu: [x64] + os: [win32] + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -307,32 +374,32 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@github/copilot-darwin-arm64@1.0.2': + '@github/copilot-darwin-arm64@1.0.11': optional: true - '@github/copilot-darwin-x64@1.0.2': + '@github/copilot-darwin-x64@1.0.11': optional: true - '@github/copilot-linux-arm64@1.0.2': + '@github/copilot-linux-arm64@1.0.11': optional: true - '@github/copilot-linux-x64@1.0.2': + '@github/copilot-linux-x64@1.0.11': optional: true - '@github/copilot-win32-arm64@1.0.2': + '@github/copilot-win32-arm64@1.0.11': optional: true - '@github/copilot-win32-x64@1.0.2': + '@github/copilot-win32-x64@1.0.11': optional: true - '@github/copilot@1.0.2': + '@github/copilot@1.0.11': optionalDependencies: - '@github/copilot-darwin-arm64': 1.0.2 - '@github/copilot-darwin-x64': 1.0.2 - '@github/copilot-linux-arm64': 1.0.2 - '@github/copilot-linux-x64': 1.0.2 - '@github/copilot-win32-arm64': 1.0.2 - '@github/copilot-win32-x64': 1.0.2 + '@github/copilot-darwin-arm64': 1.0.11 + '@github/copilot-darwin-x64': 1.0.11 + '@github/copilot-linux-arm64': 1.0.11 + '@github/copilot-linux-x64': 1.0.11 + '@github/copilot-win32-arm64': 1.0.11 + '@github/copilot-win32-x64': 1.0.11 '@types/node@20.19.37': dependencies: @@ -367,4 +434,55 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + opencode-ai@1.3.0: + optionalDependencies: + opencode-darwin-arm64: 1.3.0 + opencode-darwin-x64: 1.3.0 + opencode-darwin-x64-baseline: 1.3.0 + opencode-linux-arm64: 1.3.0 + opencode-linux-arm64-musl: 1.3.0 + opencode-linux-x64: 1.3.0 + opencode-linux-x64-baseline: 1.3.0 + opencode-linux-x64-baseline-musl: 1.3.0 + opencode-linux-x64-musl: 1.3.0 + opencode-windows-arm64: 1.3.0 + opencode-windows-x64: 1.3.0 + opencode-windows-x64-baseline: 1.3.0 + + opencode-darwin-arm64@1.3.0: + optional: true + + opencode-darwin-x64-baseline@1.3.0: + optional: true + + opencode-darwin-x64@1.3.0: + optional: true + + opencode-linux-arm64-musl@1.3.0: + optional: true + + opencode-linux-arm64@1.3.0: + optional: true + + opencode-linux-x64-baseline-musl@1.3.0: + optional: true + + opencode-linux-x64-baseline@1.3.0: + optional: true + + opencode-linux-x64-musl@1.3.0: + optional: true + + opencode-linux-x64@1.3.0: + optional: true + + opencode-windows-arm64@1.3.0: + optional: true + + opencode-windows-x64-baseline@1.3.0: + optional: true + + opencode-windows-x64@1.3.0: + optional: true + undici-types@6.21.0: {} diff --git a/render-wasm/AGENTS.md b/render-wasm/AGENTS.md index dfe9c3def9..511b7da0c9 100644 --- a/render-wasm/AGENTS.md +++ b/render-wasm/AGENTS.md @@ -59,4 +59,4 @@ parent/child relationships are tracked separately. The WASM module is loaded by `app.render-wasm.*` namespaces in the frontend. ClojureScript calls exported Rust functions to push shape data, then calls `render_frame`. Do not change export function -signatures without updating the ClojureScript bridge. +signatures without updating the corresponding ClojureScript bridge.