Commit graph

603 commits

Author SHA1 Message Date
Marc Chan
bdd49ebc47
docs: add repository-wide code review guidelines (#927)
* docs: add repository-wide code review guidelines

Introduces docs/code-review-guidelines.md as the operational review
standard layered on top of AGENTS.md, and adds a Code review guide
section to AGENTS.md that points reviewers at it.

The guide codifies the Product relevance test as a pre-implementation
gate, names the canonical list of forbidden surfaces, lists the
ownership areas in scope, and defines five review lanes: default
code/tests, contract and protocol changes, design-system additions,
skill additions, and craft additions. It also captures the secrets,
runtime data, performance, and maintainability checks that previously
lived only as oral conventions, and aligns the approval bar with the
validation rules in AGENTS.md.

AGENTS.md remains the source of truth when the two disagree; the new
doc is the operational guide on top of it.

* docs: tighten review guidelines for governance docs and bugfix discipline

- Reference scripts/guard.ts as source of truth for guard checks
- Add governance documentation as an explicit in-scope category
- Require reviewers to build a module/caller map before commenting
- Add bugfix-specific reproduction and regression-test checks
- Carve out documentation-only exception in the approval bar

* docs: align review guidelines with repository policy

Keep the review scope aligned with maintained workspace surfaces and preserve AGENTS.md as the authoritative validation bar.

Generated-By: looper 0.6.3 (runner=fixer, agent=opencode)
2026-05-08 19:18:39 +08:00
Tom Huang
d592f6087f
feat(mcp): external MCP client with daemon-managed OAuth and 39 design-focused templates (#898)
* feat(mcp): add external MCP client with daemon-managed OAuth and 17 design-focused templates

Open Design now acts as an MCP CLIENT and surfaces tools from third-party
MCP servers to the underlying agent (Claude Code, Hermes, Kimi).

Daemon
- New mcp-config / mcp-oauth / mcp-tokens modules: persist server entries
  to .od/mcp-config.json, run the OAuth dance for HTTP/SSE servers
  end-to-end on the daemon (so cloud deployments work and tokens
  survive across turns), and inject Authorization: Bearer headers into
  the per-spawn .mcp.json the daemon writes for Claude Code (or the
  ACP mcpServers map for Hermes/Kimi).
- /api/mcp/servers and /api/mcp/oauth/{start,status,disconnect}
  endpoints, plus spawn-time wiring in agents that hands the configured
  servers to the active agent CLI.
- System-prompt directive for connected external MCPs so the model
  does not chase Claude Code's synthetic *_authenticate /
  *_complete_authentication tools when the Bearer is already pinned.

Web
- Settings -> External MCP servers panel with per-row OAuth Connect /
  Disconnect / Refresh affordances and per-row template hints.
- New "Add server" picker categorized into 7 groups
  (image-generation, image-editing, web-capture, ui-components,
  data-viz, publishing, utilities) with a search box, sticky close
  button, collapsible <details> sections (auto-expand on search),
  60vh capped scroll region, and a pinned Custom-server footer.
- ChatComposer /mcp slash and MCP picker button forward to the new
  Settings tab; AssistantMessage renders MCP tool calls inline;
  markdown autolinker handles bare http(s) URLs (incl. OAuth links)
  before italic markers so OAuth callback URLs do not get
  italic-fragmented mid-token.

Contracts
- packages/contracts/src/api/mcp.ts owns the wire shapes
  (McpServerConfig, McpTemplate with stable McpTemplateCategory
  enum, McpServersResponse, OAuth start/status/disconnect bodies, the
  postMessage payload from the OAuth callback).

Templates (17 built-in)
- image-generation: Higgsfield (OpenClaw, OAuth HTTP), Pollinations,
  Allyson (animated SVG), AWS Bedrock Image (uvx).
- image-editing: Imagician, ImageSorcery.
- web-capture: just-every screenshot-website-fast, ScreenshotOne.
- ui-components: 21st.dev Magic, shadcn/ui, FlyonUI.
- data-viz: AntV Chart, Mermaid.
- publishing: EdgeOne Pages.
- utilities: Filesystem, GitHub, Fetch.

Tests
- apps/daemon/tests/mcp-{config,oauth,tokens,spawn}.test.ts cover
  storage round-trip, OAuth helpers, token persistence, spawn-time
  wiring, every template's transport / command / args / env-field
  invariants, and the canonical category enum.
- apps/web/tests/runtime/markdown.test.tsx covers the new autolinker
  ordering rules.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(mcp): add 21 more design-focused templates and a `design-systems` category

Expands the built-in MCP picker from 17 to 38 templates so users can compose
the full Open Design craft loop (design-system intake → generate → edit →
audit → publish) without leaving the Settings dialog. Every install spec is
verified live against the upstream README; templates that needed Go binaries,
multi-step `init` ceremonies, or massive runtime stacks (PostgreSQL + Redis
+ Ollama) are intentionally deferred so picking a template still resolves to
a working server in one click.

New `design-systems` category between `web-capture` and `ui-components`
(reflects the upstream-of-components position in the workflow). Mirrored in
`McpTemplateCategory` on both contracts and daemon, and `CATEGORY_ORDER` on
the web side.

New templates by category:

- image-generation (+4): prompt-to-asset (icons / favicons / OG / logos with
  free-tier routing across Cloudflare AI / NVIDIA NIM / HF / Stable Horde),
  Nano Banana (hosted streamable HTTP, virtual try-on + product placement),
  Seedream (hosted streamable HTTP, ByteDance Seedream v3-v5 + SeedEdit),
  fal.ai (uvx, 600+ models incl. FLUX / Kling / Hunyuan / MusicGen).
- image-editing (+3): Photopea (34 layered-editor tools — closes the PSD
  gap), Topaz Labs (AI upscale / denoise / sharpen), Transloadit (86+ media
  pipeline robots).
- web-capture (+1): Pagecast (browser → demo GIF / MP4 with auto-zoom).
- design-systems (+4, NEW category): Figma-Context (Framelink, designs →
  code), Design Token Bridge (Tailwind ⇄ CSS ⇄ Figma ⇄ M3 / SwiftUI / W3C
  DTCG + WCAG contrast), Design System Extractor (Storybook scrape),
  Aesthetics Wiki (cottagecore / dark-academia / y2k / … moodboards).
- data-viz (+2): MCP Dashboards (45+ chart types + KPI dashboards),
  Excalidraw Architect (hand-drawn architecture diagrams).
- publishing (+6): PageDrop, PDFSpark, OGForge, QRMint, Slideshot
  (HTML → PDF / PPTX / PNG with 7 themes), Deckrun (Markdown → PDF / video,
  hosted free tier with no key required).
- utilities (+1): A11y axe-core (WCAG 2.0/2.1/2.2 + color-contrast + ARIA).

Tests cover every new template's wiring (command, args, env / header
required-vs-optional, secret flag), the category enum invariant, and
in-category declaration order for image-generation, design-systems and
publishing buckets where the order is what users see in the picker. 21 new
test cases pass; full mcp-config suite is green.

Templates intentionally deferred (documented in PR body): figma-use
(needs Figma desktop with --remote-debugging-port=9222), m-moire (multi-step
`memi suite init` + daemon ceremony), gemini-media-mcp + trident-mcp (Go
binaries — no npx / uvx path), Pixelle-MCP (full app with web UI + ComfyUI
backend), storybook-addon-mcp (lives inside user's Storybook, not standalone),
primitiv (multi-step init / build / serve), ReftrixMCP (PostgreSQL + Redis +
Ollama + DINOv2), narasimhaponnada/mermaid (overlap with peng-shawn).

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(mcp): add figma-use template (write designs from chat) under design-systems

figma-use is the natural counterpart to Figma-Context already in this PR:
where Framelink reads Figma designs into the model, figma-use writes back
into the canvas (90+ tools — create frames / text / components / variants,
render JSX into Figma, export PNG/SVG, query nodes via XPath, lint for
WCAG / auto-layout / hardcoded colors, analyze design systems).

Wired as an HTTP MCP template (`http://localhost:38451/mcp`) because
`figma-use mcp serve` only exposes HTTP — there's no stdio mode in the
upstream `serve.ts`. No API key. Two prerequisites the user owns are
spelled out in the description so picking the template still resolves to
a working server: (1) start Figma with `--remote-debugging-port=9222`
(or `figma-use daemon start --pipe` on Figma 126+), and (2) leave
`npx figma-use mcp serve` running in a terminal.

Inserted between `design-system-extractor` and `aesthetics-wiki` so the
design-systems category reads as a workflow: read existing design (Figma
Context) → translate tokens (Token Bridge) → extract from Storybook
(Extractor) → write back to Figma (figma-use) → break creative block
(Aesthetics Wiki).

Tests cover the new template's transport (`http`), endpoint URL, the
empty header-fields invariant (no auth required), and bump the
design-systems group order to include it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(settings): i18n the External MCP / MCP server / Connectors sidebar entries and make the dialog header track the active section

The External MCP sidebar entry this PR introduces was hardcoded English
("External MCP / Add MCP tools (Higgsfield, GitHub…)"). Same for the
adjacent Connectors and MCP server entries. The dialog header was also
pinned to "Execution & model" copy, so opening Settings → External MCP
showed a header that lied about which section the user was on.

Adds six translation keys — `settings.connectorsTitle/Hint`,
`settings.mcpServerTitle/Hint`, `settings.externalMcpTitle/Hint` — and
translates them across all 17 locales (ar, de, en, es-ES, fa, fr, hu, id,
ja, ko, pl, pt-BR, ru, tr, uk, zh-CN, zh-TW).

`SettingsDialog` now derives the header title/subtitle from the active
section (11 sections total) instead of a single hardcoded pair, so each
section renders an honest header.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(e2e): pin level: 3 on dialog heading lookups for Pets and Connectors

CI's Validate workspace job (#1479) failed two Playwright cases with the
strict-mode violation:

  getByRole('dialog').getByRole('heading', { name: 'Pets' })
  resolved to 2 elements:
    1) <h2>Pets</h2>
    2) <h3>Pets</h3>

