From 6de41f072c10cf32824bd7db9bc9851afa75924c Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 28 Apr 2026 14:24:19 +0200 Subject: [PATCH] :sparkles: Add initial Serena project --- .serena/.gitignore | 2 + .serena/memories/devenv/cljs-repl-access.md | 79 +++++++++ .../js-api-to-clojurescript-binding.md | 45 +++++ .serena/project.yml | 155 ++++++++++++++++++ 4 files changed, 281 insertions(+) create mode 100644 .serena/.gitignore create mode 100644 .serena/memories/devenv/cljs-repl-access.md create mode 100644 .serena/memories/plugins/js-api-to-clojurescript-binding.md create mode 100644 .serena/project.yml diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000000..2e510aff58 --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1,2 @@ +/cache +/project.local.yml diff --git a/.serena/memories/devenv/cljs-repl-access.md b/.serena/memories/devenv/cljs-repl-access.md new file mode 100644 index 0000000000..86f2f1794f --- /dev/null +++ b/.serena/memories/devenv/cljs-repl-access.md @@ -0,0 +1,79 @@ +# ClojureScript REPL Access via shadow-cljs + +## Overview +The penpot frontend uses shadow-cljs with `:target :esm` and multi-module code splitting. The CLJS REPL evaluates code in the browser runtime via a websocket connection. + +## Known Pitfall: Rasterizer vs Workspace Runtime +The workspace page embeds a rasterizer iframe (`rasterizer.html`) that also loads the `:main` shadow-cljs build. Both runtimes register with shadow-cljs. If the rasterizer connects first, the REPL will target it instead of the workspace — and the rasterizer has an **empty app state** (its own `defonce` store instance). + +**Symptoms:** `@st/state` returns nil, `(.-title js/document)` returns "Penpot - Rasterizer". + +**Fix:** Restart the devenv (`docker restart penpot-devenv-main`) and reload the browser. After a clean restart, the workspace runtime typically connects first. + +**Verification:** Run `(.-title js/document)` — it should show your file name (e.g. "New File 1 - Penpot"), not "Penpot - Rasterizer". + +## Method 1: Interactive REPL (inside container) +```bash +docker exec -it penpot-devenv-main bash +cd /home/penpot/penpot/frontend +npx shadow-cljs cljs-repl main +``` +Requires an active browser session with penpot open. Type `:cljs/quit` to exit. + +## Method 2: Scriptable eval via clj-eval (preferred for automation) +```bash +docker exec penpot-devenv-main bash -c "cd /home/penpot/penpot/frontend && \ + printf '\n' | timeout 10 npx shadow-cljs clj-eval --stdin 2>&1" +``` + +For CLJS evaluation, wrap in `shadow.cljs.devtools.api/cljs-eval`: +```bash +docker exec penpot-devenv-main bash -c "cd /home/penpot/penpot/frontend && \ + printf '(shadow.cljs.devtools.api/cljs-eval :main \"\" {})\n' | \ + timeout 10 npx shadow-cljs clj-eval --stdin 2>&1" +``` + +Return format: `{:results ["" ...] :out "" :err "" :ns cljs.user}` + +You can target a specific runtime by client-id: +``` +(shadow.cljs.devtools.api/cljs-eval :main "" {:client-id 5}) +``` + +To list connected runtimes and their client-ids: +``` +(shadow.cljs.devtools.api/repl-runtimes :main) +``` + +## Method 3: nREPL client (tools/nrepl_eval.py) +A custom Python nREPL client exists at `tools/nrepl_eval.py`. However, it uses `(shadow/repl :main)` to switch to CLJS mode, which doesn't reliably select the correct runtime. **Prefer Method 2 for automation.** + +## Accessing App State +```clojure +(require '[app.main.store :as st]) +(some? @st/state) ;; should be true + +;; Get current page id +(:current-page-id @st/state) + +;; Get objects on current page +(let [state @st/state + page-id (:current-page-id state) + objects (get-in state [:workspace-data :pages-index page-id :objects])] + (count objects)) + +;; Get a specific shape +(let [state @st/state + page-id (:current-page-id state) + objects (get-in state [:workspace-data :pages-index page-id :objects]) + shape (get objects (parse-uuid "some-uuid-here"))] + (select-keys shape [:name :type :component-id :component-file :component-root])) +``` + +## Notes +- nREPL server runs on port 3447 inside the container, mapped to host +- The `:main` build has multiple modules: shared, main, main-workspace, rasterizer, etc. +- `app.main.store/state` is a potok store (wrapping an okulary atom) created via `defonce` +- Ignore the "WARNING: shadow-cljs not installed in project" message — it works via the running server +- Use `timeout` to avoid hanging if the browser is disconnected +- `DO NOT` call `shadow.cljs.devtools.api/repl-runtime-select` with a runtime that can't eval — it will jam the REPL until restart diff --git a/.serena/memories/plugins/js-api-to-clojurescript-binding.md b/.serena/memories/plugins/js-api-to-clojurescript-binding.md new file mode 100644 index 0000000000..6452a5503d --- /dev/null +++ b/.serena/memories/plugins/js-api-to-clojurescript-binding.md @@ -0,0 +1,45 @@ +# How the Plugin JS API connects to ClojureScript + +## Type Definitions +- `plugins/libs/plugin-types/index.d.ts` contains TypeScript type declarations (e.g. `ShapeBase`, `LibraryComponent`). +- These are **type-only** — no runtime code. The actual objects are constructed in ClojureScript. + +## Runtime Shape Proxy +- `frontend/src/app/plugins/shape.cljs` builds the JS shape proxy via `obj/reify`. +- Each method/property from the TS interface (e.g. `:component`, `:isComponentRoot`, `:componentHead`) is defined as a keyword entry in the `obj/reify` form, with a ClojureScript function as the implementation. +- The proxy is created by the `shape-proxy` function, which takes `plugin-id`, `file-id`, `page-id`, and shape `id`, and closes over them. + +## Library Proxies +- `frontend/src/app/plugins/library.cljs` defines proxies for library types like `LibraryComponentProxy` (via `lib-component-proxy`), also using `obj/reify`. +- The proxy satisfies the `LibraryComponent` TS interface, exposing `.id`, `.name`, `.path`, etc. + +## Circular Dependency Resolution +- `shape.cljs` and `library.cljs` have circular dependencies (shapes reference library component proxies and vice versa). +- `shape.cljs` declares forward references as mutable `def nil` vars (e.g. `(def lib-component-proxy nil)`, line 144). +- `frontend/src/app/plugins.cljs` patches them at load time: `(set! shape/lib-component-proxy library/lib-component-proxy)`. +- Same pattern for `lib-typography-proxy?` and `variant-proxy`. + +## Helper Utilities (`frontend/src/app/plugins/utils.cljs`) +- `locate-shape` — finds a shape by file-id, page-id, id +- `locate-objects` — gets the object tree for a page +- `locate-component` — finds the **outermost** instance root and resolves the component (uses `ctn/get-instance-root` + `ctf/resolve-component`). **Beware**: walks to outermost root, not nearest head. +- `locate-library-component` — direct lookup by file-id and component-id from file data +- `locate-file` — looks up a file by id from state + +## Key Domain Namespaces +- `app.common.types.component` (aliased `ctk`) — component predicates: `instance-root?`, `instance-head?`, `in-component-copy?`, `is-variant?` +- `app.common.types.container` (aliased `ctn`) — container/tree operations: `in-any-component?`, `get-instance-root`, `get-head-shape`, `inside-component-main?` +- `app.common.types.file` (aliased `ctf`) — file-level operations: `resolve-component`, `get-ref-shape` + +## Shape Component Data +- Component instance shapes carry `:component-id` and `:component-file` attributes directly on the shape map. +- `:component-root` flag indicates if a shape is the root of a component instance. +- `get-head-shape` finds the nearest component head (the topmost shape of the nearest component instance), while `get-instance-root` finds the outermost root. + +## Pattern for Looking Up a Shape's Own Component +Use `ctn/get-head-shape` to find the nearest head, then read `:component-id` and `:component-file` from it: +```clojure +(let [head (ctn/get-head-shape objects shape)] + (lib-component-proxy plugin-id (:component-file head) (:component-id head))) +``` +Do NOT use `locate-component` / `get-instance-root` if you want the nearest component — those walk to the outermost ancestor. diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000000..2019e8678b --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,155 @@ +# the name by which the project can be referenced within Serena +project_name: "penpot" + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# haxe java julia kotlin lua +# markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- clojure +- typescript + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# line ending convention to use when writing source files. +# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) +# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. +line_ending: + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# advanced configuration option allowing to configure language server-specific options. +# Maps the language key to the options. +# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. +# No documentation on options means no options are available. +ls_specific_settings: {} + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. +# This extends the existing exclusions (e.g. from the global configuration) +# +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project based on the project name or path. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly, +# for example by saying that the information retrieved from a memory file is no longer correct +# or no longer relevant for the project. +# * `edit_memory`: Replaces content matching a regular expression in a memory. +# * `execute_shell_command`: Executes a shell command. +# * `find_file`: Finds files in the given relative paths +# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend +# * `find_symbol`: Performs a global (or local) search using the language server backend. +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual') +# for clients that do not read the initial instructions when the MCP server is connected. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Read the content of a memory file. This tool should only be used if the information +# is relevant to the current task. You can infer whether the information +# is relevant from the memory file name. +# You should not read the same memory file multiple times in the same conversation. +# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported +# (e.g., renaming "global/foo" to "bar" moves it from global to project scope). +# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities. +# For JB, we use a separate tool. +# * `replace_content`: Replaces content in a file (optionally using regular expressions). +# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend. +# * `safe_delete_symbol`: +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format. +# The memory name should be meaningful. +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). +# This extends the existing inclusions (e.g. from the global configuration). +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# time budget (seconds) per tool call for the retrieval of additional symbol information +# such as docstrings or parameter information. +# This overrides the corresponding setting in the global configuration; see the documentation there. +# If null or missing, use the setting from the global configuration. +symbol_info_budget: + +# list of regex patterns which, when matched, mark a memory entry as read‑only. +# Extends the list from the global configuration, merging the two lists. +read_only_memory_patterns: [] + +# list of regex patterns for memories to completely ignore. +# Matching memories will not appear in list_memories or activate_project output +# and cannot be accessed via read_memory or write_memory. +# To access ignored memory files, use the read_file tool on the raw file path. +# Extends the list from the global configuration, merging the two lists. +# Example: ["_archive/.*", "_episodes/.*"] +ignored_memory_patterns: []