mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
fix-145
541
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7b24fbefd1 | fix(web): complete finished tool calls missing results | ||
|
|
ab922327f4 | refactor(daemon): split agent runtime definitions (#1063) | ||
|
|
b1d440d2bd |
refactor(daemon): split route registration (#1043)
* spec * refactor(daemon): split server route registrars * refactor(daemon): group route registrar dependencies * refactor(daemon): move remaining domain routes out of server * update doc * revert spec * fix daemon route context contract Generated-By: looper 0.5.6 (runner=fixer, agent=opencode) * fix media task persistence Generated-By: looper 0.5.6 (runner=fixer, agent=opencode) * fix: restore daemon route registrations * fix: restore static resource mutation origin checks |
||
|
|
976edaf38e |
test: harden e2e smoke and release reports (#1140)
* test: harden e2e inspect specs * test: wire e2e release reports * chore: bump packaged beta base to 0.6.1 * test: run release smoke vitest directly * test: add suite-owned tools-dev lifecycle * ci: harden stable release packaging * fix(release,e2e): gate stable signing on verify and harden suite cleanup - restore `needs: [metadata, verify]` on the stable release `build_mac`, `build_mac_intel`, `build_win`, and `build_linux` jobs so Apple signing/notarization and Windows release builds cannot run before pnpm guard, typecheck, and layout checks complete on the metadata commit. - in `runToolsDevSuite`, drop the `started` flag and always attempt `stopToolsDevWeb` in `finally`; record stop errors in diagnostics, and when the test body succeeded, escalate the stop failure to the suite result and rethrow — so orphan daemon/web processes from an interrupted `startToolsDevWeb` or a broken shutdown can no longer pass silently. Addresses PR #1140 review feedback from lefarcen and mrcfps. |
||
|
|
1dc0224599 |
fix(desktop): enforce minimum window size on main client (#1189) (#1203)
The main BrowserWindow was created with only `width: 1280, height: 900` and no `minWidth` / `minHeight`, so Electron honored arbitrary user drags. Past roughly 900×600 the project page's left/right split (chat composer + designs panel + preview pane) overlaps and the top navigation clips, which is the broken first impression reported in #1189. Pin `minWidth: 900, minHeight: 600` on the main window — preserves the usable layout floor while still fitting common 13" small-screen laptops. The ephemeral print sub-window (`show: false`, closed on print completion) is unchanged: it isn't user-resizable so a min-size floor has no observable effect there. |
||
|
|
b19aa6c907 |
Improve Codex CLI path fallback UX (#1205)
* Improve Codex CLI path fallback UX (#1193) * Handle ENOENT Codex shim fallback |
||
|
|
979733d39b |
feat(web): add Cmd+, shortcut to open settings with platform shortcut badge (#1173)
Register a capture-phase Cmd+, (mac) / Ctrl+, (win/linux) listener in App.tsx that opens Settings, and show a shortcut badge on the Settings menu item in both AvatarMenu and EntryView. Extract the duplicated isMac platform check into a shared isMacPlatform() utility in utils/platform.ts, replacing inline copies in FileWorkspace and ProjectView as well. |
||
|
|
2838a28585 |
fix: set writable OD_DATA_DIR default for nix run (#1159)
Fixes #1157 When running via 'nix run github:nexu-io/open-design', the daemon attempted to create runtime state under the Nix store package path: /nix/store/.../lib/open-design/.od/projects The Nix store is read-only at runtime, causing startup to fail with ENOENT when mkdir() tried to create the projects directory. This commit updates the nix run wrapper to export OD_DATA_DIR with a writable default ($HOME/.od) when the variable is unset. Users can still override it by setting OD_DATA_DIR before running. The Home Manager and NixOS modules already set OD_DATA_DIR, so they are unaffected by this change. |
||
|
|
d3b1804523 |
docs(readme): refresh contributors wall (#1188)
Co-authored-by: mrcfps <23410977+mrcfps@users.noreply.github.com> |
||
|
|
12708fd379 |
Update docs/assets/github-metrics.svg - [Skip GitHub Action] (#1183)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
d45bf3fb9a |
test: expand entry and settings automation coverage (#954)
* test: harden new project panel metadata coverage * test: expand entry e2e coverage * test: drop e2e docs from the guarded package * test: cover examples gallery interactions * test: cover examples preview modal actions * test: cover examples preview escape fullscreen * test: cover examples template prompt filtering * test: cover updated settings and entry tabs * test: fix entry/settings coverage type drift * test: fix example preview fetch assertion * test: fix new project panel skill fixture |
||
|
|
32fa0c23bb |
feat(daemon): Critique Theater Phase 6.2 (artifact extraction + endpoint) (#1085)
The orchestrator was leaving artifactPath = null on every shipped run because the SHIP <ARTIFACT> body never made it past the parser. Reviewers caught this on PR #1006: a rerun-style endpoint built on top of that null could not return a usable prior-art reference, and tests that synthesized artifactPath via insertCritiqueRun were hiding the gap rather than covering the feature. This PR closes that gap. The parser now hands the orchestrator a ShipArtifactPayload (round, mime, body) through a side-channel callback, and the orchestrator writes the bytes to <artifactsDir>/<projectId>/<runId>/ artifact.<ext> via a new artifact-writer module. The row's artifactPath is the absolute on-disk path. The web layer never sees that path: it fetches the bytes through GET /api/projects/:projectId/critique/:runId/artifact, which the new artifact-handler module serves with a mime-derived Content-Type, X-Content-Type-Options: nosniff, a CSP header for HTML and SVG, and the same cross-project leak guard pattern the interrupt handler uses. The body and mime intentionally never travel on the SSE wire. The SHIP PanelEvent (which doubles as the SSE payload shape) keeps its lightweight artifactRef, and the orchestrator strips body/mime before bus.emit, so a multi-megabyte artifact does not broadcast to every subscriber. The new orchestrator test asserts this explicitly. Defense in depth in the writer + handler: - mime allowlist with text/html, text/css, text/markdown, text/plain, application/json, image/svg+xml; everything else falls through to application/octet-stream + .bin so unknown payloads can't be misinterpreted as a known type; - UTF-8 byte-length cap, configurable via cfg.parserMaxBlockBytes, so multi-byte payloads can't sneak past a JS .length check; - atomic write through a sibling tmp file + rename so a daemon crash mid-write can't leave a half-written artifact under the canonical name; - path-traversal guard on the GET endpoint that resolves the row's artifactPath against the artifacts root and refuses anything that escapes it, refuses non-regular files (symlinks, dirs), and refuses files larger than the response cap. Folded in two non-blocking notes lefarcen left on PR #1016 (the contracts move) since persistence.ts was already in scope here: - P2: introduced CritiquePersistedStatus = CritiqueRunStatus | 'running' in the contracts package. CritiqueRunRow.status and CritiqueRunInsert. status now use it, and the inline `as CritiqueRunStatus | 'running'` widen in interrupt-handler.ts is gone. Public DTOs continue to use the terminal-only CritiqueRunStatus so a future endpoint can't leak a 'running' row through the wire. - P3: added AssertExhaustiveValues + a compile-time assertion that CRITIQUE_RUN_STATUSES covers every CritiqueRunStatus variant. Adding a value to ShipStatus or CritiqueRunStatus without updating the array now fails the build with a tuple naming the missing variants instead of silently dropping out of UI filters. Coverage: 174 critique tests across 14 files pass locally, including the new critique-artifact-writer (13 cases) and critique-artifact-endpoint (11 cases) suites, the inverted critique-lifecycle artifact-persistence test, and the orchestrator happy-path that asserts the SSE ship payload does NOT carry body or mime. Validated: pnpm guard, pnpm --filter @open-design/contracts build, pnpm --filter @open-design/daemon build (full tsc), pnpm --filter @open-design/web typecheck, pnpm --filter @open-design/daemon exec vitest run tests/critique (all green). This is step (b) of the four-step plan that PR #1006's closing comment laid out. Step (a) was the contracts move in PR #1016. Steps (c) (persist original_message_id / agent_id / model_id) and (d) (real rerun endpoint on top of (a)+(b)+(c)) follow. Co-authored-by: Nagendhra <nagendhra405@gmail.com> |
||
|
|
976a5900f8 |
fix: clear stale upload failure banner when previewing files (#797)
* fix: clear stale upload failure banner when previewing existing files Closes #786 - Clear uploadError in openFile() so navigating to a file dismisses the banner - Scope banner visibility to the Design Files tab so stale errors do not bleed into preview surfaces - Add test pinning that no banner is rendered when there is no upload error * fix(workspace): move upload banner into DesignFilesPanel + interactive test Per @mrcfps + @lefarcen review on PR #797: - Move the upload-error banner from FileWorkspace into DesignFilesPanel body. Hide it whenever the in-panel preview is active (the missed flow that mrcfps and lefarcen flagged: single-click preview kept activeTab on DESIGN_FILES_TAB, so the old guard left the banner mounted above the preview). - Keep a fallback banner in FileWorkspace that fires only when activeTab is not Design Files. This preserves the partial-upload visibility flagged by chatgpt-codex-connector: a partial upload opens the last successful file (flipping activeTab to a viewer) and the failure note still surfaces. - Wrap uploadProjectFiles in try/catch so thrown errors surface a banner instead of disappearing. - Replace the brittle viewer-empty assertion with two interactive vitest cases: (1) mock-fail upload, banner visible, preview file, banner hidden, close preview, banner back, dismiss, banner gone; (2) partial-upload uploaded+failed, banner appears on the viewer surface with the existing 'Uploaded N file(s), but M failed' text. - Add df-upload-banner class and stable test ids upload-error-banner and upload-error-dismiss so future tests don't rely on the generic viewer-empty class. Closes #786 staleness; addresses follow-up review. --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
35e7b622b7 | fix(web): allow pod-to-chat comment text to wrap instead of truncating (#793) (#1156) | ||
|
|
06e677cb72 | Fix pending prompt clearing for templates (#1148) | ||
|
|
84f768d4a2 |
feat: add WeChat design system, login-flow skill, and fix API mode tool_calls bug (#1083)
* feat: add WeChat design system, login-flow skill, and fix API mode tool_calls bug - Add WeChat design system (design-systems/wechat/) with full brand spec including color palette, typography, and component rules for chat UI - Add login-flow skill (skills/login-flow/) for mobile authentication flows with P0 checklist, example HTML, and i18n registration across 3 locales - Fix DeepSeek V4 bug: API/BYOK mode (streamFormat=plain) models now receive a directive to emit only <artifact> HTML blocks and suppress tool_calls, since plain adapters proxy to external providers that cannot execute tools * fix: restore full server.ts and WeChat DESIGN.md from ad46d8cd commit Restore files that were corrupted in PR #1083 head branch. The WeChat DESIGN.md was reduced to a single line (filename only) and server.ts was reduced to ~1 line. Both are restored to their original ad46d8cd state with full content. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: restore full server.ts and WeChat DESIGN.md from ad46d8cd Restore files corrupted in PR #1083: - apps/daemon/src/server.ts: restored 7106-line file - design-systems/wechat/DESIGN.md: restored 301-line WeChat design spec - skills/login-flow/SKILL.md: restored from local working state - skills/login-flow/example.html: restored 351-line example HTML * fix: only suppress tool_calls when streamFormat='plain' explicitly, remove nonexistent assets/template.html 1. streamFormat check now requires explicit 'plain' value instead of defaulting to 'plain' when undefined. This prevents normal tool-using chat runs from incorrectly inheriting the API/BYOK tool_calls suppression rule. 2. login-flow SKILL.md: removed reference to assets/template.html since that file does not exist in the skill bundle and derivePreflight() would inject a hard instruction to read it before any other tool, causing pre-flight to fail. * fix: thread streamFormat to composeSystemPrompt in server.ts call Previously the composeSystemPrompt call at line ~4940 omitted streamFormat, causing the composer to default to 'plain' and suppress tool_calls even for tool-using chat runs. Now streamFormat is passed through from the adapter definition so the API mode rule only fires when streamFormat='plain' is explicitly set. * fix: WeChat category metadata, font-family, and login-flow example interactivity WeChat DESIGN.md: - Add Category: Social & Messaging metadata so it appears correctly in picker - Fix font-family declaration: remove invalid -webkit-font-family prefix, use standard font-family so downstream CSS generation works correctly skills/login-flow/example.html: - Add password toggle click handler so show/hide actually works - Change Apple icon fill from hardcoded #fff to currentColor so it is visible on light backgrounds * fix: mirror streamFormat suppression in contracts composer and add WeChat i18n 1. packages/contracts/src/prompts/system.ts: Add streamFormat parameter to ComposeInput and ComposeInput interface, mirroring the same suppression rule from daemon prompts/system.ts. When streamFormat='plain' is passed, a directive is appended telling models not to emit tool_calls and to only output <artifact> HTML blocks. 2. apps/web/src/i18n/content.{ts,fr,ru}.ts: Add WeChat design system entries: - Add 'wechat' to DE/FR/RU_DESIGN_SYSTEM_IDS_WITH_EN_FALLBACK arrays - Add 'wechat' summary to DE/FR/RU_DESIGN_SYSTEM_SUMMARIES - Add 'Social & Messaging' category to DE/FR/RU_DESIGN_SYSTEM_CATEGORIES (matching the Category: Social & Messaging metadata in WeChat DESIGN.md) * fix: thread streamFormat='plain' into web composeSystemPrompt for api mode * test: focus localized content coverage on missing resources --------- Co-authored-by: Open Design Contributor <z@open-design.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
bfedbeca0f |
fix(prompts): add "When NOT to emit" guardrail to artifact handoff (#1143) (#1145)
* fix(prompts): add "When NOT to emit <artifact>" clauses (#1143) #1143 现场报告:当本轮只用 Edit 工具修改已有 HTML 文件、没有写出新 canonical HTML 时,AI 仍按 system prompt 的 "non-negotiable output rule" 字面收尾,把一句中文总结塞进 `<artifact type="text/html">` 块 里。下游持久化路径会把这一句 prose 当合法 HTML 落盘,污染项目文件 面板(截图见 #50 评论)。 根因在 prompt 缺少免发条款。本次修改: - 把 "Artifact handoff (non-negotiable output rule)" 改为条件化措辞 "Artifact handoff" + "When you ship a fresh deliverable…" - Workflow step 5 ("Finish") 增加 in-place edit 不发 artifact 的分支 - 新增 "When NOT to emit `<artifact>`" 子段,明确三条免发条件: - in-place edits only:本轮无新 canonical HTML 产出 → 直接说改了 哪个文件、改了什么 - body 必须是完整 `<!doctype html>` 文档 → 总结/路径/bash 输出/ 说明用普通回复,不要包标签 - 拿不准就别发 → 重发未变 artifact 没价值,发空壳 artifact 反而 误导用户、污染面板 测试:apps/daemon/tests/prompts/system.test.ts 新增 describe 块 "artifact handoff no-emit clauses (#1143)" 4 例,断言 composed prompt 含必要短语。 #50 持久化层兜底(pre-write HTML gate)由 #1144 单独跟进,与本 PR 互补:本 PR 让 AI 不去发空壳 artifact,#1144 在写盘前再挡一道, 即使 prompt 失守也不会污染项目面板。 * fix(prompts): 让 discovery 主导层也支持 artifact 免发例外 (方案 C) review (lefarcen P1, mrcfps blocker): base 层新增的 "When NOT to emit <artifact>" 例外被更高优先级的 DISCOVERY_AND_PHILOSOPHY 层中的无条件 emit 指令盖过去,导致 #1143 主路径仍可能产出空壳 artifact。 按 review 中的方案 C 修补: - discovery.ts RULE 3 之前新增 "Artifact emission is conditional" 主导层不变式段落(条件式 emit 在主导层声明一次,base 层保持详细规则) - discovery.ts:17 arc 注释 / :143 plan 模板 step 9 / :262 default arc recap 全部改为条件式(仅在本轮写出新 canonical HTML 时 emit) - deck-framework.ts:327 deck workflow step 7 同步改为条件式 测试加 2 条断言: - 负断言:组装后 prompt 不再含未限定的 "Emit single <artifact>" / "emit a single <artifact>." 行 - 正断言:discovery 层包含 "only when this turn wrote a new canonical HTML" 与 "only edited an existing HTML file" 等价表述 * test(prompts): 补 deck-mode 负断言覆盖 deck-framework.ts:327 review (lefarcen P2): 上一轮的负断言用 composeSystemPrompt({}) 调用, 不会触发 DECK_FRAMEWORK_DIRECTIVE 的拼接(仅在 skillMode === 'deck' 或 metadata.kind === 'deck' 时追加)。如果 deck-framework.ts:327 后续回 退到 "Emit single <artifact>",无参负断言依然假绿。 补一条显式的 deck-mode 断言: - 负断言:deck-mode prompt 不含未限定的 "7. Emit single <artifact>" 行 - 正断言:含本次改的 "Emit single <artifact> if a new canonical deck HTML" |
||
|
|
7c1db80893 |
fix(web): 写盘前拦截 prose-as-HTML artifact (#50) (#1144)
* fix(web): reject prose-as-HTML artifacts at write time (#50) AI 偶尔会在仅做 in-place 编辑(无新 canonical HTML 产出)时仍按 system prompt 的非协商性收尾规则发出 `<artifact type="text/html">` 块,但块内只装一句中文总结。`persistArtifact` 之前不做内容校验, 此类 prose 会作为合法 HTML 落盘到 `.od/projects/<id>/<id>.html`, 并附带 `kind: html` manifest,污染项目文件面板(截图见 #50 评论)。 新增 `validateHtmlArtifact` 纯函数:要求非空 + 长度 ≥64 + 含 `<!doctype html>` 或 `<html>` 标签(大小写不敏感、容忍 BOM)。 `persistArtifact` 在 `ext === '.html'` 分支调用 gate,失败时 通过 `setError` 报错且不写文件。 scope 限于 `<artifact>`-tag 持久化路径——FileViewer/FileWorkspace 里用户手动保存草稿 HTML 走的是不同入口,不受影响。 prompt 层根因(缺少免发条款)已拆出 #1143 单独跟进,本 PR 是 持久化层的兜底防御。 * fix(web): anchor HTML structural check at content start (#1144 review) mrcfps 在 #1144 review 指出原实现的 false negative: HTML_OPENING_TAG_RE / DOCTYPE_RE 用 .test() 在整个字符串里搜, 所以 AI 描述改动时 inline 引一个 tag 名("Updated the <html lang> attribute...")就能蒙混过关——长度过 64、含 `<html `——同样 落地为幽灵 HTML 文件。 修复:合并两个 regex 成 STARTS_WITH_DOCUMENT_RE,加 ^ anchor, 要求 trimmed 内容的首个非空白 token 必须是 `<!doctype html>` 或 `<html`。Mid-string 出现的 tag 名不再算数。 同时按 lefarcen 的非阻塞文档建议把 docblock 改写得更精确: - "structural sniff" 替代 "validation",明确不是 HTML 校验器 - 列出 not-a-linter / .jsx-tsx-skipped / 用户手动保存路径不受 影响 三条 scope 边界 - 64 字符阈值会拒收 49 字符的最小空 doc(如 `<!doctype html><html><body></body></html>`),明确这是有意 trade-off:AI 产出预期是 non-trivial deliverable 新增 3 例测试覆盖 mrcfps 描述的 false negative: - 长 prose 中 inline 引 `<html lang>` 应拒收 - 长 prose 中 inline 引 `<!doctype html>` 应拒收 - 首个 token 是 `<p>` 等非文档标签的 fragment 应拒收 |
||
|
|
93a08689e4 | fix(web): truncate entry footer pet label (#1150) | ||
|
|
e948405c22 |
fix(web): surface connector auth errors and stop silent popup close (#725) (#1128)
* fix(web): surface connector auth errors and stop silent popup close (#725) Two layered bugs caused the "Twitter Connect button does nothing" symptom: 1. ConnectorsBrowser dropped result.error from connectConnector. On Electron desktop the popup is never opened (electronAPI.openExternal path), so the existing renderConnectorAuthError(null, ...) was a no-op and the user got zero feedback. 2. registry.ts silently called authWindow?.close() whenever the connect response did not carry { kind: 'redirect_required', redirectUrl }, leaving web users with a popup that vanishes without explanation. Patch: - Add a per-connector connectorAuthorizationError state and render it as an inline banner on both ConnectorCard and ConnectorDetailDrawer (mirrors the existing cancel-failed pattern; reuses the existing .connector-authorization-error styling). - Replace authWindow?.close() with a renderConnectorAuthInfo helper that branches on auth.kind ('connected' | 'pending' | unknown) and writes an explanatory message to the popup before the user closes it. - Tests: 1 registry test for the pending/info popup branch, 2 ConnectorsBrowser tests for surfacing and clearing the inline banner. * fix(web): clear connector auth error on background status refresh Addresses review feedback from @mrcfps and the Codex bot on PR #1128: the inline error banner stayed visible even after background status refresh marked the connector as `connected` (e.g. user completes auth out-of-band through the Composio dashboard, then focus/poll/message refresh observes the connection). - Add clearConnectorAuthorizationErrorsForConnected helper next to the existing pending-state helpers; same shape, returns the same object reference when nothing changes so React skips a re-render. - Wire it into reloadConnectorStatuses so every status refresh path (pending poll, focus, OAuth callback message) drops stale errors for any connector now reported as connected. - Add 2 unit tests for the helper next to the existing pending-state helper tests in EntryView.test.ts. |
||
|
|
eabf3a6e86 |
feat: add collapsible MCP JSON field-mapping helper (#1136)
* feat(web): add collapsible MCP JSON helper component * feat(web): add collapsible MCP JSON field-mapping helper * test(web): add McpJsonHelper component tests for toggle behavior * fix(web): scope helper id per row and show helper * test(web): rewrite McpJsonHelper tests to use row-scoped ids * feat(mcp): use stable _localId for McpRow keys and aria-controls\n\n- Add _localId to DraftRow and genLocalId()\n- Use _localId as React key and helper id to avoid duplicate DOM ids\n- Move helper outside transport branches so helper is visible for all transports\n- Fix malformed template.homepage anchor * fix(web): restore _localId-scoped helperId and helper visibility for all transports * test(web): replace integration test with _localId-scoped helper tests * test(web): exercise McpJsonHelper via production McpClientSection in jsdom * fix(web): resolve typecheck errors * test(web):expand rows before querying helper toggles to fix timeout |
||
|
|
1f625cff77 |
fix(i18n): translate comments panel UI to Chinese (#1139)
The comments panel in the project page left sidebar was missing Chinese translations for all UI strings. Users with Chinese language settings would see English text in the comments section, which created an inconsistent experience. This commit adds complete translations for: - Comment section titles (attached/saved comments) - Action buttons (add/remove/add all) - Empty state messages - Comment placeholder text - Attachment-related labels Both simplified Chinese (zh-CN) and traditional Chinese (zh-TW) locales are updated to provide full Chinese language support for the comments feature. |
||
|
|
602cf704e2 | fix(web): center close button in MCP picker dialog (#1137) | ||
|
|
13005f4fea |
fix(desktop): allow about:blank popup for PDF export fallback (#1081)
The renderer's PDF export fallback uses window.open('', '_blank')
to open a blank window that is then navigated to a Blob URL.
Electron's setWindowOpenHandler only allowed blob: and od: protocols,
so about:blank was denied and the user saw a "Popup blocked" alert.
Fix: add about:blank to the allowed child window URL whitelist.
Co-authored-by: Ken <hitken@users.noreply.github.com>
|
||
|
|
9079c51ba3 |
feat(daemon): HTTP 206 range request support for video/audio files Fixes #784 (#1105)
* feat(daemon): HTTP 206 range request support for video/audio files (#784) Stream video and audio via fs.createReadStream with Accept-Ranges: bytes and 206 Partial Content responses so browsers can play and seek media inline. Non-media files keep the existing buffer path unchanged. Add parseByteRange (RFC 7233-compliant) and resolveProjectFilePath to projects.ts, and 23 unit tests covering all range edge cases. * fix(daemon): move range streaming to /raw/* route used by media viewers The inline VideoViewer and AudioViewer components fetch /api/projects/:id/raw/* (via projectRawUrl), not /files/*. Apply the HTTP 206 / Accept-Ranges streaming path to the raw route while preserving its Origin: null CORS behaviour for sandboxed iframes. Add 7 route-level HTTP tests against a real startServer() instance covering 200 full, 206 partial, suffix range, open-ended range, 416 unsatisfiable, non-media passthrough, and 404 cases. --------- Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
31f89f74fd |
fix: remove Trump pet from bundled community pets (#1103)
Fixes #1042 Problem: The Trump pet was included in the bundled community pets catalog, which appeared in the Built-in pet adoption picker. This raised concerns about keeping politically-charged content in the default pet selection. Solution: - Removed the trump pet directory from assets/community-pets/ - Removed 'trump' from the BUNDLED_PETS list in bake-community-pets.ts The pet is still available on Codex Pet Share for users who want to download it manually, but it no longer ships as a built-in option. Impact: - Trump pet no longer appears in the default pet adoption picker - Users can still access it via "Download community pets" if desired - Keeps the built-in pet selection neutral and welcoming Related: - PR #850 (previous attempt that was closed without merging) |
||
|
|
6f2584e315 |
fix(web): prevent chat messages from overflowing into workspace area (#662) (#1104)
Add overflow-x: hidden to .chat-log so any horizontally overflowing content (thinking blocks, status pills, form cards) is clipped inside the chat pane instead of spilling into the workspace area. Add min-width: 0 and max-width: 100% to .msg so flex items in the chat log column cannot expand beyond the panel width when their intrinsic content is wider than the container. Add min-width: 0 to .assistant-flow to prevent the intermediate flex container from propagating intrinsic content width up to the message boundary. This complements the existing overflow: hidden on .pane and .split-chat-slot from #740 by also constraining the intermediate flex items that can propagate width from deeply nested content up to the scroll container boundary. |
||
|
|
cbb3c0e33a |
Improve design files grouping (#1082)
Add a modified-date grouping mode to make busy design workspaces easier to scan as generated files accumulate. The new view keeps existing batch actions and pagination available, adds localized labels, and covers date boundaries with component tests. |
||
|
|
bb578b3dca |
fix: Support OpenCode Write tool display as card (#1126)
The Write tool from OpenCode AI wasn't being displayed correctly as a card. This fix addresses two issues: 1. Tool name normalization: Added support for lowercase 'write' in addition to 'Write' 2. Field naming normalization: Added support for camelCase 'filePath' in addition to snake_case 'file_path' Changes made: - Added `normalizeToolInput()` function in daemon.ts for root-level field normalization - Updated ToolCard.tsx to recognize both tool name variants and field naming conventions - Updated AssistantMessage.tsx for tool name recognition - Updated ProjectView.tsx for file path parsing in auto-open feature This ensures consistent behavior across different AI providers regardless of their tool naming conventions. |
||
|
|
29e5732f44 |
fix: prevent design system filter popover from shifting position on reopen (#960)
* fix: prevent design system filter popover from shifting position on reopen Fixes #921 The design system filter popover was repositioning incorrectly when reopened after filtering, sometimes appearing too high and becoming partially hidden at the top of the viewport. Root cause: - The popover uses position: absolute with top: calc(100% + 6px) - When filtering reduces the number of items, the popover height shrinks - On reopen, the reduced height can cause the popover to appear higher than expected, especially if the trigger button is near the top Solution: - Added min-height: 120px to .ds-picker-list - This ensures the popover maintains a consistent minimum height - Prevents position shifts when content is filtered - The popover stays anchored correctly to its trigger The 120px minimum provides enough space for ~3-4 items while keeping the popover stable across filter state changes. * fix: scope min-height to design-system picker only The .ds-picker-list class is shared by multiple pickers: - NewProjectPanel prompt-template picker - SettingsDialog MCP client picker - NewProjectPanel design-system picker Adding a global min-height: 120px would affect all pickers, causing unnecessary blank space when they have few items. This adds a dedicated .ds-picker-list-design-systems modifier class to scope the min-height fix to only the design-system picker, which is the one affected by the position-shift bug. |
||
|
|
f898741f81 |
docs(readme): refresh contributors wall (#1117)
Co-authored-by: mrcfps <23410977+mrcfps@users.noreply.github.com> |
||
|
|
fa5272ad03 |
Update docs/assets/github-metrics.svg - [Skip GitHub Action] (#1115)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
9f073f7b06 |
fix(web+desktop): handle popup-blocked PDF export with native Electron print dialog (#973)
* fix(web): add alert when pdf export popup is blocked (#664) * fix(web): implement synchronous empty-tab strategy for pdf export * Update popup-blocked alert with browser-specific instructions * Add desktop preload script exposing native print-pdf IPC channel * Add native Electron print dialog for PDF export via IPC handler * fix(desktop): resolve electron preload, ipc security, and print callbacks * fix(desktop): resolve PR review comments for IPC lifecycle and merge fallout * fix(web): prevent double-print race by moving script injection to browser path * fix: resolve timing issues for blob revocation and desktop print readiness * fix(desktop): make waitForPrintableContent descend into sandboxed iframes * fix(desktop): handle print dialog cancellation gracefully without throwing errors * fix(desktop): send raw document to desktop bridge to fix readiness timing * fix(desktop): restore iframe sandbox and implement postMessage readiness handshake * fix(desktop): separate legacy print readiness from new handshake logic to fix regression * fix(desktop): resolve print handshake race condition and add regression test * fix(web): strip allow-modals from desktop sandbox to prevent hidden window stalls * fix(desktop): ensure print readiness cache is injected for all bridge exports * fix(web): make print readiness handshake explicitly wait for image completion * chore(desktop): add 30s timeout to print readiness handshake * fix(web): defer image readiness scan until after DOM load to catch lazy images * fix(desktop): secure print readiness handshake with per-export nonce |
||
|
|
587c783dc0 |
feat(web): add Finalize design package + Continue in CLI buttons (#451) (#974)
* feat(daemon): expose resolvedDir on GET /api/projects/:id (#451 prereq)
Native projects (no metadata.baseDir) live at <projects root>/<id>, where
projects root is daemon-side state. The web client cannot reconstruct an
absolute path on its own, and shell.openPath on a relative path is
undefined behavior. Without resolvedDir, the upcoming Continue in CLI
button (#451) would render permanently disabled for native projects.
Mirrors PR #832's pattern of exposing designMdPath in its response.
Computed via the existing resolveProjectDir(...) helper. No behavior
change to existing callers; they ignore the new field.
Adds ProjectDetailResponse contract type and a focused projects-routes
test covering imported-folder, native, and unknown-id paths.
* feat(web): add parseProvenance helper for DESIGN.md staleness checks
Pure helper that extracts Project ID, design system, current artifact,
transcript message count, and generated UTC timestamp from the
`## Provenance` section emitted by the daemon's finalize synthesis
prompt (apps/daemon/src/finalize-design.ts). Used by useDesignMdState
to derive the Continue in CLI button's stale/fresh state without an
additional daemon endpoint.
Handles missing section, "none" sentinels for design system /
artifact, and malformed timestamps without throwing. Tests cover all
four branches.
* feat(web): add buildClipboardPrompt template for Continue in CLI
Inline single-source-of-truth template per #451 spec §3.4. Names the
project, the working directory, and the DESIGN.md-first operating
contract for the receiving `claude` CLI session. Trailing TODO is
the blank task slot the issue body specifies — left empty so the user
fills it in before submitting.
Also lands the shared copyToClipboard helper (jsdom-safe canonical path
+ execCommand fallback) so the new button and any future caller share
one fallback path, mirroring the inline pattern in FileViewer.tsx.
Tests cover happy-path field rendering, "none"/"unknown" sentinels
when DESIGN.md fields are absent, and both clipboard branches.
* feat(web): add useProjectDetail + useDesignMdState hooks
useProjectDetail wraps GET /api/projects/:id, surfacing the resolvedDir
field and falling back to metadata.baseDir for older daemons that don't
include it. Continue in CLI needs an absolute working directory so the
desktop bridge can openPath it; the web client never reconstructs the
path itself.
useDesignMdState fetches the project's file list, downloads DESIGN.md
when present, parses the Provenance section, and computes a stale
verdict by comparing the recorded generatedAt against the max mtime of
non-DESIGN.md files and the max conversation updatedAt. Drives the
button's three-state UI (disabled / fresh / stale) without a
daemon-side endpoint.
Tests cover happy path, fallback, and both stale branches plus the
pure computeStale helper for the null-timestamp edge case.
* feat(web): add useFinalizeProject hook with cancel + error-code mapping
Wraps POST /api/projects/:id/finalize/anthropic for the Finalize design
package button. Three concerns:
1. Lifecycle: idle → pending → success | error. Double-clicking the
button aborts the prior in-flight request before starting a new
one so the daemon never sees stacked finalize calls per project.
2. Cancellation: AbortController plumbed through fetch + a 130 s
timer (daemon timeout 120 s + 10 s buffer). Cancel returns to idle
cleanly — it's a user gesture, not an error surface.
3. Daemon error mapping: when the response is non-OK, body.error.code
drives the canonical user-facing toast string (table covers all
7 codes the daemon emits today plus a network-error catch-all).
body.error.details, when a string, surfaces alongside the category
message so account-usage-cap responses (Anthropic 400 →
UPSTREAM_UNAVAILABLE) can show the upstream's own reason instead
of just the daemon's category label — committed to lefarcen on
#450 verification reply.
Tests cover request body shape, all 8 error codes via it.each, the
network-error path, the details-surfacing branch, the cancel ⇒ idle
flow, and the unknown-code → catch-all message branch.
* feat(web): add useTerminalLaunch with electron/web detection
Capability-detected wrapper around window.electronAPI.openPath. On
desktop the bridge forwards to shell.openPath, which opens the OS
file manager at the project working directory (per Electron's
contract for directory paths — it is NOT a terminal launcher;
spawning a terminal application is deferred per #451 Non-goals). On
browser builds the hook reports web-fallback so the caller renders
a manual-instruction toast naming the working directory.
Treats any non-empty string return from shell.openPath as ok: false
so platform-specific failures surface the manual fallback toast.
Behavior is exercised end-to-end by the upcoming
ContinueInCliButton tests.
* feat(desktop): expose shell.openPath via electronAPI bridge
Adds an openPath bridge method that the Continue in CLI button (#451)
uses to surface the project working directory in the OS file manager.
shell.openPath is part of Electron's contract and resolves to '' on
success / a non-empty error string on failure; the IPC handler
forwards the result so the renderer can decide between the success
toast and the manual fallback toast without a separate error channel.
Empty / non-string inputs short-circuit to a self-describing error
string so the renderer never needs to worry about undefined-input
crashes from the main process.
Web side: extracts Window.electronAPI into a single global declaration
at apps/web/src/types/electron.d.ts so future bridge methods land in
one place. Two pre-existing inline declare-global blocks
(NewProjectPanel.tsx, providers/registry.ts) are deleted in favor of
that single source of truth — the inline ones each carried a partial
shape of the bridge and were diverging from the desktop preload.
* feat(web): add FinalizeDesignButton, ContinueInCliButton, ProjectActionsToolbar
Project-level toolbar that hosts the two new actions from #451.
Mounted between AppChromeHeader and the chat/workspace split (wiring
lands in the next commit). Per-file actions (Export PDF/PPTX/ZIP,
Deploy) stay in the FileViewer share menu.
FinalizeDesignButton has three idle labels driven by DESIGN.md
existence + staleness, plus a pending state with a spinner and a
cancel link that maps to useFinalizeProject's AbortController. Error
toasts are owned by ProjectView so the button doesn't carry its own
toast surface.
ContinueInCliButton renders disabled with a Finalize-pointing
tooltip when DESIGN.md is missing (so the workflow is discoverable
rather than hidden), enabled when fresh, and enabled with a stale
chip otherwise. Chip text is the spec's canonical "Spec is stale —
regenerate?" — N-turns-ago is deferred per spec §4.6.
Toast.tsx is a tiny transient component that mirrors
PromptTemplatePreviewModal's state-based toast pattern; supports a
secondary details line so daemon error envelopes that carry an
upstream explanation (e.g. Anthropic account-usage cap) can surface
the real reason alongside the daemon's category label.
CSS appends one block to apps/web/src/index.css mirroring the
existing app-project-title token usage; no CSS modules in this
repo (verified by grep).
* test(web): cover ContinueInCliButton states + interaction wiring
Three rendered states (DESIGN.md missing → disabled with the
Finalize-pointing tooltip; DESIGN.md fresh → enabled, no chip;
DESIGN.md stale → enabled with the canonical "Spec is stale —
regenerate?" chip), plus three onClick branches (no-op when
disabled, fires once when fresh, fires once when stale).
Click-handler integration with clipboard / shell.openPath / toast
lives in ProjectView (the button is presentational and takes the
handler in via props), so those are covered by Phase K's wiring +
the manual smoke test rather than the per-component test.
* feat(web): wire Continue in CLI + Finalize buttons into ProjectView
Mounts the new project-actions toolbar between AppChromeHeader and
the chat/workspace split, hidden when workspaceFocused so the
focus-mode artifact view stays uncluttered.
Wires the four hooks (useProjectDetail, useDesignMdState,
useFinalizeProject, useTerminalLaunch) to a single shared toast
surface. handleFinalize reads the request body from the existing
config: AppConfig prop and uses effectiveMaxTokens(config) to match
the chat-flow's maxTokens defaulting; on success it refreshes
useDesignMdState so the toolbar re-renders with the new chip state.
handleContinueInCli builds the literal clipboard prompt, copies it,
opens the working directory via shell.openPath on desktop /
falls through to a manual-instruction toast on browser, and surfaces
shell.openPath failures with a fallback toast that names the path.
Errors lift into the same toast surface (a useEffect tied to
finalize.error) so the daemon's category message + body.error.details
reach the user as the spec's two-line render — covered by hook test
16a in the prior commit.
⌘+Shift+K (mac) / Ctrl+Shift+K (others) is the keyboard
accelerator for Continue in CLI; capture-phase, platform-gated,
no-op when DESIGN.md is missing. Mirrors the existing FileWorkspace
shortcut idiom and does not collide with ⌘+P (Quick Switcher).
* fix(web): distinguish timeout abort from user cancel in useFinalizeProject
Addresses codex P2 finding on PR #974: the catch block treated every
AbortError as a user-initiated cancel and reset to idle silently. If
the internal 130 s timeout fired, users saw no failure signal but the
daemon's synthesis call may still have been in flight.
Adds a timedOutRef set inside the setTimeout callback before
controller.abort(), and branches in the catch: timeout → status
'error' with new TIMEOUT code ("Finalize timed out after 130 s. The
daemon may still be running."), user cancel → existing idle reset.
Reset the ref at the start of every trigger() so a previous timeout
doesn't poison the next call.
Adds one test using vi.useFakeTimers() that advances past 130_001 ms
and asserts the TIMEOUT error surface.
* fix(web): surface clipboard failures by rendering the prompt in the toast
Addresses codex P2 finding on PR #974: handleContinueInCli ignored
copyToClipboard's return value, so when both clipboard paths failed
(restricted browser context / insecure origin) the toast still said
"paste the prompt" though nothing had been copied — leaving users
with no manual-copy recourse in exactly the environments where the
fallback should help.
handleContinueInCli now branches on copyToClipboard's boolean return.
On failure the toast renders the prepared prompt in a scrollable
<pre> block and pins itself open (no auto-dismiss) so the user has
time to select-and-copy manually. Includes a Dismiss button + the
working directory in the secondary details line so the user has the
information needed to proceed.
The folder-open call is skipped on copy failure because there's
nothing to paste yet; the user copies first, then re-clicks Continue
in CLI when they're ready.
Toast component grows an optional Updating VS Code Server to version 41dd792b5e652393e7787322889ed5fdc58bd75b
Removing previous installation...
Installing VS Code Server for Linux x64 (41dd792b5e652393e7787322889ed5fdc58bd75b)
Downloading: 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 0% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 1% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 2% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 3% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 5% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 6% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 7% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 8% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 10% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 11% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 12% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 13% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 14% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 15% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 16% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 17% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 18% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 19% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 20% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 21% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 22% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 23% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 24% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 25% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 26% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 27% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 28% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 29% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 30% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 31% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 32% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 33% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 34% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 35% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 36% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 37% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 38% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 39% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 40% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 41% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 42% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 43% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 44% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 45% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 46% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 47% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 48% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 49% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 50% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 51% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 52% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 53% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 54% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 55% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 56% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 57% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 58% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 59% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 60% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 61% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 62% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 63% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 64% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 65% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 66% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 67% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 68% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 69% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 70% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 71% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 72% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 73% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 74% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 75% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 76% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 77% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 78% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 79% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 80% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 81% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 82% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 83% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 84% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 85% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 86% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 87% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 88% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 89% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 90% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 91% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 92% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 93% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 94% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 95% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 96% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%100%100%
Unpacking: 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11% 12% 13% 14% 15% 16% 17% 18% 19% 20% 21% 22% 23% 24% 25% 26% 27% 28% 29% 30% 31% 32% 33% 34% 35% 36% 37% 38% 39% 40% 41% 42% 43% 44% 45% 46% 47% 48% 49% 50% 51% 52% 53% 54% 55% 56% 57% 58% 59% 60% 61% 62% 63% 64% 65% 66% 67% 68% 69% 70% 71% 72% 73% 74% 75% 76% 77% 78% 79% 80% 81% 82% 83% 84% 85% 86% 87% 88% 89% 90% 91% 92% 93% 94% 95% 96% 97% 98% 99%100%
Unpacked 4009 files and folders to /home/bryan/.vscode-server/bin/41dd792b5e652393e7787322889ed5fdc58bd75b.
Looking for compatibility check script at /home/bryan/.vscode-server/bin/41dd792b5e652393e7787322889ed5fdc58bd75b/bin/helpers/check-requirements.sh
Running compatibility check script
Compatibility check successful (0) prop and the auto-dismiss
TTL is suppressed whenever code is present. CSS adds .od-toast-code
(monospace, max-height 240 with overflow-auto) and .od-toast-dismiss
styling.
Six new Toast tests cover details rendering, code rendering,
no-auto-dismiss when code is present, auto-dismiss when code is
absent, and the Dismiss button affordance.
* fix(web): make ContinueInCliButton disabled-state guidance visible
Addresses mrcfps's PR #974 review: native <button disabled> does
not fire hover/focus events in browsers we ship against, so a
`title` tooltip on the disabled button never surfaces. The only
guidance for the missing-DESIGN.md state was effectively invisible —
defeating the spec's "discoverable, not hidden" intent.
Renders the help text as a visible sibling <span> next to the
disabled button instead. Adds aria-describedby pointing the button
at the hint's id so assistive tech announces the explanation when
the disabled button gets focus. The native `disabled` attribute
stays so the button still can't be clicked or submitted.
CSS adds .project-actions-disabled-hint (muted italic, 11.5px,
matches the existing meta/secondary text style on this surface).
Test asserts the role="note" hint is in the DOM with the canonical
text and that the button's aria-describedby links to its id.
* fix(web): keep ProjectActionsToolbar at natural height inside the .app grid
The .app container was `grid-template-rows: auto 1fr` — only two
rows. Adding ProjectActionsToolbar as a third child between
AppChromeHeader and the chat/workspace split made the toolbar the
2nd grid item, so it took the `1fr` row (filling roughly half the
viewport) while the split got pushed into an implicit auto row at
its content's natural height. Surfaced as a screenshot from Bryan
showing the toolbar's background bleeding across most of the screen.
Extend grid-template-rows to `auto auto 1fr` and pin the split to
`grid-row: 3` explicitly. Now:
- Toolbar visible: row 1 = header (auto), row 2 = toolbar (auto),
row 3 = split (1fr, fills remaining viewport).
- Toolbar hidden via hidden=workspaceFocused → ProjectActionsToolbar
returns null, row 2 collapses to 0px (auto with no content), split
still fills row 3.
No JS changes; existing 609 tests still green.
* fix(web): guard useFinalizeProject state writes against superseded triggers
Addresses mrcfps's PR #974 P1 review on useFinalizeProject.ts:132
(also called out as P1.3 in lefarcen's deep-dive review).
Calling trigger() twice in quick succession aborted the first
controller and swapped abortRef to the new one, but the first
request's later AbortError catch still unconditionally called
setStatus('idle') / setError(null). That cleared the spinner and
re-enabled both toolbar buttons while the replacement finalize was
still pending — defeating the de-duplication this hook was meant to
enforce.
Adds an isCurrent() closure (`abortRef.current === controller`)
and gates every state-write site after the await: success path,
non-OK envelope path, AbortError-timeout, AbortError-cancel, and
network-error all bail early when the trigger has been superseded.
Per mrcfps: "make every state write request-scoped."
Regression test triggers twice in quick succession with a
never-resolving fetch, awaits the first promise (it rejects with
AbortError), and asserts status stays 'pending' rather than
collapsing to 'idle' under the replacement's lifetime.
* fix(desktop): allowlist-validate shell.openPath against registered project roots
Addresses mrcfps's PR #974 P1 review on runtime.ts:305 (also called
out as P1.2 in lefarcen's deep-dive review): the new
`shell:open-path` IPC handler accepted any renderer-supplied
string and forwarded it straight into Electron's `shell.openPath`,
widening the renderer→main trust boundary so XSS or a compromised
renderer dependency could open arbitrary local paths to the user.
Adds an explicit gate around the bridge:
1. validateExistingDirectory(p) — floor check that rejects empty
strings, relative paths, files, apps, and non-existent paths;
realpath-resolves so symlink games can't be used to register
one path and reach another.
2. createProjectRootGate() — Set-backed allowlist of
daemon-validated project working directories. The renderer
calls registerProjectRoot(absDir) once per project mount via
a new IPC method (preload bridge); the main process only
opens paths that pass both the floor check and the allowlist.
ProjectView wires the registration via a useEffect tied to
projectDetail.resolvedDir, so the active project's daemon-supplied
working directory is always the one being approved (not a renderer-
synthesized string).
Threat-model caveat documented in the runtime.ts comment block: an
attacker that fully controls the renderer can also call register
with arbitrary paths. Closing that gap fully requires a daemon-side
round-trip to derive the canonical resolvedDir from the daemon's
project registry, which is deferred to keep this PR focused.
Today's allowlist still defends against accidental misuse, bugs,
and common XSS payloads that don't know to call register first.
Adds apps/packaged/tests/desktop-project-root-gate.test.ts with 13
cases: floor-validation rejection cases (empty / relative / missing
/ file), happy-path resolution, symlink realpath canonicalization,
and the allowlist's register/isApproved/reset semantics. Mirrors
the existing apps/packaged/tests/desktop-url-allowlist.test.ts
pattern from PR #911 — the packaged workspace hosts the test
because apps/desktop has no vitest setup yet.
* fix(daemon): wire request-lifecycle abort signal through finalize route
Addresses mrcfps's PR #974 P1 review on
apps/daemon/src/server.ts:3831-3837 (also called out as P1.1 in
lefarcen's deep-dive review): `POST /api/projects/:id/finalize/anthropic`
called `finalizeDesignPackage(...)` without threading any
request-lifecycle abort, so cancelling the browser fetch only
aborted the UI-side request — the daemon's 60–120 s Anthropic call
kept running and still wrote DESIGN.md after the UI returned to idle.
Adds an AbortController inside the route handler, fired from
`res.on('close')`, and threads its signal into the existing
`signal?: AbortSignal` parameter on `FinalizeOptions`
(finalize-design.ts:70). `callAnthropicWithRetry` already passes
the signal through to the underlying fetch, so a client disconnect
now propagates all the way to the Anthropic SDK call.
Listener-event choice: `res.on('close')` is the canonical event
for "client disconnected before response was sent" in Express. The
common alternative `req.on('close')` fires whenever the *request*
stream finishes — for POST routes that means as soon as the
body-parser middleware drains the body, well before the route does
any work. Using req.on('close') would have flipped the abort
controller in every successful run; the test caught this empirically.
Caveat documented in the route's comment block: an abort fired
*after* the upstream response has been received but *before* the
atomic write completes still allows the write to land. The SDK
contract bounds the network round-trip, not the post-network disk
handoff.
Adds tests/finalize-route-abort.test.ts: spins up the test server,
mocks global fetch to capture the daemon-side AbortSignal at the
Anthropic call, sends the request via raw http (so we can destroy
the underlying socket), waits until the server reaches the
Anthropic call, then destroys the socket and asserts that the
daemon-side signal received an abort event within 5 s.
Three pre-existing project-watchers chokidar tests show flaky
timeouts under full-suite concurrency but pass in isolation;
unrelated to this fix.
* fix(daemon): refactor finalize-route-abort test to satisfy strict TS narrowing
The CI typecheck (`pnpm --filter @open-design/daemon typecheck`,
which runs both tsconfig.json and tsconfig.tests.json) caught what
my pre-push validation missed: TS narrowed `capturedSignal` to
literal `null` because vitest's mockImplementation closure can't
prove its callback runs, leaving the bare `let capturedSignal:
AbortSignal | null = null` permanently typed at its initial value.
At line 184 (`expect(capturedSignal?.aborted).toBe(true)`) the
right-hand side of the optional-chain became unreachable, and TS
flagged it as `Property 'aborted' does not exist on type 'never'`.
Switches to the standard ref-object pattern
(`const capture: { signal: AbortSignal | null } = { signal: null }`).
TS narrows let bindings inside closures conservatively but treats
object-property writes as opaque, so `capture.signal` reads
correctly across the closure boundary. Logic is unchanged.
(Pre-push oversight: ran `pnpm --filter @open-design/web typecheck`
but not the full repo `pnpm typecheck` after the daemon test
landed; the daemon's own typecheck would have caught this. Adding
`pnpm typecheck` back into the standard pre-push checklist.)
* fix(desktop): make shell.openPath gate daemon-controlled and reject .app bundles
Addresses lefarcen + mrcfps PR #974 P1 reviews on the previous path
allowlist (commit
|
||
|
|
726a9ab5be |
fix: hide surface filter tabs with zero count in Design Systems view (#922) (#965)
* fix: prevent comment popover header overflow when label is too long - Add min-width: 0 and overflow: hidden to comment-popover-head div - Add text-overflow: ellipsis and white-space: nowrap to strong and span - Add flex: 0 0 auto to close button to keep it fixed width - Add title attribute to header div and close button for hover tooltip * fix: hide surface filter tabs with zero count in Design Systems view (#922) * fix: reset surfaceFilter and category when counts become zero Handle dynamic systems changes where the currently selected surface filter has zero items. When surfaceFilter is no longer valid, fall back to 'all'. Also reset category to 'All' when it is no longer present in the filtered list. Fixes review feedback on PR #965. * fix: move categories memo above useEffect to avoid TDZ categories was referenced in the useEffect dependency array before its declaration, causing a Temporal Dead Zone error. Move the categories useMemo above the effect. |
||
|
|
0dfa922208 |
fix(landing-page): correct SEO canonical, add robots.txt + favicons (#1061)
The Astro `site` default was `https://open-design.dev`, but the live Cloudflare Pages deployment is bound to `open-design.ai`. As a result every `<link rel="canonical">`, `og:url`, and sitemap entry pointed at the wrong origin, and search engines saw no robots.txt or favicon at the apex. - astro.config.ts: switch the default `site` to `https://open-design.ai` and document that `OD_LANDING_SITE` stays as the preview-deploy hatch. - astro.config.ts: filter `/og/` out of the sitemap; that route is the 1200x630 OG screenshot surface and already carries `noindex`. - public/robots.txt: allow-all + Disallow `/og/` + canonical sitemap URL. - public/favicon.svg: 32x32 SVG mark mirroring the on-page `Ø` brand glyph (ink ground, paper-stroked italic ellipse, coral slash). - public/apple-touch-icon.png: 180x180 PNG rendered from the same geometry without the rounded corners (iOS applies its own mask). - index.astro: link `/favicon.svg` (`type="image/svg+xml"`) and `/apple-touch-icon.png` from the document head. Verified locally with `pnpm --filter @open-design/landing-page build`: the rendered head emits `https://open-design.ai/` for canonical and `og:url`, the sitemap contains exactly the canonical `/` URL, and the two `landing-page-deploy.yml` verification scripts (no client JS, >=16 Cloudflare resized image URLs, no local /assets/*.png) still pass. Co-authored-by: joey <joey@joeydeMacBook-Air.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
cc343f8828 |
ci: optimize beta release packaging cache (#1095)
* ci: optimize beta release packaging cache * fix: version windows builder cache * fix: forward linux app version in container |
||
|
|
c61ba320fd |
feat(nix): Add official flake with home-manager and NixOS support (#402)
* nix: add official flake with home-manager and nixos modules
* Pin pnpm version
* Format README.md
* Populate PATH files to discover installed CLIs
* Revert "Populate PATH files to discover installed CLIs"
This reverts commit 18d88781a88b8781913cf5a8b680dfb38eabf7e4.
* Fix missing sqlite issue
* Fix system issue
* Reapply "Populate PATH files to discover installed CLIs"
This reverts commit
|
||
|
|
020ecb1d6a |
feat(provider-models): sort fetched models alphabetically (#1097)
Co-authored-by: haolin122 <hl6593@nyu.edu> |
||
|
|
617fb043fe |
feat(settings): add fetch models button for BYOK providers (#1034)
* feat(settings): add fetch models button for BYOK providers * fix(settings): exclude Ollama from fetch models, add manual-entry hint * fix(provider-models): classify non-JSON upstream errors by HTTP status * fix(i18n): drop redundant English overrides from non-English locales * fix(provider-models): allow ollama through allowlist, return unsupported_protocol --------- Co-authored-by: haolin122 <hl6593@nyu.edu> |
||
|
|
e2713cb0f4 |
refactor(web): flush sidebar tabs to edge with internal scrolling (#1038)
Remove borders, padding, and overflow from the sidebar container so the NewProjectPanel tab component fills the full sidebar width. Move scroll responsibility into the tab body so only the content panel scrolls, not the entire sidebar. Tab buttons sit flush at the top edge. Co-authored-by: 克隆(宗可龙) <zongkelong@soyoung.com> Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
49b55761d3 |
feat(daemon+web): add install/uninstall for skills & design systems (#1003)
* feat(daemon+web): add install/uninstall for skills & design systems (#497 Phase 2) Phase 2 of the Library Settings feature. Adds the ability to install skills and design systems from GitHub repos or local paths, and uninstall user-installed items. Built-in items remain read-only. Daemon: - Multi-directory scanning: built-in + ~/.open-design/{skills,design-systems} - Install from GitHub (git clone --depth 1) or local path (symlink) - Uninstall removes cloned dirs or symlinks under user dir only - 4 new API routes: POST install + DELETE for skills and design systems - Reuse isBlocked() from linked-dirs.ts for path safety Web: - Install form with GitHub/Local tab switch in Library settings - Source badge ("Built-in" / "Installed") on cards - Uninstall button (trash icon) on installed items only - Native-language i18n for all 17 locales Contracts: - Add source field to SkillSummary and DesignSystemSummary - Add InstallInput, InstallSkillResponse, InstallDesignSystemResponse, UninstallResponse types * fix(daemon): follow symlinks in scanners and respect OD_DATA_DIR for user libraries Two fixes from PR review feedback: 1. Scanners (skills.ts, design-systems.ts) used Dirent.isDirectory() which skips symlinks on macOS/Linux, so locally-installed items never appeared in API responses. Now also accepts isSymbolicLink() entries. 2. User library directories were hardcoded under os.homedir(), ignoring OD_DATA_DIR. This broke test isolation and packaged runs. Now derived from RUNTIME_DATA_DIR so they respect the data dir override. |
||
|
|
63a9a08b83 |
fix(daemon): prefer opencode-cli over opencode binary so OpenCode Desktop installs resolve to the CLI (#814) (#818)
OpenCode Desktop ships two binaries on PATH after install:
- `opencode` — GUI launcher; double-clicking it opens the
desktop app, never reads stdin.
- `opencode-cli` — headless CLI; speaks the
`run --format json --dangerously-skip-permissions -`
protocol the daemon expects.
The OpenCode AGENT_DEF set `bin: 'opencode'`, so
`resolveAgentExecutable` resolved the GUI launcher for every user
with the desktop install. The daemon happily spawned that binary,
but the GUI launcher discards stdin and never emits the JSON event
stream — so the agent silently produced no output for the entire
conversation. Issue #814 tracks the user-visible symptom.
Fix: declare `bin: 'opencode-cli'` and `fallbackBins: ['opencode']`
on the OpenCode adapter. `resolveAgentExecutable` walks `bin`
first, then each fallback in order, so this gives us:
- Desktop install (both binaries on PATH) → resolves
`opencode-cli` (the CLI), GUI is ignored. The #814 fix path.
- Legacy CLI-only install (only `opencode` on PATH, no
`-cli` suffix) → falls through to `opencode`, which is the
actual CLI in that install shape, so the daemon keeps working
unchanged.
- Neither installed → falls through to null, same as before.
Same mechanism Claude Code uses to fall back to `openclaude`
(issue #235), so this is well-trodden ground in the codebase.
Tests:
- One declarative test asserts `opencode.bin === 'opencode-cli'`
and `opencode.fallbackBins.includes('opencode')` — runs on every
platform including Windows, catches anyone who accidentally
flips `bin` back to the GUI name.
- Two filesystem-backed tests pin the resolution priority: when
both binaries are on PATH (the desktop case) `opencode-cli`
wins; when only the legacy `opencode` is on PATH the fallback
takes over.
- Existing comment guarding "most agents have a single binary"
updated to remove `opencode` from the example list.
Verified locally:
- daemon vitest: 993/993
- daemon `tsc -p tsconfig.json` and `tsc -p tsconfig.tests.json`: both clean
- i18n-check passes
Reported by @giuliastro, diagnosed by @lefarcen who also asked for
the `opencode-cli --version` output in the issue thread — happy to
narrow this further if their reply turns up subcommand differences
between the two binaries.
Fixes #814
|
||
|
|
d30cf18941 |
feat(skills): add release notes one pager + supporting docs (#873)
* add release notes one pager skills and supporting templates * Update SKILL.md and template.html for release notes: refined upgrade note and enhanced responsive design elements in the template. * Update SKILL.md to clarify topnav CTA label and href destination requirements, and enhance instructions for handling missing details in release notes. * Refactor buttons to links in release notes templates and checklist: updated button elements to anchor tags for improved navigation and added explicit checklist items for handling missing sections in release notes. * Update topnav link labels in SKILL.md and template.html for release notes: changed placeholder links to specific destinations (#added, #fixed, #upgrade-note) for improved navigation clarity. * Enhance SKILL.md by adding section ID requirements for release notes structure: ensure each section (Added, Fixed, Breaking changes, Known issues, Upgrade note) includes a corresponding `id` attribute for improved navigation and consistency. * Update example.html for release notes: refine title, update logo text, and modify section summaries to indicate no entries or actions required. * Add consistent spacing for lists in template.html and update checklist.md to require matching id anchors for release-note sections * Update SKILL.md, example.html, checklist.md, and layouts.md for improved clarity and structure in release notes * fix: p2 anchor + p2 dead links * fix: add id to section block * fix: replace placeholder URLs with real destinations in SKILL.md, checklist.md, and layouts.md * fix: update release notes guidelines to omit CTAs without real destinations and ensure no placeholder URLs remain * fix: clarify CTA handling in release notes and remove placeholder links from template * fix: add guidelines for using Layout 7 in release notes to remove header CTA row * fix: add 'release-notes-one-pager' to localized content IDs for French, Russian, and German * fix: remove 'release-notes-one-pager' from localized content IDs for French, Russian, and German |
||
|
|
74c9158410 |
fix: increase top padding in modal footer for better visual balance (#957)
* fix: increase top padding in modal footer for better visual balance Fixes #913 The primary action button in modal footers (e.g., Deploy to Cloudflare Pages) was sitting too close to the top border, making the footer area feel cramped. Changes: - Increased top padding from 12px to 16px in .modal-foot - Kept bottom padding at 12px to maintain existing bottom spacing - Left and right padding unchanged (uses --modal-padding variable) This provides better visual balance and makes the primary action button feel less cramped against the divider line above it. * fix: add fallback value for --modal-padding variable The --modal-padding variable is only defined under .modal-settings, but .modal-foot is also used by generic modals (FileViewer, SketchEditor). Without a fallback, the padding declaration is invalid for those modals. This adds a fallback value of 22px to ensure the footer padding works for all modal types, not just settings modals. |
||
|
|
5390541f34 |
fix daemon browser opener on Windows (#953)
Co-authored-by: Sapientropic <194952804+Sapientropic@users.noreply.github.com> |
||
|
|
ad2c03d106 | fix(packaged): build desktop before typecheck (#1093) | ||
|
|
84f4b03cb9 |
fix: prevent project type selector arrows from overlapping tabs (#1091)
* fix: prevent project type selector arrows from overlapping tabs * fix: apply padding to parent container for arrow safe zone |
||
|
|
68dc78030a |
fix(web): prevent creating new conversation when current is empty (#1086)
* fix(web): prevent creating new conversation when current is empty * fix(web): use ref to track loaded conversation for race-free empty check * fix(web): eagerly update ref on new conversation to prevent rapid-click duplicates |
||
|
|
039cc3d674 |
fix(settings): add install onboarding links for unavailable local CLIs (#985)
* fix(settings): add install onboarding links for unavailable local CLIs * fix(settings): rename Claude config dir label to config directory --------- Co-authored-by: mrcfps <mrc@powerformer.com> |