Same root cause as the unit-test fix already in this PR: the dynamic
dialog `<h2>` now echoes the section's own `<h3>` because the dialog
header tracks the active section. Disambiguate to `level: 3` so each
assertion still pins the section heading specifically (which is what
the test intends to verify).

Audit of the rest of e2e/ for `dialog.getByRole('heading', ...)` —
settings-api-protocol.test.ts looks for "OpenAI API" / "Anthropic API"
section h3s which never appear in the dialog `<h2>` (always
"Execution & model"), so those stay safe.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(mcp): bind OAuth refresh to the issuing client and skip stale tokens

Persist the OAuth client context (token endpoint, client_id, client_secret,
issuer, redirect_uri, resource) alongside the bearer token so refresh hits
the same client the refresh_token was bound to (RFC 6749 §6). The previous
refresh path re-ran beginAuth with a dummy OOB redirect URI, which kept
getOrRegisterClient from finding the original DCR client and made
providers reject the refresh on the next chat turn. Refreshes now reuse
the persisted endpoint/client pair directly.

Also stop injecting expired access tokens at spawn time when refresh is
unavailable or fails. Pinning a stale Bearer made every Claude MCP call
401 while the prompt still treated the server as connected; on that path
we now skip the entry and let the UI surface a reconnect.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:59:20 +08:00
ashleyashli
2b5ea36f21
feat: add ib-pitch-book deck skill (Pitch Agent port) (#888)
* feat(skills): add ib-pitch-book deck skill (Pitch Agent port)

- Add investment-banking strategic-alternatives pitch book skill adapted from anthropics/financial-services Pitch Agent (Apache-2.0)

- Ship self-contained example.html (fictional NorthPeak / Hartfield case)

- Add references: compliance, attribution, conventions, P0/P1/P2 checklist

- Document in CHANGELOG [Unreleased]

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ib-pitch-book): align comps copy and DCF sensitivity base cell

- Trading comps: narrative now matches table (12.5× vs 12.4× median; explain via growth/mix)

