mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
1676 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0bfb4803e7
|
feat(daemon): add Phase 2C CLI wrappers (#2179)
* feat(daemon): add phase 2c cli wrappers Co-authored-by: multica-agent <github@multica.ai> * fix: handle desktop-gated CLI imports Co-authored-by: multica-agent <github@multica.ai> * fix: pass sidecar ipc path to agent wrappers Co-authored-by: multica-agent <github@multica.ai> * fix: make agent wrapper env explicit Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): preserve CLI import and diff edge cases Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
9a4816b101
|
feat(plugins): site-wide plugin detail pages, share-to-site links, landing deploy trigger (#2999)
* feat(plugins): site-wide plugin detail pages, share-to-site links, landing deploy trigger Why: a merged plugin PR didn't redeploy the landing site (plugins/** was missing from the deploy paths), and the desktop Share menu copied a local/404 link instead of the public marketplace URL. The landing plugin routing left by the detail-page rework also 404'd: the locale listing's cards used a multi-segment href while detail pages were single-segment, and only 388 bundled _official plugins had pages. What changed: - Deploy: landing-page deploy/ci trigger on plugins/**, and skip the slow previews step on an exact cache hit (cache key aligned across both workflows so a PR-built cache is reused by main). - Share URL: packages/contracts/plugin-url.ts owns the single-segment plugin URL scheme; the web Share menu and the landing site both derive links from it. Web links now point at https://open-design.ai/plugins/<slug>/. - Full detail coverage: detail pages now cover all 403 local plugins (_official incl. atoms + community), each rendered from its local manifest. Fixes the locale-listing 404s and the community manifest-name/catalog-id (- vs /) mismatch. - Self-host: daemon exposes OD_SITE_ORIGIN via /api/app-config; web falls back to the canonical origin until the daemon answers. Validation: pnpm guard, pnpm typecheck (all packages), contracts + web tests green, and a full build E2E confirming all 403 catalog ids and locale-listing cards resolve to built detail pages (0 missing). * chore: retrigger CI * ci(landing): carry plugins/** trigger + previews cache-hit into #2994 split workflows Merged origin/main, which split landing deploy into staging + manual production (#2994). git auto-migrated my landing-page-deploy.yml changes into landing-page-staging.yml via rename detection (plugins/** path, fallback-preview-card.ts cache key, cache-hit skip all carried). The new manual landing-page-production.yml didn't have them, so add the previews cache-key alignment + cache-hit skip there too (plugins/** path is N/A — production is workflow_dispatch only). * fix(ci): wrangler-action uses pnpm so it tolerates landing's workspace dep This PR added @open-design/contracts (workspace:*) to apps/landing-page/package.json so the landing site can share the plugin-url slug rules. But the landing deploy/preview steps run cloudflare/wrangler-action with packageManager: npm in workingDirectory apps/landing-page, and 'npm i wrangler' chokes on the workspace: protocol (EUNSUPPORTEDPROTOCOL), failing 'Validate landing page'. Switch all three landing wrangler-action steps (staging / ci preview / production) to packageManager: pnpm, which is workspace-aware. * test(e2e): bundled plugins now offer the README badge After this branch, buildPluginShareUrl returns a public open-design.ai link for bundled plugins (not just official-marketplace ones), so the home-starter share menu now shows 'Copy README badge'. Update the assertion from toHaveCount(0) to toBeVisible(). * fix(landing): drop @open-design/contracts dep, use a landing-local slug helper Per review on #2999: the marketing site must not import @open-design/contracts (AGENTS.md boundary — it's the web/daemon product-runtime contract layer). Move the slug/path helpers into landing-local app/_lib/plugin-slug.ts; the web client keeps contracts' plugin-url. The two derive the same scheme and are verified in lockstep by the e2e route check (403 share URLs -> 403 detail pages, 0 missing). landing no longer has a workspace dep, so revert the wrangler-action packageManager back to npm. * fix(landing): include plugins/_official in previews cache key Per review on #2999: generate-previews.ts builds bundled-plugin preview jobs from plugins/_official/**/open-design.json and renders fallback cards from manifest fields (title/description/mode/scenario/tags). With plugins/** now triggering the workflow but the cache key not hashing plugin inputs, a plugin-only PR/merge could exact-hit an old cache and skip the preview regen, shipping with a stale or missing /previews/plugins/<manifest-id>.png. Add plugins/_official/** to the cache key in all three landing workflows (ci, staging, production). community is not currently covered by generate-previews so its glob is omitted. * fix(plugins): include community marketplace installs in share gate hasPublicPage now covers sourceMarketplaceId === 'community' so the README badge and public detail link surface for community installs. Community manifest names carry a community- prefix that diverges from the landing-page route slug, so URL derivation uses sourceMarketplaceEntryName (community/<folder>) instead — pluginDetailSlug takes the last segment, matching the /plugins/<folder>/ route the landing page emits. Adds component tests for buildPluginShareUrl, badge copy, and the Open-in-marketplace link for a community/registry-starter record. Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code) --------- Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
92f7ff86b5
|
fix(web): constrain staged attachment tray height (#3168)
* 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): Limit the maximum height of the StagedAttachments component. related issue 3155 * fix(web): limit attachment area overflow to vertical only |
||
|
|
381e9a96e2
|
Make share deploys visibly complete (#2843)
* Make share deploys visibly complete Share deploys were uploading only the referenced entry graph, so sibling screens could fall through to provider fallback pages after deployment. They also completed silently except for the result link block inside the deploy dialog, leaving users unsure whether a redeploy finished. This includes visible files for Open Design-managed projects in real deploy/preflight payloads while preserving the selected entry as provider-root index.html. Linked-folder projects stay on the referenced-file graph so repo files that are visible in the file panel, like README.md or src/**, do not become public by accident. The web UI also shows a localized success toast at the top of the app after a successful Vercel or Cloudflare Pages upload. Constraint: Cloudflare Pages Direct Upload serves missing files through its fallback behavior, so deployment payload completeness must be handled before upload. Constraint: Linked-folder projects can expose arbitrary repository content through the file panel, so whole-project deploy expansion is limited to Open Design-managed project directories. Rejected: Reintroduce an entry-file dropdown | users wanted full project deployment semantics rather than selecting a root-only artifact. Rejected: Upload every visible linked-folder file | would make non-runtime repo content publicly reachable after Share deploy. Confidence: high Scope-risk: moderate Directive: Do not remove the selected-entry-to-index.html mapping; it keeps alternate entries like index-v1.html deployable as the root without overwriting them with the launcher. Directive: Do not expand linked-folder deploys beyond referenced web assets without an explicit user opt-in and review of the privacy model. Tested: pnpm --filter @open-design/daemon test tests/deploy.test.ts tests/deploy-routes.test.ts Tested: pnpm --filter @open-design/web test tests/components/FileViewer.test.tsx Tested: pnpm --filter @open-design/web typecheck Tested: pnpm guard * fix(web): gate share-deploy ready hint on actual ready state The 'Ready · Deployed URL' hint was unconditionally rendered whenever deployResultCards was non-empty, so a successful deploy that came back as link-delayed or protected showed contradictory copy next to the 'Public link pending' / 'Deployment protection enabled' badge. Render the hint only when deployResultState(activeDeployment?.status) is 'ready' so the success line stays consistent with the badge below. --------- Co-authored-by: nicejames <nicejames@gmail.com> |
||
|
|
fe24c8addf
|
chore: remove dead inline /api/agents route handler in server.ts (#2945)
Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
a7e7d5db18
|
Fix memory action alignment (#3175) | ||
|
|
693176457e
|
fix(daemon): skip chat probe for SenseAudio media models (#3181) | ||
|
|
a4c447eea6
|
Fix staged preview modal overlay stacking (#3186)
* 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: portal staged-preview-modal to document.body to escape .composer stacking context The .staged-preview-modal was rendered inside .composer (position:relative; z-index:1), which created a local stacking context. The modal's z-index:1200 was scoped within .composer's context instead of the root stacking context, preventing it from properly covering .app-chrome-header when active. Fix by rendering the modal via createPortal to document.body, consistent with other modals in the codebase (ConversationsMenu, FileViewer, etc.). related issue 3154 |
||
|
|
ece3d71cdd
|
fix(web): guard ChatPane scroll setState to prevent Maximum update depth error (#3187)
The scroll handler in ChatPane called setScrolledFromBottom on every scroll event. During streaming, programmatic scrollTop assignments (followLatestIfPinned via rAF + ResizeObserver) emit synchronous scroll events while React is still committing prior updates, scheduling a setState per tick. React eventually trips its update-depth guard with 'Maximum update depth exceeded' (issue surfaces in Next.js 16 Turbopack dev consoles). Use a functional updater that returns prev when the boolean is unchanged so React skips the re-render entirely. This breaks the cascade at the call site without touching the auto-follow logic. Co-authored-by: nicejames <nicejames@gmail.com> |
||
|
|
d9623da4af
|
skills: enhance frontend design (#3171)
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 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 1s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 1s
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 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local> |
||
|
|
efa808f1db
|
fix(i18n): refresh French locale (#2963) | ||
|
|
338cb4d423
|
fix(platform): support live system proxy changes (#3093)
* fix(platform): support live system proxy changes * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Honor lowercase proxy env vars within a single source before merging proxy-aware envs.\n\nGenerated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Refresh provider request proxy env on each dispatcher creation and cover it with a focused regression test. Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): enable node env proxy for user proxy vars * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) * fix(platform): support live system proxy changes Generated-By: looper 0.9.1 (runner=fixer, agent=opencode) |
||
|
|
9de5ecd87c
|
docs(readme): refresh contributors wall (#3161)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
df8a0faff6
|
feat(runtimes): register AMR (vela) as an ACP stdio agent (#2355)
* feat(runtimes): register AMR (vela) as an ACP stdio agent
AMR is the vela CLI's ACP runtime mode. `vela agent run --runtime opencode`
speaks ACP JSON-RPC over stdio (see vela's
`specs/current/runtime/manual-agent-run-openrouter.md`); per
`docs/new-agent-runtime-acp.md` we expose it through the same `streamFormat:
'acp-json-rpc'` transport that already powers Hermes, Devin, Kimi, etc.
The new `defs/amr.ts` is the entire wiring — `buildArgs` returns
`['agent', 'run', '--runtime', 'opencode']`, `fetchModels` reuses
`detectAcpModels`, and the fallback list seeds the OpenRouter ids vela's
e2e baseline uses. `executables.ts`/`app-config.ts`/`metadata.ts` get the
matching `VELA_BIN`/`VELA_LINK_URL`/`VELA_RUNTIME_KEY`/`VELA_OPENCODE_BIN`
allowlist + install/docs URLs, so users can configure the per-agent env in
Settings without leaking into other adapters.
Coverage: `tests/fixtures/fake-vela.mjs` is a minimal ACP stub that returns
the documented `initialize` / `session/new` / `session/set_model` /
`session/prompt` shapes; `tests/amr-acp-integration.test.ts` spawns it via
`child_process.spawn` and drives a full turn through `attachAcpSession` and
`detectAcpModels`, so the ACP transport contract for AMR is end-to-end
verified locally even before a real `vela` binary is installed.
Validated:
- pnpm guard
- pnpm typecheck (all workspace projects)
- pnpm --filter @open-design/daemon test (2881/2881)
Deferred: real OpenRouter-backed turn through a built `vela` binary —
the runtime def needs no changes for that path, only `VELA_RUNTIME_KEY`
and `VELA_LINK_URL` in env (or Settings).
* fix(runtimes/amr): pin a concrete default model and bare openai ids
End-to-end validation against a freshly-built `vela` (nexu-io/vela@main)
+ OpenRouter surfaced two contract details the first AMR runtime def
got wrong:
1. vela rejects `session/prompt` with `session/set_model must be called
before session/prompt`. attachAcpSession in apps/daemon/src/acp.ts
skips set_model whenever the picked model is the synthetic 'default'
id, so AMR's fallback list must NOT include DEFAULT_MODEL_OPTION. The
def now ships a concrete `gpt-5.4-mini` as both `fetchModels`'
default option and `fallbackModels[0]`, which makes attachAcpSession
always send a real `session/set_model` for AMR turns.
2. `vela --runtime opencode` auto-prepends `openai/` to whatever modelId
it forwards to opencode's openai provider. With OpenRouter-style ids
like `openai/gpt-5.4-mini`, opencode receives the double-prefixed
`openai/openai/gpt-5.4-mini` and replies `ProviderModelNotFoundError`.
The new fallback list ships the bare ids opencode's openai registry
actually knows about (gpt-5.4, gpt-5.4-mini, gpt-5.4-fast, etc.).
Stub + tests:
- tests/fixtures/fake-vela.mjs now enforces the set_model gate the same
way real vela does, so a regression that silently goes back to
model: 'default' would surface as a fatal error in tests instead of a
hidden production failure.
- tests/amr-acp-integration.test.ts pins both contracts: no 'default' /
no 'openai/' prefix in fallbackModels, and a negative case that
asserts session/prompt fails when no model is set.
Adds `apps/daemon/scripts/verify-amr-real-vela.mjs` — a small dev-time
runner that drives `attachAcpSession` against a real `vela` binary and
prints the daemon's chat events, so future protocol drift can be checked
against an actual OpenRouter call.
Verified locally: `vela agent run --runtime opencode` + OpenRouter
returns the prompted string ("AMR-E2E-PASS") through the full daemon
pipeline; daemon test suite stays 2883/2883.
* fix(runtimes/amr): substitute concrete model when chat run sends 'default'
A plugin-driven AMR run from the UI surfaced a real-world hole in the
prior commit:
json-rpc id 3: session/set_model must be called before session/prompt
The Default-design-router plugin (and any caller that doesn't pin a
real model) sends `model: 'default'` straight through, which the AMR
runtime def cannot accept — vela rejects `session/prompt` without
`session/set_model` and attachAcpSession skips set_model whenever
model === 'default'. Just leaving DEFAULT_MODEL_OPTION out of the
adapter's `fallbackModels` is not enough: the chat-run handler in
server.ts still forwarded 'default' verbatim.
This adds `resolveModelForAgent(def, resolved, env?)` as the
single source of truth for the substitution:
1. If the caller picked a real id, pass it through.
2. Else, if `def.defaultModelEnvVar` is set and the daemon process
env has a non-empty value for it, return that (operator escape
hatch — see below).
3. Else, if the def's `fallbackModels` does NOT contain a 'default'
id, return `fallbackModels[0].id`.
4. Else, return the original value (the historic shape — defs that
list 'default' themselves are untouched).
AMR sets `defaultModelEnvVar: 'VELA_DEFAULT_MODEL'`, so when
opencode's openai-provider registry deprecates `gpt-5.4-mini`
upstream, an operator can swap the fallback id without a code change
by exporting `VELA_DEFAULT_MODEL=gpt-5.5` before launching tools-dev
/ od. Worth noting the env var must live in the daemon's `process.env`
(Settings-UI per-agent env values only reach the spawned child, not
the daemon's resolver) — the new field's docblock spells this out.
Coverage:
- `tests/runtimes/resolve-model.test.ts` — 8 unit tests covering all
four resolver branches plus the env-override happy path / fallback /
ignore-when-user-picked-a-real-id case.
- `pnpm --filter @open-design/daemon typecheck` clean.
* chore(runtimes/amr): move AMR to the top of the base agent list
So `AMR (vela)` shows up first in the agent picker / status views,
ahead of claude / codex. Pure ordering change; no behavior delta.
* feat(amr): Sign-in / Sign-out button on the AMR Settings card
The first half of the AMR work assumed the operator would set
VELA_RUNTIME_KEY / VELA_LINK_URL on the daemon process and never
surfaced login state to users. This adds the missing UX so a fresh
install can drive the full path from Settings:
- GET /api/integrations/vela/status reads ~/.vela/config.json
for the active profile and returns { loggedIn, profile, user }
(without leaking the runtime/control keys themselves).
- POST /api/integrations/vela/login spawns `vela login` once
(409 if one is already in flight). The vela CLI opens the user's
browser to the device-authorization page itself — Open Design
only needs to kick the subprocess off.
- POST /api/integrations/vela/logout removes ~/.vela/config.json
so the next status read returns logged-out.
`AmrAgentCard` is a dedicated agent-card component for AMR because
the existing `<button>` row can't host an interactive sub-control
(nested interactive elements). It polls /status after a login click
until the daemon reports loggedIn=true (or 5 minutes elapse), and
exposes a Sign-out action on hover. Other adapters (claude, codex,
hermes, …) keep their existing `<button>` card.
i18n: 8 new keys (settings.amrLogin / Logout / LoggingIn / etc.)
added to en + zh-CN. Other locales spread `en` and inherit the
English copy until translations land.
Coverage:
- `tests/integrations/vela.test.ts` pins the config.json reader
against a tmp HOME — including the negative case where a profile
has user info but no runtimeKey (still logged-out), and the
secret-leak guard ("rt-secret-*" must not appear in the projection
payload).
- `tests/components/AmrAgentCard.test.tsx` covers all four UI
states (logged-out, logging-in, logged-in, logging-out) plus the
click-propagation invariant the divergent card was built to keep.
`pnpm --filter @open-design/daemon test` 2901 / 2901 passing.
`pnpm --filter @open-design/web test` 1719 / 1719 passing.
`pnpm typecheck` + `pnpm guard` clean.
Dev script side-effects: `apps/daemon/scripts/verify-amr-real-vela.mjs`
no longer requires both VELA_RUNTIME_KEY and VELA_LINK_URL — if
VELA_PROFILE is set, the vela CLI is allowed to resolve credentials
from `~/.vela/config.json`. Added the two AMR `.mjs` fixtures to
`scripts/guard.ts` allowlist with the executable-fixture / dev-runner
rationale.
* fix(connection-test): substitute model for AMR before attachAcpSession
The chat-run path in server.ts already routes the requested model through
`resolveModelForAgent` so AMR / vela (whose CLI demands an explicit
`session/set_model` before `session/prompt`) gets the def's first
concrete fallback id when the chat run ships `model: 'default'`.
`connectionTest.ts` was wiring `attachAcpSession({ ..., model: model ?? null })`
directly, which made the Test Connection button on the AMR Settings
card deadlock with the same `session/set_model must be called before
session/prompt` error the chat-run path already handles — surfaced as a
permanent "Testing connection…" spinner in the UI.
Reuse the same helper here so Test Connection mirrors chat-run behavior.
* test(amr): three-layer end-to-end coverage for the AMR login + turn flow
The PR up to this point shipped runtime + UI code with unit-level Vitest
coverage. This commit adds the cross-layer regression net the live demo
relied on:
1. apps/daemon/tests/integrations/vela.routes.test.ts (HTTP, Vitest)
Spins up the real daemon Express app via `startServer({port:0,...})`,
persists `agentCliEnv.amr.VELA_BIN = <fake>` into app-config.json,
and exercises every /api/integrations/vela/* endpoint against the
extended fake-vela stub:
- status reads ~/.vela/config.json under various states
- login spawns the fake, waits for config.json to appear, returns
pid + startedAt + profile
- 409 already-running guard with the stub's delay knob
- logout removes the file (idempotent)
- secrets (runtimeKey / controlKey) never leak in the projection
- login → status round-trip flips loggedIn=false → true
2. e2e/tests/amr/turn.test.ts (tools-dev orchestrated, Vitest)
Boots a namespaced daemon + web pair through `createSmokeSuite`,
inlines a self-contained fake `vela` binary that handles BOTH
`vela login` (writes ~/.vela/config.json) and
`vela agent run --runtime opencode` (ACP stdio with the
`session/set_model must precede session/prompt` gate the real binary
enforces), then drives a complete /api/runs lifecycle for
`agentId: 'amr', model: 'default'` and asserts the assistant message
captures the fake's streamed text. This is the test that would have
surfaced today's plugin-default-model regression (the `set_model
before prompt` error) at PR time instead of demo time.
3. e2e/ui/amr-login-pill.test.ts (Playwright)
Mocks /api/agents + /api/integrations/vela/{status,login,logout}
to drive the Settings AMR card through the full Sign in → Signed in
→ Sign out cycle. Pins the AmrLoginPill polling contract and the
aria-label semantics (the pill's accessible name is "Sign out" once
logged in, regardless of which label the hover-state text shows).
fake-vela.mjs extensions:
- Handles `vela login` argv by writing
~/.vela/config.json for the active VELA_PROFILE and exiting 0 —
mirrors real vela's on-disk side-effect without the device-auth
loop.
- FAKE_VELA_LOGIN_DELAY_MS knob so route tests can observe the
in-flight state of the spawn lifecycle.
- FAKE_VELA_LOGIN_USER_EMAIL / _USER_PLAN to assert the surfaced
user fields end-to-end.
Validated:
- `pnpm guard` + `pnpm typecheck` (all workspace projects)
- `pnpm --filter @open-design/daemon test`: 2998 / 2998 passing,
including the new 8-test integration suite.
- `cd e2e && pnpm test tests/amr`: 1 / 1 passing.
- `cd e2e && pnpm exec playwright test ui/amr-login-pill.test.ts`:
1 / 1 passing (6.7s).
* feat(amr): package native cli and refine login ui
* feat(amr): wire vela cli beta packaging
* docs(amr): document vela ci packaging review
* docs(amr): refine vela ci integration review
* fix(ci): refresh nix pnpm dependency hashes
* fix(pack): clean up Vela CLI packaging
* fix(pack): bundle Vela CLI support files
* fix(amr): recover login attempts from stale auth state
* test: expand AMR and automations coverage
* fix(amr): address review follow-ups
* test(web): align tasks fixtures with contracts
* fix(daemon): type wildcard route params
* fix(ci): refresh PR merge validation
* fix(amr): clear env credentials on logout
* feat(settings): inline local CLI model configuration
* fix(amr): recognize daemon env credentials
* [codex] Fix Vela companion packaging (#2979)
* Fix Vela companion packaging
* Update Nix pnpm dependency hashes
* [codex] Surface AMR account failures (#2980)
* fix: surface AMR account failures
* fix: cover AMR recovery error guidance
* chore: bump beta base version to 0.8.1 (#2990)
* Fix AMR profile and packaged runtime review issues
* Detect packaged AMR OpenCode companion tree
* feat(web): polish AMR frontend flows
* Polish AMR onboarding card
* fix: read AMR login state from dot-amr config (#3048)
* test: tighten AMR credential and packaging coverage
* test: restore AMR executable test env helper
* [codex] Fix packaged mac Dock identity and AMR label (#3076)
* Fix packaged mac sidecar Dock identity
* Rename AMR assistant label
* Fix AMR live models and dot-amr login state (#3073)
* fix: read AMR login state from dot-amr config
* fix: load live AMR models before runs
* fix: point AMR onboarding link to production wallet
* fix: address AMR model review feedback
* fix: persist live AMR model fallback
* [codex] Fix AMR link catalog model ids (#3088)
* Fix packaged mac sidecar Dock identity
* Rename AMR assistant label
* Fix AMR link catalog model ids
* Fix AMR model normalization typecheck
* Use live AMR model for default runs
* fix: polish AMR runtime settings UI
* Accelerate AMR startup defaults (#3092)
* Surface AMR insufficient balance wallet URL (#3099)
* fix(web): polish onboarding controls (#3112)
* fix(web): show CLI scan loading state
* Avoid duplicate AMR wallet recharge links (#3117)
* Avoid duplicate AMR wallet recharge links
* Use Vela CLI 0.0.3 test package
* chore(nix): refresh pnpm deps hash
* Fix AMR wallet guidance display
---------
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
* chore(pack): pin Vela CLI 0.0.3-test.1 (#3127)
* chore(nix): refresh pnpm deps hash
* chore(pack): pin Vela CLI 0.0.3
* chore(nix): refresh pnpm deps hash
* fix(web): suppress AMR exit 130 fallback (#3136)
* feat(web): nudge users to hosted AMR on model/auth/quota failures (#3083)
* feat(web): nudge users to hosted AMR on model/auth/quota failures
When a non-AMR agent run fails with an auth / quota / upstream model
error, surface an inline nudge under the error pill linking to Open
Design's hosted AMR gateway (https://open-design.ai/amr). The nudge
fires `surface_view` (element=run_failed_toast) on impression and
`ui_click` (element=go_amr) on the link.
Also teach the daemon to classify CLI-agent auth/quota/upstream failures
(Claude Code, codex, ...) into specific API error codes
(AGENT_AUTH_REQUIRED / RATE_LIMITED / UPSTREAM_UNAVAILABLE) instead of
the generic AGENT_EXECUTION_FAILED, so both the error message and the
nudge key off accurate codes. AMR's own runs are excluded from the
nudge — they keep the dedicated sign-in / recharge affordances.
* feat(web): rework failed-run AMR guidance into per-case error UI
Replace the single inline nudge with a per-case failed-run experience
driven by the run's error code + agent:
- The error card is now neutral gray (was red) and always carries a
retry button; it is driven by the persisted per-message error event so
it survives a reload.
- Non-AMR agent hitting a model/auth/quota wall: a theme-color promotion
card under the error card offers "switch to AMR & retry" — switches the
run to AMR, opens Settings on the AMR card, and auto-retries once the
account signs in (ProjectView polls vela login status, independent of
the Settings pill lifecycle, with success / 5-min-timeout / unmount
exits).
- AMR agent unauthorized: clearer copy + an "authorize & retry" button.
- AMR agent out of balance: clearer copy + a "top up" button to the AMR
wallet, with manual retry.
- Settings AMR card: when opened from the nudge, it scrolls into view and
pulses, and an authorize-button coachmark (a fake hand cursor that
rises in and dismisses on hover) points at the sign-in control when not
yet authorized.
analytics: surface_view (run_failed_toast) on the promotion card and
ui_click (go_amr) on its action are retained. i18n adds chat.amrCard.*
and chat.amrError.* (en / zh-CN / zh-TW translated; other locales fall
back to en) and drops the old chat.amrErrorGuidance keys.
* fix(daemon): require status context for numeric service-failure codes
Per review on #3083: the model-service classifier matched bare HTTP
status numbers (`500`, `502`, `429`, `401`), so ordinary CLI output like
`line 500`, `read 502 bytes`, or `exit code 401` could be misclassified
as a provider outage / auth wall and wrongly surface the AMR nudge. Now
a status number only counts when it carries explicit context (`HTTP 500`,
`status 503`, `code: 401`, `502 Bad Gateway`); textual provider phrases
(overloaded, bad gateway, service unavailable, rate limit, …) are
unchanged. Adds fixtures proving unrelated numeric output stays null.
* fix(web): keep error pill for failed runs ChatPane's card doesn't cover
Per review on #3083: the per-message gray error pill was suppressed for
every persisted error status event, but ChatPane only renders the
replacement top-level error card for `retryableAssistantMessage` (the
last failed assistant). So a failed turn that is no longer last (after a
follow-up) or an older failed run in history showed neither the pill nor
the card — its error detail vanished, undercutting reload/history
survival. ChatPane now passes `errorCardOwnerId` (the assistant id whose
error the card represents); AssistantMessage suppresses only that one
pill and keeps rendering StatusPill for all other error events.
* fix(daemon): don't treat a process exit code as an HTTP status
Follow-up to review on #3083: the status-context helper accepted a bare
`code` prefix, so `exit code 401` / `process exited with code 429` still
matched and got classified as AGENT_AUTH_REQUIRED / RATE_LIMITED (the
very `exit code 401` case the comment calls out as noise). `code` now
only counts when qualified (`status code` / `error code` / `response
code`) or punctuation-bound (`code: 401`); bare `exit code N` no longer
matches. Adds fixtures for exit-code lines returning null.
* chore(web): translate AMR card / error keys for 16 remaining locales
PR #3083 added 10 new `chat.amrCard.*` / `chat.amrError.*` keys but only
provided en/zh-CN/zh-TW translations; the other 16 locales fell back to
English. Translate the card title/body, three chips, primary CTA, and
the AMR self-error (auth / balance) messages and buttons for ar, de,
es-ES, fa, fr, hu, id, it, ja, ko, pl, pt-BR, ru, th, tr, uk.
* fix(amr): address review feedback on #2355
Targeted fixes for the unresolved review threads on #2355. Each fix
includes / updates a focused test.
- runtimes/executables.ts: `packagedVelaOpenCodeCompanionTree` now
verifies the inner `opencode` executable exists + is runnable, not
just the directory. This closes the false-positive availability path
that let `detectAgents()` surface AMR as available even when the
packaged companion was empty / partially copied (mrcfps, 4 threads).
- runtimes/executables.ts: `resolveAmrOpenCodeExecutable` now prefers
the bundled `<OD_RESOURCE_ROOT>/bin/libexec/opencode/opencode` over a
stale `opencode` on the user's PATH, so packaged AMR builds can't be
hijacked by a global installation.
- web/EntryShell.tsx: when the Local CLI scan returns an available
agent and the previously-selected agent is AMR, switch the selection
to the first available local agent so the runtime and persisted
agent agree before Continue.
- server.ts (model-probe branch): for AMR, check `readVelaLoginStatus`
BEFORE rejecting on an empty live-model catalog — a signed-out user
was getting `AMR_MODEL_UNAVAILABLE` ("choose a model") instead of
the correct `AMR_AUTH_REQUIRED` (sign-in affordance).
- server.ts (default model fallback): if the user asked for the AMR
agent default and the cached id is no longer in the FRESH catalog,
fall back to `liveModels[0]` from the probe instead of rejecting the
run as `AMR_MODEL_UNAVAILABLE`.
- integrations/vela.ts: route `vela login` through
`createCommandInvocation` so an npm/Node-style `vela.cmd` / `.bat`
shim on Windows gets the correct `cmd.exe /d /s /c …` wrapping with
verbatim args (matches `execAgentFile` / chat-run spawning).
- tools/pack/src/linux.ts: in containerized Linux builds, bind-mount
the host directory of `OPEN_DESIGN_VELA_CLI_BIN` and rewrite the env
to the container-side path. The host path was being passed in as-is
even though the default container only mounts /project, /tools-pack
and cache/home — `copyOptionalVelaCliBinary` saw a missing path.
Deferred (out of scope for this PR):
- `od amr status/login/logout/cancel` CLI subcommands (AGENTS.md
UI/CLI dual-track rule, server.ts:5763) — sizable surface; tracked
for a separate focused PR.
- Strict `--require-vela-cli` for Windows + mac-x64 beta builds:
prematurely blocked — `@powerformer/vela-cli` only publishes the
`darwin-arm64` platform binary today; adding the flag elsewhere
would fail the builds. Revisit once win/x64/linux binaries ship.
* fix(amr): hoist sendAmrAccountFailure above the AMR catalog preflight (TDZ)
The new signed-out AMR branch in the catalog preflight at server.ts:10875
calls `sendAmrAccountFailure(...)` to emit AMR_AUTH_REQUIRED, but the
const declaration sat ~100 lines below at the outer function scope. Because
`const` is TDZ-aware, that branch would have thrown `ReferenceError:
Cannot access 'sendAmrAccountFailure' before initialization` for the
exact users it tries to help — defeating the original intent.
Hoist the helper to just above the AMR preflight block so it's available
to every AMR code path in this function. Behavior elsewhere is unchanged.
Also rerun the daemon test suite: `launch.test.ts > resolveAgentLaunch
uses packaged built-in Vela for AMR` was creating the
`<resourceRoot>/bin/libexec/opencode/` companion *directory* only, but
this PR's earlier tightening of `packagedVelaOpenCodeCompanionTree`
also requires the inner `opencode` executable. Add it to that fixture
to match the new contract; the test was a sibling of the executables /
env-and-detection fixtures already updated in
|
||
|
|
72426b942a
|
fix(daemon): align Codex launch permissions on Windows and WSL (#3037)
Use danger-full-access when WSL_DISTRO_NAME is set and pass default_permissions=":workspace" so newer Codex builds can write inside the project directory instead of staying read-only. |
||
|
|
62972f14a3
|
fix(web): portal plugin details modal (#3065)
Signed-off-by: jaehanbyun <awbrg789@naver.com> |
||
|
|
39ae2cbc57
|
docs: add media execution policy proposal (#3096) | ||
|
|
269a385ee2
|
fix(daemon): reconcile missing artifact manifests on run end (#2893) (#3110)
* fix(daemon): reconcile missing artifact manifests on run end (#2893) When an agent writes HTML via write_file instead of create_artifact, no .artifact.json manifest sidecar is created. If the run then terminates (inactivity watchdog, user cancel, or process exit), the HTML file exists on disk but the manifest is missing — breaking the artifact panel, finalize, and export flows. Add a best-effort reconciliation step in the child.on('close') handler that lists project HTML files and calls reconcileHtmlArtifactManifest for any missing sidecars. The IIFE runs asynchronously after design.runs.finish() so it never blocks run finalisation. * fix(daemon): scope run-end reconciliation to files modified during the run The review on #3110 flagged that listing the entire project tree and reconciling every HTML file without a sidecar is too broad — for imported-folder projects (metadata.baseDir), pre-existing HTML files would receive spurious manifests. Record runStartTimeMs at the beginning of startChatRun and filter the reconciliation loop to only touch HTML files whose mtime >= that timestamp. Add a regression test that backdates a pre-existing HTML file and verifies it is skipped while a new file is reconciled. * test(daemon): fix mtime ordering in reconciliation regression test The runStartTimeMs was recorded after writing the new file, so its mtime fell before the threshold and the reconciliation filter skipped it. Move the timestamp capture to before the write to match the real startChatRun semantics. |
||
|
|
d3a5e2901b
|
Add release-beta-s self-hosted workflow placeholder (#3150)
* Add self-hosted beta release workflow placeholder * ci: register self-hosted runner labels for release-beta-s lint actionlint flagged the custom self-hosted labels nexu-win and release-beta as unknown. Add them to .github/actionlint.yaml so the release-beta-s workflow passes the lint gate. Generated-By: looper 0.0.0-dev (runner=fixer, agent=claude-code) --------- Co-authored-by: libertecode <libertecode@proton.me> |
||
|
|
4a15903165
|
fix: increase subtab-pill gap to prevent active tab overlap (#3134)
Increase gap from 2px to 4px in .subtab-pill to prevent the active tab's box-shadow from visually overlapping adjacent tabs. The previous 2px gap was insufficient for the shadow to render cleanly between tabs, causing the Style tab to obscure neighboring Content/Attributes/HTML tabs. Closes #2904 |
||
|
|
abe72af2a2
|
fix(ci): resolve blog-SEO base ref to a SHA in merge queue (#3137)
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 2s
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
The "Lint changed blog SEO" and "Guard blog URL changes" steps in landing-page-ci.yml fell back to the literal "HEAD^" when no base SHA was available. On merge_group (and first-push) events there is no pull_request.base.sha / before, so BASE became "HEAD^", and the blog-indexing scripts' assertSafeGitRef (regex /^[A-Za-z0-9_./:-]+$/, which forbids "^") threw `Unsafe git ref for base: HEAD^`, failing the merge queue for every PR. Resolve the fallback to a concrete commit with `git rev-parse HEAD^` (checkout uses fetch-depth: 0, so it is always available), mirroring blog-indexing-on-deploy.yml. The resulting 40-hex SHA passes the ref guard. |
||
|
|
ce9fa687ca
|
ci: trigger PR exploration via maintainer /explore comment (no approval) (#3139)
* ci: trigger PR exploration via maintainer "/explore" comment (no approval) Add a low-friction way to run the sandbox exploration: a maintainer comments "/explore" on a PR. - on: issue_comment (kept workflow_dispatch). The job `if` allows the comment path only when it is on a PR and the commenter has write access (author_association OWNER/MEMBER/COLLABORATOR), so randoms cannot trigger it; untrusted PR code still runs only inside the Docker sandbox. - Drop the agent-pr-explore environment approval gate: both triggers are already write-gated and there is no auto-trigger, so the extra manual approval is redundant. R2 creds are repo-level secrets (no env-scoped secrets), so they stay available without the environment. - Feedback: 👀 reaction on the command + a placeholder comment carrying the report marker (so the run yields one evolving comment), 🚀 on success, and 👎 + a failure note (with the run link) on failure. Does not auto-run on every PR, so unrelated PRs stay clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: don't clobber a produced report with the /explore failure note Review: the failure-feedback step ran after the always() report step, so on the failure-with-report case (sandbox wrote a report then exited non-zero) it overwrote the just-posted report with the generic "failed before producing a report" note — losing the useful output. Guard it: if the report file exists, leave the posted report in place and skip the failure note/reaction. Only post the short failure note when no report was produced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dadf8a5bc3
|
Add tracking for Automations, Plugin Detail & Loop (#3103)
* Add tracking for Automations, Plugin Detail, and Plugin Loop - RoutinesSection: track new_automation, create, save, cancel, run_now, edit, pause, resume, delete, history button clicks - PluginDetailView: track back navigation and use_plugin action - PluginLoopHome: track clear_active, submit, card_details, card_use - Extend AutomationsClickProps with new CRUD elements - Add PluginDetailClickProps and PluginLoopClickProps contracts * fix: address review comments on plugin/automation tracking - Extract onBack handler in PluginDetailView to cover both error-path and success-path back buttons with tracking - Move create/save tracking from submit button onClick into the form submit handler to capture keyboard submissions and avoid false positives from validation failures * fix: move submit tracking into submit() handler in PluginLoopHome Same fix as RoutinesSection: tracking now fires inside submit() so keyboard Enter submissions are captured and the !trimmed guard prevents false positives. --------- Co-authored-by: qiongyu1999 <2694684348@qq.com> |
||
|
|
279da4f3b6
|
chore(contributors): remove migrated data files (#3138) | ||
|
|
2302efa5db
|
fix(amr): close ACP stdin on abort so vela tears down OpenCode (#3097)
* fix(amr): close ACP stdin on abort so vela tears down OpenCode When an AMR (vela) run is cancelled, attachAcpSession.abort() sent a `session/cancel` RPC but left the child's stdin open. The vela ACP bridge keeps running until it sees EOF (or is signaled), and it only shuts down its private OpenCode `serve` process on a clean exit — so on abort the OpenCode server lingered until the caller's SIGTERM fallback, and leaked entirely if the parent was killed before cleanup ran. End stdin after sending the cancel (mirroring the clean-completion path) so the agent receives EOF and shuts down its own runtime promptly, independent of signal timing. * fix(amr): end stdin on abort even before session/new resolves Addresses review on #3097: abort() still returned early when sessionId was unset, so the stdin EOF only happened after session/new completed. Cancelling during ACP startup (before the session exists) left the OpenCode-teardown window open until the caller's SIGTERM fallback — and a parent hard-kill before that could still strand the private OpenCode process. Move stdin.end() out of the sessionId guard so abort always closes stdin when the pipe is writable; gate only the session/cancel RPC on sessionId. Add a regression test that aborts during startup and asserts stdin is ended with no session/cancel emitted. |
||
|
|
68bbcfe9e2
|
Fix/projects page/button (#3130)
* 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): directly import remixicon.css in layout to fix icon font loading
* fix(web): resolve remixicon font not rendering via public/ directory
RemixIcon icons were blank in Chrome and showed garbled PUA codepoints
in Firefox. The font file was referenced with a relative URL in CSS
loaded through @import, which Turbopack does not resolve.
Fix: copy remixicon.woff2 to public/ and change the @font-face src
to an absolute URL (url("/remixicon.woff2")), so the browser fetches
it directly from the server root without relying on the bundler.
|
||
|
|
1b1fba165f
|
fix(landing-page): drop Homepage CTA from plugin detail page (#3129)
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 1s
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 2s
ci / Workspace unit tests (push) Failing after 1s
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 1s
ci / Validate workspace (push) Failing after 1s
ci / Runtime trace (push) Has been skipped
Per product feedback: the Homepage button (rendered when a plugin's manifest carries a `homepage` field that differs from its `sourceUrl`) sent visitors off to upstream-author or original-source URLs before they had a chance to explore the plugin via Open Design itself. On a marketing detail page that's a leak, not a feature. Removes the conditional block that rendered `<a class="btn btn-ghost">Homepage ↗</a>` between the GitHub link and the Share button. The header-action row is now exactly three controls everywhere: Use this plugin → · Find on GitHub → · Share ↗. The `plugin.homepage` data field stays available on the `BundledPluginRecord` shape since the in-app catalog row, JSON-LD, and any future author-bio surface can still consume it. The `pcopy.detailHomepage` i18n key (with full 18-locale coverage) stays for the same reason — `PluginsCopy` is `Partial<>` everywhere and removing it would mean a 18-locale block edit for zero functional gain. apps/landing-page typecheck stays at 0 errors. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
ae7a417208
|
ci: add idempotent provision script for the agent-pr-explore runner (#3122)
* ci: add idempotent provision script for the agent-pr-explore runner The self-hosted runner's setup was hand-assembled and easy to lose on a rebuild — most dangerously the codex-acp pin: expect-cli bundles codex-acp 0.10, which is incompatible with ChatGPT-account auth (every model rejected); we run 0.15, but any expect-cli reinstall silently reverts it and breaks the agent. Add a self-contained, idempotent provision script that brings the runner's config layer back to a working state and is safe to re-run: codex model pin (gpt-5.4), the codex-acp 0.15 pin (npm pack + extract + chmod), deploy-key generation, base-repo git mirror seed/refresh, pnpm-store/reports dirs, the weekly image-refresh helper + cron, and the readiness self-check helper. The header documents the manual/secret steps it intentionally does not automate (base toolchain + colima, the interactive `codex login`, registering the deploy key on the repo, and registering the Actions runner service). Verified idempotent against the live runner (all checks pass, no config disturbed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: provision — update codex model key in place, don't truncate config.toml Review: step 2 overwrote the whole ~/.codex/config.toml with just the model line whenever the exact pin wasn't already present, dropping any other Codex settings on a re-run — destructive, contradicting the idempotent goal. Now: replace an existing `model =` line in place (sed), append only when the key is absent, and leave the rest of config.toml untouched. Verified preservation locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: provision — create ~/.ssh before ssh-keygen on fresh host Review: on the fresh-rebuild path this script targets, ~/.ssh usually does not exist, so `ssh-keygen -f ~/.ssh/od_agent_deploy` fails with "No such file or directory" and the deploy key (and downstream mirror bootstrap) never gets created. mkdir -p the key's parent dir (chmod 700) before keygen, and only print the pubkey when it actually exists. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b8cfee0c60
|
fix(diagnostics): capture daemon/web logs in packaged bundles (#3126)
Packaged diagnostics bundles never contained the daemon or web
`latest.log` — the very logs that hold the agent/critique run flow — so
support exports could not explain "sent prompt to the agent, then
nothing happened" reports.
Root cause: the sidecar `base` means different things per launch path.
tools-dev passes the pre-namespace source root, so
`resolveNamespaceRoot(base, namespace)` is correct. But the packaged
orchestrator launches every child with `base = <namespaceRoot>/runtime`
(apps/packaged/src/{paths,sidecars}.ts) while logs live a level up at
`<namespaceRoot>/logs`. The diagnostics builders re-appended the
namespace and resolved every log to
`<namespaceRoot>/runtime/<namespace>/logs/...` → ENOENT. renderer.log
only survived by accident: the desktop main process wrote it to the
same wrong path the reader looked in.
Add `resolveRuntimeNamespaceRoot(runtime, contract, runtimeMode)` to
`@open-design/sidecar` which walks up out of the `runtime/` dir in
packaged (runtime-mode) launches and falls back to the dev layout
otherwise. Route the desktop renderer-log path and both diagnostics
exporters (desktop IPC + daemon HTTP) through it so writer and reader
stay in lockstep and renderer.log lands next to the desktop log dir.
Tests: sidecar unit specs for both layouts; a daemon export spec that
writes a real `<namespaceRoot>/logs/daemon/latest.log` and asserts the
bundle captures its contents (red on main → ENOENT placeholder, green
here).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bc44a8add3
|
ci: relay contributor card events to worker (#3113) | ||
|
|
74fa8a754a
|
fix: dismiss tab search on outside mouse down (#3102)
Co-authored-by: icc <iccccccccccccc@users.noreply.github.com> |
||
|
|
c554f14973
|
fix(web): refresh chat skills after Settings skill mutations (#3020)
SkillsSection kept its own skills list in sync after create/delete, but App-level skills (used by the chat composer) were only loaded at boot. Propagate a refresh callback so new skills appear in chat immediately. Fixes #3017 |
||
|
|
3abcb3a4d2
|
fix(connectors): expire stale auth credentials (#2385)
* fix(connectors): expire stale auth credentials Mark connector credentials as expired when provider reads report auth-shaped failures so Memory stops presenting stale connected apps as healthy. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(connectors): avoid expiring grants on platform 401 Only delete connector credentials for provider tool errors attributable to the current connector so Composio platform auth failures do not wipe valid grants. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
54f225d6b3
|
ci: retry PR-context gh calls so a transient API blip doesn't abort the run (#3128)
* ci: retry PR-context gh calls so a transient API blip doesn't abort the run The early PR-context gathering calls `gh pr diff`, `gh pr view`, and `gh api .../files`. gh hits api.github.com under the hood, and a single transient timeout/5xx there aborts the whole run before any exploration (seen on #3083: "could not find pull request diff: Get \"https://api. github.com/...\": net/http timeout"). These were the only network calls in the run without a retry (source fetch + npm already retry). Add a small gh_retry helper (4 attempts, linear backoff) and wrap the three read-only context calls. gh writes nothing to stdout on a failed API call, so retrying is safe even for the calls piped into the context file; the retry warning goes to stderr. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: address review — buffer gh retries to files (no paginated duplication) Review (Siri-Ray): wrapping `gh api --paginate` retries inline while the context block is redirected to the file means a mid-pagination failure leaves partial pages in the context, and the retry appends them again — duplicating the patches section and burning the context budget the agent reads. Replace the in-pipe gh_retry with gh_retry_file: each call buffers to its own file per attempt (`>` truncates on open, so a failed/partial attempt is discarded before the next), and the context block just cats the finished files. Fetch PR body + patches to files up front, then assemble. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a6a56099ca
|
ci: show per-case pass/fail status emoji in agent report (#3118)
Reviewers asked for at-a-glance outcomes. Instruct the agent to begin each "Cases Tested" bullet with a status emoji (✅ pass / ❌ fail / ⚠️ warning / ⚪ inconclusive) and a bold case name, so the report shows which checks passed or failed without reading each line. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
bf61a39cb5
|
ci: clean agent report (write-to-file) + slim artifacts/uploads (#3116)
* ci: clean agent report (write-to-file) + slim artifacts/uploads Four related cleanups to the agent PR exploration output: 1. Clean report. The PR comment / report.md was assembled by dumping the entire verbose expect.log (ACP init logs, "Git failed" warnings, the ~24KB echoed prompt, ANSI codes, progress checklist) under the trace header -- ~28KB of noise. Instead, instruct the agent to write its final Markdown report to a file via its file-write tool, and have the runner read that file directly. Verified: Codex writes a clean report to the given absolute path. Falls back to an inconclusive note if the agent did not finish. 2. Drop duplicate trace/video. The script copied playwright-smoke-trace.zip -> playwright-trace.zip (a ~28MB legacy duplicate) and the webm likewise, and uploaded both to R2. Keep only the canonical smoke-named artifacts. 3. Slim the GitHub artifact. The trace zips and videos are already on R2; exclude *.zip / *.webm from the uploaded artifact so it drops from ~56MB to <1MB (report + logs only). 4. Persist report on the runner. Copy the report / agent-report / expect.log / trace URL to a stable host dir ($HOME/.cache/agent-pr-explore/reports/pr-<n>) so dry runs (skip_comment) can be inspected without downloading the artifact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: address review — keep advisory reports + recursive artifact excludes Review findings on the report/artifact cleanup: 1. Regression fix: the non-app-surface and deterministic-verifier branches write their pre-baked advisory report (Inconclusive / Pass / Fail) and never run the agent, so they don't produce agent-report.md. After switching write_agent_report_artifact to read only agent-report.md they fell through to the "agent did not write a final report" fallback, dropping the real advisory (and mis-reporting on .github-only PRs like this one). Fix: those branches now write their advisory directly to $agent_report_file — single source of truth for the report body. 2. Recursive artifact excludes: the source Playwright recording lives at artifacts/playwright-video/<uuid>.webm; non-recursive !*.webm / !*.zip didn't match the subdirectory. Use **/*.zip and **/*.webm so the slim actually holds. 3. Drop the now-dangling summary.legacyTrace field (the legacy trace copy is no longer produced), matching the legacyVideo removal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ee1eab77c6
|
feat(landing): add Community link + first-party Ambassadors page (#3066)
* feat(landing): add Community link to top nav
Adds a 'Community' entry to the landing-page nav between Blog and the
Star CTA, linking to /community/ which Cloudflare Pages 302-redirects to
the contributors honor-cards page (currently a Vercel deploy).
Translations added for all 18 locales. The nav slot was previously empty
after Blog because Contact had been intentionally pulled from the bar
(left as a footer + #contact anchor).
* fix(landing): use literal /community/ href so non-default locales don't 404
PerishCode review caught that href('/community/') routes through
localizedHref and produces /zh/community/, /ja/community/, etc. The
_redirects rule only matches the literal /community/ and the
[locale]/[...path].astro catch-all does not generate community pages,
so 17 of the 18 translated locales would have hit a Cloudflare 404.
The destination is a single non-locale-aware external page, so skip
the locale prefix entirely — same shape as the GitHub Star and
Download CTAs.
* feat(landing): host community + ambassadors page first-party
Lands the contributors / ambassadors page as a static asset at
apps/landing-page/public/community/index.html, served at /community/
on open-design.ai. Drops the temporary 302 to the Vercel preview URL
(d5458c46-…vercel.app) — that hostname was a deploy-time UUID Vercel
could recycle, which the reviewer correctly flagged as a follow-up.
The page now opens with an Ambassadors section: vocation, patronage,
covenant — three columns of the program in Renaissance-atelier voice,
with a single Apply on Discord CTA pointing at the ambassador channel
(discord.gg/2p7Ajbxw3h). Maintainers / leaderboards / good-first-issues
sit below as before. Header.tsx comment updated to point at the new
source of truth instead of the deleted redirect rule.
* fix(community): drop time-bound claims, tighten bot heuristic, drop dead CORE_TEAM entry
PerishCode review on
|
||
|
|
e9944e0783
|
skills: fix GSAP review follow-ups (#3111) | ||
|
|
1083df8769
|
Hide social sharing without explicit preview URL (#3108) | ||
|
|
e4b7aeae5a
|
skills: add official GSAP skill bundle (#3109) | ||
|
|
e40947ac0d
|
Add social sharing for template previews (#2924)
* Add template social sharing menu * Update plugin share e2e expectations * Add additional template social share targets * Remove Bilibili template share target * Open social share destinations in new tabs * Address template share review feedback * Use canonical public plugin share URLs * Gate public plugin share links by marketplace provenance * Update plugin share e2e for local-only badges * Limit public share URLs to official marketplace |
||
|
|
2540e8a92b
|
docs(readme): refresh contributors wall (#3077)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
b3b6291dbb | chore(card): generated card | ||
|
|
9ebf21af6e | chore(events): append pr_merged | ||
|
|
416f8aefa5 | chore(contributors): +30pts for @itsmeved24 (PR #3021) | ||
|
|
35794eab86
|
docs(creative-memory): add integration-shape proposal (#3021)
* docs(creative-memory): add integration-shape proposal Captures the integration-boundary decisions the product/pipeline team needs to make before any creative-memory implementation lands in the live generation loop. Covers the four areas lefarcen requested in the issue thread: signal capture points, retrieval insertion into generation/critique, user controls and escape hatches, and how Section 11's raw-events + content-addressed-derivations contract reshapes the boundary. Doc-only. No code lands with this PR. Each section enumerates the option space, names a working lean, and flags the explicit decision that needs a product/pipeline call. Written to apply to either implementation (PR #1746 or a team-internal one), per the resolved foundation-vs-reference question. Refs: #1637 * docs(creative-memory): self-contain the integration-shape doc Per @mrcfps's review on PR #3021: the doc was referencing files that live on the parked add/creative-memory-system branch (PR #1746), not on main, which left the primary references dead from main's perspective. Fix: inline the essential engine context (external API, storage shape, raw-events / content-addressed-derivations contract) as a Background section, drop the dead links to packages/creative-memory-system/, and replace inline open-questions.md cross-references with terse parenthetical statements that stand on their own. The doc is now self-contained at the level reviewers need: the §5 decision summary stays the entry point, and no reference depends on an external file. PR #1746 is still linked as the RFC/prototype thread for anyone who wants the full simulation suite or open-questions ledger. * docs(creative-memory): align §2 with the live composer, declare relationship to ## Personal memory Per Looper review on PR #3021: §2's 'Generation prompt today' stack did not match apps/daemon/src/prompts/system.ts:454-483, which was a real correctness issue because §2 is the integration contract for prompt placement. As written, an implementer could ship a second memory layer or move the existing personal-memory block without deciding how the two memories interact. Patch: - Replace the simplified five-line stack with the live thirteen-block order, gated as the composer gates each block. - Add Decision A: replace, append, or sibling block relative to the existing ## Personal memory slot. Lean A3 (sibling block, shared precedence). - Reframe Decision B (insertion point) against the live order. Lean B1 (after ## Personal memory, before custom instructions). - Add Decision C: precedence wording for the new block, mirroring the existing 'brand wins / skill wins' clause. - Update §5 decision summary: items renumbered, three new explicit calls (relationship, insertion point against live order, precedence wording). - Touch the Purpose paragraph to mention the existing ## Personal memory slot, since it is now the headline §2 question. * docs(creative-memory): name the §2 prefix scope and route raw-events through OD_DATA_DIR Per Looper review on PR #3021. Two doc-accuracy fixes: 1. §2 'Live composer order' stopped at ## Active skill (block 13), but composeSystemPrompt keeps appending mode-specific blocks after that (plugin / per-stage atom / metadata / deck framework / media contract / Codex imagegen / critique / visual-direction override / connected-MCP / Claude AskUserQuestion). As written, an implementer could treat block 13 as the tail of the stack and miss precedence layers a creative-memory block has to coexist with. Fix: rename the subsection to 'Memory-relevant prefix of the live composer order', add a 'Tail blocks (mode-specific, after ## Active skill)' note that summarizes the tail without listing every gate, and call out API_MODE_OVERRIDE as block 0. Source line numbers cited as a range with the disclaimer that gating + order are the durable shape. 2. §4 raw-events ownership option B wrote '.od/memory/raw_events.jsonl' literally, which hard-codes a repo-rooted path and bypasses the OD_DATA_DIR precedence documented in AGENTS.md FAQ. Packaged installs and NixOS / Home Manager modules already point OD_DATA_DIR at a writable directory because the install root may be read-only. Fix: rewrite option B as '<OD_DATA_DIR>/memory/<userId>/raw_events.jsonl' or a 'raw_events' table inside '<OD_DATA_DIR>/app.sqlite'; update option C wording for consistency; add a paragraph below the table explaining the OD_MEDIA_CONFIG_DIR > OD_DATA_DIR > <projectRoot>/.od precedence so this section cannot be read as endorsing a hard-coded .od/ path. * docs(creative-memory): correct the raw-events precedence — OD_DATA_DIR only Per Looper review on PR #3021. The previous patch's precedence paragraph copy-pasted the full chain (OD_MEDIA_CONFIG_DIR > OD_DATA_DIR > <projectRoot>/.od) from AGENTS.md, but that chain is the resolution rule for media-config.json specifically — OD_MEDIA_CONFIG_DIR is a narrower override that relocates only API credentials, not the daemon data root. Applying it to raw events would route preference event data into the credentials directory, which is the wrong contract. Fix: rewrite the paragraph so raw events follow only the daemon data-root contract (OD_DATA_DIR if set, else <projectRoot>/.od). Add an explicit follow-up paragraph that OD_MEDIA_CONFIG_DIR is NOT part of this precedence, with a one-line explanation of why mentioning it as a contrast — so anyone reading the AGENTS.md chain alongside this doc knows the difference. |
||
|
|
98b396335d | chore(card): generated card | ||
|
|
334001c7fa | chore(events): append pr_merged | ||
|
|
4634ef0ddf | chore(contributors): +30pts for @522700967-wq (PR #3010) | ||
|
|
f8c860a505
|
feat(landing-page): localize plugins library across 18 locales (#3010)
* feat(landing-page): localize plugins library across 18 locales PR #2926 shipped the new `/plugins/` library hub + four kind sub-routes + detail pages, but the chrome was English-only — visitors landing on `/zh/plugins/` saw the old marketplace registry placeholder rendered by the catch-all instead, and detail pages rendered identical English copy regardless of locale prefix. This PR brings the plugin surface to feature parity with `/zh/skills/`, `/zh/templates/`, `/zh/systems/`, `/zh/craft/`. ## What changes - New `app/_lib/plugins-i18n.ts` — single source for all plugin chrome copy (hub, list pages, chip rails, share dialog, detail-page meta labels). English baseline + 17 locale overrides keyed on `LandingLocaleCode` (the same short-code shape `localeFromPath()` returns). Missing keys per locale fall back to English so a partially-translated locale still renders sensibly. Translations cover hub copy, four tile titles + blurbs, seven artifact-kind labels + descriptions, 23 scene-subcategory labels, 18 detail-page chrome strings, and a six-key share-dialog table with a per-locale `shareTemplate({title, url})` function (translated for every locale where `_lib/i18n.ts` already had one — same voice). - `app/pages/plugins/{,templates/,templates/[kind]/,skills/,systems/, craft/,[slug]/}/index.astro` — every hardcoded English string now reads `getPluginsCopy(locale)` keys. Page logic and routing unchanged. - New short-code wrappers under `app/pages/[locale]/plugins/` — six files (hub + three sub-routes + `[kind]/` and `[slug]/`) following the same pattern `[locale]/skills/index.astro` already uses: each re-exports the canonical page component and adds a per-locale `getStaticPaths()` so the build emits 17 locale prefixes per plugin route. Total plugin-route prerender count goes from ~390 to ~7 000, matching the existing skill/template scaling. - Catch-all (`[locale]/[...path].astro`) — old `getPublicPlugins` / `getRegistryCounts` registry rendering removed (placeholder UI that was never wired to a real marketplace data source). Plugin routes now live exclusively under `[locale]/plugins/...` short-code wrappers, so the catch-all stops claiming `'plugins'` as a route root. The dead-code path also drops a `pluginCounts.all` reference the title row was reading. - `.plugins-tile-grid` styles promoted from a scoped `<style>` in the default-locale hub to global `app/sub-pages.css` so the short-code wrapper renders the same hub markup without re-mounting per-page CSS — `display: contents`-style scoping pitfalls in Astro's per-component CSS scoping made this the cleanest fix. ## Surface area - [ ] **UI** — new page / dialog / panel / menu item / setting / empty state in `apps/web` or `apps/desktop` - [ ] **Keyboard shortcut** — new or changed - [ ] **CLI / env var** — new `od` subcommand or flag, new `tools-dev` flag, or new `OD_*` env var - [ ] **API / contract** — new `/api/*` endpoint, new SSE event, or changed shape in `packages/contracts` - [ ] **Extension point** — new entry under `skills/`, `design-systems/`, `design-templates/`, or `craft/`, or change to the skills protocol - [ ] **i18n keys** — new translation keys (full plugin chrome added across all 18 locales) - [ ] **New top-level dependency** — adding any new entry to the **root** `package.json` - [ ] **Default behavior change** — changes what existing users experience without opting in - [x] **None** — landing-page-only restoration of i18n parity for the plugin surface ## Validation - `pnpm --filter @open-design/landing-page typecheck` → 0 errors - `pnpm --filter @open-design/landing-page build:static` → 16 127 pages built (+6 584 over current main: ~388 plugin detail pages × 17 locale prefixes plus the hub + four sub-routes × 17 locales). - `copy-example-html.ts` reports `266 entry files + 65 referenced files`, identical to before — no regression in the asset-mirroring pipeline. - Local Playwright smoke (`/zh/plugins/...`): - `/zh/plugins/` renders `<title>插件库 · Open Design</title>`, label `插件库`, h1 `407 个可组合的构件。`, four tiles labelled `模板 / 技能 / 设计系统 / 工艺`. - `/zh/plugins/templates/video/` renders h1 `48 视频`, scene chips `全部 / 动效 / 短视频 / 营销 / 产品 / 数据讲解`. - `/zh/plugins/example-article-magazine/` share dialog renders `复制下面的文案、然后跳到你想分享的平台粘贴即可` etc., share template auto-interpolates plugin title + URL into Chinese voice. - All 18 locale prefixes (`/zh`, `/zh-tw`, `/ja`, `/ko`, `/de`, `/fr`, `/ru`, `/es`, `/pt-br`, `/it`, `/vi`, `/pl`, `/id`, `/nl`, `/ar`, `/tr`, `/uk`) → 200 across hub + four sub-routes + sample detail page. - English `/plugins/` unchanged (default-locale path bypasses the `[locale]/...` wrapper). * feat(landing-page): finish plugins i18n chrome across 18 locales The first localization pass shipped a partial fix: hub headings, lead copy, two-level page chrome, detail-page metadata labels, the share dialog, and the chip rail were still falling back to English on every non-English locale because plugins-i18n.ts only filled a chrome slice for `zh` and the file header even claimed "7 artifact-kind labels and 25 scene-subcategory labels are translated" for every locale that did not yet have those blocks. Three changes close the visible gap: 1. plugins-i18n.ts: fills the 27 still-missing chrome fields per locale for zh-tw / ja / ko / de / fr / ru / es / pt-br / it / vi / pl / id / nl / ar / tr / uk. Includes the 7-key category map, the 23-key subcategory map, hubHeading / hubLead, the 4 *Label / *Heading / *Lead triples for the templates / skills / systems / craft hub pages, the 4 tile blurbs, the 4 browse buttons, sceneLabel, allChip, the 12 detail-page metadata labels (mode / scenario / platform / surface / author / manifest id / tags / preview caption / find on GitHub / homepage / open in new tab) and bucket label map, the detail share dialog (title / copy link / jump-to), and the header-side nav.plugins entry. zh receives the same 11 detail-page and share-dialog labels it was also missing. 2. header.tsx + site-footer.astro: routes the hardcoded "Plugins / Templates / Skills / Systems / Craft" labels through `nav.*` from HeaderCopy, so every locale gets its own dropdown trigger and footer column. Adds `nav.plugins` to HeaderCopy and fills it in 18 locales with the local form ("插件" / "プラグイン" / "Plugins" / "Plug-ins" / "Plaginy" / "الإضافات" / etc). 3. plugin-row.astro + content-i18n.ts: chip rail. The bundled-plugin branch now runs raw `mode` / `scenario` slugs through the shared localizeTaxonomyValue, and that helper now also consults the plugins-i18n subcategory map before giving up. localizeTaxonomyValue now returns undefined on a true miss instead of the unknownTag placeholder, so chips drop quietly instead of showing "Category" / "分類" / "Categoría" for taxonomy slugs we have not localized yet. Callers that genuinely want the placeholder (`localizeContentTag`, blog `category`, system noun) still keep the explicit fallback. Out of scope and tracked separately: per-plugin title and description in plugins/_official/* (author-supplied English metadata, ~401 plugins without an i18n schema in the manifest yet — needs RFC + tooling before the manifests can be expanded), and adding the long tail of mode / scenario / category slugs (`code-migration`, `plugin-sharing`, `tune-collab`, `live-artifacts`, `engineering`, ...) to TAXONOMY_TERMS so chips render localized labels for every taxonomy value rather than dropping silently. * feat(landing-page): cover plugins chip rail long-tail taxonomy slugs PR #3010's first round localized the high-frequency mode/scenario chips (prototype, video, image, marketing, design, ...) but left the ~37 mode/scenario and 14 category slugs that show up in real `od.*` metadata — code-migration, plugin-sharing, design-system, planning, scenario, refine, discovery, handoff, token-map, tune-collab, orbit, live-artifacts, engineering, healthcare, hr, sales, support, default-router, downstream-export, figma-migration, media-generation, plugin-authoring, validation, 3d-shaders, animation-motion, audio-music, creative-direction, design-systems, diagrams, documents, image-generation, marketing-creative, screenshots, slides, video-generation, web-artifacts, ... — falling through to undefined and dropping their chip silently on every non-English locale. The data layer is the source of truth here, so this expansion lands in `content-i18n.ts:TAXONOMY_TERMS` / `CATEGORY_LABELS` rather than the plugins-i18n catalog: a single dictionary entry per slug fans out to every chip-rail consumer (catalog rows, detail metadata, the templates/[kind] facets) without each consumer touching its own copy. Translations cover all 17 non-`en` locales. Brand and product nouns (Figma, Open Design, BYOK, plugin) stay literal; technical taxonomy slugs get short equivalents that read as chips rather than full prose. The result on `/ja/plugins/skills/` matches `/plugins/skills/` chip-for-chip (30 chips both sides) instead of dropping 27 of them the way the previous iteration did. * feat(landing-page): read manifest title_i18n / description_i18n on bundled plugins PR #3010's prior rounds localized chrome and chip rails but the catalog's most prominent text — each row's plugin name and blurb — stayed English on every non-English locale. The plugin manifest schema (`packages/contracts/src/plugins/manifest.ts`) has supported `title_i18n` and `description_i18n` (Record<locale, string>) on every manifest from spec v1; ~24 of the 401 first-party manifests already carry one for `zh-CN`. The reader was just never wired to use them. This change does the reader half: bundled-plugins.ts captures the two i18n maps off each `open-design.json`, plugin-row.astro and the detail page resolve them at render time via two new helpers (`resolveBundledTitle`, `resolveBundledDescription`) that mirror the short→long fallback chain documented in the manifest spec (`htmlLang` like `zh-CN` → short `LandingLocaleCode` like `zh` → primary tag → `en` → English baseline). The static-paths pass still runs once for all locales — it has to, since each manifest produces one URL — but the title/description shown on the rendered page now reads the locale off `Astro.url.pathname` and picks the right entry out of the maps. Verified locally: `/zh/plugins/example-card-twitter/` now reads "Twitter 分享卡 / 推特金句 / 数据卡, 适合配推文" from the manifest's existing `zh-CN` block instead of the English baseline. Plugin-data half follows in a separate commit. The 17 non-English locales × 401 manifests need backfilling so the reader has something to resolve to; that's data, not schema, and lands as a sequence of manifest patches rather than tangled with this code change. * feat(plugins): translate scenarios bucket title/description across 17 locales Closes the first chunk of #3028. Eleven scenarios plugins (the default-scenario bundle for each taskKind: code-migration, figma-migration, media-generation, new-generation, tune-collab, plugin-authoring; the default design router; the React / Vue / Next.js downstream-export starters; and the Refine baseline) get title_i18n + description_i18n filled for all 17 non-English locales the landing page serves (zh-CN, zh-TW, ja, ko, de, fr, ru, es, pt-BR, it, vi, pl, id, nl, ar, tr, uk). The reader landed in 7ddfe36; this commit is data-only. taskKind slugs that other docs reference by name (`code-migration`, `figma-migration`, `tune-collab`, etc.) stay literal in the descriptions so cross-references still resolve. Brand nouns — Open Design, Next.js, React, Vue, Figma — also stay literal. `/ja/plugins/od-code-migration/` now reads "コードマイグレーション(デフォルトシナリオ)" instead of the English baseline; `/zh/plugins/skills/` shows "代码迁移(默认场景)" in the catalog row. Remaining buckets (image-templates 45, video-templates 50, examples 140, design-systems 142 = 377 plugins) follow in subsequent commits in this PR. * fix(landing-page): drop CJK template wrap when source name is still English The Chinese / Japanese / Korean fallback templates for craft, skill, template, system, plugin, and blog text splice the source `name` / `title` into a CJK sentence frame: ``${name}工艺规则``, ``Open Design 指南:${topic}``, ``${name} は…のスキルです``. When the underlying SKILL.md / craft markdown / blog frontmatter still ships an English name (true for ~95% of the catalog today), that produces mid-sentence script straddling on `/zh/...`, `/zh-tw/...`, `/ja/...`, `/ko/...` like: H1 : "Editorial typography hierarchy工艺规则" Lead : "这条 Open Design 工艺规则定义 Editorial typography hierarchy 的执行标准…" Plug : "video 插件 · 3D Animated Boy Building Lego" That reads worse than the all-English fallback, because the visitor parses the page in two scripts at once. Adds a `nameNeedsEnglishFallback` guard that fires for the four CJK locales whenever the spliced-in name has no CJK characters of its own, and threads it through every `localizeXxxText` helper: craft, template, system, plugin, skill, blog. When it fires the helper returns the raw English content untouched, so the section renders end-to-end in one language. Chrome (header, footer, breadcrumb, buttons, share dialog) keeps its CJK rendering — only the title-and-lead block falls back. Side benefit: the same guard kicks in on the long tail of plugin manifests still pending `title_i18n` / `description_i18n` backfill (tracked in #3028), so `/zh/plugins/<bundled>/` no longer pairs a "video 插件 · 3D Animated Boy Building Lego" title with a Chinese breadcrumb. The page reads "3D Animated Boy Building Lego" + the English manifest description, while header / footer / breadcrumbs stay localized. Once a manifest ships its i18n maps, the chrome and body re-converge automatically. Non-CJK non-Latin scripts (ar, vi, ...) keep the previous behavior — their templates already read tolerably with English names. If that turns out to be wrong on a real audit, the same guard generalizes by adding the matching Unicode range and locale set. * feat(plugins): translate image-templates bucket title/description across 17 locales 44 of 45 image-templates plugins get title_i18n + description_i18n filled for all 17 non-English locales (zh-CN, zh-TW, ja, ko, de, fr, ru, es, pt-BR, it, vi, pl, id, nl, ar, tr, uk). Generated via Claude Sonnet 4.5 over the OpenRouter gateway, ~$1.38 in API spend, 156s wall-clock. Brand and cultural references stay literal (Open Design, Lego, Hanfu, Showa, Pokémon, Black Myth: Wukong). Long AI generation prompts collapse to a 1-2 sentence summary capturing what the plugin does — the description doubles as catalog blurb on the landing site, not as the actual generation prompt (which lives in example.html / the manifest's preview entry). Skipped: `profile-avatar-realistically-imperfect-ai-selfie` returned malformed JSON on three retries; will rerun with a tighter prompt in a follow-up commit. Catalog rows for that plugin keep falling back to the raw English fields per #3010's reader change, so nothing breaks. Tracking: closes the image-templates row in #3028. * feat(plugins): translate video-templates bucket title/description across 17 locales 49 of 50 video-templates plugins get title_i18n + description_i18n filled for the 17 non-English landing locales. Generated via Claude Sonnet 4.5 over OpenRouter, ~$1.47 in API spend, 177s wall-clock. HyperFrames templates, the Three Kingdoms cinematic series, the Seedance/short-film prompts, and the K-pop / wuxia / anime variants all get a 1-2 sentence catalog blurb in each locale; brand and cultural tokens (Black Myth: Wukong, Hanfu, Showa, Pokémon, Three Kingdoms / 三国志, Lego, Disney, K-pop, HyperFrames) stay literal. Skipped: `live-action-anime-adaptation-water-vs-thunder-breathing-duel` returned malformed JSON on three retries; will rerun in followup. Falls back to the raw English fields per the reader landed in |