The verify job ran `pnpm typecheck` (root script) which executes
`pnpm -r run typecheck` *before* the daemon build. The e2e workspace's
typecheck imports types from `apps/daemon/dist/*.js`, so on a fresh
clone (every CI run) it fails with TS2307 cannot-find-module.
Drive the order explicitly inside the verify job:
1. install deps
2. build daemon (produces dist/*.js + .d.ts)
3. workspace typecheck
4. check:residual-js
5. workspace tests
This keeps the root `typecheck` script untouched (which other dev /
contributor workflows may depend on) — the workflow simply imposes the
correct order itself. The atomic publish job already prevented orphan
tags/releases when the first dispatch failed at typecheck.
Co-authored-by: Elian <elian@EliandeMacBook-Pro.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(prompt-templates): add dance storyboard and ancient-china MMO HUD templates
- social-media-post-sensational-girl-dance-storyboard-8-shots: 8-shot storyboard
prompt set with shared global style tokens, negative prompt, and character
lock, tuned for GPT-Image-2. Produces a continuous dance choreography as a
coherent 8-frame sequence.
- game-ui-ancient-china-open-world-mmo-hud: HUD mockup for a Black Myth: Wukong
style ancient-China open-world MMO, centered on a female swordswoman
protagonist. Covers character panel, minimap with bagua frame, skill hotbar,
quest tracker, chat window, and world-space nameplates with Chinese
typography rules.
- scripts/import-prompt-templates.mjs: preserve hand-authored templates on
re-run by keeping any JSON whose source.repo is not the upstream CC-BY
corpus. Previously clearDir wiped the whole directory, which would delete
first-party curated prompts on the next import.
Both templates carry source attribution to nexu-io/open-design under
Apache-2.0. Categories reuse the existing 'Social Media Post' and 'Game UI'
enum values already present in the gallery.
* feat(prompt-templates): add preview images for dance and MMO HUD templates
- assets/prompt-templates/image/social-media-post-sensational-girl-dance-storyboard-8-shots.jpg:
an 8-panel storyboard render produced from the template itself, downscaled to
1024x1536 JPEG for gallery thumbnails.
- assets/prompt-templates/image/game-ui-ancient-china-open-world-mmo-hud.jpg:
a reference HUD screenshot rendered from the template prompt via gpt-image-2,
downscaled to 1536x1024 JPEG.
- Both templates now reference these previews via a raw.githubusercontent.com
URL pointing at nexu-io/open-design main, matching the pattern used by the
existing YouMind-sourced seeds (cms-assets.youmind.com) so every card in the
Prompt Gallery carries a thumbnail once the PR merges.
---------
Co-authored-by: Joey <joey@open-design.local>
* docs: add TRANSLATIONS.md i18n contribution guide
Captures the implicit conventions around adding a new locale: which
files to touch (types.ts, locales/, index.tsx, READMEs), how to keep
the language switcher synchronized across all README variants, the
backport policy, and a starter zh-CN ↔ zh-TW glossary extracted from
PR #194.
Resolves the action item from #195. CONTRIBUTING.md cross-links into
this file from its existing "Localization maintenance" section.
The "Open questions" section is intentionally preserved as a signal to
future contributors that translation memory tooling, drift detection,
and freshness badges are live design discussions.
* docs(translations): address review feedback from #196
- Standalone-file rationale: add inline paragraph (lefarcen #196:5)
- Maintained-locales table: add Status column, mark zh-TW README as
in-flight via #194 instead of claiming it exists, clarify ja/ko as
README-only (codex #196:28, lefarcen #196:23)
- Step 4: include the import line + DICTS map example so contributors
don't hit a TS error after copy-paste (lefarcen #196:54)
- Step 6: drop the impossible "order by LOCALES" rule — switcher set is
the union of UI-dict and README-only locales, ordering must just stay
consistent across all READMEs (codex #196:61)
- Backport policy: define a concrete drift threshold (≥20 keys OR
6 months), reference the deferred CI follow-up (lefarcen #196:70)
- Stale-locale signal: add concrete artifacts — table marker +
frontmatter comment (lefarcen #196:77)
- Glossary: split Core (OpenCC-handled) from Idiomatic (judgment calls)
and add the missing rows from #194 — 兜底/活的/計畫/色票/規格文件/介入修正/出包/捆綁→納入
(lefarcen #196:93)
- Native-speaker review: ground "~7 days" as starting point, not hard
policy; move it to Open questions for future tuning (lefarcen #196:128)
- Resolve TM-tooling contradiction by introducing a "Deferred decisions"
section — distinguishes "decided to defer" from "still open"
(lefarcen #196:137)
Add white-space: nowrap to .viewer-action, .viewer-toggle, and .viewer-tab
to prevent Chinese/Japanese text from breaking to a new line when the
toolbar runs out of horizontal space. English text was unaffected because
browsers treat whole words as atomic units, but CJK characters can break
between any two characters by default.
* fix(daemon): add CORS header to raw project file endpoint
srcdoc iframes have a null origin. When preview HTML fetches component
files from /api/projects/:id/raw/*, the browser blocks the response
because the route returned no Access-Control-Allow-Origin header.
Only respond with the header when the request Origin is the string
"null" — the signature of a srcdoc iframe. Real cross-origin requests
from other websites remain blocked, keeping the endpoint safe even if
the daemon is deployed on a remote server.
Fixes#134
* fix(daemon): add CORS header to raw project file endpoint
srcdoc iframes have a null origin. When preview HTML fetches component
files from /api/projects/:id/raw/*, the browser blocks the response
because the route returned no Access-Control-Allow-Origin header.
Only respond with the header when the request Origin is the string
"null" — the signature of a srcdoc iframe. Real cross-origin requests
from other websites remain blocked, keeping the endpoint safe even if
the daemon is deployed on a remote server.
Also add an OPTIONS preflight handler to future-proof the route for
artifacts that may send custom request headers, and add test coverage
for the null-origin allow / real-origin block behaviour.
Fixes#134
---------
Co-authored-by: jiangding001 <jiangding001@ke.com>
* docs(xiaohongshu): address review feedback from #24
Ten review notes from #24, eight applied in this file, one tracked in PR
body, one explicitly deferred per reviewer guidance.
- §2: clarify the brand-red split — the +6 red-channel shift between
#FF2442 (token) and #FF2E4D (component layer) is documented as
intent-undocumented-upstream rather than left implicit.
- §2: add a heads-up that danger reuses --primary, so destructive
actions and primary CTAs are visually identical out of the box;
recommend outline + brand-red text or a leading destructive icon as
a defensive default.
- §2: state dark-mode trigger explicitly (prefers-color-scheme + manual
override; both :root[dark] and .force-dark honored).
- §5: explain *why* the masonry uses translate3d + ResizeObserver
rather than CSS Masonry — the latter is still flagged in most
browsers as of 2026.
- §6: add a dark-mode row to the elevation table; alpha-on-black
shadows are invisible on the #19191E canvas, so drop hover shadows
and replace modal shadow with a 1px hairline at rgba(255,255,255,.07).
- §7: forbid fabricating the 小红书 wordmark / RED logotype as artifact
output (tokens are not protectable, the wordmark is — emit a labelled
grey block instead).
- §7: forbid referencing RED Number standalone in generated CSS without
the PingFang fallback chain.
- §7: add a concrete shadow threshold to the heavy-shadows Don't —
alpha > 0.15 or spread > 16px is the cutoff.
- §9: new "Brand Red Disambiguation" subsection at the top of the Agent
Prompt Guide making the per-surface rule explicit (default: #FF2442;
pixel-replica only: #FF2E4D; never mix on one component).
Schema-drift between CONTRIBUTING.md and the bundled design-system
corpus is intentionally not addressed here — the reviewer flagged it as
a wider-scope cleanup affecting all ~70 systems and explicitly said
"no action requested in this PR" if treated as such.
* docs(xiaohongshu): correct channel-delta in brand-red rationale
Codex review bot caught a numerical error in #54: #FF2442 → #FF2E4D
is not "+6 on the red channel" — both colors share the same red
channel (FF = 255). The actual delta is +10 on green (24 → 2E) and
+11 on blue (42 → 4D), which together raise lightness slightly and
shift the hue a touch toward pink.
Document review badge: P2.
* Refactor project name from "Open Claude Design" to "Open Design"
- Updated project name in package.json, package-lock.json, and README files.
- Changed CLI commands and references from "ocd" to "od".
- Adjusted file structure references in documentation and code to reflect new naming conventions.
- Enhanced .gitignore to include new runtime data files.
- Updated metadata in LICENSE file to match new project name.
* chore: update next-env.d.ts route types path
Made-with: Cursor
* docs(readme): refresh stats, agents, skills and add metrics workflow
Make all three READMEs (en / zh-CN / ko) tell the truth about what the
project actually ships, and add lightweight community signals at the top
and bottom.
- Hero block: live for-the-badge GitHub stats (stars, forks, issues,
PRs, contributors, commit activity, last commit) sit directly under
the banner, with the smaller flat-square project-meta row (License,
Agents, Design systems, Skills, Quickstart) below them and the
language switcher below that.
- Counts updated to reality: 31 skills (was 19), 72 design systems
(was 71), 10 coding-agent CLIs + OpenAI-compatible BYOK (was 7).
- "At a glance", architecture, and prompt-stack tables updated to
cover /api/templates, /api/import/claude-design, /api/proxy/stream,
/api/artifacts/lint, sidecar IPC, and per-namespace runtime data.
- New "Beyond chat — what else ships" section covering Claude Design
ZIP import, BYOK proxy with SSRF block, saved templates, tab
persistence, artifact lint, sidecar protocol + headless desktop, and
Windows-friendly spawning.
- Skills tables rebuilt by mode (prototype, deck) and scenario; the
"template" mode claim is removed.
- Supported coding agents table expanded to all 10 CLIs (Claude Code,
Codex, Gemini, OpenCode, Cursor Agent, Qwen, Copilot, Hermes, Kimi,
Pi) plus a BYOK row, with accurate stream formats and argv shapes.
- Roadmap re-flowed to mark shipped vs pending items.
- Contributors wall (contrib.rocks), Repository activity (lowlighter
metrics SVG), and Star History added to all three READMEs, with
cache_bust=2026-04-30 on the contrib.rocks and star-history image
URLs to bypass GitHub camo caching.
- Korean README harmonised end-to-end with the English/Chinese ones.
- New .github/workflows/metrics.yml regenerates
docs/assets/github-metrics.svg daily; ship a placeholder SVG so the
image works before the first scheduled run.
Made-with: Cursor
* docs(readme): address PR #173 review feedback
- Replace invalid 0x14 control character in github-metrics.svg with an
em-dash so the placeholder is well-formed XML and renders as an
image (P1: was breaking SVG parse before the first metrics run).
- Clarify the placeholder SVG subtitle to spell out the token model:
GITHUB_TOKEN gives core stats; METRICS_TOKEN unlocks richer plugins
(traffic, follow-up). Reduces "do I need a secret?" confusion.
- Rewrite the metrics.yml inline auth comment to match: METRICS_TOKEN
is optional and only enables richer plugins; GITHUB_TOKEN is enough
for core metrics. Previous comment read as if METRICS_TOKEN was
mandatory.
- Soften the BYOK fallback row in all three READMEs (EN / zh-CN / ko)
with a catch-all phrase ("or any other OpenAI-compatible provider")
so the listed vendors don't read as exhaustive.
Closes#141.
When the user clicked the Fullscreen button, requestFullscreen() put the
stage element into native browser fullscreen and React's `fullscreen`
state was set true. Pressing Esc was meant to exit the overlay, but in
browsers like Firefox the browser consumes Esc to drop its native
fullscreen element without delivering keydown to JS. The React state
stayed true, the `ds-modal-fullscreen` class lingered, and only a second
Esc reached the keydown handler that flipped the state.
Subscribe to `fullscreenchange` so the React state mirrors the native
state. When the browser exits its fullscreen element, the overlay drops
on the same keystroke. The keydown handler is still needed for the
fallback path (no native fullscreen API support, where requestFullscreen
is undefined and only React state is set).
Adds three regression tests in e2e/tests/preview-modal-fullscreen.test.tsx
covering the bug fix path, the keydown fallback, and a non-collapse
guard for transitions where another element is still fullscreen.
Co-authored-by: d 🔹 <258577966+voidborne-d@users.noreply.github.com>
tools-dev generated a temp web tsconfig with Windows backslash relative paths in extends, which Next/TypeScript failed to resolve in some environments. Normalize runtime tsconfig/dist path strings to POSIX separators so dev config resolution works consistently across Windows/Linux/macOS.
* fix(daemon): preserve non-ASCII filenames on multipart upload
multer@1 hands callers latin1-decoded multipart filenames, and
sanitizeName() then collapses every non-ASCII character to '_'. A
Chinese DOCX uploaded as 测试文档.docx therefore landed on disk as
____.docx, while the response shipped a latin1-mangled originalName
back to the client. The chat composer compared that to the UTF-8
File.name from the picker, missed the match, and reported "some
files could not be stored" even though the bytes were already on
disk.
Add decodeMultipartFilename() that round-trip-checks latin1->utf8
so genuine latin1 names aren't corrupted, and switch sanitizeName()
to keep \p{L}\p{N} (Unicode letters/digits) while still stripping
path separators, control characters, and Windows-reserved
punctuation. Apply the decoder to all three multer storage filename
callbacks so /api/upload, /api/import/claude-design, and
/api/projects/:id/upload all return UTF-8 originalNames.
Closes#144
* fix(daemon): address review feedback on filename decoder
Three changes from review:
1. decodeMultipartFilename now bails out early when any code point in
the input exceeds 0xFF. multer can hand us an already-decoded UTF-8
string when the client uses the RFC 5987 `filename*` parameter, and
the previous round-trip-only check would corrupt those names — for
example, a properly decoded `测试文档.docx` could be re-mangled into
`KՇc.docx` because the latin1-encoded bytes happened to form a
valid UTF-8 sequence. Code points above 0xFF can never appear in a
genuine latin1-decoding-of-utf8 input, so they're an unambiguous
signal that no further decoding should run.
2. decodeMultipartFilename now handles null/undefined defensively (it
was relying on multer always populating originalname; this just
makes the helper safer to reuse from other call sites).
3. The inline sanitiser regex in the `upload` and `importUpload`
multer instances is replaced with a direct `sanitizeName()` call,
matching what `projectUpload` already did. This keeps a single
source of truth for the allowed character set so future tweaks
only have to land in one place.
Adds two more tests to the existing sanitize-name suite covering the
above-0xFF early return and the null/undefined inputs (13/13 in that
file, 58/58 across the daemon).
resolveProjectRoot only stripped a 'dist' suffix from the caller's
module directory, so when the daemon sidecar is started by tools-dev
(which runs apps/daemon/src/server.ts directly through tsx) the
computed PROJECT_ROOT pointed at apps/ instead of the repo root.
That made SKILLS_DIR, DESIGN_SYSTEMS_DIR, STATIC_DIR and the default
RUNTIME_DATA_DIR all resolve to non-existent paths, so /api/skills
returned an empty list and the web "examples" page reported
"No skills available. Is the daemon running?".
Treat 'src' the same as 'dist' so resolution works for both the tsx
source entry and the compiled build, and add a regression test for
the src case alongside the existing dist/daemon-root cases.
* feat(daemon): add pi coding agent adapter
Add pi (https://pi.dev) as a supported coding agent, using its
--mode rpc JSON-RPC protocol over stdio for structured event streaming.
Changes:
- apps/daemon/pi-rpc.js: new RPC session handler that drives pi's
--mode rpc protocol, translating typed agent events (text_delta,
thinking_delta, tool_use, tool_result, usage, status) into the
daemon's UI event format. Auto-resolves extension UI requests
(fire-and-forget consumed, dialogs auto-approved) so pi stays
unblocked in the headless web UI. Kills the process after agent_end
since pi's RPC process is designed for multi-prompt sessions.
- apps/daemon/agents.js: add pi agent definition with custom
fetchModels (pi --list-models outputs to stderr, not stdout),
575+ models from 20+ providers, reasoning/thinking level support
via --thinking flag, and streamFormat 'pi-rpc'.
- apps/daemon/server.js: wire pi-rpc stream format to
attachPiRpcSession; skip stdin.end() for pi-rpc since the RPC
session manages stdin bidirectionally.
- apps/daemon/acp.js: export createJsonLineStream for reuse by
pi-rpc.js.
- apps/daemon/pi-rpc.test.mjs: 19 unit tests covering model list
parsing (TSV, dedup, edge cases), RPC event translation (text,
thinking, tools, usage, compaction, retry), sendCommand wire
format, extension UI auto-resolution.
- e2e/tests/structured-streams.test.ts: add pi RPC tool_use/tool_result
event mapping test alongside existing Claude/Copilot fixtures.
Verified end-to-end: daemon /api/chat → pi RPC → SSE stream with
status, text_delta, usage, and tool events. Live E2E test passes
(OD_E2E_RUNTIMES=pi). All 59 project tests green.
* refactor(daemon): migrate pi-rpc to TypeScript
Follow upstream #118 TypeScript migration convention: rename
pi-rpc.js → pi-rpc.ts and pi-rpc.test.mjs → pi-rpc.test.ts
with @ts-nocheck header (same as all other daemon modules).
Import paths remain ./pi-rpc.js per NodeNext module resolution.
* fix(daemon): avoid duplicate usage events in pi-rpc handler
Pi emits both message_end and turn_end per turn, both carrying
usage data. Emitting from both handlers caused double-counting
in the UI and any consumer that aggregates usage.
Remove usage emission from the message_end branch since turn_end
is the canonical per-turn usage source. Keep tool call extraction
in message_end (unique data not available in turn_end).
Add regression test confirming exactly one usage event is emitted
when both message_end and turn_end carry usage for the same turn.
Addresses Copilot P2 review on PR #117.
* fix(daemon): scope pi RPC id counter per session, bump graceful shutdown
Move nextRpcId and sendCommand inside attachPiRpcSession as local
state, matching the pattern in acp.ts where nextId is scoped per
session. Prevents RPC id collisions across concurrent /api/chat
requests.
Bump post-agent_end SIGTERM grace period from 2s to 5s and make it
configurable via PI_GRACEFUL_SHUTDOWN_MS env var for resource-
constrained machines.
Add test confirming concurrent sessions get independent id sequences.
* fix(daemon): wrap parser.feed in try-catch in pi-rpc
Catch errors from parser.feed and route them through the
existing fail() handler instead of letting them propagate
as unhandled exceptions.
Postinstall assumed `npm_execpath` always points to pnpm's JS entry and
invoked it via `node $npm_execpath`. When pnpm is installed as a
standalone binary (e.g. `@pnpm/exe` via mise / volta), `npm_execpath`
points to an ELF/Mach-O/PE executable and Node fails with
"Invalid or unexpected token" parsing the binary as JS.
Branch on the executable's extension: keep wrapping `.js`/`.cjs`/`.mjs`
entries with `node`, but spawn other paths directly so standalone pnpm
binaries work too.
Co-authored-by: decker <decker502@qq.com>
Surface daemon log tails when tools-dev waits for daemon status and add targeted guidance for native Node addon ABI mismatches.
Generated-By: looper 0.0.0-dev (runner=worker, agent=gpt-5.5)
* feat(dev): add desktop tools-dev control plane
* refactor(sidecar): split Open Design contracts
Move Open Design-specific sidecar protocol definitions into @open-design/contracts so sidecar and platform can remain descriptor-driven primitives.
* refactor(daemon): organize package sources
Keep daemon app code, tests, and sidecar entrypoints in separate package directories so each layer can be built and verified independently.
* chore(repo): streamline maintenance entrypoints
Centralize agent guidance by directory and reduce root command chains while preserving the existing build scope.
* docs: translate agent guidance to English
* fix(sidecar): tolerate stale IPC sockets
Remove stale Unix socket files only after confirming no listener is active, so tools-dev can restart after unclean shutdowns.
The settings modal had no max-height constraint, causing its content
to exceed the viewport height with no way to scroll. The footer action
buttons (Cancel / Save) were pushed out of view and unreachable.
- Add max-height: calc(100vh - 64px) to .modal-settings
- Wrap settings sections in .modal-body (flex: 1, overflow-y: auto,
min-height: 0) to create a scrollable content area
- Pin .modal-head and .modal-foot with flex-shrink: 0 so they remain
always visible regardless of content height
Co-authored-by: jiangding001 <jiangding001@ke.com>
Co-authored-by: mrcfps <mrc@powerformer.com>