mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
1744 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
65802542a2
|
fix(chat): surface OpenCode usage-limit/provider failures instead of a bare timeout (#3316)
* fix(chat): surface OpenCode provider failures from its log on a silent stall OpenCode's headless `run --format json` mode swallows provider failures: a 429 usage-limit is marked retryable and retried silently with nothing on stdout/stderr, so the chat run only dies via the inactivity watchdog and the daemon shows a bare "request timed out" with no reason. The real error (statusCode + "Monthly usage limit reached…") is recorded only in OpenCode's own session log. On a failed OpenCode close where stdout/stderr carry no signal, read the newest OpenCode session log, extract the latest `service=llm` provider error (scoped to that one line so the embedded request body can't contaminate the classification), and emit a structured, retryable SSE error (RATE_LIMITED / AGENT_AUTH_REQUIRED / UPSTREAM_UNAVAILABLE) carrying the provider's message. Refs #982. * fix(chat): emit recovered OpenCode failure from the watchdog path, bound to the run Addresses review on #3316. Blocking: the recovery previously ran only in the child-close handler, but in the inactivity-watchdog stall path (the exact case this targets) failForInactivity sends its error and finish()es the run — which clears run.clients — before the child closes. So the structured error reached zero live SSE clients and only surfaced on reload. Recover and send the OpenCode failure inside failForInactivity, before finish(), on the same pre-teardown send path the generic stall message already uses. Keep the close-handler branch for the case where OpenCode exits non-zero on its own (clients still attached). Non-blocking: bind the log lookup to the current run via an mtime gate (since=run.createdAt) so a stale or concurrent session's error can't be misattributed — skip log files last written before the run started. * docs(opencode-log): note the concurrent-run limitation of the mtime gate * fix(chat): skip close-handler failure emit when the watchdog already finished the run Non-blocking review follow-up on #3316: on the silent-stall path both failForInactivity and the child-close handler fired for the same run, so the recovered RATE_LIMITED error was sent twice and the events-log stream was reopened after finish() had closed it. Guard the close-handler failure emit with !design.runs.isTerminal(run.status) — the watchdog already sent the error and finalized the run; finalization below still runs (finish() no-ops once terminal). |
||
|
|
9b9a18af5b
|
fix(daemon): validate skillId on POST/PATCH /api/projects against runtime source-of-truth (#3293)
* fix(daemon): validate skillId on POST /api/projects against runtime source of truth * fix(daemon): validate skillId on PATCH /api/projects/:id, sharing the POST validator * test(daemon): cover skillId canonicalization, design-template ids, empty-string + null normalization, type rejection |
||
|
|
e30a4a2202
|
fix(platform): search mise shims dir so mise-installed CLIs are detected (#3319)
* fix(platform): search mise shims dir so mise-installed CLIs are detected - Add ~/.local/share/mise/shims (and MISE_DATA_DIR override + legacy ~/.mise/shims) to wellKnownUserToolchainBins. - This makes Pi, Kimi, and other mise-managed coding agents visible to the daemon even when launched from GUI contexts with stripped PATH. - Added tests for default and MISE_DATA_DIR cases. - Also pinned pnpm@10.33.2 in root mise.toml for better mise ergonomics. Before/after: more local CLIs now appear in the runtime picker (Kimi, Pi, Antigravity, Kilo, etc.). Refs: discussion in session around improving detection for common mise users. * fix(platform): address Copilot review on mise shims logic - Generalize the shims comment (no hard-coded CLI examples). - Make per-version Node toolchain scanning respect MISE_DATA_DIR (use the same mise root for installs as for shims). - Avoid duplicate shims entries when MISE_DATA_DIR makes legacy path identical to the primary one. Addresses the three inline comments from copilot-pull-request-reviewer on PR #3319. * test(platform): extend MISE_DATA_DIR test to cover installs scanning Addresses non-blocking review feedback from @nettee on PR #3319. The previous test only asserted shims behavior under a custom MISE_DATA_DIR. This extends it to also create fixture trees under customMise/installs/node/... and customMise/installs/npm-openai-codex/... and assert that the install paths are discovered while default-root paths are excluded. This makes the test robust against regressions in the installs scanning logic (existingMiseNpmPackageBinDirs + node version dirs). * fix(platform): only fall back to ~/.mise/shims when no MISE_DATA_DIR is set Addresses the remaining non-blocking review comment from @nettee on PR #3319. When an explicit MISE_DATA_DIR is provided, we no longer inject the legacy ~/.mise/shims path. This prevents stale shims from a previous mise layout from being re-introduced into detection. Also added a regression assertion in the MISE_DATA_DIR test. * fix(daemon): make claude-stream dedup robust when final assistant wrapper lacks msgId Prevents duplicated text and thinking output (especially visible during design system generation with AMR/Vela). Root cause: the textStreamed guard fell back to whenever the final message arrived without a string uid=501(ramarivera) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),399(com.apple.access_ssh),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),400(com.apple.access_remote_ae) (common in some AMR flows and design system tasks), causing the full content to be re-emitted even if it had already been delivered via streaming deltas. Fix: track whether any text or thinking was streamed via deltas for the current message and use that as a reliable fallback for the final wrapper instead of only trusting presence. * revert: remove dedup from claude-stream (PR #3319 should stay clean) |
||
|
|
51963cff78
|
docs(readme): refresh contributors wall (#3271)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 2s
ci / Detect CI change scopes (push) Successful in 1s
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 1s
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 1s
ci / Web workspace tests (push) Failing after 2s
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
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
482e318afe
|
Update docs/assets/github-metrics.svg (#3267)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
6f532ca35c
|
fix(web): snapshot the srcDoc bridge frame in Mark mode so deck capture works (#3304)
The Mark tool (#3081/#3277) captured the preview via the *active* iframe. For URL-load previews — decks especially — the active frame is the bridgeless URL iframe, while the snapshot bridge lives only in the (mounted but hidden) srcDoc transport frame. So Send on a deck timed out and showed 'Could not capture the preview. Try again to avoid sending only ink.' Snapshot the srcDoc-render-mode frame instead (capture mode already keeps it on full content, so it carries the bridge), with a short retry while it finishes swapping to full content. Falls back to the active frame for the non-URL-load case where they are the same. Red spec: PreviewDrawOverlay.test 'snapshots the srcDoc bridge iframe, not the visible URL-load frame' fails on main (targets the URL frame), passes here. |
||
|
|
9f09d1b649
|
fix(landing-page): wire up mobile nav toggle on the homepage (#3295)
The homepage runs its own inline header enhancer instead of importing the shared header-enhancer.astro component, and that inline copy only ported the scroll-headroom and GitHub stars/version logic — it never included the hamburger toggle handler. As a result the mobile menu button rendered (and animated to an X via CSS) but clicking it did nothing on / and /<locale>/, while sub-pages that do import the shared enhancer worked fine. Port the same toggle handler into the homepage inline enhancer: click flips .is-open on header.nav (which CSS expands into the dropdown panel below 1080px), and outside-click, Escape, and any in-menu link close it, keeping aria-expanded in sync. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
2ea2c91a92
|
fix(pack): add missing download and host packages to Linux INTERNAL_PACKAGES (#2837)
* fix(pack): add missing download and host packages to Linux INTERNAL_PACKAGES The native (non-containerized) Linux AppImage build fails with npm 404 errors because @open-design/download and @open-design/host — runtime dependencies of @open-design/desktop and @open-design/web — were not included in the INTERNAL_PACKAGES list. Without tarballs for these two packages, npm install in the assembled app directory tries to resolve them from the public registry where they don't exist. Add both packages to INTERNAL_PACKAGES and their build steps to buildWorkspaceArtifacts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(pack): apply same download/host fix to mac and win lanes, add regression test 1. Extend the INTERNAL_PACKAGES fix to mac/constants.ts, mac/workspace.ts, win/constants.ts, and win/app.ts so all three pack lanes produce tarballs for @open-design/download and @open-design/host. 2. Add internal-packages-coverage.test.ts that derives required workspace runtime deps from apps/desktop and apps/web package.json files and asserts every pack lane's INTERNAL_PACKAGES includes them. This prevents the same drift from recurring when a new workspace dependency is added. 3. Update win-app.test.ts and workspace-build.test.ts mock directory lists to include the two new packages. * fix(pack): include runtime packages in workspace build cache * fix(pack): install platform with desktop prebundle packages --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
0c4b7e50be
|
fix(web/router): defer popstate dispatch to microtask (#2490)
* fix(web/router): defer popstate dispatch to microtask navigate() previously dispatched a synchronous popstate event after mutating window.history, which caused React 18 to emit: Cannot update a component (Router) while rendering a different component (App). To locate the bad setState() call inside App, follow the stack trace as described in https://react.dev/link/setstate-in-render This happens whenever a caller invokes navigate() from inside a useState updater (e.g. App.tsx:479 routing first-run users through the onboarding panel from inside the setConfig() update). The synchronous popstate dispatch reaches useRoute() subscribers which then call setRoute() while the parent component is still rendering. Defer the popstate dispatch to a microtask. The window.history call itself stays synchronous so the URL bar updates immediately; only subscriber updates are pushed past the current render commit, which removes the warning without changing observable behaviour for any existing caller. * fix(web/router): cover deferred navigation timing --------- Co-authored-by: Visionboost <contact@visionboost.fr> Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
e71938767e
|
feat(community): add Showcase + Contribute + moderators, restructure nav and footer (#3291)
Adds two new entry points and reworks the community page chrome to match the wider landing-page direction (PRs #3222 and #3230). Showcase / Plugin Everything (above Ambassadors) Pitches Open Design as 'studio and gallery' in one address: anything shipped through the studio (content, products, templates, Skills, workflows) can return as a plugin, and the strongest pieces are carried out to the registry, to X, to Discord's #showcase channel, to the newsletter, and to the video reels. Right column holds a zero-code Contribute card with a curl installer, a copy-to-clipboard button, and a three-step flow for the od-contribute Skill. Hero CTA row Three buttons, in a single row: Stage your masterpieces (Showcase), Become an ambassador (Ambassadors), Contributors hall of fame (Maintainers). Top nav Pulls the breadcrumb out of the brand mark, surfaces Contributors / Ambassadors / Showcase as anchors, and adds GitHub + X icon buttons next to the Join Discord pill (mirrors PR #3230). Footer Restructured into columnar layout with brand summary plus Products, Plugins, and Community columns; copyright moves to a bottom rule. Ambassadors Renaissance-voice three-column program (Vocation / Patronage / Covenant) with an Apply on Discord CTA to the ambassador channel. Discord Card spans wider (max-width 1440px), copy reframed as 'the front line of the agent-design era', two moderator profiles on the right (Koki from the founding team, Victor as Discord steward), channel list and CTAs on the left. Recent signal Kicker and headline framed as this week's leaderboard; backed by a hand-curated RANKING_SNAPSHOT. A real refresh pipeline remains a follow-up; data is hand-updated until then. Other notes Punctuation pass: replaced most em-dashes in prose with colons, periods, commas, semicolons, parentheses; em-dashes only remain in data placeholders, page title, and HTML comments. Logo size bumped to 32px and now uses an alt of 'Open Design'. Co-authored-by: koki yanlai xu <koki@kokideMacBook-Air.local> |
||
|
|
8ec162bb26
|
fix: pet hover card gets cut off at screen edges (#2860)
* fix: pet hover card gets cut off at screen edges * fix: address review feedback - viewport clamping + unadopted pet wake - Add window.innerHeight check to prevent bottom-edge clipping - Increase menuH estimate for safer positioning - Open pet settings instead of no-op Wake for unadopted pets * fix: address review feedback on pet menu positioning and wake action - Add viewport height check (viewH) to prevent bottom-edge clipping - Increase menuH estimate for safer positioning - Open pet settings instead of no-op Wake for unadopted pets |
||
|
|
cdf34897ba
|
add comment composer keyboard submit shortcut (#2941)
Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
0bd07b2a3d
|
fix(daemon): grok-build — pass prompt inline as -p value, drop stdin (#2259)
* fix(daemon): grok-build runtime — pass prompt inline as -p value, drop stdin Grok Build CLI 0.1.212 enforces `-p, --single <PROMPT>` as a value-requiring flag — invoking with bare `-p` and piping the prompt to stdin now fails with: error: a value is required for '--single <PROMPT>' but none was supplied The previous runtime def used `promptViaStdin: true` + `buildArgs` returning `['-p']`, which only worked against earlier grok builds that read the prompt from stdin when `-p` had no inline value. This change inlines the prompt as the `-p` argument value and flips `promptViaStdin: false`. Linux `MAX_ARG_STRLEN` (128 KB) is enough headroom for typical Open Design prompts; if we ever hit `E2BIG` on a very large brief, a follow-up could shell out to `--prompt-file <tempfile>`. Verified against grok 0.1.212 (b7b8204a4) — single-turn invocations now return clean text replies instead of exit 2. * fix(daemon): declare grok-build argv prompt budget + regression coverage @mrcfps' review on #2259 flagged that moving the Grok Build adapter from the (no-longer-working) stdin path to argv would regress oversized composed prompts from the actionable AGENT_PROMPT_TOO_LARGE error we already emit for DeepSeek to a raw spawn ENAMETOOLONG / E2BIG instead. Fixed by mirroring the DeepSeek argv-budget shape: - grok-build.ts: `maxPromptArgBytes: 30_000` (same headroom as DeepSeek, ~2.7 KB under the Windows CreateProcess 32_767-char cap) so `checkPromptArgvBudget` pre-flights composed prompts (system + history + skills + design-system content + user message) before spawn. - prompt-budget.ts: Grok-Build-specific message — names the `-p / --single` flag, the xAI CLI 0.1.212+ behavior change, and points the user at stdin-capable adapters (claude / codex / hermes) when they need to ship large local context. - Tests: 3 new vitest cases in prompt-budget.test.ts — pin the budget field, exercise the strict-overrun + at-limit + CJK byte-count guards exactly like the DeepSeek regression set, and assert the Grok-named diagnostic copy. New `grokBuild` + `grokBuildMaxPromptArgBytes` helpers exported alongside the existing `deepseek*` ones. All 23 prompt-budget tests pass locally (`pnpm exec vitest run tests/runtimes/prompt-budget.test.ts`). --------- Co-authored-by: Sriram Sivakumar <sriram155@gmail.com> Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
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
|