mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
409 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
79554198ec |
fix: clear stale mention state when @ button insertion is invalid
When the @ button is clicked at a position where the regex does not match (e.g., immediately after a word or on a second click after the button just inserted @), the handler now explicitly clears any existing mention state instead of leaving it intact. This prevents the popover from remaining bound to a previous cursor position, which would cause picking a file to replace the old mention location and leave the newly inserted @ behind. Mirrors the handleChange behavior which already clears mention when the regex stops matching. |
||
|
|
7b64564b5c |
fix: add @ button to composer for consistent mention picker access
Both typing @ in the textarea and clicking the @ button now open the same mention popover with the same file listing, using the same mention-detection logic. Changes: - Added a dedicated @ button in the composer-tools-wrap, before the sliders/CLI-tools trigger - Clicking @ inserts '@' at cursor and opens MentionPopover with the full project file list — identical to the typed @ path - Added i18n keys chat.mentionButtonTitle and chat.mentionButtonAria with en and zh-CN translations Closes #3174 |
||
|
|
32d820e4ee
|
fix(daemon): typecheck leaf modules (#943)
* update drift * fix(daemon): typecheck leaf modules * fix(daemon): decode Qoder stdout buffers Generated-By: looper 0.5.6 (runner=fixer, agent=opencode) |
||
|
|
4f647f56ba
|
[codex] Optimize Composio connector previews (#907)
* Optimize Composio connector previews * Fix partial connector tool preview hydration * Cancel pending connector authorization on daemon * Preserve Composio cached tool counts * Avoid pending state after OAuth launch failure * Preserve static tool count fallback * Fix connector preview retry state * Remove Composio auth config metrics * Hydrate unknown connector tool previews * Fix remaining connector review threads * Stop failed connector preview spinner * Hydrate only targeted agent connectors |
||
|
|
e13adf2e63
|
feat(daemon): finalize design package endpoint (closes #450) (#832)
* feat(daemon): scaffold /api/projects/:id/finalize/anthropic (refs #450) Phase C of the PR 2 plan for issue #450: scaffold the route + module shape so subsequent phases (D-I) land function bodies and tests against a stable surface that already passes typecheck. What lands here: - apps/daemon/src/finalize-design.ts: module-level constants (DEFAULT_BASE_URL, DEFAULT_MAX_TOKENS=16000, INPUT_BODY_CAP_BYTES=384KiB, LOCK_FILENAME=.finalize.lock, OUTPUT_FILENAME=DESIGN.md, DEFAULT_TIMEOUT_MS=120s); inline interfaces for the request/response shape (kept out of packages/contracts per scope rules); two error classes - FinalizePackageLockedError (mirrors PR #493's TranscriptExportLockedError) and FinalizeUpstreamError (carries upstream HTTP status for the route's error mapping); function stub that throws "not yet implemented". - apps/daemon/tests/finalize-design.test.ts: vitest harness with describe.skip placeholder so the file imports cleanly. Real cases land in phases D-I. Default-import of node:fs (per memory: vi.spyOn cannot redefine on the frozen ESM Module Namespace; CJS exports object is mutable). - apps/daemon/src/server.ts: route handler at POST /api/projects/:id/finalize/anthropic, slotted next to the existing :id/deploy* family. Validates apiKey/model non-empty, optional baseUrl via the existing validateExternalApiBaseUrl closure (forbidden -> 403, invalid -> 400), optional maxTokens positive number; calls getProject (404 on miss); calls finalizeDesignPackage (which throws, caught and mapped to 500 for now); maps known error classes (FinalizePackageLockedError -> 409, FinalizeUpstreamError -> 502) pre-emptively. Path shape rationale (Bryan-confirmed): project-scoped path matches every sibling /api/projects/:id/* route in server.ts (deploy, deployments, deploy/preflight); provider-namespaced segment leaves a clean expansion line for /api/projects/:id/finalize/openai etc. as follow-ups. Field-name rationale: apiKey, baseUrl, model, maxTokens match ProxyStreamRequest verbatim (packages/contracts/src/api/proxy.ts:8-19) so a future caller can reuse the same body shape. baseUrl is optional here (intentional divergence from the proxy at server.ts which requires it) so standard Anthropic users do not need to set it; Bedrock / self-hosted-proxy users still can. Verification: pnpm --filter @open-design/daemon typecheck exits 0; finalize-design.test.ts loads cleanly with 1 skipped placeholder; no other tests touched. Refs nexu-io/open-design#450 (PR 2 scaffold; pipeline body in subsequent commits) * feat(daemon): transcript truncation helper for /finalize prompt Phase D of the PR 2 plan for issue #450: lands the helper that bounds the transcript section of the synthesis prompt. Why this exists: real-world signal at authoring time was a local project transcript already at 3.95 MB. Anthropic's claude-opus-4-7 context cap is roughly 200K tokens (~700 KB at typical density). Inserting an unbounded transcript would 4xx upstream on the first real call. This helper keeps the on-disk .transcript.jsonl lossless (PR #493's contract) while making the prompt-inclusion bounded. Strategy: - Cap output at INPUT_BODY_CAP_BYTES (384 KiB) so the prompt has room for the system prompt + design system body + current artifact + room for the synthesis output. - Always preserve the header line - it carries projectId, schemaVersion, conversation/message counts, attachment counts; synthesis quality depends on knowing the original sizes. - Split equal byte budgets between head and tail so both project genesis and most-recent intent survive. Two thinking segments separated only by mid-session truncation lose the same kind of boundary that PR #493 preserves between thinking blocks - that's accepted; smarter semantic chunking is a follow-up. - Insert a single `{"kind":"truncated","reason":"size","omittedBytes":N}` sentinel JSON line between the head and tail so a synthesis consumer can detect the gap. omittedBytes is the difference between the original UTF-8 byte length and the output's UTF-8 byte length. - If the head + tail budgets together cover the whole body (e.g. all message lines are tiny), no marker is emitted - the output is the input verbatim. Tests: - "returns the input verbatim when the JSONL fits under the 384 KiB cap" pins that small transcripts pass through unchanged with no marker. - "head+tail truncates with a single marker line when the JSONL exceeds the 384 KiB cap" pins that output is bounded, header survives, exactly one marker emitted with non-zero omittedBytes, both ends of the body preserved, and at least one middle message omitted. Suite delta: +2 tests in finalize-design.test.ts. Refs nexu-io/open-design#450 * fix(daemon): resolve noUncheckedIndexedAccess in truncateTranscriptForPrompt D1 (0eaa123) shipped with `body[headIndex]` and `body[i]` typed as `string | undefined` under TypeScript's `noUncheckedIndexedAccess` strict mode. Local typecheck would have caught it but the prior verification piped through `tail` which masked the non-zero exit code of `tsc`. Coalesce each access via `?? ''` (the array is from `String.split('\n')` so undefined elements are not actually reachable; the coalesce is a type-narrowing convenience, not a behavior change). Verification: `pnpm --filter @open-design/daemon typecheck` exits 0; `pnpm --filter @open-design/daemon test finalize-design` shows 2/2 + 1 skipped, identical to the pre-fix run. Refs nexu-io/open-design#450 * feat(daemon): current-artifact resolver for /finalize Phase E of the PR 2 plan for issue #450: resolves which artifact (if any) accompanies the transcript + design system in the synthesis prompt. Priority order (Bryan-locked in plan §6): 1. The file referenced by tabs.is_active = 1 IF an <name>.artifact.json sidecar exists on disk. Sidecar presence is the discriminator: an inferred manifest from `inferLegacyManifest` (e.g. for a bare .html with no sidecar) does NOT count, and an active tab pointing at a non-artifact file (.md, .txt) falls through. 2. Newest project file with a real .artifact.json sidecar, sorted by manifest.updatedAt descending. Files without an updatedAt sort last so legacy pre-streaming manifests do not get accidentally promoted. 3. Returns null - "no artifact in scope". The Phase H caller will emit `artifact: null` in the response and the prompt's "Current artifact" section will read "none". Sidecar presence is checked via `existsSync` on the on-disk path, NOT via the `artifactManifest` field returned by readProjectFile/listFiles (those run inferLegacyManifest as a fallback for known kinds, which would otherwise cause a bare .html with no sidecar to look like an artifact). Tests: - "returns the active-tab artifact when its sidecar is present, even if a newer artifact exists elsewhere": pinned.html (older updatedAt) is in the active tab; newer.html (newer updatedAt) is not. Resolver returns pinned.html - intent (active tab) beats recency. - "falls through to newest .artifact.json when active tab points at a non-artifact file": README.md is the active tab (no sidecar); design.html has a real sidecar. Resolver falls through and returns design.html. - "returns null when no active tab and no .artifact.json sidecars exist": only a README.md is in the project; no tabs row. Resolver returns null. Suite delta: +3 tests in finalize-design.test.ts (5 active total). Refs nexu-io/open-design#450 * feat(daemon): synthesis prompt construction for /finalize Phase F of the PR 2 plan for issue #450: builds the system + user prompts that get sent to Anthropic's Messages API in the synthesis call. Pure function; no IO, no side effects. System prompt (literal, stored as a module-level constant): instructs Claude to emit a DESIGN.md document with a fixed 7-heading structure (# DESIGN.md / ## Summary / ## Brand & Voice / ## Information Architecture / ## Components & Patterns / ## Visual System / ## Open Questions / ## Provenance). The Provenance section is required to list project ID, design system, current artifact, transcript message count, and the UTC generation timestamp. User prompt (built at runtime): structured payload with the truncated transcript JSONL, the design system body, and the current artifact body, each under a ## heading. Missing inputs (no design system selected, no artifact in scope) produce explicit "none" headings + parenthetical placeholder body so Claude does not hallucinate content for absent sections. Truncation is the caller's concern - this function does not re-truncate. The caller (Phase H pipeline) feeds in a JSONL that has already been bounded by truncateTranscriptForPrompt. Tests: - "includes the transcript JSONL verbatim and the generation context": pins all section headings, the transcript body verbatim, the design system body verbatim, the artifact body verbatim, and every generation-context line. - "falls back to \"none\" + parenthetical when no design system is selected": designSystemId=null and designSystemBody=null -> heading reads "## Active design system: none" with the parenthetical body. - "falls back to \"none\" + parenthetical when no artifact is in scope": artifact=null -> heading reads "## Current artifact: none" with the parenthetical body. Suite delta: +3 tests in finalize-design.test.ts (8 active total). Refs nexu-io/open-design#450 * feat(daemon): Anthropic call + retry strategy for /finalize Phase G of the PR 2 plan for issue #450: lands the upstream Claude Messages API call with a single transient-error retry, plus the response extractor that turns Anthropic's content array into the DESIGN.md body. What lands here: - appendVersionedApiPath: inlined from the connectionTest helper at apps/daemon/src/connectionTest.ts:188-195 (it is not exported there). Appends /v1/messages when the base URL has no /vN segment, otherwise appends /messages directly. Same semantics; ~5 lines. - callAnthropicWithRetry: POSTs to <base>/v1/messages with the canonical Anthropic headers (content-type, x-api-key, anthropic-version: 2023-06-01) and body shape ({ model, max_tokens, system, messages, stream:false }). One retry on transient (HTTP 429 or 5xx); on terminal failure throws FinalizeUpstreamError carrying the upstream HTTP status and raw body text. The route handler in Phase I maps status to AUTH_FAILED / RATE_LIMITED / UPSTREAM_FAILED and runs the body through redactSecrets before exposing it as `details`. - extractDesignMd: concatenates content[].text for every block where type === 'text', preserving order. Throws FinalizeUpstreamError(502) on three malformed-response shapes: non-object payload, missing content array, zero text blocks. The route handler maps the throw to 502 UPSTREAM_FAILED so synthesis cannot land a half-empty DESIGN.md on disk. - Test-only `_sleepMs` injection on the call params so the retry-delay sleep is instant under vitest. Default sleep uses setTimeout. Retry posture (1 retry on transient) is opinionated; the maintainer's "standard exponential backoff" answer was directional and a single retry matches the existing daemon's posture (transcript export and connectionTest do zero retries) while staying inside the daemon's blocking-fast posture for /finalize. Tests: - callAnthropicWithRetry: throws on 401 with no retry; retries once on 429 and resolves on second 200; throws after both 5xx attempts; propagates AbortError when signal is pre-aborted. - extractDesignMd: concatenates ordered text blocks; throws on missing content array; throws on content with zero text blocks. A spurious typecheck error from `exactOptionalPropertyTypes` (signal typed as AbortSignal | undefined where RequestInit expects AbortSignal | null) was resolved by conditionally spreading signal into the RequestInit literal. Suite delta: +7 tests in finalize-design.test.ts (15 active total). Refs nexu-io/open-design#450 * feat(daemon): wire /finalize pipeline end-to-end Phase H of the PR 2 plan for issue #450: stitches together every phase D-G primitive into the full finalizeDesignPackage pipeline that the route handler in Phase I will expose over HTTP. Pipeline (in execution order, all inside a try/finally that always releases the lockfile): 1. getProject(db, projectId): defensive 404 (the route validates first; this throw catches direct CLI/script callers). 2. mkdirSync(<projectDir>, { recursive: true }): some projects have DB rows but no on-disk dir yet (PR #493's same fix). 3. fs.openSync(.finalize.lock, 'wx'): EEXIST -> FinalizePackageLockedError (mirror PR #493's TranscriptExportLockedError). 4. exportProjectTranscript(db, projectsRoot, projectId, { now }): produces .transcript.jsonl on disk; we read the body and run it through truncateTranscriptForPrompt to bound the prompt-inclusion size. 5. readDesignSystem(designSystemsRoot, designSystemId): returns null when the project has no design_system_id selected, when the design system directory does not exist, or when the DESIGN.md file is missing. 6. resolveCurrentArtifact(db, projectsRoot, projectId): active tab -> newest .artifact.json by manifest.updatedAt -> null. 7. buildSynthesisPrompt({...}): system + user prompt (per Phase F). 8. callAnthropicWithRetry({...}): one retry on 429/5xx; throws FinalizeUpstreamError on terminal failure. 9. extractDesignMd(payload): concatenates content[].text blocks; throws FinalizeUpstreamError(502) on malformed shape. 10. Atomic write: writeFileSync({flag:'wx'}) -> reopen for fsync -> rename. Errors unlink tmp before rethrowing. 11. Lock release in finally (always closeSync + unlinkSync). Bounded blocking: the function uses its own AbortController + 120s timeout when the caller does not supply a signal. Caller-supplied signal takes precedence. Type tightening: switched the local Db interface to `type Db = Database.Database` (better-sqlite3) so the function signature is compatible with `exportProjectTranscript`'s typed parameter. Source file already had a `better-sqlite3` import in claude-design-import area of the daemon, so no new dependency. Tests: - "writes DESIGN.md atomically on the happy path": end-to-end with seeded project + conversation + 2 messages + design system on disk; asserts file at exact path + body bytes match the fetch mock. - "response carries every documented field with correct types": designMdPath/bytesWritten/model/inputTokens/outputTokens/artifact/ transcriptMessageCount/designSystemId all present and typed. - "emits design system 'none' in the prompt when no design_system_id is set": fetch mock asserts on the body it receives. - "throws FinalizePackageLockedError when .finalize.lock is already held": pre-create lockfile; assert throw + DESIGN.md not written + pre-existing lock NOT unlinked (we did not own it). - "replaces an existing DESIGN.md atomically on a second finalize": inject a sentinel between two finalize calls; assert sentinel is gone after second run. - "cleans up tmp file AND lock file on every error path": mock fs.writeFileSync to throw on the tmp path; assert no DESIGN.md.tmp.* remain, no DESIGN.md, no .finalize.lock. - "uses the default https://api.anthropic.com baseUrl when baseUrl is omitted": fetch URL begins with the default; baseUrl=undefined path. vi.restoreAllMocks() now runs in afterEach so the writeFileSync spy from the cleanup test does not leak into subsequent tests. Suite delta: +7 tests in finalize-design.test.ts (22 active total). Refs nexu-io/open-design#450 * feat(daemon): /finalize HTTP route handler + error mapping Phase I of the PR 2 plan for issue #450: replaces the Phase C stub's catch-all 500 with status-aware error mapping that surfaces the right HTTP status + error code for each documented failure mode, and adds HTTP-layer tests that boot startServer to exercise the route's validation branches. Route handler changes: - :id format guard: an inline regex matching isSafeId at apps/daemon/src/projects.ts:556-558 rejects unsafe ids with 400 BAD_REQUEST before any DB or filesystem work. Without this, an id like 'bad!id' would either fail getProject as 404 (wrong code) or reach the function and throw 'invalid project id' (mapped to 500). - FinalizeUpstreamError mapping is now status-aware: - upstream 401 -> 401 AUTH_FAILED - upstream 429 -> 429 RATE_LIMITED - upstream 5xx (or our own 502 sentinel for malformed responses) -> 502 UPSTREAM_FAILED In all cases the upstream raw text is run through redactSecrets so the apiKey cannot leak through `details` even if the upstream echoes the inbound headers. - AbortError mapping: when the 120s AbortController fires (or the caller pre-aborted the signal), surface as 503 TIMEOUT. - Default case: console.error the error per daemon convention; client sees 500 INTERNAL with the message routed through redactSecrets. - Imported redactSecrets alongside the existing connectionTest imports (apps/daemon/src/server.ts:51). HTTP-layer tests (boot startServer({port:0,returnServer:true}) once in beforeAll, mirror the proxy-routes.test.ts pattern): - "400 BAD_REQUEST when baseUrl is not a valid URL (test #13)": baseUrl='not-a-url'. - "403 FORBIDDEN when baseUrl points at a private internal IP (test #14)": baseUrl='http://10.0.0.1'. Note: validateBaseUrl explicitly allows loopback (for local OpenAI-compatible servers) and only blocks non-loopback private IPs (10/8, 172.16/12, 192.168/16, fc00::/7, fe80::/10). - "400 BAD_REQUEST when apiKey is missing (test #15)": apiKey omitted. - "400 BAD_REQUEST when :id contains characters outside the safe-id regex (test #16)": id='bad!id' contains '!' which is not in [A-Za-z0-9._-]. Suite delta: +4 tests (26 active in finalize-design.test.ts). Full daemon suite: 1078/1078 pass; baseline+26 (the +5 above plan target reflects retry+extract split into more granular unit tests than originally enumerated; all real, none skipped). Refs nexu-io/open-design#450 * fix(daemon): tighten isSafeId to reject pure-dot project ids Addresses the P1 path-traversal finding from @lefarcen on PR #832 (https://github.com/nexu-io/open-design/pull/832#discussion_r3202512644). The pre-fix `isSafeId` at apps/daemon/src/projects.ts:556-558 used regex `/^[A-Za-z0-9._-]{1,128}$/` which permitted pure-dot ids (`.`, `..`, `...`) because `.` is in the character class. `projectDir` and `resolveProjectDir` both delegated to `isSafeId`, so an id of `..` would resolve to the PARENT of `.od/projects/` via `path.join`. Threat model (per @lefarcen): - An attacker creates a project row whose stored id is `..` (or another pure-dot variant) — for instance via a workflow that writes the row directly without going through the API. Subsequent finalize/write ops keyed by that id then escape the project tree. - A direct CLI / scripted caller passing `..` as the project id reaches the function without HTTP normalization saving us. (Express normalizes %2e%2e to .. and collapses path segments, which yields 404 for the URL `/api/projects/%2e%2e/...` in practice — but that's Express's protection, not ours.) Fix: - isSafeId now explicitly rejects pure-dot ids (`/^\.+$/.test(id)`) before the char-class regex check. Empty string and inputs longer than 128 chars are also rejected explicitly so the function fails closed on edge cases. - isSafeId is now exported from apps/daemon/src/projects.ts so the /finalize route handler in apps/daemon/src/server.ts can use the same validator instead of re-implementing the regex inline. This prevents drift between the route guard and the projectDir guard, which was how this hole originally appeared. Tests (in finalize-design.test.ts because that's where the threat was flagged; isSafeId is daemon-wide so a dedicated test file would also work): - isSafeId rejects `.`, `..`, `...`, `....` - isSafeId rejects ids with `/`, `\`, `!`, leading whitespace - isSafeId rejects empty string and >128 chars - isSafeId rejects non-string inputs (null/undefined/number) - isSafeId accepts plain ids, ids with mid-string dots, UUIDs, single chars Suite delta: +7 tests (33 active in finalize-design.test.ts). Full daemon suite: 1085/1085. Refs nexu-io/open-design#832 * fix(daemon): address PR #832 P1 findings — imported folders + network 502 Addresses two of the three P1 findings from @lefarcen on PR #832: 1. Imported-folder projects route DESIGN.md to metadata.baseDir (https://github.com/nexu-io/open-design/pull/832#discussion_r3202512656, also flagged independently by @chatgpt-codex-connector at #discussion_r3202430470) The pipeline previously called `projectDir(projectsRoot, projectId)` unconditionally, which resolves to `.od/projects/<id>`. For projects created via /api/import/folder the project row's `metadata.baseDir` carries the user's actual folder; without threading metadata through, finalize would silently land DESIGN.md in the hidden daemon data dir and the current-artifact resolver would miss the user's real files. Fix: switch from `projectDir` to `resolveProjectDir(projectsRoot, projectId, metadata)` in both `finalizeDesignPackage` and `resolveCurrentArtifact`. Thread `project.metadata` (from `getProject`'s normalized row) through both call paths. The resolver gets a new optional `metadata` parameter; native projects pass null and get identical behavior. 2. Network failures and JSON parse errors now map to 502 UPSTREAM_FAILED (https://github.com/nexu-io/open-design/pull/832#discussion_r3202512661) Pre-fix, only HTTP-non-OK responses were wrapped as FinalizeUpstreamError. DNS failures (ECONNREFUSED, ENOTFOUND), fetch TypeErrors, and `response.json()` SyntaxErrors fell through to the route's catch-all and surfaced as 500 INTERNAL — incorrect: those are upstream-level failures, not daemon bugs. Fix: - Wrap callAnthropicWithRetry in a try/catch that passes FinalizeUpstreamError and AbortError through verbatim, but rewraps any other thrown error as FinalizeUpstreamError(502, '', message). - Wrap response.json() in a try/catch that rewraps SyntaxError as FinalizeUpstreamError(502, '', "upstream Anthropic returned non-JSON body: ..."). - The route handler's existing FinalizeUpstreamError mapping then correctly maps these to 502 with the message in `details` (run through redactSecrets first). Tests: - "writes DESIGN.md under metadata.baseDir for imported-folder projects": inserts a project row with metadata.baseDir pointing at a user-folder temp dir; asserts result.designMdPath lands there AND the hidden .od/projects/<id> dir does NOT contain a DESIGN.md. - "rewraps fetch network rejection as FinalizeUpstreamError(502)": fetchImpl throws TypeError with cause.code='ENOTFOUND'; assert thrown error has name=FinalizeUpstreamError and status=502. - "rewraps 200 with non-JSON body as FinalizeUpstreamError(502)": fetchImpl returns 200 with text/html body; response.json() throws SyntaxError internally; assert FinalizeUpstreamError(502). Suite delta: +3 tests (36 active in finalize-design.test.ts). Full daemon suite: green at last check; will re-verify before push. Refs nexu-io/open-design#832 * refactor(daemon): move /finalize DTOs to contracts + map error codes + validate active-tab Addresses the P2 and P3 findings from @lefarcen on PR #832: P2 — Error codes + DTOs not in packages/contracts https://github.com/nexu-io/open-design/pull/832#discussion_r3202512673 Reverses my plan's locked decision #10 ("no contracts changes in this PR; inline the request/response types"). That rule came from the predecessor PROMPT brief's anti-pattern table; @lefarcen's review is fresher signal and supersedes it. Drift risk between the daemon's inline types and any future PR 3 web client is real. - New contracts module: packages/contracts/src/api/finalize.ts with FinalizeAnthropicRequest / FinalizeArtifactRef / FinalizeAnthropicResponse. Re-exported from the package root and made addressable via `@open-design/contracts/api/finalize` subpath. - Daemon source imports the canonical types from contracts and re-exports the public type names so internal references keep working without touching every call site. - Daemon-local error codes remapped to existing ApiErrorCode union members (apps/daemon/src/server.ts), per @lefarcen's suggested mapping: FINALIZE_IN_PROGRESS -> CONFLICT AUTH_FAILED -> UNAUTHORIZED UPSTREAM_FAILED -> UPSTREAM_UNAVAILABLE TIMEOUT -> UPSTREAM_UNAVAILABLE (status 503) INTERNAL -> INTERNAL_ERROR HTTP status codes are unchanged; only the `code` field in the error JSON body changed. P3 — Active-tab name not validated before sidecar probe https://github.com/nexu-io/open-design/pull/832#discussion_r3202512684 resolveCurrentArtifact now runs the active tab's name through validateProjectPath BEFORE composing it into a path.join expression. An invalid tab (traversal segments, absolute path, null byte, reserved segment) causes resolveCurrentArtifact to fall through to the newest-artifact branch rather than abort or probe outside the project directory. Tests: - "falls through (does not throw) when active tab name contains traversal segments": injects a malformed `tabs.name = '../../../etc/passwd'` row directly via SQL (bypassing production tab-creation validation), seeds a real artifact, asserts the resolver returns the real artifact rather than the malformed name. Suite delta: +1 test (37 active in finalize-design.test.ts). Full daemon suite: 1089/1089 green. Refs nexu-io/open-design#832 * fix(contracts): publish /api/finalize as standalone runtime entrypoint Addresses @mrcfps's CI-red review on PR #832 (https://github.com/nexu-io/open-design/pull/832, inline comment on packages/contracts/package.json). The previous J3 commit added `./api/finalize` as a type-only subpath: the entry had only a `types` field, no `default`. That broke the contracts package-runtime gate (packages/contracts/tests/package- runtime.test.ts:38-47) which asserts every exports entry exposes both a `.mjs` runtime and a `.d.ts` types target. mrcfps proposed two fixes; this commit takes path B — make finalize a first-class published module rather than a type-only re-export from the package root. Path B vs path A (a peer-AI second opinion via /collaborate confirmed): under NodeNext + ESM with exports-map semantics, TypeScript validates re-exported symbols against the published module-identity surface. Because the previous J3 had `./api/finalize` neither declared as an exports-map entry nor materialized as a standalone .mjs, TS omitted the re-exported names during package boundary analysis. Even at runtime `import('@open-design/contracts').FINALIZE_SCHEMA_VERSION` worked from the bundled index.mjs but the type-checker rejected it. Path B aligns the runtime and declaration surfaces. Changes: - packages/contracts/esbuild.config.mjs: add `./src/api/finalize.ts` to entryPoints so dist/api/finalize.mjs is generated as a standalone module rather than only inlined into the bundled root. - packages/contracts/package.json: re-add `./api/finalize` to the exports map with both `default: ./dist/api/finalize.mjs` AND `types: ./dist/api/finalize.d.ts`. Mirrors `./api/connectionTest`'s shape (the canonical pattern for first-class submodule entries). - packages/contracts/src/api/finalize.ts: keep the runtime export `FINALIZE_SCHEMA_VERSION = 1` (giving the standalone module a real value to emit beyond the type-only interfaces) and update the doc-comment now that the standalone .mjs is wired. - apps/daemon/src/finalize-design.ts: switch the type import from the inline declarations introduced in the prior J3 fallback to `import type { ... } from '@open-design/contracts/api/finalize'`. Re-export the names so internal references inside finalize-design.ts keep working without touching every call site. Verified: - node --input-type=module -e "import('@open-design/contracts/api/finalize').then(m=>console.log(JSON.stringify(Object.keys(m))))" prints ["FINALIZE_SCHEMA_VERSION"] — runtime resolution clean. - pnpm --filter @open-design/contracts test: 6/6 (including both package-runtime.test.ts cases on the rebuilt exports map). - pnpm --filter @open-design/daemon typecheck: exits 0. - pnpm --filter @open-design/daemon test: 1089/1089 (no regression vs the prior J3 number). Refs nexu-io/open-design#832 --------- Co-authored-by: DevForgeAI CI/CD Engineer <devforge-ai@development.ai> |
||
|
|
1e8926271b
|
Harden security scan findings and upgrade dependencies (#806)
* feat: add accent color control and launcher for Open Design * fix: remove launcher binary from PR * test: cover accent appearance edge cases * Harden security scan findings and upgrade deps * Address proxy security review * Pin jsdom for web test stability --------- Co-authored-by: ferasbusiness666 <ferasbusiness666@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
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) |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
09f6a7ccce
|
docs(zh-CN): trim BYOK proxy fallback line from intro (#915) | ||
|
|
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 |
||
|
|
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 |
||
|
|
8fee22d358
|
Fix stuck chat runs and unintended cancels (#896)
* Fix stuck chat runs and unintended cancels * Harden chat run stall watchdog |
||
|
|
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. |
||
|
|
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> |
||
|
|
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 |
||
|
|
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.
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
8930b9650c
|
feat: Add a toggle to reveal media provider API keys (#867) | ||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
42ae1da03d
|
Add Turkish README translation (#843) | ||
|
|
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> |
||
|
|
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 |
||
|
|
ef4e673ad0
|
docs(readme): refresh contributors wall (#856)
Co-authored-by: mrcfps <23410977+mrcfps@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
11225b2d7e
|
fix(desktop): cleanly quit macOS packaged app (#422)
Co-authored-by: Fu Yizheng <fyz3120@sina.cn> |
||
|
|
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> |
||
|
|
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) |
||
|
|
32df17b87b
|
Fix desktop preview interactions and connector auth feedback (#864)
* Fix desktop preview modal interactions * Fix connector auth failures surfacing |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |