mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
1731 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
73b2dc853f
|
Fix project empty state create action (#3082)
Co-authored-by: saifulla-khan <saifulla-khan@users.noreply.github.com> Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
881571dea7
|
fix(media): route custom-image edits through images API (#3087)
* fix(media): route custom-image edits through images API * fix(media): normalize custom-image endpoint suffixes --------- Co-authored-by: Artist Ning <dingkuake@yeah.net> Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
fe58db2ba1
|
fix(web): target comment picker elements precisely (#3263)
Resolve Comment picker hit testing against meaningful visible DOM leaves before falling back to annotated ancestors, while preserving Inspect mode's annotation-first selector behavior. Filter generated React root annotations from Comment targets, keep real element bounds separate from hoverPoint, and avoid rendering the comments drawer inline when a configured dock portal is not mounted. |
||
|
|
1006efa2f6
|
Improve onboarding AMR runtime card (#3276)
* Improve onboarding AMR runtime card * Fix onboarding AMR test expectations |
||
|
|
593bf2f03c
|
fix(composer): ellipsis overflow for referenced filenames (#3269)
Inline @-mentioned filenames in the composer can be very lengthy, causing line wrapping and visual crowding in the input area. - Switch display from inline to inline-block for max-width - Cap width at min(240px, 25vw) - Apply overflow: hidden + text-overflow: ellipsis + white-space: nowrap - Remove box-decoration-break (unused since content won't wrap) - Tweak vertical-align for consistent inline-block alignment Closes #3261 Co-authored-by: freshtemp-labs <freshtemp-labs@users.noreply.github.com> |
||
|
|
071db7ca1b
|
[codex] Stabilize HTML deck navigation state (#3142)
* fix: stabilize html deck navigation state * fix: avoid misclassifying transform decks as scroll decks * fix: detect default root-scroller decks --------- Co-authored-by: Nongzi <3051966228@qq.com> |
||
|
|
be09fe92da
|
fix: keep settings/handoff/avatar buttons fixed to the right in project header (#3279)
Move the three buttons (settings, handoff, avatar) from fileActionsBefore to the actions slot so they always stay pinned to the right edge of the header, regardless of how many extra controls (Share, Present, etc.) are injected via portal during HTML preview. Co-authored-by: qiongyu1999 <2694684348@qq.com> Co-authored-by: Claude Opus 4 <noreply@anthropic.com> |
||
|
|
d6d42c3600
|
fix(pack): bundle download and host packages in Linux AppImage assembly (#2845)
The Linux AppImage path assembles INTERNAL_PACKAGES as `file:` tarballs and runs `npm install --omit=dev` in an isolated app directory. `pnpm pack` rewrites each tarball's `workspace:*` refs to a concrete version, so any runtime @open-design/* dependency missing from INTERNAL_PACKAGES is resolved from the public npm registry and 404s. Linux ships webOutputMode "server" and tarball-installs every INTERNAL_PACKAGES entry, including @open-design/desktop and @open-design/web. @open-design/host (dep of web + desktop, added in #2246) and @open-design/download (dep of desktop, added in #2677) landed after the Linux package list was written and were never added to it, so `pnpm exec tools-pack linux build --to appimage` fails with: npm error 404 Not Found - GET .../@open-design%2fdownload mac/win default to "standalone", where desktop/web/packaged/daemon are prebundled with esbuild and excluded from the tarball install (shouldInstallInternalPackageFor{Mac,Win}Prebundle). The packages they do install have no download/host dependency, so those lanes correctly omit them and need no change — this fix stays scoped to linux.ts and touches no mac/win or workspace-build code. Add both packages to the Linux INTERNAL_PACKAGES and build them in buildWorkspaceArtifacts (download depends on platform). Add a cross-lane regression test that, for each lane, derives the set it actually installs (honoring the standalone prebundle exclusion) and asserts that set is closed under its runtime @open-design/* dependencies. The test is red on the linux lane without this fix and green with it, while mac/win pass either way — encoding why only Linux needs these packages. |
||
|
|
da19ff3ca0
|
feat(mocks): replay-based mock CLIs for 14 of OD's supported agents (opencode/codex/claude/gemini/cursor-agent/deepseek/qwen/grok + ACP family devin/hermes/kilo/kimi/kiro/vibe) (#3241)
* feat(mocks): replay-based mock CLIs for opencode/claude/codex/deepseek/qwen/grok
Drops in a `mocks/` top-level dir that pretends to be the real agent
CLIs by streaming pre-recorded sessions in each CLI's native stdout
protocol. Zero LLM tokens.
## Use cases
- **E2E tests** in `apps/daemon/tests/` — exercise the full chat-server
pipeline against a known trace, assert UI events / artifacts.
- **Self-validation during dev** — iterate on `claude-stream.ts` /
`json-event-stream.ts` parser changes without burning provider budget.
- **Regression harness** — replay the same trace before and after a
charter / parser change; diff the daemon events the UI surfaces.
- **Demo / onboarding** — show what a 17-tool claude editing session
looks like end-to-end, offline.
## How
- 6 bash wrappers (`mocks/bin/`) shadow the real CLIs when PATH-overlaid.
- `mocks/mock-agent.mjs` reads `mocks/recordings/<trace>.jsonl`, picks
one via env var (`SYNCLO_EXPLORE_MOCK_TRACE` / `_POOL` /
`_BY_PROMPT_HASH`), streams the trace in the requested format.
- Each format renderer matches the EXACT JSON shape the OD daemon
parser expects, verified line-by-line against
`apps/daemon/src/{json-event-stream,claude-stream}.ts`:
| CLI | streamFormat | parser source |
| ------------------------- | ------------------------- | ------------------------------------------ |
| `opencode` | `json-event-stream` | `handleOpenCodeEvent` |
| `codex` | `json-event-stream` | `handleCodexEvent` |
| `claude` | `claude-stream-json` | `createClaudeStreamHandler` |
| `deepseek` `qwen` `grok` | `plain` | `server.ts` (raw stdout) |
## Quick start
```bash
export PATH="$PWD/mocks/bin:$PATH"
export SYNCLO_EXPLORE_MOCK_TRACE=04097377 # 8-char prefix OK
export SYNCLO_EXPLORE_MOCK_NO_DELAY=1
echo "any prompt" | opencode run
echo "any prompt" | claude -p --output-format=stream-json
echo "any prompt" | codex exec
```
The mock binary announces the picked trace id on stderr:
`[mock-opencode] picked 04097377… via fixed`.
Recording selection (env, in priority order):
- `SYNCLO_EXPLORE_MOCK_TRACE=<id>` — fixed (prefix OK)
- `SYNCLO_EXPLORE_MOCK_BY_PROMPT_HASH=1` + stdin prompt — `sha256(prompt) % N`
- `SYNCLO_EXPLORE_MOCK_POOL=<tag>` — random within `agent:claude` /
`skill:agent-browser` / `outcome:failed` / etc.
- (default) uniform random
- `SYNCLO_EXPLORE_MOCK_SEED=<str>` — reproducible "random"
- `SYNCLO_EXPLORE_MOCK_NO_DELAY=1` — skip inter-event waits
## Dataset
179 anonymized Langfuse traces from this project's own production
telemetry:
- 9 agents: claude 57 · opencode 41 · codex 38 · gemini 25 ·
cursor-agent 11 · qwen 2 · copilot 2 · deepseek 2 · antigravity 1
- outcomes: succeeded 144 · failed 35
- skills: default 71 · ad-creative 50 · algorithmic-art 30 ·
agent-browser 22 · video-hyperframes 2 · plus magazine-web-ppt /
brainstorming / data-report / penpot-flutter-design-source 1 each
- 124 multi-turn (sessions with ≥2 turns)
- 18 produce `<artifact>` output
- ~4.5 MB on disk total
Anonymization: `/Users/<name>/` → `${HOME}/`,
`C:\Users\<name>\` → `%USERPROFILE%\`, project UUIDs →
stable `proj-001`, `proj-002`, …. Tool input/output payloads
preserved verbatim (templated UI, no cell-level PII).
## Smoke test
`bash mocks/scripts/smoke-test.sh` — 6 checks across all 6 agents.
All pass on this branch (verified locally):
```
✓ opencode first event = step_start
✓ codex first event = thread.started
✓ claude first event = system
✓ deepseek emitted plain text (144 chars on first line)
✓ qwen emitted plain text (144 chars on first line)
✓ grok emitted plain text (144 chars on first line)
All mock CLIs working. ✅
```
## Adding more recordings
The exporter that produced this set lives in
[nexu-io/agent-pr-explore](https://github.com/nexu-io/agent-pr-explore)
(see `cli/src/local/orchestrator/langfuse-import.ts` + the `local
langfuse-import` CLI command). Operators with the Langfuse keys can pull
more by tag / outcome / artifact / multi-turn filter, then run
`local recordings anonymize --out-dir ~/Documents/open-design/mocks/recordings`.
`mocks/README.md` has the full instructions.
## Out of scope (follow-ups)
- **ACP agents** (`devin`, `hermes`, `kilo`, `kimi`, `kiro`, `vibe`) need
a JSON-RPC server on stdio rather than a one-shot stream — separate
`format-acp.mjs` module not yet written.
- **Per-agent json-event-stream variants** (`cursor-agent`, `gemini`,
`qoder`, `copilot`, `pi`) currently fall back to the `plain` renderer;
their parsers are in `apps/daemon/src/json-event-stream.ts` and follow
the same template as `format-codex.mjs`.
## AGENTS.md updates
- Added `mocks/` to the top-level content directories listing
- Added a Validation strategy bullet pointing here for agent-stream /
parser changes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(mocks): add opencode-cli/kiro-cli/vibe-acp bin aliases and unref ACP timeout
- Add mocks/bin/opencode-cli, kiro-cli, vibe-acp wrappers for the primary
RuntimeAgentDef bin names OD resolves before any fallback. Without these,
a PATH-overlaid OD daemon run bypasses the mock entirely (opencode-cli,
kiro-cli) or cannot find the mock at all (vibe-acp, which has no fallback).
- Include opencode-cli, kiro-cli, vibe-acp in the smoke-test ACP/JSON loop
so coverage is verified end-to-end.
- Call .unref() on the 30s safety timeout in format-acp.mjs so a completed
ACP session exits promptly instead of waiting the full 30 seconds.
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
* feat(mocks): add vela (AMR) — login / models / ACP with strict set_model gate
Extends mocks/ to cover OD's own AMR runtime. `vela` is the bin name
`apps/daemon/src/runtimes/defs/amr.ts` specifies (`bin: 'vela'`,
`streamFormat: 'acp-json-rpc'`). It's richer than the generic ACP
agents — covers full login + models + chat-session lifecycle.
### What vela does (mirrored from apps/daemon/tests/fixtures/fake-vela.mjs)
1. `vela login` — writes ~/.amr/config.json with a fake profile (controlKey,
runtimeKey, user{email,name,plan}, profile-specific apiUrl/linkUrl).
The on-disk projection is what OD's daemon login route + AmrLoginPill
poller read; production goes through device-auth, the mock skips
straight to the file write.
2. `vela models` — prints the production-shaped public model catalog as
newline-separated `public_model_* vela` lines. Override via
FAKE_VELA_MODELS env.
3. `vela agent run --runtime opencode` — ACP JSON-RPC server with three
vela-specific protocol extensions:
a. `initialize` response carries `agentCapabilities`
(`promptCapabilities.embeddedContext`) + `models`
(`currentModelId` + `availableModels`).
b. `session/new` response carries the same `models` block.
c. **Strict set_model gate**: `session/prompt` is rejected with
JSON-RPC -32602 ("session/set_model must be called before
session/prompt") UNLESS `session/set_model` (or
`session/set_config_option`) has been called for the current
sessionId. Mirrors real vela 0.0.1 contract; catches regressions
in `attachAcpSession` that silently skip set_model.
### Error injection envs (in sync with fake-vela.mjs)
FAKE_VELA_SESSION_ID - sessionId returned by session/new
FAKE_VELA_TEXT - override assistant text
FAKE_VELA_THOUGHT - optional thought_chunk before text
FAKE_VELA_SESSION_NEW_ERROR - fail session/new
FAKE_VELA_SET_MODEL_ERROR - fail session/set_model
FAKE_VELA_PROMPT_ERROR - fail session/prompt
FAKE_VELA_REQUIRE_SET_MODEL='0' - disable the strict gate (legacy)
FAKE_VELA_LOGIN_USER_EMAIL - email written into config profile
FAKE_VELA_LOGIN_USER_PLAN - plan written into config profile
FAKE_VELA_LOGIN_DELAY_MS - sleep before write (test in-flight)
FAKE_VELA_LOGIN_FAIL - print + exit 1
FAKE_VELA_MODELS - override models stdout
VELA_PROFILE - profile slot (prod | test | local)
### Components
`mocks/lib/format-vela.mjs` (~205 LOC)
- Full ACP server with vela protocol extensions
- Strict set_model gate
- Error injection plumbing
`mocks/lib/vela-subcommands.mjs` (~90 LOC)
- runVelaLogin() — writes ~/.amr/config.json
- runVelaModels() — prints catalog
`mocks/bin/vela` — dispatcher wrapper. Forwards `vela <subcmd>` to
mock-agent.mjs which routes to login/models or falls through to ACP.
`mocks/mock-agent.mjs` — parseArgs now collects positionals so the vela
dispatcher can read subcommand from there; switch case added for vela.
`mocks/scripts/smoke-test.sh` — +4 assertions:
vela models prints ≥10 catalog lines
vela login writes ~/.amr/config.json with the requested email
vela agent run ACP roundtrip (initialize+models+set_model+stream+result)
vela strict set_model gate rejects prompt without prior set_model
### Verified locally
✓ vela models printed 15 catalog lines
✓ vela login wrote ~/.amr/config.json with profile.prod.user.email
✓ vela agent run ACP roundtrip (initialize+models, set_model accepted, prompt streamed)
✓ vela strict set_model gate rejects session/prompt without prior set_model
All 21 smoke checks pass (up from 17 with previous P3 ACP commit).
### AGENTS.md + README updates
AGENTS.md — mention `vela (AMR — vela CLI)` alongside ACP agents in
the directory listing entry.
mocks/README.md — protocol table row + dedicated vela section with
subcommand contract, strict gate explanation, env-injection cheat
sheet. Mock-tree listing updated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(mocks): honor REPORT_FILE env when --report-file flag not given
Harnesses that spawn the mock without translating their report-path
contract to the mock's CLI flag (notably nexu-io/agent-pr-explore's
orchestrator, which passes REPORT_FILE as env per the existing
opencode/claude/codex agent launchers) wouldn't get a report file
written, so the harness's "agent exit 0 but produced no report"
check would always fire and mark mock runs as failure even though the
stdout stream was complete.
Fix: in mock-agent.mjs parseArgs, fall through to process.env.REPORT_FILE
when --report-file wasn't provided on argv. Each format renderer already
accepts opts.reportFile and writes the recording's final assistant text
to it (`format-*.mjs` already had this — only the wiring was missing).
Verified: synclo-explore run with `mock=true, mock_trace=04097377`
against the opencode wrapper now produces a plan.md with the recording's
17-tool claude editing session report. ~1.5s per run vs ~70s real opencode.
* mocks: move recordings to Cloudflare R2; PR→main→Action upload path
The 179-recording corpus (~4.5 MB raw, ~280 KB after compression) has
been moved off git into Cloudflare R2 at the bucket open-design-mocks
under recordings/v1/. The repo now ships:
- mocks/manifest.json — the canonical catalog (renamed from
recordings/index.json) with sha256 + storage hints; consumers
fetch this to discover what exists, then pull individual jsonl
files on demand
- mocks/scripts/fetch-recordings.sh — parallel, sha256-verified,
idempotent puller for the public r2.dev URL
- mocks/scripts/add-recording.sh — local maintainer helper that
validates a new .jsonl and copies it into recordings-staging/
(no R2 calls; no credentials needed)
- mocks/scripts/upload-to-r2.mjs — called only by the CI workflow
- mocks/scripts/lib/manifest-utils.mjs — shared sha256/meta/
rebuild-histograms logic, used by both add-recording (preview)
and upload-to-r2 (actual write) so the entry shape never drifts
- .github/workflows/sync-mocks-to-r2.yml — fires on push to main
when mocks/recordings-staging/ changes; uploads to R2, updates
manifest, commits cleanup back; serialized via concurrency group
Trust model: R2 write credentials (CLOUDFLARE_API_TOKEN,
CLOUDFLARE_ACCOUNT_ID) are repo secrets; nobody can push from a
laptop. Read stays public via the r2.dev URL.
Why not pnpm install integration: contributors who do not touch
agent code do not pay the fetch cost. Fetch happens on first
smoke-test run (auto-fallback) or when a mock spawn needs data.
Repo size: -4.55 MB net (delete 179 jsonl, +280 KB manifest +
scripts). Smoke test (21 checks) still green against the fetched
corpus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* mocks: scope R2 write token to a dedicated secret name
Use CLOUDFLARE_R2_MOCKS_TOKEN (instead of reusing the shared
CLOUDFLARE_API_TOKEN that landing-page-*.yml uses for Pages deploys)
so the R2 write capability can be scoped to just the
open-design-mocks bucket without bleeding extra capability into the
Pages workflows.
Also hardcode the powerformer CF account_id directly in the workflow
(account IDs are not secret and the shared CLOUDFLARE_ACCOUNT_ID
secret may point at a different account).
Workflow now fails fast with an actionable error message + dashboard
link if the secret is unset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* mocks: switch R2 sync to S3-compat API (wrangler getMemberships gate)
wrangler 4.x calls /memberships before any r2 action, requiring
user:read scope. R2 "Object Read & Write" tokens deliberately lack
that scope (defense in depth — a leaked token should not enumerate
account-level resources). The workflow now uses the aws CLI talking
straight to the R2 S3-compatible endpoint with SigV4, no membership
lookup.
Secret rotation: CLOUDFLARE_R2_MOCKS_TOKEN (Bearer) is replaced by
CLOUDFLARE_R2_MOCKS_AK / CLOUDFLARE_R2_MOCKS_SK (matching the
existing CLOUDFLARE_R2_RELEASES_AK/SK naming convention). End-to-end
tested locally: PUT recording → manifest rebuild → manifest PUT →
staging cleanup all green.
aws CLI is pre-installed on ubuntu-latest, so no install step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* mocks: scrub synclo namespace; use OD_MOCKS_* env prefix throughout
These mocks were copy-pasted from synclo-explore, where they
originated, and inherited the SYNCLO_EXPLORE_MOCK_* env-var
convention. That brand-bleed is not appropriate in OD: rename the
public env surface to OD_MOCKS_* (matching OD-native prefixes like
OD_MOCKS_CACHE_DIR, OD_TRACE_R2_UPLOAD, OD_EXPECT_TIMEOUT_SECONDS).
Renames:
SYNCLO_EXPLORE_MOCK_TRACE → OD_MOCKS_TRACE
SYNCLO_EXPLORE_MOCK_BY_PROMPT_HASH → OD_MOCKS_BY_PROMPT_HASH
SYNCLO_EXPLORE_MOCK_POOL → OD_MOCKS_POOL
SYNCLO_EXPLORE_MOCK_SEED → OD_MOCKS_SEED
SYNCLO_EXPLORE_MOCK_NO_DELAY → OD_MOCKS_NO_DELAY
SYNCLO_EXPLORE_MOCK_RECORDINGS_DIR → OD_MOCKS_RECORDINGS_DIR
SYNCLO_EXPLORE_MOCK_SMOKE_TRACE → OD_MOCKS_SMOKE_TRACE
SYNCLO_OD_MOCKS_I_KNOW_WHAT_IM_DOING → OD_MOCKS_ALLOW_LOCAL_UPLOAD
Also drop the inline harvester usage from README. The harvester is an
external CLI in nexu-io/agent-pr-explore — its README is the right
place for langfuse-import flags, anonymization options, etc. OD only
documents its own staging→PR→Action workflow.
Smoke test (21 checks) still green; OD_MOCKS_TRACE end-to-end
verified to route correctly.
Consumers of the OLD env names (notably the orchestrator in
nexu-io/agent-pr-explore) need a matching rename. No back-compat
shim here — the explore side has zero external users today and a
one-line follow-up is cleaner than a permanent deprecation layer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* AGENTS.md: align mock env names with mocks/ rename (SYNCLO_* → OD_MOCKS_*)
Missed in the prior commit (
|
||
|
|
4b7c018a9b
|
feat(contrib): add od-contribute skill for non-coder contributors (#3172)
* feat(contrib): add od-contribute skill for non-coder contributors
Adds a Claude Code skill at .claude/skills/od-contribute/ that walks any OD
user — including non-coders — through a first-PR contribution flow:
- Ship a Skill / Design System made with OD
- Translate README / QUICKSTART / CONTRIBUTING to a new language
- Fix a typo / dead link / write a use-case blog post
- Report a high-quality bug (issue path, no PR)
The skill replaces the test-driven dev-loop of auto-github-contributor with
type-specific no-code validators (frontmatter parse, markdown link check,
code-fence balance, structural overlap with reference DESIGN.md files), so
artifact-only contributions don't have to pretend to be code.
This commit only adds files under .claude/ — no product code, no build
config, no runtime dependencies. .gitignore is amended with three explicit
exceptions so the skill is tracked while personal Claude state (sessions,
settings, etc.) stays ignored as before.
Next steps (separate PRs):
- Wire the OD app to mount this skill for its embedded agent
- Add a "Ship to GitHub" UI button in OD that invokes /od-contribute
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* feat(contrib): English-by-default skill + zip installer for non-coders
Two follow-ups to the initial od-contribute skill:
1. Skill content is now English with an explicit instruction at the top
telling the agent to mirror the user's chat language for every user-
facing prompt. Generated artifacts (PR titles, commit messages, PR/
issue body) stay English regardless — GitHub convention.
2. tools/od-contribute-installer/ ships a cross-platform installer that
drops the skill into every supported agent's home dir without the
user opening a terminal:
install.command macOS double-click
install.bat Windows double-click
install.sh Linux
Targets covered:
~/.claude/skills/od-contribute/ Claude Code (native)
~/.claude/commands/od-contribute.md Claude Code slash command
~/.agents/skills/od-contribute/ Codex CLI (canonical)
~/.codex/skills/od-contribute/ Codex CLI (legacy, only
written if ~/.codex/ exists)
Verified Codex CLI reads the same SKILL.md frontmatter format as
Claude Code (source: openai/codex codex-rs/core-skills/src/loader.rs).
Added agents/openai.yaml sidecar inside the skill for Codex picker UX.
3. build-zip.sh produces od-contribute-installer.zip (~37KB) from the
in-repo skill. The zip is meant to be hosted as a GitHub Release
asset; the marketing site button points at:
github.com/nexu-io/open-design/releases/latest/download/od-contribute-installer.zip
(See tools/od-contribute-installer/HOSTING.md for the manual release
recipe; CI workflow can come later.)
The zip itself is gitignored — distribute via Releases, not source.
Still no product code touched, no build config changed.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* refactor(contrib): drop zip installer; ship single curl one-liner
Replace tools/od-contribute-installer/ (4 install scripts + zip build
machinery) with a single self-bootstrapping tools/install-od-contribute.sh.
User flow becomes:
1. Click button on opendesign.so
2. Modal shows: paste this into your AI agent's chat:
curl -sSL https://raw.githubusercontent.com/nexu-io/open-design/main/tools/install-od-contribute.sh | bash
3. Agent runs it via its Bash tool. User never touches a terminal.
4. /od-contribute is live in their next chat.
Why this is better than the zip approach:
* Zero downloads visible to the user — no .zip in their Downloads folder
* Zero unzip step
* Zero terminal window flash (the agent's Bash tool runs in-process)
* Zero per-OS installer files (.command/.bat/.sh) to maintain
* Auto-updates: re-running the one-liner pulls the latest skill from main
The script downloads only the skill subtree (.claude/skills/od-contribute/
and .claude/commands/od-contribute.md) from a GitHub tarball — no `git`
dependency, just curl + tar (universally available).
Targets remain the same:
~/.claude/skills/od-contribute/
~/.claude/commands/od-contribute.md
~/.agents/skills/od-contribute/
~/.codex/skills/od-contribute/ (only if ~/.codex/ exists)
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* chore(contrib): remove leftover zip artifact
Build artifact accidentally committed in the previous commit.
Cleaning up so the binary doesn't live in git history.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): make skill work in sandboxed agents (Codex.app, Cursor)
macOS App Sandbox apps like Codex.app cannot reach the system keychain
where `gh auth login` stores the GitHub token by default. Result: the
skill's check-prereqs.sh fails on `gh auth status` with a misleading
"not authenticated" error, even when gh works fine in the user's regular
shell.
Two changes:
1. config.sh: if GH_TOKEN isn't set in the env, fall back to reading a
.gh-token file at the skill root. Lets a user (or the OD app, or a
future OAuth Device Flow bootstrapper) drop a token there once and
have every skill script pick it up automatically.
2. check-prereqs.sh: accept GH_TOKEN-from-env as a valid auth path
alongside `gh auth status`. When neither works, the error hint now
shows BOTH options:
A) gh auth login from a regular terminal (any agent)
B) gh auth token > <skill>/.gh-token (sandboxed agents)
Verified: in my local Claude Code (where gh has keychain access), the
keychain path still wins and nothing changes. With GH_TOKEN exported,
check-prereqs.sh succeeds without even consulting gh auth status.
Future: implement OAuth Device Flow inside the skill so non-coder users
hitting this in Codex.app can authenticate by clicking a link, no
terminal involved. That's a separate PR.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* chore(contrib): move install script into skill folder (CI policy fix)
The repo's tools/ directory has a strict allowlist policy enforced by
scripts/guard.ts — only AGENTS.md, dev/, pack/, and serve/ are permitted
top-level entries. Moving install-od-contribute.sh out of tools/ and into
.claude/skills/od-contribute/install.sh:
- Satisfies the guard policy (no scripts/guard.ts edit needed)
- Co-locates the install script with the skill it installs (cleaner
mental model: skill folder is self-contained)
- The install URL stays inside the gitignore exception we already
established for .claude/skills/od-contribute/
Public install URL changes from
raw.githubusercontent.com/.../main/tools/install-od-contribute.sh
to
raw.githubusercontent.com/.../main/.claude/skills/od-contribute/install.sh
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): address @nettee/looper review feedback (3 blocking issues)
Three real bugs caught by the looper review bot, all fixed:
1) create-pr.sh:48 — git diff missed untracked files
`git diff --quiet || git diff --cached --quiet` ignored untracked paths,
so the most common contribution shape (a brand-new Skill folder, a new
translation file, a new doc) hit the else branch and pushed an empty
commit. Replaced with `git status --porcelain` which sees untracked,
plus a post-stage sanity check via `git diff --cached --quiet` so we
skip the commit cleanly if everything turned out to be in .gitignore.
2) validate-skill-submission.sh:34 — frontmatter parse too lenient
The awk fence-counter accepted `---` anywhere in the file as the
opening fence. A SKILL.md with prose before the YAML block parsed as
"valid frontmatter" by this script while the actual loaders (Claude
Code + codex-rs/core-skills) required the fence on line 1 and would
reject it. Added an explicit head -n 1 check so leading prose is
rejected with a clear error before awk runs.
3) check-prereqs.sh:87 — gh api user failure swallowed
`GH_USER="$(gh api user --jq .login 2>/dev/null || echo '?')"` set
GH_USER to literal "?" when the API call failed (revoked token,
missing 'repo' scope, network), then the script exited READY=1.
Downstream that propagated to TARGET_FORK="?/open-design" and
blew up at push time.
Dropped the `|| echo '?'` fallback. An empty GH_USER now triggers a
structured error with three common causes and the recovery command,
and exits 2.
While here, also fixed a related bug: this script sources config.sh
which has `set -euo pipefail`, so -e leaked in and aborted the
script silently the moment any check failed (instead of accumulating
diagnostics like the original auto-github-contributor design
intended). Added explicit `set +e; set -uo pipefail` after sourcing
to restore the "keep checking past failures" behavior the comment
on line 7 promised.
Smoke-tested all four fixes locally:
- create-pr.sh: git status --porcelain correctly sees untracked files
- validator: rejects SKILL.md starting with prose, passes well-formed
- check-prereqs.sh: with stubbed gh that fails `gh api user`, now
exits 2 with the structured error (was: silent exit 1)
- check-prereqs.sh: happy path on real machine unchanged
Thanks @nettee for the careful review.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): macOS Bash 3.2 + over-strict link validator (review round 2)
Two more blocking issues from the looper review, plus one related bug I
caught while re-testing on real OD docs.
1) discover-i18n-gaps.sh: removed Bash 4 dep (declare -A)
macOS still ships Bash 3.2.57 by default and most agent-spawned bash
subprocesses inherit that. `declare -A SEEN_LANG=()` failed with
`declare: -A: invalid option`, crashing Step 3b before any translation
target could be shown.
Replaced the associative array with a newline-delimited string set
(\n<lang>\n bracket form to avoid prefix-overlap false matches like
zh vs zh-CN). Verified end-to-end on /bin/bash 3.2.57 against the
actual OD repo: returns the correct 28 stale-translation rows
across the four English source docs.
Also fixed a latent path-stripping bug in the same loop: `find`
emits `./README.zh-CN.md` with leading `./`, so `${path#README.}`
wasn't stripping the prefix at all. Switched to basename-first.
2) validate-markdown.sh: --reference flag for i18n / docs-edit flows
The validator was treating every relative link target as a file path
and failing on slugs like `skills/blog-post/` that are website
router routes, not files in the checkout. A structure-preserving
translation of README.md couldn't pass even when the user changed
nothing except language.
Added --reference <orig> flag. The validator now builds a "known
already-broken" set of refs from the source file and excuses those
in the new file. Newly-introduced broken refs still fail.
Without --reference (e.g. brand-new blog file with no prior version),
the relative-ref check is skipped entirely with a SKIP note — since
we can't tell route slugs from file paths in isolation, failing
would be wrong. Code-fence balance + external-link health still run.
Updated SKILL.md so the i18n branch (3b.6) and the docs branch
(3c.6) call validate-markdown.sh with --reference pointing at the
English source / HEAD revision respectively.
3) (caught while testing) URL extraction regex too loose
`grep -oE 'https?://[^) ]+'` was capturing trailing quotes from HTML
<img src="..."> tags in OD's README, e.g.
https://cms-assets.youmind.com/.../foo.jpg"
The trailing `"` made the curl HEAD return 404. Tightened the
character class to also stop at `"`, `'`, `<`, `>`, `[`, `]`.
With this fix, README.md now passes all checks (20 external links
verified 2xx/3xx).
Smoke-tested on macOS /bin/bash 3.2.57 with the actual nexu-io/open-design
working copy. All four scenarios behave correctly:
- README.md without --reference → SKIP relative-ref check, PASS overall
- README.md with --reference itself → 34 refs excused as pre-existing, PASS
- Newly-introduced broken ref → FAIL (regression catch preserved)
- Old test cases (skill validator, prereq check) → still pass
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): preserve .gh-token across install.sh reruns
`install_skill_to()` did `rm -rf $dest` before copying in the new skill,
which wiped any user-local state files. The most consequential one is
`.gh-token` — sandboxed agents (Codex.app, Cursor) write a GitHub token
there because they can't reach the macOS keychain (see check-prereqs.sh's
hint and config.sh's fallback path).
Effect: the documented upgrade path ("re-run the curl one-liner to pull
the latest skill") would silently lose the token on every refresh, and
the very next /od-contribute run would fail at the prereq gate with
"no GitHub credentials available", forcing the user back through manual
token setup. This affects exactly the audience the PR is aimed at.
Fix: stash any file in PRESERVE=(.gh-token) to a tempdir before rm -rf,
restore after the copy, re-chmod 600 on the way back. Test:
1. Pre-seed .gh-token in all three target dirs
2. Run installer
3. Verify all three tokens still present, contents unchanged, perms 600
Centralized the preserved-state list as PRESERVE=() so future per-user
state (e.g. an OAuth-flow-saved refresh token) only has to be added in
one place.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
* fix(contrib): i18n stale false-positive + tier markdown link check (round 4)
Two more blocking issues from looper, both real.
1) discover-i18n-gaps.sh: false-stale on same-commit translations
`git log --since=@<epoch>` is INCLUSIVE of the boundary epoch, so when
the English source and a translation get touched in the SAME commit
(a very common pattern: bulk i18n refresh, structural edits applied
across all locales), the shared commit was counted toward
english_commits_since_translation. Result: an already-current
translation was reported with `status="stale", english_commits_since_
translation=1`, and Step 3b would suggest it for refresh — driving
users into no-op PRs.
Reproduced exactly per looper's case: README.md and README.uk.md both
have last commit
|
||
|
|
937946c6fa
|
Improve model picker search and shared BYOK catalogs (#3262) (#3278) | ||
|
|
755d84e64c
|
feat(web): merge Draw + Screenshot into one Studio mark tool (#3081) (#3277)
Forward-ports chaoxiaoche's Studio toolbar work from #3081 onto current main. The preview toolbar drops to 4 controls — Comment, Mark (the merged Draw/Screenshot tool with box-select + pen sub-tools), Edit, Comments — matching the latest design. The standalone Screenshot button and its copy-to-clipboard path are removed; capture now flows through the mark overlay. Also carries #3081's comment select-all/clear-selection panel and keeps the Draw send guard added in #3270 (Send disabled mid-run, Queue stays). Reconciled with main work that postdates #3081's base so nothing is lost: - Preserves #2190's preview iframe keep-alive pool and the AnnotationHoverPopover hover card (re-added on top of #3081's BoardComposerPopover, with its own anchor helper so it doesn't clash with the composer popover anchoring). - i18n: keeps every locale key main added; adopts #3081's mark wording. Behavior change: the comment side-panel Clear now deselects instead of batch-deleting selected comments (per #3081); per-comment delete and send-selected remain. Validation: pnpm --filter @open-design/web typecheck (clean), full web vitest (2354 passed), pnpm guard. Co-authored-by: chaoxiaoche <fanzhen910412@gmail.com> |
||
|
|
76c7d31c53
|
chore: bump vela cli to 0.0.4 (#3239)
* chore: bump vela cli to 0.0.4-test.0 * chore: refresh lockfile for vela cli 0.0.4-test.0 * chore(nix): refresh pnpm deps hash * fix: materialize electron before mac release checks * fix: rebuild electron when mac framework links are invalid * revert: drop release workflow experiments * chore(nix): refresh pnpm deps hash * fix: stop blocking beta mac release on electron symlink preflight * fix: stop using custom electron dist for beta mac packaging * fix: guard oversized chat images and opencode overflow * chore: bump vela cli to 0.0.4 * chore(nix): refresh pnpm deps hash * fix(daemon): surface prompt-image stat failures instead of dropping them resolveSafePromptImagePaths only swallowed unresolvable path input; once a path was confirmed inside UPLOAD_DIR and existed, a statSync failure (EACCES/EPERM, a file vanishing mid-run) silently dropped the image and let the run continue without that prompt context. Since this helper is now also the 1 MB enforcement point, that turned an infra/validation failure into a 'successful' run with missing required context. Collect those into a new failedImages bucket and fail the run with INTERNAL_ERROR at the call site, mirroring the oversized-image guard. Add a unit test covering statSync throwing. --------- Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
3f4fd58937
|
feat(landing-page): surface Discord + X in header, restructure site footer (#3230)
Some checks failed
ci / Detect CI change scopes (push) Successful in 0s
visual-baseline / Capture visual baselines (push) Waiting to run
landing-page-ci / Validate landing page (push) Failing after 2s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 2s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 2s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
* feat(landing-page): surface Discord + X in header, restructure site footer
Two related public-chrome adjustments:
- **Header gains compact Discord + X icon buttons.** Both community
channels were previously buried in the footer, so the typical
visitor never saw them on a page-deep scroll. They now sit before
the Download / Star CTAs in `nav-side`, share the ghost-button
outline language, and stay icon-only with `aria-label` so they
read as social affordances rather than competing with the text
CTAs. At ≤1080px the icon buttons hide alongside the existing
ghost CTA, so the bar still collapses cleanly into the hamburger
panel — Star stays in the bar at every breakpoint.
- **Footer restructured into 4 columns: Products / Plugins /
Resources / Connect.** The old `Plugins / Open Design / Connect`
three-column layout muddled three different things — sister
products, the artifact catalogue, and contributor channels —
under one roof, so visitors hunting for "the other thing this
team makes" had nowhere obvious to go.
- **Products** (new) lists the team's apps: Open Design (links
to homepage) and HTML Anything. Two entries by design — adding
more products without an editorial pass would dilute the
column.
- **Plugins** mirrors the topbar `Plugins` dropdown verbatim:
Templates / Skills / Systems / Craft, with no count prefix on
Systems / Craft so it reads identically to the nav.
- **Resources** (renamed from `Open Design`) carries the
docs-style links: Official source / Quickstart / Agents locaux
/ Compare / Claude Design alternative. The old column heading
was confusing because the OD logo + brand name already sit
under the column.
- **Connect** gains an X / Twitter row pointing at
`@nexudotio`. The brand entries on this column are
contributor / community surfaces only — code, releases,
chat, social, RSS, contact form.
Implementation:
- `_components/header.tsx` — `DISCORD` and `X_TWITTER` consts at
the top alongside `REPO`. Two `<a class="nav-icon">` blocks with
inline SVG before the existing Download / Star CTAs.
- `_components/site-footer.astro` — `HTML_ANYTHING` and `NEXU_IO`
consts. `<div class="sub-footer-col">` re-ordered to put
Products first, Plugins second (no longer carries `counts.*`
values), Resources third, Connect fourth (with the new X / Twitter
row).
- `globals.css` — `.nav-icon` rule cloned from the ghost CTA's
visual language (transparent + 1px line, fills on hover) but
square (36×36 round) so it reads as a social-icon affordance.
Added `display: none` for `.nav-side .nav-icon` to the existing
≤1080px and ≤880px media queries so the icons follow the same
collapse behaviour as the Download CTA.
- `sub-pages.css` — `.sub-footer-grid` switches from
`1.6fr 1fr 1fr 1fr` to `1.4fr 1fr 1fr 1fr 1fr` (brand + 4
columns). At ≤1080px it falls back to a 3-column shape so each
column has room to breathe; at ≤720px it stays a single column
(existing behaviour).
- `i18n.ts` — adds `products`, `resources`, `xTwitter`,
`sisterProjects`, `htmlAnything`, `nexuIo` to `LandingUiCopy.footer`
(the last three are kept around even though `sisterProjects` is no
longer rendered after the column was renamed Products — they're
harmless and avoid churning the type if a future iteration brings
the Sister-projects framing back). All 17 non-English landing
locales gain translations for the new keys via the existing
`LOCALIZED_LANDING_FOOTER_COPY` map (and the `LANDING_UI_COPY_OVERRIDES`
block for `zh` / `zh-tw`). Translations were generated with
`claude-haiku-4-5` over OpenRouter, with explicit instructions
to keep "Open Design", "HTML Anything", and "X / Twitter" in
English and to render "Products" / "Resources" in sentence case
per locale convention. Spot-checked against rendered pages on
`/zh/`, `/zh-tw/`, `/ja/`, `/ko/`, `/de/`, `/fr/` (and `/ar/` for
RTL) for natural phrasing.
Validation: `pnpm --filter @open-design/landing-page typecheck` ->
0 errors / 0 warnings; local dev server smoke-tested on en root
(`/html-anything/`) and 5 locale variants (`/zh/`, `/zh-tw/`,
`/ja/`, `/de/`, `/fr/`) — header renders 2 nav-icon buttons,
footer renders 4 localized column headings in the correct order
with the right link targets.
* fix(landing-page): address PR #3230 review — locale-aware HTML Anything link + drop unused const
Two non-blocking inline review points from @PerishCode on PR #3230:
- The HTML Anything entry in the new Products column hardcoded
`https://open-design.ai/html-anything/` via a top-level
`HTML_ANYTHING` const, but `/html-anything/` is a real localized
route in this app (`pages/[locale]/html-anything/index.astro`)
and `open-design.ai` is the same site's live domain. A visitor
on `/zh/…` clicking through landed on the English route and lost
locale context, and hardcoding the production domain meant a
preview build would surface a link that bounces visitors back
to prod. Switch to `href('/html-anything/')` so the locale prefix
+ the current site's domain (resolved by `localizedHref`) are
honored, matching every other footer link.
- `NEXU_IO` was declared at the top of the component but never
referenced — leftover from an earlier iteration that listed
`nexu.io` as a Sister-projects entry before the column was
renamed Products and reduced to OD + HTML Anything. Removed.
No behavior change beyond the locale routing fix; the i18n keys
and column structure stay as they landed in the original commit.
* fix(landing-page): correct nav-icon comment to match actual responsive behaviour
The JSX comment introduced for the new Discord + X icon buttons in
PR #3230 claimed the icons "survive at narrow widths while text-only
nav items get pushed off". The CSS that shipped in the same PR does
the opposite: both `@media (max-width: 1080px)` and `@media (max-width:
880px)` blocks add `.nav-side .nav-icon { display: none; }`, so at
narrow widths the icons collapse alongside the ghost Download CTA
while the text nav <ul> moves into the hamburger panel — only the
Star CTA remains visible in the bar.
Rewrite the comment to describe the actual responsive contract so
the next reader of `header.tsx` doesn't have to cross-reference
`globals.css` to figure out which surface stays. Reviewer flag from
@PerishCode on PR #3230.
No code-path change; comment-only.
* fix(landing-page): correct sub-footer 1080px comment to describe actual 3-column grid
The CSS comment introduced for the new sub-footer grid claimed the
≤1080px breakpoint drops to "brand + 2x2 grid of columns" — but the
rule produces a 3-column grid, not a 2x2.
`.sub-footer-grid` has 5 children at this breakpoint (the brand
block + the four footer columns) and `.sub-footer-brand` carries
no `grid-column` span, so with `grid-template-columns: 1.6fr
repeat(2, 1fr)` they flow as: row 1 = brand · Products · Plugins,
row 2 = Resources · Connect · empty cell. The brand sits inline
with two columns rather than on its own, and the four content
columns are not a clean 2x2.
The layout itself is fine; only the comment misleads the next
reader about how the columns wrap. Same flavor as the `header.tsx`
icon comment fixed in
|
||
|
|
98a2c63973
|
feat(daemon): add Antigravity agent adapter (#3157)
* feat(daemon): add Antigravity agent adapter
Adds Google Antigravity (`agy` CLI) as a coding-agent runtime. Detection
picks up `agy` on PATH, the daemon spawns `agy -p "<prompt>"` for a
single non-interactive turn, and the assistant text reply streams back
on stdout. OAuth is shared with the Antigravity IDE through the system
keyring, so users who have signed into the desktop app are authenticated
on first run with no extra step.
`agy` v1.0.3 has no JSON / stream-json / ACP output mode (upstream issue
#119), no `--model` flag (issue #35), and no MCP forwarding hook yet —
the adapter ships with `streamFormat: 'plain'` and a single `default`
fallback model so the model picker doesn't mislead users into thinking
their choice is wired through. We will upgrade buildArgs + add a
dedicated event parser when upstream ships structured output.
Also gitignores `.antigravitycli/`, the project-local config directory
`agy` auto-creates on every run (upstream issue #175).
* fix(daemon): Antigravity adapter — stdin prompt, brand icon, form loop, empty-output guard
- Switch prompt delivery from argv to stdin (`agy -p -`) to avoid the
30KB maxPromptArgBytes limit that blocked real-world composed prompts
- Add official Antigravity brand SVG icon to agent picker
- Fix repeated question-form loop for plain agents by injecting an
OVERRIDE block when form answers are already present in the transcript
- Add empty-output guard for plain agents so expired auth or silent
failures surface a user-visible error instead of a blank "Done" turn
* feat(daemon): expand Antigravity adapter — model picker, form-loop fix, OAuth launcher, log-file classification
PR #3157 follow-up integrating four iterations from end-to-end manual
testing on Gemini 3.5 Flash + GPT-OSS 120B Medium through `agy` v1.0.3.
Each section is independently verifiable; combined they're what made
the first successful artifact generation work end-to-end.
## Model picker via settings.json (agy has no --model flag)
agy v1.0.3 ships no `--model` CLI flag (upstream issue #35), but the
TUI Switch-Model picker writes the chosen label to
`~/.gemini/antigravity-cli/settings.json`'s `"model"` field, and every
`-p` invocation re-reads that file on startup — verified by capturing
the `--log-file` line `Propagating selected model override to backend:
label="<model>"`. Antigravity's `fallbackModels` now lists the 8
labels its TUI exposes (Gemini 3.1 Pro / 3.5 Flash variants, Claude
Sonnet/Opus 4.6 Thinking, GPT-OSS 120B Medium) and `buildArgs`
persists the user's choice to settings.json right before spawn. The
synthetic `default` id is preserved — picking it leaves settings.json
untouched so a user who switches models from agy's own TUI keeps
their choice.
Introduces `RuntimeAgentDef.supportsCustomModel?: boolean`. AMR's
hardcoded blocklist in `SettingsDialog.tsx` migrates to the
declarative flag (it rejects free-form ids at the ACP layer), and
antigravity opts out because its label set is a server-side enum that
silently fails on unrecognised strings.
## Form-loop fix (transcript sanitizer + stronger OVERRIDE)
The discovery form loop on weak/medium plain-stream models (GPT-OSS
120B Medium, Gemini 3.5 Flash) had two reinforcing causes:
1. `buildDaemonTranscript` packed the prior assistant turn's
literal `<question-form>` markup into the user request on the
next turn, giving the model a template to echo. New
`sanitizePriorAssistantTurnForTranscript` strips
`<question-form>...</question-form>` blocks and ```json fences
that match form-schema shape, replacing them with a brief
placeholder. User content is preserved verbatim (a user who
legitimately mentions `<question-form>` in chat keeps their
message intact).
2. The OVERRIDE block on form-answered turns was 4 lines and only
banned the bare `<question-form>` tag — models still emitted the
fenced JSON, form-asking prose ("Got it — tell me the following"),
and fake system events ("subagents stopped"). The new
`FORM_ANSWERED_SYSTEM_OVERRIDE` enumerates each anti-pattern and
pins them via tests, so silently weakening any line reintroduces
the regression.
Also adds RuntimeAgentDef.resumesSessionViaCli + RuntimeContext.
hasPriorAssistantTurn as forward-looking abstractions (skipTranscript
option on composeChatUserRequestForAgent). Antigravity does NOT opt
in — agy's `-c` resume activates an internal agentic loop with tool
retries and fallback-to-cached-response on tool errors that the OD
system prompt cannot steer; reverted after seeing byte-identical
form re-emissions caused by agy's own retry logic, not OD's transcript.
## One-click OAuth via system terminal
agy print mode can't complete Google Sign-In on its own (the OAuth
callback page asks the user to paste an auth code back into agy, but
`-p` has no input field). Before this commit the auth banner only
told the user to "open a terminal yourself."
Adds `POST /api/agents/antigravity/oauth-launch` and a cross-platform
launcher in `runtimes/terminal-launch.ts`:
- macOS: osascript → Terminal.app `do script "agy"` + activate
- Linux: tries x-terminal-emulator, gnome-terminal, konsole,
xfce4-terminal, xterm in order
- Windows: `cmd /c start "Open Design" cmd /k agy`
The endpoint hardcodes the `agy` command (no user input → no shell
injection surface) and is loopback-gated like the other daemon
endpoints. The chat's `AGENT_AUTH_REQUIRED` banner now renders a
"Sign in via terminal" button next to Retry; clicking it spawns the
terminal so the user can finish OAuth in one click.
## Silent-failure classification (auth vs quota via --log-file)
agy print mode is silent on stdout/stderr for both missing-OAuth AND
quota-exhausted failures — the upstream
`RESOURCE_EXHAUSTED (code 429): Individual quota reached` and the
`not logged into Antigravity` line only surface in agy's
`--log-file`. Without log inspection the daemon misread quota as
"auth required" and showed the wrong banner.
`RuntimeContext.agentLogFilePath` carries a daemon-owned per-run temp
path that antigravity's buildArgs translates to `--log-file <path>`.
The empty-output guard now reads that log on a `code === 0 &&
!childStdoutSeen` exit, feeds the tail to
`classifyAgentServiceFailure`, and routes:
- "not logged into Antigravity" → AGENT_AUTH_REQUIRED with
antigravityAuthGuidance
- "RESOURCE_EXHAUSTED" / "quota" / → RATE_LIMITED with
"Individual quota reached" antigravityQuotaGuidance
- none of the above (rare) → fall back to auth guidance
as the most likely cause
Both surface a terminal launcher in the auth banner: auth gets "Sign
in via terminal", quota gets "Switch model in terminal" — same
endpoint, contextual label. The handler is identical (open agy in a
terminal); the user either signs in or uses agy's Switch Model
picker to pick a model with available quota.
## Validation
- `pnpm guard` pass
- `pnpm --filter @open-design/daemon` runtime + telemetry suites:
192 passed, 1 skipped (the 1 pre-existing `task-type` failure on
origin/main is unrelated to this change)
- `pnpm --filter @open-design/web` typecheck pass; sse / amr-guidance
/ AgentIcon suites pass (51 web tests)
- Manual end-to-end on darwin + Gemini 3.5 Flash and GPT-OSS 120B
Medium: turn-1 question-form rendered correctly, turn-2 produced
`<artifact>` with full HTML (3.3KB Modern Minimal design) instead
of re-emitting the form. agy `--log-file` content correctly
classified as RATE_LIMITED when Gemini Pro quota was exhausted,
and as AGENT_AUTH_REQUIRED when keychain was cleared.
* fix(web/test): align amrAgent fixture with supportsCustomModel contract
The AMR agent definition in the daemon ships `supportsCustomModel: false`
so the Settings model picker hides the free-text "Custom…" option. The
PR changed `allowCustomModel` from `selected.id !== 'amr'` (hardcoded)
to `selected.supportsCustomModel !== false` (declarative), but the test
fixture was not updated to carry the same field — causing the
`__custom__` sentinel to appear in the picker under test.
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
* fix(daemon): align formAnswerTransition wording with main + scope build directive to discovery
CI surfaced two failures on the merge with main:
- chat-route.test marks submitted discovery form answers ... expected
the main-version wording 'Do not emit another <formId> form.'
- telemetry-message-finalization keeps non-discovery form answers
active ... expected task-type to fall through the else branch
('Treat these form answers as the active user turn'), not the
discovery RULE 2/RULE 3 build branch.
The colleague's earlier
|
||
|
|
bf7152dbdc
|
fix(web): disable Draw direct-send during an active run, keep Queue (#3270)
Reinstates the Studio tool hardening from #3081 on top of current main: while a task is streaming, the Draw/annotation primary Send action and its Enter shortcut are disabled, so an annotation can no longer leak into the active run while the button shows a disabled reason. This is the synthesis of two stacked-merge-divergent changes rather than a wholesale revert: Queue stays available, so the value from #1961 (kami) is preserved — an annotation made during a run is still staged for the next turn instead of being dropped. Only the button/Enter availability changes; the downstream queue/streaming-staging handler in ChatComposer is untouched. - PreviewDrawOverlay: send('send') and canSend now respect sendDisabled. - Reframed the streaming Draw test to assert Send is disabled while Queue still emits a queued annotation (preserving the "annotate during a run" coverage). - Added unit coverage for the Enter/Send guard and Queue availability while a task is running. |
||
|
|
912c7e380a
|
fix(plugin): infer semantic roles for token maps (#3231)
Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> |
||
|
|
bbf4809a7e
|
fix(web): use surface-appropriate noun in plugin/template preview unavailable copy (#3229)
After #2840 wired plugin and design-template 404s into the same "no shipped preview" placeholder the skills tab uses, the placeholder copy still hard-coded "skill" — so users opening a Community/Plugins card whose manifest declares a preview entry that doesn't ship saw "No shipped preview for this skill." on a card that is clearly not a skill. Adds a noun discriminator to PreviewView.unavailable so the placeholder reads with the right word per surface — "this skill" on the Skills tab, "this plugin" on Community/Plugins, "this template" on deck-mode design-templates. Locales gain three new preview.noun* strings (with appropriate per-language demonstrative+article) and the existing unavailable title/body interpolate a {noun} placeholder. Also fixes a CSS gap in .ds-modal-unavailable surfaced by the same path: the title and body divs were collapsing onto a single line under .ds-modal-empty's default flex-row. Mirrors the existing .ds-modal-error column+gap layout. Refs #897, #2840. |
||
|
|
055680a67d
|
fix(daemon): dedupe scheduled routine slots (#1971)
* fix(daemon): dedupe scheduled routine slots Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): claim scheduled routine runs atomically Co-authored-by: multica-agent <github@multica.ai> * Fix routine loser snapshot rollback Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): defer scheduled routine side effects Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): terminate in-memory run on scheduled prepare failure If `prepare()` throws after `persistPreparedRun()` has mutated the routine run with real project/conversation/agentRunId values, the catch in `RoutineService.start_` previously left the in-memory chat run queued (no `discard()`), so its `completion` promise hung waiting on `design.runs.wait(run)` forever, and the `routine_runs` row stayed pinned to `routine-pending-*` placeholders even though the underlying project/conversation rows for those real IDs had been created. The catch now calls `handlerStart.discard?.()` so the in-memory run terminates as `canceled`, releasing `completion`, and passes the real IDs through `updateRun` so the persisted failed row reflects what was attempted instead of the placeholder sentinels. A cleanup failure inside `discard()` is logged via `console.error` rather than swallowed, following the same surface-don't-swallow rule the loser cleanup path uses. The original prepare error is still rethrown so the scheduler advances to the next cadence (the slot claim is already terminal, so retrying the same slot would just duplicate-claim and lose). Added regression coverage in `apps/daemon/tests/routines.test.ts` for both the normal prepare-failure path (real IDs persisted, discard fired, completion resolved) and the case where the cleanup itself also throws (failure surfaces via console.error, the row is still finalized with the real IDs). Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): clear placeholder IDs on scheduled prepare failure Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): finalize routine prepare failures * fix(daemon): defer manual routine setup cleanup Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): drop loser chat runs and rollback partial snapshot pins Two follow-ups from the latest scheduler-claim review: - Duplicate scheduled losers used to call `design.runs.finish(run, 'canceled')`, exposing a phantom canceled routine run on `/api/runs` even though no `routine_runs` row, conversation, or messages were ever committed. Split the handler tear-down into `discardUnstarted` (used for never-inserted paths — drops the in-memory run via the new `design.runs.drop()`) and the existing `discard` (used after `prepare()` runs — still finalizes as canceled and rolls back partial state). - `resolvePluginSnapshot()` calls `linkSnapshotToProject()` before linking the conversation/run, so a failure mid-link could leave the reused project pinned to a snapshot the routine never durably claimed while `resolvedRoutineSnapshot` stayed null. Capture the intermediate snapshot id in `partiallyAppliedSnapshotId` when the resolver throws, and let `discard()` fall back to it for `restoreProjectSnapshotLink` so the previous project pin is restored either way. Regression coverage added in `tests/routine-schedule-claims.test.ts`: - A scheduled loser does not surface a phantom canceled chat run via `/api/runs` after the slot is lost. - A resolver that throws after `linkSnapshotToProject()` (forced via a SQLite trigger on `conversations.applied_plugin_snapshot_id`) still restores the reused project's previous pin in `discard()`. * fix(daemon): return prepared routine run ids Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: kami.c <kami.c@chative.com> |
||
|
|
afc6e9a39f
|
feat(landing-page): localize templates subcategory chip labels across 16 locales (#3256)
The "scene" chip rail under each `/plugins/templates/<kind>/` page shipped 23 chip labels in English (`UI & product mockups`, `Brand & logo`, `Storyboards`, `Social & content`, `Avatar & portrait`, `Illustration & style`, plus the rest of the 24-slug subcategory map covering all seven artifact kinds). Only the `zh` override carried a translation; every other non-English locale fell back to English on its scene rail. The result: a visitor reading the rest of `/ja/plugins/templates/image/` in Japanese (hero, kind chips, FAQ, card chrome — all localized in PR #3218) hit a row of English chips at the bottom that read as machine output rather than first-party copy. This change fills `subcategory: { ... }` for the remaining 16 landing locales: `zh-tw`, `ja`, `ko`, `de`, `fr`, `ru`, `es`, `pt-br`, `it`, `vi`, `pl`, `id`, `nl`, `ar`, `tr`, `uk`. The existing `zh` translation is untouched. Brand-name tokens (`UI`, `HyperFrames`, etc.) stay in English; localizable terms (`Apps`, `Brand`, `Logo`, `Avatar`, `Storyboards`, …) are translated where the language has a clean native equivalent. Conjunctions follow locale convention — `&` for Latin-script locales that read it as native chrome, `·` for CJK locales where it works better than `&` next to ideographs, and `و / & / และ`-style natural conjunctions for the rest. Translations were generated with `claude-haiku-4-5` over OpenRouter using a single batch script with explicit instructions on chip-width budget (≈120px, target 1–4 native words), sentence casing, and brand-token preservation. Output was validated for JSON shape (every locale returns all 23 slugs) before splicing into the override blocks. Validation: pnpm --filter @open-design/landing-page typecheck -> 0 errors / 0 warnings; local dev (port 3067) renders the chip rail in Japanese / Russian / Traditional Chinese / Arabic / German / French on `/<locale>/plugins/templates/image/` (and the same rail on the other six artifact kinds, which share the subcategory slug map). Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
9c6a69490b
|
fix(web): localize mention picker copy (#3255) | ||
|
|
5319e14dc0
|
docs: sync README skill and design-system counts to 137 / 150 (#3254)
* docs: bump skill count to 137 in TL;DR and header badge * docs: sync at-a-glance and comparison-table counts, drop broken arithmetic * docs: sync remaining body references to 137 skills |
||
|
|
d0921ed335
|
fix(skills): avoid orphan web prototype files (#3253) | ||
|
|
4a0900ca81
|
fix(web): remove passive video play badge (#3252) | ||
|
|
f67d245744
|
docs(i18n): fix zh-TW README parity drift from English (#3251)
- Comparison table: design systems 72 -> 129 (match EN README) - Repository structure tree: add missing kami-deck.html template entry Both were drift from the English README. The deeper EN-wide count inconsistency (badge 149/131 vs body 72/31) is tracked in #3250. |
||
|
|
20136c4da9
|
fix(skills): stream-copy fallback when skill staging hits cross-fs EPERM (#3249)
* fix(skills): fall back to a stream copy when skill staging hits EPERM `fs.cp` copies each file with copy_file_range(2), which the kernel rejects across some filesystem pairs — e.g. a container image layer (`/app`) copied onto a ZFS/overlay bind mount (`/data`) — surfacing EPERM. Node doesn't fall back to a userspace copy, so skill staging failed and degraded to absolute paths, losing the `.od-skills` write barrier. Retry recoverable copy errors (EPERM/EXDEV/ENOTSUP/EOPNOTSUPP) with a dereferencing read/write copy that works across any source/dest filesystem; non-recoverable errors still degrade as before. A test seam injects a synthetic EPERM since the real errno only reproduces on those mounts. * fix(skills): preserve source file mode in the EPERM stream-copy fallback The cross-filesystem fallback copied contents with createWriteStream, which opens the destination at the default 0644 and drops the source's exec bit. Skills shell out to staged helper scripts (e.g. skills/pptx-html-fidelity-audit/scripts/*.py), so on the EPERM/EXDEV path this fallback repairs they would fail with EACCES. chmod (masked to 0o777, so the agent-writable staging copy never inherits setuid/setgid/sticky) + utimes each copied file from the source stat so the fallback matches fs.cp's mode/timestamp preservation. Adds a regression test that stages an executable fixture through the synthetic-EPERM seam and asserts the exec bit survives. |
||
|
|
08c350fb0f
|
fix(analytics): bucket feedback agent/model directly on the event (#3240)
* fix(analytics): bucket feedback agent/model directly on the event
Reason × agent / reason × model splits on
`assistant_feedback_reason_submit` were 25-74% `unknown` because the
event only carried `run_id` — analyses had to join back to
`run_created/run_finished`, which loses rows whenever the feedback is
given to a message whose run sits outside the query window (the common
case for feedback on older messages), and whose `model_id` was `null`
to begin with (the user didn't pick a specific model — went with the
agent's default).
Carry `agent_provider_id` and `model_id` directly on every feedback
event so the analyses no longer need to join. Replace `null/unknown`
with the `default` bucket via `modelIdForTracking` (and let
`agentIdToTracking` fall through to `other`) at every emit site —
`null` was an analyst-hostile mix of "no selection" and "join failed";
`default` is a real, analysable bucket. On `run_finished`, upgrade the
model to the agent-reported value from initializing/model status
events when the user did not pick one — covers ACP, claude-stream,
copilot-stream, json-event-stream, qoder, pi-rpc.
* fix(analytics): use feedbackAgentProviderIdToTracking and assistantFeedbackModelId for feedback events
Wire API-mode agent ids (anthropic-api → anthropic) and agentName-parsed
model ids through the feedback emit path. Previously the feedback props used
agentIdToTracking (no anthropic-api case) and assistantModelDetail (no
agentName fallback), causing model_id='default' and agent_provider_id='other'
for API-mode agents.
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
* fix(analytics): extend feedback/run schema for full agent/model coverage
Layered on top of the conflict resolution and the v1 emit switchover
in
|
||
|
|
45873a551b
|
fix: improve queue screenshot preview modal backdrop (#3215)
Increase the backdrop opacity from 44% to 75% and add a blur effect to better separate the queue screenshot preview from the file-list preview panel underneath. This prevents visual overlap and makes it clearer which preview surface is active. Fixes #3167 |
||
|
|
ef8f518b3b
|
Fix status detail URL parsing (#3208) | ||
|
|
98651ecae2
|
fix: localize queue UI strings in Chinese mode (#3213)
- Queued → 已排队 - to Send → 待发送 - Edit queued task → 编辑排队任务 - Save → 保存 - Cancel → 取消 - Edit → 编辑 - more queued → 个排队 - Queued follow-up → 已排队的后续任务 Fixes #3173 |
||
|
|
afc5f52445
|
Fix/issue/3149 (#3162)
* fix(docker): fix container startup crash due to missing OD_API_TOKEN * fix(docker): forward OD_API_TOKEN to fix docker container boot loop * fix(docker): enforce non-empty OD_API_TOKEN for docker-compose * fix(deploy): automate OD_API_TOKEN generation in installer and close compose loop * docs(readme): guide manual deployment users to configure OD_API_TOKEN * docs(readme): align working directory paths for manual deployment instructions * docs(readme): align working directory paths for manual deployment instructions * docs(readme): restore git clone context for first-time users * fix(web): add min-width constraints to plugin filter span and pill button related issue 3149 |
||
|
|
49573f031a
|
Update docs/assets/github-metrics.svg (#3159)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
1efa1dc7b5
|
Add preview iframe keep-alive pool (#2190)
* Add preview iframe keep-alive pool * Fix active preview eviction on prompt context changes * Evict preview iframes on skill/design-system registry edits Bridge Settings → Skills / Design Systems to App.tsx so the keep-alive pool drops any preview iframe whose project depends on the affected id after every successful mutation. Without this, body-only edits leave SkillSummary / DesignSystemSummary fields untouched and ProjectView's signature-driven eviction never fires, so the active preview keeps serving stale prompt context. The handler also re-fetches the App shell's skill / design-system lists so summary-field changes propagate to ProjectView's signature on the next render. Also extend IframeKeepAlivePool.evictMatching with an includeActive option so the new handler can drop the currently-visible iframe along with parked ones; the fallback pool only ever holds active entries so includeActive is a no-op there. Regression tests: - App.previewKeepAlive: clicking a Settings stub that fires onSkillsChanged / onDesignSystemsChanged drives evictMatching with includeActive=true and a predicate that matches projects using the affected id while skipping unrelated projects. - SkillsSection: onSkillsChanged fires after a body-only edit and after a delete. * fix: reattach active keep-alive iframe after eviction * fix(web): refresh design systems after rename --------- Co-authored-by: kami.c <kami.c@chative.com> |
||
|
|
1c2a1c4459
|
Add launch review regression coverage and stabilize daemon tests (#3207)
* Add launch review E2E regression coverage * Harden daemon launch review regressions * Stabilize daemon runtime tests * fix(tests): restore e2e preflight typing Generated-By: looper 0.8.1 (runner=fixer, agent=codex) * fix(tests): make fake plugin runtime ESM-safe Generated-By: looper 0.8.1 (runner=fixer, agent=codex) * Stabilize e2e fake agent and regression tests * fix(tests): repair fake agent cjs runtime Generated-By: looper 0.8.1 (runner=fixer, agent=codex) * fix(review): harden plugin authoring checks Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * fix(tests): bind plugin authoring run to seeded conversation Generated-By: looper 0.9.2 (runner=fixer, agent=codex) |
||
|
|
82203fe4a7
|
fix(landing-page): community page brand mark + license + nav trim + twitter handle (#3222)
* fix(landing-page): trim community page nav + correct twitter handle Two small corrections to the static `apps/landing-page/public/community/index.html` page served at https://open-design.ai/community/: - Drop the `Skills` and `Design systems` shortcuts from the page-level top nav. The site-wide topbar already routes to the unified `/plugins/` hub (Templates / Skills / Systems / Craft are all faceted from there since PR #2880 / #2926 / #2958), so the Contributors page nav exposing two of those four facets out-of- context reads as inconsistent — visitors who clicked through were bypassing the hub. Keep `Ambassadors` (in-page anchor), `GitHub`, and the Discord pill; everything else in this list is a contributor-facing destination. - Update the footer X / Twitter link from `x.com/nexu_io` to `x.com/nexudotio`. The `@nexudotio` handle is the active product account; `@nexu_io` was a stale earlier handle. No JS / build-pipeline change — the page is static HTML served from `public/`, so the diff is three lines. * fix(landing-page): swap community page brand mark from letter "O" to logo image The Contributors page top nav rendered a hand-rolled black circle with a white "O" letter inside as the brand mark, which doesn't match the rest of the site (homepage / sub-page header both use the same `/logo.webp` image). On a Contributors page where the goal is to read as a first-party Open Design property, having a different brand mark in the corner reads as a different site. Replace the `<span class="brand-mark">O</span>` literal with an `<img src="/logo.webp">` and rewrite the local `.brand-mark` / `.brand-mark img` rules to match the homepage's pattern: an inline-flex 22×22 wrapper with a 5px-radius image inside (≈22% of side, the same app-icon silhouette convention `globals.css` uses for the homepage 44×44 mark, scaled down). The asset is the same `/logo.webp` already shipped in `public/`, so no new file is added. * fix(landing-page): correct community footer license string MIT → Apache-2.0 The Contributors page footer rendered `© 2026 Open Design · MIT-licensed · Built by contributors, in public.` — but Open Design has shipped under Apache-2.0 since launch (the repo `LICENSE`, every page footer elsewhere on the site, and the in-product chrome all say Apache-2.0). MIT was a copy-paste leftover from an older draft and is materially wrong: the two licenses differ on patent grants and trademark / attribution mechanics, so showing the wrong one to a contributor reading the page could shape downstream reuse decisions. Single-string change: `MIT-licensed` → `Apache-2.0`. Confirmed via grep that no other reference to MIT remains in the landing-page tree. --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
6ac1450925
|
feat(landing-page): localize templates page chrome + FAQ + categories across 17 locales (#3218)
PR #3185 introduced 9 new copy keys for the templates grid chrome (`templatesHeroEyebrow`, `templatesHeroLead`, `templatesCounterLabel`, `cardFeaturedTag`, `cardReadFullPrompt`, `cardUseTemplate`, `cardShareAria`, `faqHead`, `faqItems`) and used `pcopy.category[slug]` labels and descriptions on the kind facets. The English base was filled in but the per-locale `overrides` map was left as a follow-up, so every non-English visitor saw English chrome on `/<locale>/plugins/templates/` and English H1 + lead on `/<locale>/plugins/templates/<kind>/` (except `zh`, which already shipped a `category` override before PR #3185). This change fills in all 17 non-English landing locales for those new chrome keys, FAQ Q&A, and the artifact-category labels: zh, zh-tw, ja, ko, de, fr, ru, es, pt-br, it, vi, pl, id, nl, ar, tr, uk. Brand names (`Open Design`, `Claude`, `Claude Design`, `Anthropic`, `OpenAI`, `HyperFrames`, `Cloudflare`, `Apache-2.0`, `BYOK`, `PR`, `GitHub`) stay in English in every locale per the SEO anchor strategy. Artifact category labels are localized with the native-language word each design / dev community would actually search for: `プロトタイプ` (ja), `프로토타입` (ko), `Prototyp` (de), `Prototipo` (es), `Прототип` (ru), and so on. `zh` keeps its existing `category` translation untouched since it was already shipped — only the new chrome + FAQ keys land for that locale. Translations were produced with `claude-haiku-4-5` via OpenRouter and spot-checked against rendered pages on 5 locales (zh, ja, ko, de, fr) for natural phrasing, brand-name preservation, and HTML-tag / entity / variable integrity. The remaining 12 locales follow the same prompt and are expected to be merge-ready as a v1; native speakers in the community can refine wording later via small PRs without coordinating across the whole grid. Validation: pnpm --filter @open-design/landing-page typecheck -> 0 errors / 0 warnings; local dev (port 3062) renders 231 cards on each of /zh/, /ja/, /ko/, /de/, /fr/ /plugins/templates/ with hero eyebrow / H1 / counter / CTA / FAQ head / first FAQ Q all localized, and /ja/plugins/templates/prototype/ H1 reads "プロトタイプ" with a localized lead (was English on prod before this PR). Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
cd1790abab
|
Harden AMR Link startup model discovery (#3198)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 1s
ci / Detect CI change scopes (push) Successful in 0s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 1s
ci / Workspace unit tests (push) Failing after 2s
ci / Daemon workspace tests (push) Failing after 2s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
|
||
|
|
56fe5c5036
|
fix(amr): stage external image attachments into workspace (#3226)
* fix(daemon): forward AMR image attachments through ACP * fix(amr): stage external image attachments into workspace * fix(amr): stage prompt image paths safely |
||
|
|
a4ec7808a6
|
docs: sync CONTRIBUTING translations with English source (#3131)
The English `CONTRIBUTING.md` got two updates that never made it into the localized variants: - `3790c003` (#1170) — replaced the Windows-native disclaimer with a pointer to the new `docs/windows-troubleshooting.md` guide. - `6341b267` (#1520) — added a "Use the PR template" bullet to the Commits & pull requests checklist so reviewers can call out empty sections instead of asking for them piecemeal. This PR ports both changes into the German, French, Japanese, Brazilian Portuguese, and Simplified Chinese variants so non-English contributors land on the same merge bar. |
||
|
|
94ad2284a0
|
Fix pnpm native executable invocation (#2144) | ||
|
|
831208b823
|
Refine Studio preview interactions (#3000)
* Refine studio preview interactions * Fix deck toolbar navigation for transform tracks * Fix manual edit preview close * Fix Simple Deck toolbar scrolling * Fix preview screenshot capture * Fix deck preview progress sync * Refine edit target selection for grouped elements (#3068) * Prefer child edit targets over grouped parents * Keep edit inspector header and footer fixed * Shorten floating edit inspector * Show readable edit target names * Allow dragging the floating edit inspector * Add explicit edit inspector actions * Show preview comment count in toolbar * Separate annotation and comment toolbar groups * Remove annotation toolbar divider * Close edit inspector from footer actions * Hide edit inspector until target hover --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> * Fix manual edit iframe regression test * Fix Studio interaction review feedback Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * Fix saved comment link classification Generated-By: looper 0.9.2 (runner=fixer, agent=codex) --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
4abc08bb17
|
fix(web): restore changes silently dropped by PR #2461 sync merge (#3210)
* fix(web): remove Ingest source panel from Automations tab (#2711) * fix(web): remove Ingest source panel from Automations tab The Automations tab carried a free-form "Ingest source" composer that let users paste arbitrary content (URL, repo path, connector event, chat snippet) and turn it into a source packet plus evolution proposals. The form was confusing next to the routine/template flow on the same page, exposed an internal canonicalization concept users don't need to think about, and shipped before the surrounding evolution-proposal flow was wired into a coherent end-to-end story. Drop the UI surface only: - Remove the <section className="automations-ingest"> block, the Template / Source / Compression / Connector selects, the title/source ref/content fields, the recent-packets list, and the Ingest button. - Drop the now-dead local state (sourcePackets / sourceForm / ingestingSource), the patchSourceForm and submitSourceIngestion helpers, the SOURCE_KIND_OPTIONS / COMPRESSION_OPTIONS constants, the SourceIngestionForm type and DEFAULT_SOURCE_FORM, the /api/automation-source-packets refresh leg, and the sourcePackets side-write inside crystallizeRun. - Remove the matching .automations-ingest / .automation-ingest-* CSS block (plus the two responsive overrides) from tasks.css. - Delete the test case that drove the form in TasksView.templates.test. Backend stays intact: apps/daemon/src/automation-ingestions.ts, the POST /api/automation-ingestions route, `od automation ingest` CLI, the routine-evolution call site, and the AutomationContentPacket / AutomationSourceKind / AutomationTokenCompressionMode contracts all remain, since routine scheduling still depends on them. * fix(web): drop crystallize test assertion on removed packet list The crystallize test was asserting that the new content packet's title shows up on the page. That assertion only passed because the daemon response was being side-written into the deleted sourcePackets state and rendered in the Ingest source recent-packets strip. With that UI removed, the packet title has no surface to land on; the proposal title (`Skill: Artifact polish loop run`) is still asserted and remains the real signal that crystallize succeeded. * test(e2e): restore #2305 / #2578 e2e regressions lost in PR #2461 merge Sync merge |
||
|
|
9032fbb689
|
Fix #3169: Show confirmation toast after export/download (#3183)
* Fix #3169: Show confirmation toast after export/download Adds a success toast ("Export started") after any export/download action completes. The toast uses the existing Toast component with the same pattern as commentSavedToast and templateSavedToast (2.2s auto-dismiss). The toast fires from within fireShareExport on both sync and async success paths, covering all export formats: PDF, PPTX, ZIP, HTML, Markdown, image, JSX, and React HTML. Co-authored-by: CommandCodeBot <noreply@commandcode.ai> * Gate export toast to file export formats only The toast was previously wired inside fireShareExport for all callers, which incorrectly showed "Export started" for template save and deploy modal opens. Gate to pdf/pptx/zip/html/markdown only. Also fix comma to semicolon in types.ts. Co-authored-by: CommandCodeBot <noreply@commandcode.ai> --------- Co-authored-by: CommandCodeBot <noreply@commandcode.ai> |
||
|
|
b8cdf5f0ea
|
feat(mcp): generation loop + one-click Codex install (#3141)
* feat(mcp): add project creation, capability discovery, and generation tools
Lets an external coding agent (Codex, Cursor, …) drive a full design
loop over `od mcp`, not just read/write files: create a project,
discover what Open Design can make, commission a generation run, poll
it, and open the result in a browser. Complements the existing
write_file / delete_file / delete_project management tools.
New tools:
- create_project — make an empty project to generate into (start_run
needs one). Derives a slug id from the name unless given.
- list_skills / list_plugins — discover what you can ask OD to make.
- start_run / get_run / cancel_run — commission a run (OD spawns its
own agent), poll to completion, cancel. start+poll because MCP is
request/response and generation is minutes-long.
- get_run / get_project now return a browser-openable previewUrl
(entry file served raw; HTML entries render directly).
The external agent never runs a skill itself — it commissions OD to,
so the prior "skills not on MCP" boundary no longer applies.
* feat(mcp): make get_run preview hint directive
Reword the hint MCP clients receive when a run finishes so the agent
is more likely to surface the previewUrl to the user proactively —
mention the user-facing browser explicitly and call out that clients
with a built-in browser pane (e.g. Codex CLI's right-side browser)
should navigate to it directly. Also nudge start_run's hint to flag
that a previewUrl will arrive on success, so the agent knows what to
do with it before it ever sees get_run.
Pure text change; no behavior change in the tool surface or daemon.
* feat(mcp): one-click Install / Remove for Codex from Settings
Adds a toggle button on Settings → Integrations → Codex panel that runs
`codex mcp add open-design …` / `codex mcp remove open-design` via the
daemon, so users no longer need to copy TOML and paste it into
~/.codex/config.toml by hand. The copy-snippet path is unchanged and
remains the fallback when the Codex CLI isn't on PATH.
The daemon shells out to Codex CLI rather than rewriting config.toml
itself — that way we inherit Codex's own merge / dedupe / validation
rules and only track its argv. The runner is dependency-injected for
testability.
New endpoints (under /api/mcp/install/codex/*):
- GET status — probes `codex mcp get open-design`; returns
{ available, installed } so the UI can render the toggle state.
- POST — runs `codex mcp add open-design --env K=V … -- <node> <cli.js> mcp`,
reusing the same payload as /api/mcp/install-info.
- DELETE — runs `codex mcp remove open-design`.
The web UI renders the toggle only inside the Codex client panel
(`client.id === 'codex'`). When Codex CLI is missing it shows a
disabled button with an explanatory hint instead of vanishing, so users
know why one-click isn't available.
* feat(mcp): teach agents to clarify ambiguous format requests
When the user asks for a "PPT" / "deck" / "slides" / "PDF" / "doc",
that's two very different deliverables: Open Design natively produces
browser-viewable HTML/SVG (including HTML-rendered decks), but the
user may actually want a binary .pptx / .docx / .pdf — which OD does
NOT produce and which the agent would have to export from OD's output
itself. Add a paragraph to the MCP server instructions telling the
agent to ASK which one is wanted before kicking off work, rather than
silently picking one or dual-tracking both paths.
Pure prompt-text change in the instructions block; no tool surface or
behavior change. Costs ~10 lines of session-init context (one-time
per MCP session), versus dual-tracked .pptx hedging Codex was
otherwise doing on every ambiguous request.
* feat(mcp): surface agent messages, skip OD discovery, slim list_plugins
Three fixes uncovered while exercising the full MCP-driven generation
loop end-to-end with a real Codex client. Each one is a real
blocker / footgun for the external agent.
1. get_run now includes agentMessage — the inner agent's textual
output reassembled from the SSE event stream. Without this, runs
that ended in a discovery-style clarifying question (e.g. a
<question-form>) looked like "succeeded with empty output" mysteries
to the outer agent. The hint now branches on whether previewUrl
exists: with preview = show preview + relay agentMessage as the
inner agent's note; no preview = relay agentMessage as the actual
deliverable (almost always a clarifying question).
2. create_project sets skipDiscoveryBrief:true by default. The outer
agent IS the user-facing surface for MCP-driven runs, so OD's own
interactive discovery stage just creates a confusing
nested-clarification loop where its question form ends up dropped
(no files = no artifact). Better to let the outer agent gather
requirements and pass a precise prompt or plugin to start_run.
3. list_plugins flattens the daemon's bulky 16-field plugin record
(fsPath, sourceMarketplaceId, installedAt, …) into the few fields
an agent actually picks plugins on: id, title, description, kind,
tags. description / kind come from manifest.description /
manifest.od.{taskKind,kind} which the previous pass-through dropped
on the floor.
* feat(mcp): smart entry fallback + list_agents
Two fixes uncovered by exercising the full Codex-driven loop on a real
machine. Both close the gap between "Open Design has the data" and
"the external agent can find it".
1. get_project / get_run now fall back to scanning the project's file
list when metadata.entryFile is missing. We hit the case where
write_file (and a half-finished inner-agent run) put a perfectly
viewable index.html into the project, but metadata.entryFile stayed
null — so the outer agent got no previewUrl from MCP and resorted
to guessing a file:// path. Priority: declared entryFile, then
index.html anywhere, then a single .html at the project root.
Pure read-side change; no extra fetch when entryFile is already
set.
2. list_agents lets the outer agent stop guessing 'claude' / 'codex' /
'gemini' for start_run.agent. The daemon already exposed
/api/agents with 19 supported CLIs and an `available` flag. The
MCP wrapper defaults to filtering to installed agents only (so the
agent never picks one whose binary won't spawn), with
includeUnavailable:true as an opt-in to see uninstalled ones plus
their installUrl. Models truncated to 10 with modelsCount carrying
the real total — keeps the response token-economical even for
agents (opencode) with 100+ models.
* feat(mcp): tell the outer agent runs take 5–30 min, don't bypass
Direct response to a real Codex client observably cancelling an
in-flight run after 3 polls and substituting its own write_file
output ("文件时间戳没推进 → 我直接覆盖生成") — exactly the failure
mode this MCP surface exists to avoid.
start_run's hint and the session-init instructions block now both
state explicitly:
- Runs typically take 5–30 minutes.
- status:running with unchanged file mtimes is the inner agent
thinking, NOT a hang.
- Do not cancel_run out of impatience.
- Do not substitute write_file as a "faster" workaround — that
discards OD's pipeline-driven design quality.
- Poll every 30–60 seconds; report "still working" to the user
between polls.
- Only call cancel_run if the user explicitly asks.
Pure prompt-text change; no surface or behavior change. Costs ~10
lines of one-time session-init tokens + ~80 more tokens per
start_run response, in exchange for the outer agent actually
trusting the run.
* feat(mcp): persist run events to disk + expose tail-able path
Closes the in-flight visibility gap that made real Codex clients
cancel a 24-min run after 3 polls and substitute their own
write_file output, simply because polling get_run showed no change.
Daemon: every SSE event is now mirrored to a JSON-Lines file at
<RUNTIME_DATA_DIR>/runs/<runId>/events.jsonl. The path is wired
through createChatRunService's new `runsLogDir` option (null
disables, preserving legacy in-memory-only behavior). statusBody
exposes the path as `eventsLogPath`. Failures are best-effort — a
broken stream destroys itself and the run keeps going on the
in-memory event log (SSE clients are unaffected).
MCP: get_run already passed statusBody through, so eventsLogPath
surfaces automatically. The new value is that get_run during a
running status now adds a directive hint telling the outer agent to
`tail -n 50 -f <path>` in its own shell to see live progress —
that's the signal that makes the agent trust the run and stop
cancelling. The succeeded-status hint mentions the path too, for
forensics. No new tool; the field rides existing get_run polls.
Spec-first throughout:
- runs.test.ts adds 4 tests covering write-per-emit, statusBody
field, null-runsLogDir back-compat, and the no-IO guarantee
when persistence is disabled.
- mcp-runs.test.ts adds 1 test for the running-status hint.
* fix(mcp): get_run hint directs callers to pass project explicitly
The success hint in get_run previously said "project defaults to this
run's project", which is misleading: get_artifact has no run context and
falls back to /api/active when project is omitted, not to the run's
project. A client following the old guidance after creating a fresh or
non-active project could fetch the wrong project's files or fail with
"no active project".
The hint now embeds the run's projectId and tells callers to pass it
explicitly: get_artifact({ project: "<id>" }). A focused regression test
in mcp-runs.test.ts verifies the hint contains the projectId and does
not contain the incorrect active-context fallback guidance.
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
* fix(contracts): add eventsLogPath to ChatRunStatusResponse
The daemon's statusBody() returns eventsLogPath but the shared DTO
lacked this field, leaving web/CLI/MCP callers without a typed
accessor.
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
* feat(mcp): bind MCP runs to OD conversations + studio deep links
Closes the last gap that made MCP-driven runs feel like a parallel
side door: the user could not see the conversation in OD's studio
page even though the run was real, finished, and had files.
Daemon side: POST /api/runs now falls back to the project's default
conversation when the caller (MCP / SDK) only supplied projectId.
It synthesizes an assistantMessageId, writes a user message with the
prompt as content, and lets the existing
`pinAssistantMessageOnRunCreate` helper create the empty assistant
row. The existing `appendMessageAgentEvent` accumulation path then
streams text_delta events into the assistant row's content — same
as the web /api/chat flow. The response body now echoes the
resolved conversationId + assistantMessageId so MCP callers can
build a deep link.
`buildMcpInstallPayload` now also surfaces `webBaseUrl` (read from
OD_WEB_PORT, the env tools-dev exports for the web listener). MCP
clients use it to build studio deep links.
MCP side: `start_run`, `get_run`, `get_project` now return a
`studioUrl` — a browser-facing OD URL pointing at the studio page
that shows the file preview AND the chat history side by side. The
hint on each tool was updated to tell the outer agent to hand
studioUrl to the user as the primary link (previewUrl falls back to
raw-file when the user only wants the rendered output). The
webBaseUrl is fetched once via /api/mcp/install-info and cached for
5s to keep per-poll cost flat; a tiny `_resetWebBaseUrlCache` export
lets tests start each case with a clean cache.
Contracts: `ChatRunCreateResponse` gains optional conversationId +
assistantMessageId; `ChatRunStatusResponse` gains optional
eventsLogPath. Both additive, no consumer breakage.
Spec-first throughout:
- get_run includes studioUrl on success when webBaseUrl + conversationId are available
- get_run omits studioUrl when webBaseUrl is null
- start_run returns studioUrl and conversationId for the new run
- get_project returns studioUrl using the project default conversation
* fix(mcp): add skill/skillId to start_run so listed skills are actionable
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
* fix(test): update mcp-get-project test to handle getWebBaseUrl fetch
The get_project handler now calls getWebBaseUrl (added with the studio
deep-link feature), which fetches /api/mcp/install-info. The test mock
only handled the /api/projects/:id URL and expected a single fetch call,
causing the assertion to fail with "called 2 times" instead of 1.
Fix: handle the /api/mcp/install-info URL in the fetch mock (returning
webBaseUrl: null), update the call count expectation to 2, and call
_resetWebBaseUrlCache in afterEach to prevent cache bleed between tests.
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
* feat(mcp): tell agents to render studioUrl as a clickable markdown link
Observed in a real Codex client: Codex received studioUrl correctly
but rendered it as inline code (gray code-span), which its built-in
browser pane does NOT make clickable. The user had to copy-paste the
URL into a browser by hand even though Codex / Cursor / Zed all
auto-link markdown `[label](url)` syntax and would navigate it in
their right-side preview pane.
The three studioUrl-mentioning hints now explicitly tell the agent
to render the URL as a markdown link (e.g.
`[Open Open Design studio](URL)`) and never as inline code or bare
text. Pure prompt-text change.
* fix(runs): resolve default agent when MCP caller omits agentId; add McpRunCreateRequest contract type
- POST /api/runs: when no agentId is provided, resolve from app-config
or first available CLI before spawning — mirrors the pattern the
routine handler already uses. Prevents 'unknown agent: undefined'
failures on the create_project -> start_run(prompt) MCP path.
- packages/contracts: add McpRunCreateRequest interface for the
projectId-only / SDK caller shape so typed callers can construct the
request without casts. Exported via index.ts's existing chat re-export.
- packages/contracts/tests: add compile fixture verifying projectId-only,
projectId+message, and projectId+message+agentId shapes all type-check.
- apps/daemon/tests: add mcp-runs test asserting agent arg omitted in
start_run does not include agentId in the POSTed body.
Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
|
||
|
|
9d65e26c0f
|
feat(landing-page): card grid + share popover for /plugins/templates/ (#3185)
* feat(landing-page): YouMind-style grid + share popover for /plugins/templates/ The list-style catalog rows that landed in PR #3010 read as a long table of items rather than a discoverable grid. Product feedback (after benchmarking against youmind.com/zh-CN/seedance-2-0-prompts) wanted: - A YouMind-shape card with a top accent band, video / poster preview area, author + attribution row, an excerpt frame, and a primary CTA paired with a share button. - Hover-autoplay on the 46 video templates whose manifest carries a Cloudflare Stream MP4. The data was already there since PR #3010; the catalog row just rendered the poster as a static `<img>`. - A counter chip on the right of the hero that surfaces the live total (`Total · 231`) instead of baking the number into the H1 ("231 runnable templates."). The hero now reads as `OPEN SOURCE CLAUDE DESIGN` eyebrow + `Templates.` static H1, which also threads the brand keyword into the page's SEO surface. - A six-question FAQ block below the grid covering license, BYOK keys, contribution, and the "open source Claude Design alternative" positioning explicitly. Implementation: - `_components/template-card.astro` — new card component. Accent band hue is derived from `od.mode` so artifacts of the same kind get a consistent color (video green, prototype blue, deck mustard, image wisteria, hyperframes coral, audio amber, live-artifact teal), falling back to a stable per-index hue for unrecognized modes. Featured tag (yellow, on-brand) is visible when the manifest tag list contains `featured`; the rest of the card is locale-resolved via the same `resolveBundledTitle` / `resolveBundledDescription` helpers PR #3010 added. - `pages/plugins/templates/index.astro` + `[kind]/index.astro` — grid layout (`.tpl-grid`, `repeat(auto-fill, minmax(340px, 1fr))`), hero with counter chip, FAQ section on the parent only. Adjacent filter strips share a single divider rather than drawing one each, so the kind + scene chip block reads as one filter unit instead of three stacked horizontal cuts. - Hover-autoplay observer + share button click handler bundled into one `<script>` per page so they share the same boot lifecycle. The earlier split version dispatched `astro:page-load` from the autoplay block before the share block's listener attached, which dropped the share click on the floor; the merged init() runs eagerly when DOM is ready, re-runs idempotently on `astro:page-load` (Astro view transition), and uses `data-tpl-init` / `data-tpl-share-bound` markers to prevent double-binding. - Card share is a popover, not a system share sheet. The detail page's `<dialog class="detail-share-dialog">` UI is reused (single instance per page populated per click), but `<dialog>.show()` runs in non-modal mode and JS positions it via `getBoundingClientRect()` to unfold above-right of the trigger button. Outside-click and Escape close the popover; the existing `data-share-copy` / `data-copy-link` handlers in `header-enhancer.astro` wire Copy text + Copy link automatically. Width tuned to 420px so it fits next to a 340px-wide card without spilling onto the next column. - `_redirects` already covers retired Skills + Craft routes (PR #3010) so this grid pivot doesn't need new redirects. Out of scope for this PR (kept lean): - Multi-locale hero + FAQ copy. Hero / FAQ render in English on every locale right now; the `pcopy.tileTemplates` chip rail and per-card title/description still localize per PR #3010. Locale rollout for the hero + FAQ is a follow-up. - Sort + filter buttons in the YouMind reference top-right (we still show artifact-kind chips only). Sort by featured weight is the most likely next step. - `od.featured` weight as a featured proxy. We currently key off `tags?.includes('featured')` which is 0-match across the catalog today; promoting the numeric weight into `BundledPluginRecord` is a separate small commit. `pnpm --filter @open-design/landing-page typecheck` clean (0 errors). * feat(landing-page): localize templates chrome + FAQPage JSON-LD + hover-only autoplay Three follow-ups Looper flagged on the YouMind-style grid (PR #3185): - **Localizable hero / FAQ / card chrome.** PR #3185 wired the grid through `pcopy` for record titles + descriptions but hard-coded the surrounding chrome — hero eyebrow / lead / counter label, FAQ head, Featured tag, "Read full prompt", "Use this template", and the share-button `aria-label` — to English. `/ja/plugins/templates/`, `/zh-CN/plugins/templates/video/`, etc. now ship those strings via `pcopy.*` keys (`templatesHeroEyebrow`, `templatesHeroLead`, `templatesCounterLabel`, `cardFeaturedTag`, `cardReadFullPrompt`, `cardUseTemplate`, `cardShareAria`, `faqHead`, `faqItems`). English is the base; per-locale overrides for hero copy + 6 FAQ Q&A pairs remain a follow-up (the PR-#3185 "Out of scope" item), so the 17 non-English locales fall back to English chrome instead of showing undefined values. - **`FAQPage` JSON-LD entity.** The visible accordion was a SEO surface but `jsonLd` was still a single `CollectionPage`. Switched it to an array and appended a `FAQPage` whose `mainEntity` is each question + answer from `pcopy.faqItems`, so the structured-data payload search engines see and the visible <details> share one source of truth — drift between them is now mechanical, not editorial. - **Hover-only autoplay (not viewport autoplay).** The previous observer played every video the moment its card scrolled into the viewport, which contradicted the PR's stated hover-autoplay contract and spawned N simultaneous decoders on a casual scroll. The IntersectionObserver now hydrates `data-src` -> `src` lazily (one-shot, then unobserve) at a 300px rootMargin; `play()` and `pause()` are gated to `pointerenter` / `pointerleave` (plus `focusin` / `focusout` for keyboard users) on the parent `.tpl-media` host so hovering anywhere on the preview frame triggers playback. Same change applied to the `[kind]` route so faceted pages behave identically. Validation: pnpm --filter @open-design/landing-page typecheck -> 0 errors / 0 warnings; local dev (port 3061) renders 231 cards / 46 data-tpl-autoplay markers / FAQPage entity present in jsonLd / 6 FAQ summaries; zh-CN locale falls back to English chrome (expected, the locale routes themselves remain out of scope per PR #3185). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
50f85b509a
|
fix(analytics): fill run and feedback metadata (#3194)
* fix(analytics): fill run and feedback metadata * fix(analytics): map feedback API providers |
||
|
|
71ad9eb292
|
fix(media): enforce legacy media policy for run tokens (#3205) | ||
|
|
b746efefe2
|
fix: clear selected preview comments (#3144)
* fix: The "clear" button for comments is not functioning; the comments no longer have serial numbers.
* fix: The active pin always renders {visibleComments.length + 1}, but showActivePin (= commentCreateMode) is also true while editing an existing comment: onOpenComment at line 6821 calls setCommentCreateMode(true) and setActiveCommentTarget(snapshot) against the saved comment the user just clicked. In that path the overlay now stamps a stale number on top of an existing saved marker (e.g. clicking the pin showing 2 paints an additional 3 at the same position), which contradicts the invariant this PR is restoring — that preview-area numbers match the side-panel numbers.
---------
Co-authored-by: 郑惠 <14549727+felicia-study@user.noreply.gitee.com>
|
||
|
|
ed16de6f92
|
Fix workspace tab separator artifact (#3105)
Co-authored-by: Lucas-FManager <luong.nguyen188@gmail.com> |
||
|
|
f70fa0eb35
|
docs(media): describe external media composition (#3201) |