- DCF: base-case sensitivity cell at 9.0% WACC × 2.5% g = $56.40, matching implied equity / share

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: register ib-pitch-book in i18n fallbacks; add demo disclaimer banner

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: address ib-pitch-book review feedback

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: prevent ib-pitch-book dense slide overflow

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:49:57 +08:00
ashleyashli
abe96caf06
feat: enable Vaunt contributor recognition with 5-tier system (#908)
* feat: enable Vaunt contributor recognition with 5-tier system

Adds .vaunt/config.yaml that maps the Open Design contributor scoring rules
(spec §2.6) to Vaunt's achievement and point-action model. Once merged, the
already-installed Vaunt GitHub App will:

- Backfill historical PR / review / issue / comment / discussion data for
  the existing ~3.77k contributors silently (no comments are posted to old
  threads — only achievements granted on Vaunt's own platform)
- Calculate cumulative points per the rules in
  open-design-bot/src/scoring.ts
- Auto-grant the matching tier achievement (Spark / Signal / Node / Beacon /
  Nova) once thresholds 0 / 30 / 150 / 700 / 2,500 are crossed

Tier icons (500x500 PNG, Twemoji on dark-space gradient) are checked in
under .vaunt/icons/ and referenced via raw.githubusercontent URLs.

Once Vaunt has scanned the repo we will use its API to populate
data/contributors.json + generate a CONTRIBUTORS.md leaderboard. No public
visibility for contributors until that step lands in a follow-up PR.

* fix(vaunt): correct actor for review triggers + drop mis-mapped issue_resolved

Addresses @mrcfps's review on #908 (two scoring/trigger mismatches that
would have produced incorrect backfill numbers).

1. **Review actor mismatch.** Vaunt's documented actor matrix only allows
   `actor: author` with `action: review`; `reviewers` is reserved for
   `pull_request` triggers. With the previous config, no review event would
   match, so reviewers earned 0 of the intended 3 review points, and the
   Spark achievement's review entry-path was equally broken. Both review
   triggers (point action `pr_review` + Spark achievement) now use
   `actor: author`.

2. **`issue_resolved` was wrong rule.** scoring.ts awards 6 points for
   `discussion_answered_accepted`, not for closing an issue. The previous
   `issue_resolved` action awarded 6 points to anyone who closed any issue,
   which would have skewed both the historical backfill and future totals.
   Removed `issue_resolved` entirely + removed it from every tier's sum().
   Added an inline note explaining that `discussion_answered_accepted` is
   not currently expressible in Vaunt (its discussion action exposes only
   `closed`, no "answered" signal) and that the rule is moot today since
   the repo has Discussions disabled — when both conditions change, we
   re-add it.

Also added a header comment with Vaunt's actor/action matrix so future
edits don't re-introduce the same kind of mismatch.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ashleyashli <ashleyashli@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:44:12 +08:00
Tom Huang
1d1df52f3b
feat(skills/live-artifact): add 7 example dashboards + contract demo (#716)
* feat(skills/live-artifact): add 7 example dashboards + contract demo

Seven self-contained HTML prototypes under skills/live-artifact/examples/,
each with a distinct visual identity and built-in interactivity for video
demos:

  stock-dashboard.html     - Bloomberg-style trading floor (dark)
  crypto-dashboard.html    - DeFi/web3 cyber terminal with on-chain ribbon
  crm-table-live.html      - multi-dim CRM with Grid/Kanban/Gallery/Calendar
                             view switcher (light productivity)
  monday-operator-live.html - editorial Monday-morning briefing (paper)
  competitor-radar-live.html - mission-control radar with rotating sweep
                             and RGB threat tiers
  baby-health-live.html    - soft pastel parental panel
  stock-portfolio-live/    - full live-artifact contract example: 102
                             escaped html_template_v1 bindings + 7
                             data-od-repeat blocks, ready to register
                             via 'tools live-artifacts create'

Each interactive HTML carries refresh-with-flash, view switching, AI
panel regeneration, clickable rows/cards that mutate state, and toast
notifications. Self-contained - only Google Fonts as external dep.

stock-portfolio-live/ demonstrates the daemon contract: template.html +
data.json + artifact.json + provenance.json. Refresh runners can rewrite
data.json without re-authoring the template.

* fix(skills/live-artifact): address PR #716 review feedback

- Unroll data-od-repeat blocks into indexed data.* bindings so renderHtmlTemplateV1 can interpolate them (it does not expand data-od-repeat or repeat-local aliases like {{t.label}}).
- Rename catalysts[].body to catalysts[].text to satisfy the bounded JSON validator's forbidden-key list (body is rejected case-insensitively); update template binding accordingly.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

* fix(skills/live-artifact): make stock-portfolio provenance.json contract-compliant

- generatedBy: free-form string -> "agent" (LiveArtifactProvenanceGenerator enum)
- sources[].kind -> sources[].type with LiveArtifactProvenanceSourceType enum values
  (connector for brokerage/quotes connectors, derived for AI recommendation)
- Drop non-contract per-source `note` and top-level `summary`/`transformations`/
  `refreshContract`/`safetyNotes` fields; preserve their content under the
  contract-allowed `notes` field so the example survives schema validation.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

* fix(skills/live-artifact): use strict ISO-8601 generatedAt in provenance

The daemon's `validateIsoDate` requires `Date.toISOString()` round-trip
equality, so timezone-offset notation like `2026-05-06T14:32:18-05:00`
fails validation even though it parses. Switch to the canonical UTC form
`2026-05-06T19:32:18.000Z` (same instant), which the validator accepts.

* feat(skills): surface examples/*.html as derived skill cards + Live filter

A skill that ships hand-crafted samples under examples/*.html (e.g.
live-artifact's stock dashboard, baby health monitor) now lights up one
gallery card per file instead of a single parent card whose preview can
only ever show one of them. The parent stays in the listing tagged
aggregatesExamples=true so findSkillById and Use this prompt still
resolve back to its SKILL.md body, but the Examples tab hides it so the
derived <parent>:<child> cards aren't shadowed by a duplicate preview.

Subfolder layouts (examples/<name>/template.html + data.json) are
deliberately skipped — their templates still hold {{data.x}}
placeholders that only the daemon-side renderer fills in, so showing
the raw template would render visible braces in the gallery. Ship the
baked output as examples/<name>.html alongside the folder to surface it.

Adds an examples.modeLive filter pill (translated across all 21 locales)
that selects skill.scenario === 'live', so refreshable / connector-backed
samples are easy to find without scrolling through every desktop
prototype. live-artifact's SKILL.md gains scenario: live so it (and
every derived card) lights up there.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(web): parallelize entry-view bootstrap so each tab renders independently

Bootstrap used to wait on a single Promise.all behind a global
'Loading workspace…' placeholder, which made the slowest endpoint
(typically /api/agents on cold start, since it probes CLI versions)
gate every tab including the ones that don't need agents at all.

Splits the global bootstrapping flag into per-resource loading flags
(agentsLoading, skillsLoading, dsLoading, projectsLoading,
promptTemplatesLoading) plus a daemonConfigLoaded flag for the merged
daemon config. Each tab now blocks only on the data it actually needs:
Examples renders as soon as skills land, Design Systems on dsList,
Designs on projects+skills+designSystems, etc.

Auto-selecting the first available agent and the default design system
moves into dedicated effects gated on daemonConfigLoaded so they no
longer race ahead of the daemon-stored choice and overwrite it with a
freshly picked first-available pick.

EntryView swaps its single loading prop for skillsLoading,
designSystemsLoading, projectsLoading, promptTemplatesLoading so each
inner tab can pick the right gate without leaking the parent's coarse
state.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:38:29 +08:00
Joey-nexu
9ed4ea1263
feat(skill): add github-dashboard (#666)
* feat(skill): add github-dashboard

* docs(skill): add github-dashboard screenshot

* fix(skill): address github-dashboard review

* fix(i18n): cover github-dashboard skill in de/ru/fr fallback lists

The localized-content coverage test asserts every skills/<id>/SKILL.md
appears in each locale's skills list. Adding github-dashboard to the
EN-fallback id list keeps de/ru/fr CI green.

* fix(i18n): cover mission-control design system in de/ru/fr fallback lists

Same backfill as the one applied on PR #714: mission-control was added
in #858 without locale fallback entries, so the localized-content
coverage test breaks any open PR once GitHub merges current main into
its head ref.

---------

Co-authored-by: joey <joey@joeydeMacBook-Air.local>
Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 17:21:12 +08:00
Eli
09f6a7ccce
docs(zh-CN): trim BYOK proxy fallback line from intro (#915) 2026-05-08 17:00:27 +08:00
Sid
8b0625aa6f
fix(web): unbreak Create button on plain HTTP / LAN-IP deployments (#849) (#900)
`crypto.randomUUID()` is restricted to secure contexts (HTTPS or
`localhost`), so when Open Design is served over plain HTTP on a
LAN IP — the standard Docker / unRAID / NAS self-hosted setup,
e.g. `http://192.168.1.10:17573` — Chromium silently makes the
function undefined. Calls then throw
`TypeError: crypto.randomUUID is not a function`, which the
`try/catch` around `createProject()` swallows by returning `null`,
which the click handler reads as "no project, do nothing". The
Create button effectively becomes a silent no-op for every LAN-IP
user (issue #849, also reported as #394).

Centralize the call into a new `apps/web/src/utils/uuid.ts` helper
with a three-tier fallback per @lefarcen's review:

  1. `crypto.randomUUID()` — secure-context happy path, native and
     cryptographically random.
  2. `crypto.getRandomValues()` + RFC 4122 §4.4 byte layout — still
     available in non-secure contexts since the Web Crypto API is
     not gated by `isSecureContext`. Yields a real v4 UUID with
     crypto-quality entropy.
  3. `Math.random()` — last-resort polyfill for environments
     missing both, kept because the IDs we generate (project ids,
     message ids, client request ids) are scoped to a single
     user's local browser session — cryptographic uniqueness
     isn't required, just enough entropy to avoid collisions.

Replace all four `crypto.randomUUID()` callsites confirmed in
@lefarcen's audit:

- `apps/web/src/state/projects.ts:48` (createProject id)
- `apps/web/src/components/ProjectView.tsx:986` (user message id)
- `apps/web/src/components/ProjectView.tsx:1013` (assistant message id)
- `apps/web/src/components/ProjectView.tsx:1263` (daemon stream
  clientRequestId)

with calls to the new `randomUUID()` helper.

Tests: 6 new tests in `apps/web/tests/utils/uuid.test.ts` cover
each fallback tier, RFC 4122 v4 format validation (regex + explicit
version/variant nibble checks), the explicit "doesn't throw when
`crypto.randomUUID` is undefined" assertion that pins the #849
root cause, and a 1000-iteration uniqueness check on the
`getRandomValues` path.

Verified locally:
- web vitest: 522/522 (was 516, +6)
- web `tsc -b --noEmit` clean
- `tsx scripts/i18n-check.ts` passes
2026-05-08 16:50:59 +08:00
Marc Chan
b06f26a5fd
test: strengthen e2e PR coverage (#796)
* test: strengthen e2e PR coverage

* fix: address e2e PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* ci: cache Windows packaged smoke builds

* test: fake additional agent runtimes

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Route tools-pack mac starts through a launch-time packaged config override so portable packaged smoke runs keep using the namespace runtime root that inspect and logs expect.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Fall back to the packaged app's embedded config when the build output config is missing so installed mac starts still work.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: align packaged mac PR smoke with tools-pack runtime mode

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Keep blake3-wasm out of the packaged mac daemon prebundle so the standalone runtime loads the Cloudflare asset hasher from node_modules instead of crashing in ESM.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Skip the portable mac launch override when the bundled packaged config is missing so installed fallback app targets can still boot with packaged defaults.

Add a regression test covering the missing-config start path.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(pack): remove duplicate mac prebundle dependency key
2026-05-08 16:48:10 +08:00
shangxinyu1
8fee22d358
Fix stuck chat runs and unintended cancels (#896)
* Fix stuck chat runs and unintended cancels

* Harden chat run stall watchdog
2026-05-08 15:47:44 +08:00
Et cetera
c0c1f6555c
add support for VP_HOME environment variable in agent resolution (#859)
* feat: add support for VP_HOME environment variable in agent resolution

- Introduced a new .node-version file to specify Node.js version.
- Enhanced agent resolution tests to include scenarios for VP_HOME, ensuring proper handling of Vite+ global installs.
- Updated platform code to resolve user-scoped home directories, allowing for custom Vite+ installations to be prioritized.
- Added tests to verify that the resolution logic correctly honors the VP_HOME environment variable and integrates with existing user toolchain paths.

* feat: enhance VP_HOME support in sidecars and platform

- Updated the PACKAGED_CHILD_ENV_ALLOWLIST to include VP_HOME for environment variable forwarding.
- Exported functions resolvePackagedChildBaseEnv and resolvePackagedPathEnv for better accessibility in tests.
- Added tests to validate VP_HOME handling in packaged child environments and ensure correct path resolution.
- Adjusted wellKnownUserToolchainBins to prioritize VP_HOME/bin in the toolchain path resolution.
2026-05-08 15:14:37 +08:00
Marc Chan
e14b8092ea
feat: add Orbit activity summaries (#681)
* feat: add Orbit activity summaries

* fix(orbit): make runs navigable while agent continues

* fix(web): widen minimum chat panel

* feat: support Orbit template selection

* fix(daemon): avoid bogus skill side-file preflight

* fix(web): collapse orbit artifact project cards

* fix(web): preserve orbit project card titles

* fix: improve Orbit run daily briefing

* fix: handle Orbit digest data failures

* fix: load Orbit templates and connector tools reliably

* fix: keep Orbit summary counts consistent

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: apply Orbit template skill context

* fix: cache and curate connector tools for Orbit

* fix: align Orbit defaults and connector discovery

* fix: simplify Orbit template settings

* fix: move connectors into settings

* fix: compact connector settings catalog

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: prevent connector action button from stretching into pill

The icon-only connect/disconnect buttons in the embedded connectors
catalog inherited min-width: 92px / 106px from the non-embedded pill
rules, overriding the 24px square sizing and causing the buttons to
overlap the card head text. Reset min-width to 0 in the embedded
icon-only rule so the compact square layout holds.

* fix(web): align live artifact file rows

* fix: clean up Orbit connector settings lifecycle

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address Orbit review regressions

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* feat(web): localize Orbit and connector settings

* feat(web): gate Orbit runs without connectors

* feat(web): refine connector settings UX

* feat(web): safeguard Composio key clearing

* fix(web): refresh Composio tool badges

* feat(web): show connector logos

* feat(daemon): localize Orbit prompt window

* fix(daemon): clarify blocked connector callback closes

* test(daemon): harden flaky async probes

* fix(web): align Indonesian connector locale keys

* test(web): align connector browser props

* fix(web): preserve explicit credential clears

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): time out Composio logo proxy fetches

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): localize Indonesian connector settings copy

Translate the new connector settings strings in the Indonesian locale and lock them with a regression test so this surface no longer silently falls back to English.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve discovered connector tools

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve onboarding autosave completion

Keep settings autosave from clearing onboarding completion after the close gesture, and expose the desktop main types from source so workspace validation can typecheck packaged imports without a prior desktop build.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): defer Composio catalog cache hydration

Load persisted Composio catalog data only after the runtime data directory is configured so startup cannot read another namespace's cache. Add a regression test that exercises the module-load singleton path.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): treat discovery completion independently

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve latest settings draft on close

Use the latest persisted settings draft when the dialog closes so onboarding completion does not race a stale daemon sync and overwrite newer Orbit/template selections.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): avoid syncing draft Composio key on Orbit run

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): localize Orbit settings copy

Translate the new Indonesian Orbit and autosave strings so the settings UI no longer falls back to English and the locale regression stays covered.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): prefer fresh connector catalog state

Keep refetched connector status/auth data authoritative while retaining discovery-only tool metadata so the connectors UI stays consistent after refreshes.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): declare Indonesian locale fallback keys explicitly

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): inline Indonesian fallback strings for CI

Replace the Indonesian locale's per-key English lookups with explicit strings so workspace typecheck no longer depends on brittle build-mode resolution in CI.

Add a regression test that blocks those per-key English lookups from reappearing in the CI-sensitive fallback sections.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): restrict proxied connector logos to image MIME types

Reject non-image upstream logo responses so the daemon never serves third-party HTML from its localhost origin.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* test(e2e): align settings dialog regressions

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): decouple Orbit runs from media sync failures

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): keep SPA catch-all export-compatible

Disable dynamic catch-all params for the exported SPA shell so Next.js static builds can emit the root route again. Add a regression test covering the route config against the web export mode.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve Orbit config and workspace routes

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): block SVG in connector logo proxy

Reject SVG and other unsafe proxied logo responses so third-party logo content cannot execute under the daemon origin, while keeping raster logo fetches working and making rejected responses non-cacheable.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): fall back to static catalog for empty cache

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): disable Orbit run before connector gate resolves

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(desktop): export shipped desktop types

Point the desktop ./main type export at the generated declaration so installed consumers resolve the published file set.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): restore persisted question form selections

Render historical submitted answers directly so reloaded question forms keep their locked selections visible.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): retry forced media sync autosave

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): keep Composio logo timeout through body read

Keep the Composio logo fetch timeout active until the response body is fully consumed so stalled body reads abort and clear the inflight cache entry. Add a regression test that proves a delayed body read times out and the next request can recover.\n\nGenerated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): refresh Orbit gate after connector auth

Re-check connector availability when the settings window regains focus so Orbit unlocks as soon as a connector finishes authenticating in the same settings session.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): keep connector detail tool lists intact

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): ignore malformed Orbit summaries

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(e2e): stabilize design-system multi-select flow

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): cap Composio logo cache growth

Bound the Composio logo cache with LRU eviction and expired-entry pruning so repeated untrusted logo requests cannot grow daemon memory without limit.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): bound proxied Composio logo payloads

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): align autosave settings tests

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): remove stray CSS conflict marker

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fixer: address PR #681 follow-up items

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): restore restart routes and connector flows

* fix(web): keep SPA export route static

* fix(web): stabilize chat scroll tests

---------

Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 14:27:46 +08:00
shangxinyu1
aec9428b08
Fix desktop preview and packaged app interactions (#879)
* Fix packaged deck navigation interactions

* Fix connector auth in packaged app and localized content coverage

* Fix Electron connector browser handoff contract
2026-05-08 14:26:10 +08:00
lefarcen
b9d30aa30e
test(web): de-flake chat-scroll-preservation across tab switches (#886)
The earlier shape installed instance-level Object.defineProperty mocks
on the *remounted* chat-log only after `await switchTab('Chat')`. Inside
that act() the component schedules a rAF that writes scrollTop on the
new element; depending on whether jsdom's rAF polyfill flushed before
the await resolved, the write either landed on the still-default
prototype setter (lost) or the not-yet-installed instance setter (also
lost). The instance mock's closure-captured remountedTop then served
its initial 0 forever and the assertion failed nondeterministically
across CI runs without any product-code change.

Patch the geometry at HTMLElement.prototype level so any chat-log
React mounts later automatically reads/writes through a
test-controlled `geom` object. The component's restore rAF can fire
at any point and still write to the same place the assertion reads
from. Verified 8/8 clean local runs.
2026-05-08 14:16:12 +08:00
Muhammad Anas
c2e8fc3b02
feat(design-systems): add Urdu Modern (Indus Script) system (#714)
* feat: add Urdu Modern design system

* fix: address review comments (font mismatch, contrast, and i18n fallback)

* fix: address all review comments for Urdu design system

* fix: resolve i18n crash, font URL mismatch, and markdown syntax error

* fix: remove font URL space and update Quick Start font token

* docs: fix quick start link syntax and icon source order

* fix(i18n): cover urdu design system in de/ru/fr locale dictionaries

The localized-content coverage test scans design-systems/*/DESIGN.md and
asserts every id appears in each locale's designSystems list, and every
`> Category:` value is a key in designSystemCategories. Adding the new
`urdu` design system without these entries breaks de/ru/fr CI.

Add urdu to the EN-fallback id list and translate the new
'Editorial / Personal / Publication' category for all three locales.

* fix(i18n): cover mission-control design system in de/ru/fr fallback lists

Mission Control was added in #858 but its design-system id was never
added to the locale fallback arrays, so the localized-content coverage
test breaks once main and any open PR share the same merge ref.

---------

Co-authored-by: unknown <muhammadanas0261@gmail.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 13:05:55 +08:00
Joey-nexu
063e3b59c2
add otd-operations-brief live-artifact template (#794)
Adds a Mono Crimson Operations Brief live-artifact template under

templates/live-artifacts/otd-operations-brief/. The template ships:

- template.html: html_template_v1 source, fully unrolled (no

  data-od-repeat — daemon renderer is scalar-only) for 4 KPIs,

  14 bar rows, and 8 lowest-OTD rows;

- data.json: default sample with pre-computed bar fills, prior-year

  ticks, and CSS class names so the template binds purely as scalars;

- artifact.json + provenance.json: stored-snapshot fixtures that

  mirror specs/2026-04-29-live-artifacts/examples/minimal-static/;

- DESIGN.md: full Mono Crimson Operations Brief 9-section design

  spec (warm off-white canvas, charcoal bars, single-accent crimson);

- index.html + preview.png: pre-rendered default display sample so

  reviewers can see the artifact without spinning up a daemon.

Template-level only — no feature/code changes.

Co-authored-by: joey <joey@joeydeMacBook-Air.local>
2026-05-08 12:53:24 +08:00
Sohaib Kamran
47a014d377
Add BMW M design system (#579)
* Add BMW M design system

* Address BMW M design system review feedback

* Fix BMW M palette swatch parsing
2026-05-08 12:49:32 +08:00
Nagendhra Madishetti
661d11e60b
fix(web): confirm before clearing the saved Composio API key (#877)
The Clear button on Settings → Connectors removed the daemon-stored
Composio key in a single click with no recovery — a stray click
wiped a credential the user had to fetch back from app.composio.dev.

Wrap the existing onClick in window.confirm() matching the same
pattern the codebase already uses for destructive actions
(conversation delete, design delete, FileWorkspace file delete,
and the Media providers Clear button shipped alongside this in
issue #737). The prompt copy stays in English to match the rest
of the Composio section, which is hardcoded English today.

Updated the existing 'clears a saved Composio key' test to
auto-accept the prompt, plus added a sibling test asserting that
dismissing the prompt leaves the daemon-stored key intact in the
saved payload.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 12:39:04 +08:00
NotLeaped84
751b2357f1
feat(design-systems): add Mission Control design system (#858)
* feat(design-systems): add Mission Control design system

* fix(mission-control): address all reviewer comments - add font extraction labels, remove CSS duplication, fix T+/T- comment, add use case motivation, acknowledge light mode edge case
2026-05-08 12:32:51 +08:00
Tuola-waj
0c383af332
add trading analysis dashboard template skill (live artifacts) (#824)
* add trading analysis dashboard template skill for live artifacts

Package the Wall-Street-style dashboard as a template-mode skill with a default example, checklist, and seed template, and register i18n fallback coverage for the new skill id.

* fix(skill): address P1 chart axis labels, units, and legends

lefarcen review:
- checklist required >=3 charts with axis labels, units, and legends, but
  template ships with 2 charts and neither had labels/units/legends.
- Adjusted checklist P0 to require >=2 charts (matches actual template) and
  kept the axis-label / unit / legend sub-gates.
- Annotated both Option Greeks and Cumulative PnL charts with x-axis tick
  labels, y-axis tick labels, axis titles with explicit units (Strike $,
  Sensitivity Δ/Γ, Session Time ET, Equity $ USD), and a legend row naming
  each plotted series. Charts now satisfy the P0 gate.

* fix(skill): make localStorage access safe inside sandboxed preview iframes

mrcfps Looper review:
Open Design renders HTML artifacts in sandboxed iframes that allow scripts
but not same-origin access. The template's top-level localStorage.getItem /
setItem calls could throw SecurityError before demo, theme, palette, and
chart handlers were registered, leaving the artifact static in the primary
preview path.

Wrap reads/writes in safeGetTheme / safeSetTheme helpers that swallow
SecurityError so the document can still apply the active theme on
documentElement and continue initializing interactive handlers when storage
is unavailable. Persistence becomes best-effort, interactivity becomes
guaranteed.

* fix(skill): align Option Greeks x-axis tick labels with strike circles

lefarcen review (a346e80 regression):
The axis ticks added in a346e80 placed 145/150/155/160/165 at x=40/120/200/300/380,
but the linked .strike-150/-155/-160 circles already sit at cx=200/300/380. With
the previous labels, hovering Option Chain row data-strike=150 would highlight
the chart point at x=200, where the axis read 155. The label-versus-data hover
link was visually inconsistent.

Shift the axis tick labels to 140/145/150/155/160 at x=40/120/200/300/380 so
strike 150/155/160 labels sit directly under their circles, restoring the
table↔chart hover-link semantics.

---------

Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local>
Co-authored-by: Tuola Ge <gexingli@refly.ai>
2026-05-08 12:08:43 +08:00
Tom Huang
2df8b775ec
feat(skills): add 32 zhangzara HTML deck templates (#704)
* feat(skills): add 32 zhangzara HTML deck templates

Vendored from upstream MIT-licensed
zarazhangrui/beautiful-html-templates — one Open Design skill per template
(name prefix `html-ppt-zhangzara-`) so each template surfaces as its own
entry in the Examples panel and renders its own preview.

Each skill ships:
- SKILL.md (frontmatter + workflow), description, triggers, and
  od.upstream pointing at the source folder
- example.html (the self-contained deck; daemon's preview route looks
  for <skillDir>/example.html)
- template.json (upstream metadata snapshot, with `slug` re-prefixed to
  `zhangzara-<base>` and a `source` URL)
- assets/deck-stage.js / assets/styles.css for the 8 templates that
  ship a runtime; HTML refs rewritten so the daemon's iframe URL
  rewriter resolves them through /api/skills/<id>/assets/

scripts/guard.ts allowlist updated with the `html-ppt-zhangzara-` prefix
so the vendored upstream JS runtimes pass the residual-JS check.

* fix(skills, i18n): address PR #704 review feedback

- Add the 32 new html-ppt-zhangzara-* skill ids to the de/ru/fr
  SKILL_IDS_WITH_EN_FALLBACK arrays so the localized-content
  coverage e2e test passes. The vendored upstream templates are
  English-only; falling back to the upstream English description
  is the right semantic for this batch.
- Also add the pre-existing social-media-dashboard skill and
  totality-festival design system to the same fallback arrays
  (introduced in #678 without i18n coverage). Tagged with TODOs
  so localized copy can land in a follow-up.
- Ship the upstream MIT LICENSE file in each
  skills/html-ppt-zhangzara-*/ folder so the copyright/permission
  notice travels with the vendored copy, as MIT requires for
  redistributing substantial portions. Update each SKILL.md's
  Source section to reference the bundled LICENSE.
- For the 8 runtime-backed templates (creative-mode,
  editorial-tri-tone, neo-grid-bold, peoples-platform,
  pin-and-paper, pink-script, soft-editorial, stencil-tablet),
  expand the workflow's clone step to instruct the agent to copy
  the assets/ folder alongside example.html — the skill HTML
  references assets/deck-stage.js (and assets/styles.css for
  pin-and-paper) as project-local paths, so cloning the HTML
  alone produces an artifact whose runtime 404s.

Verified locally:
- pnpm guard passes.
- pnpm --filter @open-design/web typecheck passes.
- pnpm --filter @open-design/web test passes (309/309).
- pnpm --filter @open-design/e2e test passes (6/6 active,
  including localized-content coverage for de/ru/fr).

* fix(i18n): drop duplicate totality-festival fallback after merge with main

Main already added 'totality-festival' to the design-system EN-fallback
lists; the TODO entry from this branch became a duplicate after merge.

* fix(skills, guard): address PR #704 follow-up review

- Pin Chart.js CDN to 4.4.7 in coral and cartesian example.html so
  vendored decks no longer track the latest jsDelivr major.
- Narrow scripts/guard.ts zhangzara allowlist to a regex that only
  permits skills/html-ppt-zhangzara-*/assets/deck-stage.js, restoring
  the TypeScript-first guard for any other JS under those skill dirs.
- Reconcile slide_count and 'Slides in demo' with actual <section
  class="slide"> counts: broadside 20 -> 16, monochrome 18 -> 16,
  neo-grid-bold 13 -> 12.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(daemon): keep resolveDataDir return path stable, canonicalize at compare site

The realpathSync wrapper inside resolveDataDir was rewriting every
/var/... result to /private/var/... on macOS, which broke 11 hermetic
assertions in tests/resolve-data-dir.test.ts (absolute paths, relative
paths, and \$HOME / \${HOME} / ~ expansions whose mkdtempSync roots live
under /var/folders/...). It also changed the public OD_DATA_DIR
resolution contract for any downstream caller that compared against the
expanded user-supplied path.

Restore resolveDataDir to return the expanded resolved path unchanged,
and introduce RUNTIME_DATA_DIR_CANONICAL — a one-shot realpath of
RUNTIME_DATA_DIR — used only at the narrow folder-import comparison
site that needs to match against a user-supplied realpath() result. The
import-path symlink protection from #624 still works (a /var-rooted
data dir now compares against its /private/var canonical form), while
resolveDataDir keeps its stable, user-shaped contract.

Verified locally: pnpm --filter @open-design/daemon test (1083/1083),
including all 12 resolve-data-dir.test.ts cases.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 12:02:59 +08:00
VanJay
369d136d19
Add Docker Compose deployment workflow (#65)
* Add Docker Compose deployment workflow

* Address Docker deployment review feedback

Harden publishing inputs and temporary credential handling, and tighten Docker runtime defaults requested by the PR review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix Docker publish build in CI mode

Set CI=true during the image build so pnpm prune can run non-interactively inside Docker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix Docker runtime dependency layout

Use pnpm deploy for the daemon package so the runtime image includes production dependencies where Node resolves them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Use legacy pnpm deploy in Docker build

Allow pnpm v10 deploy to package the daemon workspace without requiring injected workspace packages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Align Docker runtime with Node 24

Use Node 24 for both build and runtime stages and update image verification for the workspace daemon dependency layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Remove legacy OD_HOST Docker binding fallback

Use OD_BIND_HOST as the single daemon bind-host setting for Docker deployment and origin validation.

* Update Docker image verifier for daemon dist runtime

Check the packaged daemon dist entrypoint and allow npm from the Node 24 runtime image while still rejecting build-only tools.

* Allow private LAN browser origins for daemon

* Share daemon origin validation helpers

Move browser origin validation into a shared daemon module so tests exercise the production logic and cover the remaining private LAN edge cases.

* Harden Docker Compose port exposure

Bind the Compose deployment to localhost by default and pass the published port through to the daemon origin checks so host-port overrides remain same-origin.

* Keep deployment hosts out of local-only no-origin checks

Require an actual matching Origin before configured deployment origins can satisfy local-only daemon guards, preventing no-Origin remote clients from bypassing those checks.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: mrcfps <mrc@powerformer.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 11:51:51 +08:00
nettee
8930b9650c
feat: Add a toggle to reveal media provider API keys (#867) 2026-05-08 11:46:21 +08:00
Nagendhra Madishetti
665e52b295
fix(daemon): pin OD_DATA_DIR in /api/mcp/install-info env so the macOS-packaged MCP server does not EPERM on .od/projects (#857)
* fix(daemon): pin OD_DATA_DIR in /api/mcp/install-info env so spawned MCP processes do not fall back to .od inside the macOS app bundle

Reporter (#848) ran a packaged Open Design 0.5.0 on macOS and pointed
Antigravity's MCP config at the bundle's daemon-cli.mjs. The MCP
process is launched by the IDE outside the packaged app's environment,
so it does not inherit OD_DATA_DIR. The daemon-cli import path runs
mkdirSync('<cwd>/.od/projects') before dispatching to MCP mode, and
<cwd> resolves to the read-only macOS app bundle, hitting EPERM.

The /api/mcp/install-info endpoint already serializes env into every
client snippet (Cursor, Claude Code, VS Code, Zed, Windsurf,
Antigravity, Codex). Add OD_DATA_DIR: RUNTIME_DATA_DIR to that env
so the snippet pins the daemon's resolved data root, and the spawned
MCP process writes to the same directory the daemon already uses
regardless of how the IDE was launched.

Test added asserts env.OD_DATA_DIR is propagated.

* refactor(daemon): extract buildMcpInstallPayload so the test asserts the production helper, not a fixture mirror

Reviewer flagged that the previous test asserted env.OD_DATA_DIR on a
copy of the handler's payload-construction logic, which would silently
pass if the real handler ever diverged from the fixture. Move the
env / args / buildHint shape into a pure exported helper
(apps/daemon/src/mcp-install-info.ts), wire both server.ts and the
test fixture through it, and drop the inline duplicates.

The test now exercises the same code path that ships, so any
regression in the env block (missing OD_DATA_DIR, wrong format, lost
ELECTRON_RUN_AS_NODE) fails it.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 11:35:23 +08:00
Priyanshu Kayarkar
c55d058024
fix(web): differentiate recent and your designs sorting (#845)
* fix(web): differentiate recent and your designs sorting

* fix(web): remove the immediate return statement from sorting logic

* fix(web): add sorting your design tab by creation time

* fix(web): update card timestamps

* fix(web): align sort keys and timestamps across designs tab views
2026-05-08 11:34:53 +08:00
Nagendhra Madishetti
6de802ba70
feat(daemon): add critique interrupt endpoint + project-keyed run registry (Task 6.1) (#819)
Phase 6.1 of the Critique Theater rollout: a single new endpoint and the
in-process registry that backs it.

POST /api/projects/:projectId/critique/:runId/interrupt cascades an
AbortController to the orchestrator that owns the spawned CLI so the
parser can flush best-so-far state and emit critique.interrupted before
the process exits. Backed by a new in-process run registry that the
orchestrator wiring registers each run into before runOrchestrator is
invoked, and unregisters in a finally block.

The registry is keyed by (projectId, runId), not just runId. A request
to interrupt project p1's runId cannot find or abort a registry handle
that belongs to project p2 even if their ids ever collide. The HTTP
handler also performs its own DB-row projectId check before calling the
registry, so cross-project leakage is blocked at two layers.

The endpoint is idempotent on already-interrupted rows: a client that
lost the first response and retries observes 202 with prevStatus
"interrupted" rather than a 409 conflict. Other terminal statuses
(shipped, failed, timed_out, degraded, below_threshold, legacy) still
return 409 because those runs reached their real terminal state on
their own and an interrupt is no longer meaningful.

Recovery path for stale running rows: when registry.interrupt returns
false (the in-process registry has no AbortController for this
projectId/runId pair) but the DB still says 'running', the endpoint
marks the row 'interrupted' directly with recoveryReason='no_live_handle'
and returns 202 with recovered=true. This window opens after a daemon
restart in the gap before reconcileStaleRuns sees the row old enough.
Without the recovery branch the endpoint would lie: 202 accepted, no
child signaled, no critique.interrupted event, row stuck running. The
new persistence helper markRunInterruptedRecovery mirrors the per-row
write reconcileStaleRuns already does, gated on status='running' so a
row that just transitioned terminal is not overwritten.

Task 6.2 (rerun endpoint) is intentionally not in this PR. The earlier
draft conflated row insertion between the handler and runOrchestrator
(primary key collision) and did not actually start a new agent spawn.
Rerun needs a real chat-run path with prior-art context, an artifact-id
validator, and SQL LIKE escaping that the row lookup path is missing
today; it is cleaner shipped as a follow-up than wedged into this PR.

Tests:
- critique-run-registry: 17 cases covering register, get, interrupt,
  unregister, list, plus the new (projectId, runId) composite key
  invariants (cross-project register, cross-project get/interrupt
  isolation, unregister keying).
- critique-interrupt-endpoint: 17 cases covering 202 happy path, 404 on
  unknown run, 404 on cross-project run, 404 cross-project leak guard at
  the registry layer, 409 on terminal statuses, 202 idempotent retry on
  already-interrupted, stale-handle defense, 202 + recovered on a stale
  running row with no live handle, 400 on bad params.

Incidental: apps/web/src/i18n/locales/id.ts was missing 18 fileViewer
deploy/Cloudflare keys after upstream landed PR #805 (R2 release
publishing). Without those keys the workspace web typecheck fails on
the i18n Dict equality check, blocking CI on every PR. Added Indonesian
translations for the missing keys to unblock.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 11:29:37 +08:00
esadomer
42ae1da03d
Add Turkish README translation (#843) 2026-05-08 11:29:03 +08:00
Terence !_!
e52720aa12
feat(daemon): add language boost support for Minimax TTS (#773)
* feat(daemon): add language boost support for Minimax TTS

Add --language CLI flag to support language boost parameter for Minimax TTS.
This enables better pronunciation for specific languages like Cantonese (Yue).

* docs(media): add --language flag to media generation contract

Document the language boost parameter for Minimax TTS, enabling
better pronunciation for specific languages like Cantonese (Yue).

* fix(media): correct Cantonese language_boost value and add input validation

- Use correct MiniMax value 'Chinese,Yue' for Cantonese (no space)
- Add type guard in server.ts to reject non-string language values
- Trim language string before sending to MiniMax API

---------

Co-authored-by: root <root@DELLN40.asiacredit.org>
2026-05-08 11:26:34 +08:00
Chris
9674f48f2f
fix(postinstall): auto-rebuild better-sqlite3 on Node.js ABI mismatch (#813)
* fix(postinstall): auto-rebuild better-sqlite3 on Node.js ABI mismatch

prebuild-install fetches a prebuilt binary for the Node.js version active
at install time. On systems where the Node ABI differs from Node 24 (e.g.
Arch Linux system Node, Node 22 LTS, Node 25), or after switching versions,
the addon fails to dlopen at daemon startup.

postinstall now tries to load the native addon after the workspace builds.
On failure it locates node-gyp from the pnpm virtual store (bundled with
better-sqlite3) and rebuilds from source — no external tooling beyond a
C++ compiler required. pnpm install becomes self-healing across Node versions.

Also adds a QUICKSTART troubleshooting entry for users with ignore-scripts=true
who need to run `node scripts/postinstall.mjs` manually.

* fix(postinstall): correct better-sqlite3 path and rebuild mechanism

Two bugs in the initial implementation caught in review:

- better-sqlite3 is declared by apps/daemon, not the workspace root.
  node_modules/better-sqlite3 at root does not exist in a normal pnpm
  install, so existsSync() was always false and the check never ran.
  Fix: resolve via createRequire from apps/daemon/package.json.

- better-sqlite3@12.9.0 depends only on bindings and prebuild-install,
  not node-gyp. The assumed sibling path in the pnpm store does not
  exist, so the rebuild branch was hitting the "not found" exit instead
  of rebuilding. Fix: use pnpm --filter @open-design/daemon rebuild
  better-sqlite3 so pnpm manages node-gyp through its own lifecycle.

Also expands the QUICKSTART troubleshooting entry with the manual
rebuild command, a verification step, and build tool prerequisites.

* fix(quickstart): scope better-sqlite3 verification to daemon package
2026-05-08 11:25:26 +08:00
github-actions[bot]
ef4e673ad0
docs(readme): refresh contributors wall (#856)
Co-authored-by: mrcfps <23410977+mrcfps@users.noreply.github.com>
2026-05-08 11:18:54 +08:00
Nagendhra Madishetti
655d561f38
fix(web): show explicit error/retry state when example preview HTML fails to load (#863)
* fix(web): show explicit error/retry state when example preview HTML fails to load

Reporter (#860) saw the example preview modal stuck with the toolbar
buttons greyed out and only restarting the app got back to a usable
state. Lefarcen confirmed the diagnosis: when /api/skills/:id/example
fails, fetchSkillExample returns null, the modal stays at preview.loading
forever, and the share menu's disabled={!activeHtml} guard sits in the
disabled position with no recovery path.

Three changes:

1. fetchSkillExample now returns a discriminated { html } | { error }
   instead of collapsing every failure into null, so callers can tell a
   real fetch failure from a normal load.

2. PreviewView gains an optional error field. When set, PreviewModal
   renders a stacked title/body/Retry affordance instead of the
   indefinite "Loading…" placeholder. Retry re-fires onView so the
   parent can re-run its fetch.

3. ExamplesTab tracks per-skill errors alongside per-skill html, clears
   the in-flight value before each fetch, and wires onView from the
   modal into loadPreview so the Retry button actually retries.

i18n: three new keys (preview.errorTitle, preview.errorBody,
preview.retry), translated across all 17 locales. The locales-aligned
test stays green.

CSS: .ds-modal-error stacks the new content vertically inside the
existing .ds-modal-empty positioning, no other modals affected.

* fix(web): stabilize preview onView and guard parallel preview fetches

Codex caught a real bug in the round-1 fix: the inline
onView={() => loadPreview(...)} prop was recreated on every parent
render, and PreviewModal's mount effect re-fires onView whenever its
identity changes. A persistent fetch failure would update state,
recreate the prop, re-fire the effect, re-run loadPreview, and burn
through the error UI in a flash instead of waiting for a Retry click.

Pin a stable onPreviewView via a useRef-backed callback so the modal
sees a single identity for the lifetime of the panel; loadPreview is
reached through the ref, so its closure refresh on state updates no
longer leaks into the modal's effect dependencies.

While in this surface, also add lefarcen's race guard: a synchronous
inFlightRef Set so two parallel loadPreview calls (e.g. card hover
firing while the modal opens) cannot both pass the cache check before
either setState lands. The first caller adds the id pre-await; the
second sees it and exits early. try/finally clears the entry on both
success and failure paths.

Adds tests/components/preview-modal-error-state.test.tsx covering:
- error UI renders when view.error is set,
- Retry click calls onView with the active view id,
- re-rendering with the same onView identity does not re-fire the
  modal's mount effect (pins the no-auto-retry contract).

* fix(web): close Retry over the active skill id, not the modal-internal view id

mrcfps caught a real regression in round 2: PreviewModal calls
onView(activeId) where activeId is the modal-local view id ('preview'
in this component). The previous round forwarded that argument
straight into loadPreview, so the mount effect and Retry button hit
/api/skills/preview/example instead of /api/skills/{skill-id}/example.
The new error state could not actually recover.

Mirror the active skill id into a ref alongside loadPreviewRef and
have onPreviewView ignore the modal-forwarded argument, fetching the
selected skill via the ref instead. The callback identity stays
stable, so the no-auto-retry contract from round 2 still holds.

Adds tests/components/examples-tab-retry.test.tsx that mounts the
real ExamplesTab, mocks fetchSkillExample to reject, opens the
preview, clicks Retry, and asserts the second call hits the same
skill id (and explicitly never gets called with 'preview').

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 11:16:14 +08:00
kami
2eae7da24b
feat: support Cloudflare Pages custom domains (#851)
* Support Cloudflare Pages custom domains without hiding pages.dev fallback

Keep the default Pages preview as the first public link while optional owned-zone binding provisions DNS and Pages custom-domain state in parallel.

Constraint: Cloudflare deploys must use the existing direct-upload API path with no Wrangler dependency.

Constraint: pages.dev must stay visible even while custom-domain verification is pending.

Rejected: Vercel custom-domain support | outside requested Cloudflare-only scope.

Rejected: overwriting arbitrary CNAME records | risks taking over user-managed DNS.

Confidence: high

Scope-risk: moderate

Directive: Do not expose providerMetadata through public deploy contracts; keep custom-domain DNS ownership checks conservative.

Tested: pnpm --dir apps/daemon exec vitest run -c vitest.config.ts tests/deploy.test.ts tests/deploy-routes.test.ts

Tested: pnpm --filter @open-design/contracts build && pnpm --filter @open-design/contracts typecheck && pnpm --filter @open-design/contracts test

Tested: pnpm --filter @open-design/web typecheck && pnpm --filter @open-design/web test -- providers/registry.test.ts components/FileViewer.test.tsx i18n/locales.test.ts

Tested: pnpm i18n:check && pnpm guard && pnpm typecheck

Tested: pnpm --filter @open-design/daemon build && pnpm --filter @open-design/web build && git diff --check

Not-tested: real Cloudflare account/token/domain smoke test

* Preserve Cloudflare fallback correctness under large accounts and races

Constraint: Cloudflare Pages keeps pages.dev as the primary usable fallback while custom domains remain optional typed metadata.
Rejected: Treating custom-domain DNS or binding failure as a top-level deployment failure | pages.dev can still be ready and usable.
Confidence: high
Scope-risk: moderate
Directive: Keep custom-domain finality tied to Cloudflare Pages API active status plus URL reachability; do not expose providerMetadata.
Tested: pnpm --dir apps/daemon exec vitest run -c vitest.config.ts tests/deploy.test.ts tests/deploy-routes.test.ts; pnpm --filter @open-design/web test -- components/FileViewer.test.tsx i18n/locales.test.ts providers/registry.test.ts; pnpm --filter @open-design/daemon typecheck; pnpm --filter @open-design/web typecheck; pnpm i18n:check; git diff --check; pnpm guard; pnpm typecheck; pnpm --filter @open-design/daemon build; pnpm --filter @open-design/web build
Not-tested: Real Cloudflare token/account/zone smoke test.

* Keep impeccable design notes local

Constraint: .impeccable.md is local assistant/design context and should not be part of the PR diff.
Rejected: Keeping the file tracked while adding it to .gitignore | tracked files are not ignored by Git.
Confidence: high
Scope-risk: narrow
Directive: Keep .impeccable.md untracked and ignored; do not rely on it for required project documentation.
Tested: git check-ignore -v .impeccable.md; git diff --check
Not-tested: Full workspace tests not rerun for ignore-only metadata change.
2026-05-08 11:11:22 +08:00
Nagendhra Madishetti
77824ec029
fix(web): preserve Chat scroll position across Chat/Comments tab switches (#790) (#841)
* fix(web): preserve Chat scroll position across Chat/Comments tab switches (#790)

The chat-log <div> in ChatPane is conditionally rendered (the inner
`{tab === 'chat' ? <>...</> : null}` branch). When the user switches
to Comments and back, the chat-log is unmounted and remounted; the
remounted element starts at scrollTop=0, and the initial-bottom-scroll
effect skips because didInitialScrollRef.current is already true from
the original mount. Result: the conversation view jumps to the top
instead of preserving the user's reading position.

Replaced the empty-deps scroll listener with a tab-keyed effect that:
1. Captures scrollTop in the existing onScroll handler so the saved
   position is always current.
2. On every mount of the chat-log (when tab becomes 'chat'), restores
   the saved scrollTop on the next animation frame so layout finishes
   before the scroll write lands.

The existing scrolledFromBottom signal that drives the jump-to-bottom
button is folded into the same handler and now correctly re-attaches
on every chat-log remount, fixing a secondary issue where that listener
would silently stop firing after a tab toggle.

* fix(web): preserve bottom-pinned chat across off-tab streaming and snapshot on unmount

Round 1 saved an absolute scrollTop, so a user who left Chat while
pinned to the bottom came back above any new messages that streamed
in while Comments was open. Save a discriminated state instead:
{ pinnedToBottom: true } when the user was within 50px of the bottom,
otherwise { scrollTop }. On remount, pinned state snaps to the new
scrollHeight so bottom-followers stay pinned; non-pinned state
restores the absolute offset.

Also snapshot the final scroll state in the effect cleanup before
removing the listener, so programmatic scrolls or layout shifts
right before unmount don't leave the ref stale.

Adds tests/components/chat-scroll-preservation.test.tsx covering
both branches.

* fix(web): clear saved chat scroll state on conversation switch

The savedChatScrollRef persisted across conversation changes, so
switching to Comments while on conversation A and then switching
to conversation B would, on returning to Chat, restore A's
scrollTop instead of starting fresh at the bottom.

Reset the ref alongside didInitialScrollRef when activeConversationId
changes. Added a third test covering the cross-conversation case.

* fix(web): scroll new conversation to its bottom when conv switch happened off-tab

When activeConversationId changed while the user was on the Comments
tab, the conversation-reset effect cleared didInitialScrollRef and
the saved scroll ref, but the initial-bottom-scroll effect couldn't
do anything because logRef.current was null. Returning to Chat then
left the new conversation at scrollTop: 0 instead of its initial
bottom.

Add `tab` to the initial-scroll deps so the effect re-runs when the
chat-log remounts, picks up the cleared didInitialScrollRef state,
and scrolls the fresh conversation to its scrollHeight.

Updated the cross-conversation test to assert the new conversation
lands at its bottom (1000), not at scrollTop: 0.

* fix(web): resync jump-to-latest button when restoring saved chat scroll position

The rAF restore branch wrote scrollTop but never refreshed
scrolledFromBottom, so a user who left Chat ~60px from the bottom
and returned to find new messages stacked underneath would land
hundreds of pixels above the latest turn while the jump-to-latest
button stayed hidden until they manually scrolled.

Recompute the distance and update scrolledFromBottom inside the
restore rAF, mirroring what onScroll already does. Adds a test that
asserts the jump-to-latest button is visible immediately after a
non-pinned restore over a grown scrollHeight.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 11:10:56 +08:00
fyz3120
11225b2d7e
fix(desktop): cleanly quit macOS packaged app (#422)
Co-authored-by: Fu Yizheng <fyz3120@sina.cn>
2026-05-08 11:10:14 +08:00
Nagendhra Madishetti
604d3660f2
fix(web): reserve clearance for MCP Copy button so it stops overlapping the snippet (#847)
* fix(web): reserve clearance for the MCP Copy button so it stops overlapping the snippet

The Copy button is absolutely positioned at top: 8 right: 8 over the
snippet <pre>, but the <pre> only had padding: 12px 14px so the first
line of the command sat directly under the button. Wrapped bash one-
liners also reached the right edge and continued behind it.

Reserve the clearance in the <pre>'s own padding instead of moving
the button: padding: '40px 80px 12px 14px' keeps the button anchored
where it is, lets the first line render below it, and stops a wrapped
one-liner short of the button column.

Closes #632

* fix(web): bump MCP snippet right padding to clear the wider Copied state

Reviewer pointed out 80px right clearance can be tight at elevated
font sizes / zoom: the post-click Copied state (icon + text + button
padding + 8px right offset) reaches close to that limit. Bump to
104px so there's a comfortable buffer in either button state.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 11:07:25 +08:00
Tom Huang
6e473a4f77
feat(skills): teach hyperframes skill the HTML-in-Canvas API (#852)
* feat(skills): teach hyperframes skill the HTML-in-Canvas API

Vendored skill predates upstream v0.5.1, which added the drawElementImage
HTML-in-Canvas guide and the vfx-iphone-device / vfx-liquid-glass /
vfx-portal catalog blocks. Without that reference, agents asked to build
'live HTML on a 3D phone screen' compositions had no idea the API
existed and produced renders where the device screen was blank or static.

- Add references/html-in-canvas.md adapted from the upstream guide,
  with an OD-specific note about render-loop re-capture (the most
  common reason output 'looks dead' inside a generated composition).
- Cross-link the new reference from SKILL.md and add triggers for
  'html in canvas', 'drawElementImage', 'html shader', and the two
  most-requested vfx blocks.

Daemon render path is unchanged: 'npx hyperframes render' already
auto-enables --enable-features=CanvasDrawElement, and we always pull
the latest published hyperframes via npx, so no version pinning
needs to move.

* docs(skills): wait for canvas paint in hyperframes HTML-in-Canvas examples

The drawElementImage API only refreshes its element snapshot when the
canvas paints. Calling it during initial script evaluation can throw
because no snapshot exists yet, and calling it later from outside a
paint event silently reads the previous snapshot. On HyperFrames'
seek-driven renders that surfaces as a failed or stale first texture.

- Drive the basic capture example from canvas.onpaint and kick it off
  with requestPaint() instead of calling drawElementImage at script
  eval time.
- Rewrite the per-frame re-capture pattern to put drawElementImage
  inside onpaint and call requestPaint() from the render loop, so
  each frame sees a fresh snapshot rather than the previous one.
- Add a callout explaining the paint-event requirement so agents do
  not regress to the script-eval-time pattern.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

* docs(skills): add vfx-portal trigger to hyperframes skill

The hyperframes skill's reference callout already names vfx-portal
alongside vfx-iphone-device and vfx-liquid-glass as effects that
should auto-load references/html-in-canvas.md, but the triggers list
only includes the other two. A prompt like "make a vfx-portal clip"
therefore misses the HTML-in-Canvas guidance the new reference adds.

- Add "vfx-portal" to triggers so the trigger surface matches the
  documented entry points.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)
2026-05-08 11:07:15 +08:00
shangxinyu1
32df17b87b
Fix desktop preview interactions and connector auth feedback (#864)
* Fix desktop preview modal interactions

* Fix connector auth failures surfacing
2026-05-08 11:05:41 +08:00
github-actions[bot]
915c041545
Update docs/assets/github-metrics.svg - [Skip GitHub Action] (#853)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-08 11:03:44 +08:00
emilneander
959bfaa817
fix(daemon): make MCP install snippet survive daemon port changes (#846)
* fix(daemon): make MCP install snippet survive daemon port changes

`od mcp` now discovers the live daemon URL via the sidecar IPC
status socket on every spawn, so the Settings -> MCP server snippet
no longer bakes in `--daemon-url <port>`. Pasted client configs
stay valid across daemon restarts even when the daemon binds an
ephemeral port (tools-dev, packaged). Resolution order is
--daemon-url > OD_DAEMON_URL > IPC discovery > http://127.0.0.1:7456
so explicit overrides still win for direct `od` launches.

* fix(daemon): MCP snippet works in non-default namespaces and direct launches

Propagate OD_SIDECAR_NAMESPACE / OD_SIDECAR_IPC_BASE into the snippet
env so non-default namespace daemons stay reachable; the spawned MCP
client does not inherit the daemon's env, so without this it would
probe the default-namespace socket and miss. Restore --daemon-url in
the snippet for direct `od --port X` launches that have no IPC
socket. Reword `od mcp --help` so it does not imply live URL
tracking; each new spawn rediscovers, but a running MCP server
caches the URL until the client restarts.
2026-05-08 10:59:09 +08:00
Nagendhra Madishetti
fa63278b84
fix(web): give MCP server Copy button a solid surface so it reads against the code block (#742) (#840)
* fix(web): give MCP server Copy button a solid surface so it reads against the code block (#742)

The Copy button in the MCP server section is positioned absolute over a
syntax-highlighted <pre> code block. button.ghost's default
background: transparent let the dark code surface bleed through, so on
some themes the button rendered nearly invisible against the snippet
backdrop. Users could miss the primary copy affordance entirely.

Pinned background: var(--bg-panel), an explicit border, and a small
shadow to the inline style so the button floats as a visible chip
above the code block in both light and dark themes. Hover/disabled
behavior remains delegated to the existing .ghost class rules so the
visual contract elsewhere in the app stays unchanged.

* fix(web): move MCP Copy button surface to a CSS class so hover still works

Previous round set background and border inline on the button, which
overrode button.ghost:hover:not(:disabled) from index.css and silently
killed the hover state change. Move the solid panel background, border,
and shadow into a scoped .mcp-copy-btn class with its own
:hover:not(:disabled) rule, and keep only positioning inline.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 10:58:56 +08:00
Nagendhra Madishetti
6b117913b9
fix(web): truncate long inspect-panel labels so they cannot spill past the panel edge (#780) (#838)
* fix(web): truncate long inspect-panel labels so they cannot spill past the panel edge (#780)

The picker's inspect panel renders the selected component's label as a
<strong> inside .inspect-panel-title. The grid container had min-width: 0
so it could shrink, but the inner <strong> rule only set font-size with
no overflow constraints. A deeply-nested component with a long generated
selector path produced a label longer than the 296px panel width, and
the text spilled out past the panel's right edge instead of clipping
inside the title's background frame.

Added white-space: nowrap + overflow: hidden + text-overflow: ellipsis
on .inspect-panel-title strong so the label truncates within the panel
boundary. The full string remains accessible to users via the title
attribute already present on the sibling <code> element that renders
the same selector context.

* fix(web): expose full inspect-panel label via title attribute on truncated <strong>

Reviewer flagged the comment claiming the full label was accessible
via the sibling <code>'s title — but that <code> carries
target.selector, not target.label. Add title={target.label || target.elementId}
to the <strong> itself so the truncated label is recoverable on hover,
and align the CSS comment.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 10:58:41 +08:00
Nagendhra Madishetti
b3259f5baa
fix(web): keep Design Files tab visible when workspace tabs scroll (#842)
When many tabs open, the tab strip scrolls horizontally and the
Design Files entry slides off the left edge, leaving no obvious
way back to it.

Pin the Design Files button with position: sticky and a small
shadow so it stays anchored at the left while the rest of the
strip scrolls behind it.

Closes #775

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 10:57:44 +08:00
Nagendhra Madishetti
42bcfb6561
fix(web): keep inspect-panel close button on a stable single-line layout (#785) (#839)
The .inspect-panel-head row laid out a flexible title block next to the
Close button with display: flex and gap: 10px, but no shrink ceiling on
the button. When the selected component had a long generated selector,
the title block consumed almost all available width and the button
shrank below its natural glyph width. On some font/zoom combinations the
single-character label rendered stacked vertically rather than as a
normal horizontal control.

Pinned flex-shrink: 0 on .inspect-panel-head > button so the close
control reserves its natural size regardless of how much the title
expands. The button stays on a single horizontal line for any selector
length the panel can render.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 10:57:20 +08:00
Nagendhra Madishetti
8bb9900603
fix(web): scope settings save validation + sanitize payload to active sidebar section (#739) (#827)
The footer Save button's enabled state was computed purely from execution-mode
completeness (BYOK requires apiKey + model + valid baseUrl; Local CLI requires
a selected available agent). That check ran regardless of which sidebar section
the user was on, so a draft mode toggle on the execution section that left
required fields empty would lock the Save button across every other section.

After clicking BYOK without filling fields and navigating to Language or
Appearance, the user could not save unrelated changes in those sections even
though they had nothing to do with execution mode.

Two paired helpers in apps/web/src/components/SettingsDialog.tsx address this:

shouldEnableSettingsSave(cfg, activeSection, agents, isBaseUrlValid) returns
true on any section other than 'execution' so unrelated sections do not get
blocked by an incomplete execution draft. On 'execution' it keeps the
original mode-completeness check unchanged (within-section invariant).

sanitizeSettingsSavePayload(cfg, initial, activeSection, agents,
isBaseUrlValid) is the counterpart used at the onSave call site. When Save
is enabled on a non-execution section but the user's draft execution config
is incomplete, it reverts the execution-mode fields (mode, apiKey,
apiProtocol, apiVersion, apiProtocolConfigs, apiProviderBaseUrl, baseUrl,
model, agentId, agentCliEnv, maxTokens) to their `initial` values so the
unrelated section change is committed without leaving the app in a broken
execution state. Within the execution section, or when execution is already
valid, the cfg passes through unchanged.

Both lefarcen and chatgpt-codex flagged this persistence gap on the first
revision of this PR; mrcfps marked it blocking. The sanitize helper is the
fix lefarcen suggested (revert-to-initial when the active section is not
execution and the execution draft is incomplete).

Tests in apps/web/tests/components/SettingsDialog.test.ts:
- shouldEnableSettingsSave: 4 cases (the cross-section fix, daemon mode
  validity, api mode validity, regression guard for within-execution).
- sanitizeSettingsSavePayload: 5 cases (revert path, no-op when execution
  is valid, no-op on the execution section itself, every non-execution
  section covered, edge case where the agent registry says unavailable but
  initial cfg was already valid daemon).

Local: web tests 33/33, web typecheck and pnpm guard all clean.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 10:57:12 +08:00
Jie Zhu
57d49ed798
fix(web): increase project meta line-height to prevent descender clipping (#834)
* fix(web): increase project meta line-height to prevent descender clipping

The project type label (e.g. 'blog-post · Neutral Modern') had its
descender characters (g, p, y) clipped by the header bottom border.
Increasing line-height from 12px to 15px gives 11.5px font enough
vertical space for lowercase descenders to render without being cut off.

* fix web: bump project meta line-height and parent max-height to fit descenders

.app-project-title .meta had line-height: 12px on font-size: 11.5px,
leaving only 0.5px of vertical breathing room. Descender glyphs (g, p,
q, y, j) extend below the baseline and were clipped by overflow: hidden.

Bump .meta line-height from 12px to 15px and .app-project-title
max-height from 31px to 32px so the full 16px + 1px gap + 15px = 32px
budget fits without clipping.

Refs: https://github.com/nexu-io/open-design/pull/834
2026-05-08 10:57:01 +08:00
Tom Huang
56bf6ee1b6
feat: agent-callable research command and /search (#615)
* feat: pre-generation research (Tavily) for grounded generation

Adds an optional pre-generation research step so the agent can produce
slides / prototypes / decks grounded in real sources instead of guessing.

User flow:
  1. Settings -> Tavily Search -> paste API key (or set TAVILY_API_KEY).
  2. Click the new Research button in the chat composer.
  3. On send, the daemon runs a Tavily search, prepends the findings
     as a <research_context> block ahead of the system prompt, and
     spawns the agent. Research progress shows up as status pills in
     the chat stream; the agent cites sources inline as [1]/[2]/...

Phase 1 surface:
  - Single provider (Tavily), single depth ('shallow'), no LLM
    synthesis pass (Tavily's `answer` is the summary).
  - Composer toggle only; no popover / depth picker yet.
  - Reuses the existing `status` SSE agent payload + StatusPill UI
    so no new event variants or renderer code are needed.

Layers touched:
  - contracts: ResearchOptions / Source / Findings DTOs;
    ChatRequest.research; export from index.
  - daemon: apps/daemon/src/research/{index,tavily}.ts orchestrator
    + provider; tavily added to MEDIA_PROVIDERS and ENV_KEYS; hook
    in startChatRun before prompt assembly.
  - web: ChatComposer toggle + ChatSendMeta; threaded through
    ChatPane / ProjectView / streamViaDaemon into ChatRequest.

Side fix (required to land the feature, but useful on its own):
  contracts internal relative imports lacked the `.js` suffix that
  NodeNext module resolution requires. This was already breaking
  `pnpm --filter @open-design/daemon typecheck` on main; without the
  fix, none of the new research types were visible to the daemon.
  All internal contracts imports now carry `.js`.

Spec: specs/current/research-feature.md (phases 2-4 outlined for
follow-up: composer popover, multi-provider, deep recursion, example
skills with research_recommends).

Verified:
  - pnpm --filter @open-design/contracts typecheck/test
  - pnpm --filter @open-design/daemon typecheck (the chokidar
    project-watchers test is a pre-existing flake, unrelated)
  - pnpm --filter @open-design/web typecheck
  - node scripts/verify-media-models.mjs

* fix(daemon): clamp Tavily max_results to 20

Tavily's /search endpoint requires `max_results` in [0, 20]; sending a
larger value (e.g. when `research.depth: "deep"` resolves to 30) returns
400 and `runResearch` silently falls back to no-research. Clamp at the
provider boundary so Phase 2 depth tiers above 20 still produce results
instead of failing the request.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

* Remove stale research merge leftovers

* Add agent-callable research search

* Fix Indonesian locale typecheck

* Fix research command invocation edge cases

* Harden slash search prompt expansion

* Honor research source caps in command contract

* Require search reports in design files

* Add research data provider settings

* Wire web research provider fallback order

* Update research provider fallback wording

* Revert "Update research provider fallback wording"

This reverts commit 86fb6001e3.

* Revert "Wire web research provider fallback order"

This reverts commit 4c9e16036b.

* Revert "Add research data provider settings"

This reverts commit 23630d1746.

* Add Dexter and Last30Days research skills

* Add DCF and Last30Days OD skills

* Add Last30Days and Dexter skills

* Resolve research review threads

---------

Co-authored-by: a1chzt <chizblank@gmail.com>
2026-05-08 10:33:44 +08:00
shangxinyu1
7107623ee2
test: expand entry and settings automation coverage (#811)
* test: harden new project panel metadata coverage

* test: add settings and connector sync coverage

* test: expand entry e2e coverage

* test: satisfy exact optional property types in entry connector flow

* test: keep entry Playwright coverage under e2e/ui

* test: tighten coverage docs and settings test cleanup

* test: drop e2e docs from the guarded package

* docs: move automation coverage docs out of e2e

* test: restore clipboard cleanup without delete

* test: match composio save dialog behavior

* test: avoid placeholder assertion after composio save

* test: expect closeModal on settings saves

* test: align settings save assertions with closeModal flags

* test: fix settings save mocks

* test: align composio replacement hint
2026-05-08 09:30:16 +08:00
lefarcen
2bb029cb58
release: Open Design 0.5.0 (#820)
0.5.0 已从 c21cbc6 发布(https://github.com/nexu-io/open-design/releases/tag/open-design-v0.5.0);本次 squash 把版本 bump 与 CHANGELOG [0.5.0] 条目带到 main 历史,便于后续 0.5.1 release 在 main 上走标准 dispatch 流程。
2026-05-08 00:41:01 +08:00
nmsn
31c3ceac53
fix: prevent comment popover header overflow when label is too long (#833)
- Add min-width: 0 and overflow: hidden to comment-popover-head div
- Add text-overflow: ellipsis and white-space: nowrap to strong and span
- Add flex: 0 0 auto to close button to keep it fixed width
- Add title attribute to header div and close button for hover tooltip
2026-05-07 23:44:35 +08:00
Nagendhra Madishetti
9c64ef1b2b
fix(web): wrap long note text inside picker/comment popover so it cannot push the layout sideways (#782) (#830)
The .board-note-item flex container holds a span (note text) and a
Remove button. The span had no width hints, so an unbroken long string
(URL, hash, base64, etc.) tried to fit on one line and pushed the row
wider than the 320px popover, distorting the overlay's right edge and
the surrounding picker UI.

Added flex: 1 + min-width: 0 + overflow-wrap: anywhere to the note
span. flex: 1 lets the span take remaining width next to the Remove
button; min-width: 0 lifts the default flex-item min-content floor so
shrinking actually works; overflow-wrap: anywhere allows the long
string to break at any character when natural word boundaries aren't
enough. No layout change for normal-length notes.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-07 22:35:14 +08:00