Support --ws in all relevant commands, fix some bugs in the CLI and add more
precondition validation.
The guide was adjusted to give a better overview of the multi-workspace setup
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run-devenv-agentic now seeds the container's global git config from the
host on every bring-up so commits made inside the devenv (typically by
coding agents reached through start-coding-agent) carry a real
author/committer instead of the bare `penpot@<container>` fallback that
makes review unworkable.
New optional flags on run-devenv-agentic:
--git-user-name NAME author/committer name
--git-user-email EMAIL matching email
When a flag is omitted the value is resolved from the host's effective
`git config user.{name,email}` (plain `git config`, no --global) so a
per-repo override in <repo>/.git/config wins over ~/.gitconfig -- matching
what `git commit` on the host would actually record. If neither flag nor
host config provides a value the script prints a warning and continues;
in-container commits will fail until the user fixes it.
start-instance applies the values via two `docker exec ... git config
--global user.{name,email}` calls right after the container reaches
Running, before start-tmux.sh is launched. Keeps the write logic next to
the rest of the per-instance bring-up and out of the tmux script -- no
env-var contract between manage.sh and the tmux entry point is needed.
Docs updated in docs/technical-guide/developer/devenv.md (new "Git
identity inside the container" section) and the agentic-devenv guide.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Improve parallel-workspaces developer CLI,
and add an opt-in layer that lets four AI
coding agents (Claude Code, opencode, VS Code Copilot, OpenAI Codex CLI)
drive a specific workspace through a single launcher command.
Parallel-workspace semantics
----------------------------
each run-devenv-agentic call brings up one wsN;
--ws N (integer; default 0) targets a specific workspace and auto-starts
ws0 first when N>=1 so the worker invariant holds. --sync is forbidden on
ws0 and re-seeds the workspace from the live repo for ws1+. Stop semantics
mirror the start invariant -- ws0 is the last to stop, shared infra stops
with it, --all walks every instance highest-first. The worker policy
section explains why workers run only on ws0 (Postgres FOR UPDATE
SKIP LOCKED is safe across many workers but the cron dedup primitive is
best-effort, and :telemetry / :audit-log-archive are not idempotent).
Per-instance Valkey Pub/Sub isolation, msgbus topology, and the
"async task notifications miss ws1+ tabs" caveat are stated explicitly.
The mem:prod-infra/core memory captures the same external-services and
task-queue / Pub-Sub topology in agent-readable form, and
mem:backend/core and mem:critical-info now cross-link it so backend work
surfaces the horizontal-scaling constraints from the start.
AI coding agent integration
---------------------------
New top-level .devenv/ directory holds committed templates
(templates/{claude-code,opencode,vscode}.json and templates/codex.toml,
each with \${PENPOT_MCP_PORT} and \${SERENA_MCP_PORT} placeholders) plus
committed shared entries (matching shared/* files for Playwright, the
only workspace-independent server we ship today).
./manage.sh start-coding-agent <claude|opencode|vscode|codex> [--ws N]
launches the chosen client against one workspace. It cd's into the
target's directory (the live repo for ws0; workspace-path "wsN" for ws1+)
and refuses to launch unless (a) the binary is on PATH, (b) the
workspace directory exists for ws1+, and (c) the instance is up
(devenv-main-running) -- the MCP servers only exist while the devenv is
running. The agentic-devenv guide is restructured around this Quick
start path, with a per-client table and a Manual configuration fallback
for clients we don't cover.
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add support for running N parallel devenv instances under separate compose
projects sharing Postgres, MinIO, mailer, and LDAP. Each instance has its
own main container, Valkey, source checkout, tmux session, and host port
range offset by 10000 (3449 -> 13449 -> 23449, etc.).
./manage.sh run-devenv-agentic --n-instances N reconciles the running set
to exactly {ws0..ws(N-1)}: missing instances are created (workspace sync
from the live repo via git ls-files + per-instance env-file generation
under docker/devenv/instances/ + detached tmux startup), surplus instances
are stopped highest-first via compose down (never -v), already-running
instances are left untouched. ws0 binds the live repo at PWD; ws1+ are
scratch clones under ~/.penpot/penpot_workspaces/.
Backend workers (enable-backend-worker) are gated on PENPOT_BACKEND_WORKER
in backend/scripts/_env; ws1+ overlays disable them so async-task
notifications stay bound to a single Valkey Pub/Sub instance.
Compose helpers wrap docker compose with env -i so per-instance overlay
--env-file actually overrides defaults.env -- without the strip, the shell
env from sourcing defaults.env at startup would shadow the overlay (Compose
gives shell precedence over --env-file).
Other:
- Drop network aliases (- main, - redis); use container_name for
cross-container DNS so multiple instances on the shared network don't
fight over the same DNS name.
- Pin volume names via name: (PENPOT_*_VOLUME) so volumes survive project
renames; ws0 keeps the pre-existing physical names (penpotdev_*).
- Remove cross-project depends_on from main.yml (postgres/minio-setup now
live in penpotdev-infra); manage.sh ensure-infra-up docker-waits on the
minio-setup one-shot.
- Strict arg parsing in run-devenv / run-devenv-agentic; --n-instances 0
rejected.
- Remove unused Host-matched server block from the Caddyfile.
Memory mem:devenv/core and developer docs updated.
Co-authored-by: Codex <codex@openai.com>
Move shared services into an infra compose file and keep the main devenv container plus Valkey in a separate compose file driven by defaults.env. Parameterize host-side ports, container names, source path, and runtime env while keeping container-internal ports fixed for same-origin proxying.
Make tmux startup idempotent, add attach-devenv for the live instance, move shared MinIO user setup to infra startup, and let exporter scripts load backend _env.local overrides.
Co-authored-by: Codex <codex@openai.com>
The S3 storage backend uses DefaultCredentialsProvider which includes
WebIdentityTokenFileCredentialsProvider in its chain. However, that
provider requires software.amazon.awssdk/sts on the classpath to call
AssumeRoleWithWebIdentity. Without it, the provider silently fails and
credentials cannot be resolved when using IRSA on EKS.
Closes#9927
Signed-off-by: Joshua C <joshua.cullum@gmail.com>
Co-authored-by: Joshua C <joshua.c@data-edge.co.uk>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
`cls/show-in-viewer` unconditionally dissoc'ed `:hide-in-viewer` on the
interaction destination, so every `add-interaction`, `add-new-interaction`,
and `update-interaction` call silently re-enabled the destination's
view-mode visibility — even when the user had just deliberately hidden
that frame. Reporter (#9049) hid a board, dragged a prototype arrow at
it, and watched the board reappear in View Mode.
Make `show-in-viewer` a no-op when the destination already has
`:hide-in-viewer true`. The auto-unhide still fires on destinations with
no explicit hide flag (the original ergonomic — new prototype targets
default to visible), but explicit user intent is now preserved across
interaction-add / interaction-update.
Behaviour change: dropping the auto-unhide on explicitly-hidden
destinations matches the reporter's expectation ("nothing would show up
in View Mode unless explicitly marked as such") and the surrounding
`:hide-in-viewer`-aware UI in `measures.cljs`, which already lets users
toggle the same property directly.
Closes#9049.
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Fixes#9135.
The <link href="css/ui.css"> tag in
frontend/resources/templates/index.mustache references a CSS file that
the build pipeline never produces:
- compileStyles() in frontend/scripts/_helpers.js only writes main.css
(always) and debug.css (dev-only) — there is no write to ui.css
- compileStorybookStyles() writes ds.css (design system), not ui.css
- No ui.scss source exists anywhere in frontend/resources/styles/
The reference was added in 45d04942c ("✨ Add example ui
storybook") but no corresponding build step was added to emit the file.
Result: every page load issues a request for /css/ui.css that nginx
returns as 404. In self-hosted Penpot deployments behind a reverse
proxy, the SPA's CSS init promise rejects on the 404, the React root
never mounts, and the user sees a black screen.
This patch removes the dead reference. If a future change actually
emits ui.css (or another distinct UI bundle), the <link> can be
re-added at that time.
Co-authored-by: Admin <admin@Admins-MacBook-Pro.local>
The version preview banner in `enter-preview` derives its title from
`(:label snapshot)` directly. For system-created autosaves that
field is the internal snapshot label (e.g. `internal/snapshot/20`),
so the banner shows the raw internal string while the History sidebar
already renders the same autosave through `workspace.versions.autosaved.version`
plus a localized date. The mismatch makes it hard to be sure which
sidebar entry you're previewing, especially when several autosaves
sit close together (#9503).
Switch the label resolution to mirror the sidebar's `snapshot-entry*`:
- `:created-by "system"` snapshots format the label as
`(tr "workspace.versions.autosaved.version" (ct/format-inst ...
:localized-date))` — the exact same translation key + date format
the sidebar's autosave group already uses
- `:created-by "user"` (pinned) versions keep their custom `:label`
with the existing `unnamed` fallback
No behavior change for pinned/user-named versions or for the
restore/exit dialog buttons.
Closes#9503.
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Fix DTCG token import discriminator and group-level $type inheritance
Closes#8342.
The DTCG Community Group Final Report (W3C, 2025-10-28) specifies:
"The presence of a $value property definitively identifies an object
as a token."
"A token's type can be specified by the optional $type property [...]
Furthermore, the $type property on a group applies to all tokens
nested within that group."
Two bugs in `common/src/app/common/types/tokens_lib.cljc` violate the
spec and silently break import of any third-party DTCG file that uses
group-level type inheritance:
1. `flatten-nested-tokens-json` used `(not (contains? v "$type"))` as
the group-vs-token discriminator. A group node carrying only a
`$type` (to set a default for child tokens) was misidentified as a
token, then immediately discarded because it had no `$value`.
2. `schema:dtcg-node` declared both `$type` and `$value` as required,
so even after the discriminator was fixed any leaf token that
relied on group-level type inheritance failed `dtcg-node?`
validation and never reached the parser.
The combined effect: importing a spec-compliant DTCG file that
expressed types at the group level produced a TokensLib with no
tokens at all, because every leaf was discarded as "unknown type".
Penpot-exported files were unaffected because Penpot always emits
both `$type` and `$value` on every token and never attaches `$type`
to a group, so the existing tests covered only the inline-type
shape.
- `schema:dtcg-node`: mark `$type` optional.
- `flatten-nested-tokens-json`: use `$value` as the discriminator
(anything without `$value` is a group), accept an optional
`inherited-type` accumulator that carries the nearest enclosing
group `$type` down through recursion, and resolve a token's type
from its own `$type` first, falling back to the inherited type.
A token's own `$type` always wins over the inherited one (per
spec).
Added `parse-dtcg-group-type-inheritance` covering both cases:
- group `$type` is inherited by tokens that don't declare their own
(`colors.red`, `colors.blue`, `space.small`)
- token `$type` overrides the inherited group `$type`
(`colors.danger`, `space.large`)
Existing DTCG round-trip tests continue to pass because they all
declare `$type` at the token level, which the new code still honours.
CHANGES.md entry added under the 2.17.0 Bugs-fixed section.
* 📚 Do not update CHANGES.md
We are changing the procedures to not update the changelog on each PR. Instead, we use github tracking to check what issues come in a release, and update the changelog automatically in a batch.
Signed-off-by: Andrés Moya <hirunatan@hammo.org>
---------
Signed-off-by: Andrés Moya <hirunatan@hammo.org>
Co-authored-by: MilosM348 <milos.milic001@outlook.com>
Render owned organizations in the delete-account modal with the same
org-avatar* component used across the dashboard, so logo and avatar
background are shown consistently and initials are extracted via
d/get-initials instead of a raw first-character substring.
Extends the get-owned-organizations-summary endpoint and the underlying
nitrate API schema to carry :avatar-bg-url and :logo-id, deriving
:custom-photo from logo-id with the public uri, matching the pattern
already used by set-team-org-api.
* ⚡ Improve performance and fix orphan detection in validate-file
- Add `*ref-shape-cache*` dynamic var to memoize `find-ref-shape`
lookups per page, avoiding repeated O(depth) ancestor walks.
- Add `*children-sets*` pre-computed maps for O(1) parent-child
containment checks, replacing linear `some` scans.
- Short-circuit `inside-component-main?` when the shape context
already implies a main component.
- Use single-pass reduce with early exit for duplicate detection
(children, swap slots) instead of count/distinct or frequencies.
- Guard `check-missing-slot` to skip expensive `find-near-match`
when the shape already has a swap slot.
- Refactor variant-set validation to use `run!` with direct `get`.
- Refactor `check-ref-cycles` to use a single `reduce-kv` pass.
- Fix `get-orphan-shapes`: the original `map` pipeline produced
nils so orphan shapes were never validated; rewrite with
`reduce-kv` for correct results.
- Add `validate-file-affected!` for change-scoped validation,
replacing full file validation in `process-changes-and-validate`
to only validate pages and components touched by the changes.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ✨ Improved validation
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>