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