Commit graph

1039 commits

Author SHA1 Message Date
Marc Chan
619087a6b4
refactor(web): split global CSS by ownership (#2609)
* refactor(web): split global CSS by ownership

* test(web): expand CSS imports in style checks

* fix(web): keep privacy consent banner above modals
2026-05-25 05:48:28 +00:00
Hashem Aldhaheri
5d032aedec
fix(web): treat plugin preview 404 as unavailable, matching skill helper (#2840)
Bundled plugins whose manifest declares `preview.entry` but ship no
example HTML (e.g. example-live-artifact pointing at ./index.html that
isn't in the package) made the daemon return 404 on
/api/plugins/:id/preview. The web turned that into the generic
"Couldn't load this example. The example HTML failed to fetch." error
modal, which suggested a transient failure even though Open Design was
running fine and the asset was simply absent.

Mirror the symmetric treatment fetchSkillExample already has from
#897: map 404 -> { unavailable: true, kind: 'html' } in both
fetchPluginPreviewHtml and fetchPluginExampleHtml, and forward the
typed unavailable view through PluginExampleDetail so PreviewModal
renders its calm "no shipped preview" placeholder. Other HTTP failures
keep their existing { error: 'HTTP N' } shape so genuine errors still
surface a Retry affordance.

Red test was added first (registry.test.ts) and confirmed to fail on
main with the old "HTTP 404" payload before this commit was applied.
2026-05-25 05:19:01 +00:00
Chris Seifert
0f5ac37c22
Capture native (Swift) source in the GitHub design import (#2847)
A design system imported from a SwiftUI repo could never be published. The
import's file scorer was web-only, so every .swift file scored 0 while the
repo's config dotfiles (.zenflow, .vscode, .zed) scored on the generic text
bonus and won the top-N selection. Even when a source file was selected, the
snapshot writer's text-file allowlist didn't include .swift, so it was dropped.
The result: snapshots were all config dotfiles, which the project files API
hides, so the publish gate saw zero evidence snapshots and stayed blocked no
matter how many times the intake ran.

Three layers fixed in tools-connectors-cli.ts:
- scoreDesignFile now scores native/design-token source (Swift, Kotlin, Go,
  Rust, etc.), with a high boost for token files like ColorSystem.swift,
  Typography.swift, Spacing.swift.
- shouldSkipRepoPath skips editor/CI/agent tooling dirs (.vscode, .zed, .idea,
  .zenflow, .github, .husky, ...) so their files stop crowding out real source.
- isTextSnapshotPath recognizes native source extensions so selected files are
  actually written.

Also teach the design-system swatch extraction to read SwiftUI colors:
swift-colors.ts parses Color(red:green:blue:), Color(hue:saturation:brightness:)
(HSB), and Color(white:), evaluating decimal, hex-byte, and division component
expressions (0xF4 / 255, 220 / 360), and design-systems.ts uses it as a new
swatch form. scoreDesignFile, shouldSkipRepoPath, and isTextSnapshotPath are
exported for unit tests.

Verified against a real SwiftUI repo: the intake now captures ColorSystem,
TypographySystem, SpacingSystem and the view/model source instead of config
dotfiles.
2026-05-25 05:17:47 +00:00
Chris Seifert
472e10a991
Polish the design system review panel (#2848)
* Polish the design system review panel

- Float the "Needs work" feedback form below the review buttons as a popover
  instead of cramming it into the section header row.
- Collapse a section once it is marked "Looks good" so reviewed sections tidy
  away. Clicking Looks good always collapses; the chevron still re-expands.
- Make the inline preview iframe scrollable and drop the click-to-open wrapper.
- Make the whole section header the expand/collapse trigger via a stretched
  button, with the title rendered as display-only text (no nested buttons).
- Rework the publish header: name the system in the h1 ("Review <name> design
  system"), move publishing into a Publish button inline with the h1, and make
  the disabled-state tooltip say what is actually needed.
- Animate the generation mark's small block (hop, spin, bounce) while waiting,
  with a reduced-motion guard.
- Tighten the title/subtitle gap to 2px.

* fix(web): reopen a regenerated design-system section instead of leaving it collapsed

renderReviewCard collapsed any section whose stored review decision was "looks-good", reading only the decision and not the section's current status. When a section is regenerated after approval its status moves back to "updated", but the stale "looks-good" decision kept it collapsed, so the "review it again before publishing" notice and the review buttons stayed hidden. A regenerated change was easy to miss in the re-review queue.

reviewedGood now also requires !needsAttention, so a section that moved back to a needs-attention status reopens by default. Manual collapse with the chevron and the force-open during an active run are unchanged.

Adds a regression test that marks a section looks-good, regenerates its preview file, and asserts the section stays expanded. It is red on the old decision-only check and green with the status guard.

* fix(web): keep the disabled-publish guidance reachable instead of on the disabled button

The publish button carried its disabled-state guidance in a title attribute, but the button is disabled in exactly the case that sets the title (!published && !githubEvidence.ready). A disabled control does not fire hover or focus, so the tooltip never appeared, and the explanation for why publishing was blocked went missing right when it was needed.

The guidance now lives on a wrapper span that is never disabled. The disabled button is set to pointer-events: none so a hover falls through to the wrapper and surfaces its title, and the wrapper carries cursor: not-allowed so the blocked cursor stays.

Adds a regression test that asserts the guidance sits on a non-disabled wrapper holding the button, not on the button itself. It is red when the title is on the disabled button and green with the wrapper.
2026-05-25 05:17:17 +00:00
Vitaly Sinitsin
02baffd7fe
reduced horizontal padding, a bit smaller font and smaller gap between elements in unitrow (#2862) 2026-05-25 05:16:46 +00:00
jasonyang365
590cfcb46f
fix(daemon): run Trae CLI ACP with yolo (#2856)
Co-authored-by: AI Bot <bot@example.com>
2026-05-25 04:06:52 +00:00
Chris Seifert
085963a1e2
fix(web): show published design systems as "Published" on project cards (#2849)
A published design-system project still showed its last generation run status on its card. When that run had failed, the published system read as "Failed" on the home Recent projects strip and in the Projects grid. The system is published and live, so showing a stale run failure there is wrong.

Cards now read "Published" when the backing design system's status is published, keyed off the design system summary instead of the project run status. Every other project keeps its run status.

A shared isPublishedDesignSystemProject helper lets the home strip and the Designs grid apply one rule. The label uses a new designs.status.published key in the locale files, with a green style that matches the existing succeeded color.
2026-05-25 03:14:26 +00:00
Chris Seifert
af997b7cf5
feat: rename editable design systems from Settings + od CLI (#2812)
* feat: rename editable design systems from Settings + od CLI

Editable (user-created) design systems can already be renamed via
PATCH /api/design-systems/:id, but the capability was not surfaced
in the UI or CLI.

- Settings -> Design Systems: editable cards show a hover-reveal pencil
  next to the name that opens a rename modal; built-in cards stay
  read-only. Reuses common.rename/save/cancel (no new i18n keys).
- CLI: 'od design-systems rename <id> --title <new> [--json]', backed by
  a unit-tested pure arg parser (design-system-rename-args.ts).

Both surfaces call the existing PATCH endpoint.

* Route od design-systems --help and -h to the rename-aware usage

The dispatcher only special-cased the `help` subcommand, so
`od design-systems --help` and `-h` fell through to the generic library
list, which advertises only `list` and `show`. That left `rename` off the
main discovery path even though this PR ships it.

Pulled the usage text and the help-arg check into a small pure module so
`help`, `--help`, and `-h` all render the same rename-aware usage, and added
a test that asserts the flag forms route to help and that the text lists
rename. The pure module keeps the assertion off process.exit / console.log.

* Reject --title flag-as-value and keep the rename modal open on failure

Two rename edge cases from review.

CLI: parseDesignSystemRenameArgs took the next token after --title
unconditionally, so `rename user:acme --title --json` parsed the title as
"--json" and could rename the system to a flag name instead of failing usage
validation. A separate --title value must now be a real token; a leading dash
means the user uses the --title=<value> form. Malformed inputs return null,
which the CLI surfaces as a usage error.

Web: commitRename closed the modal unconditionally, but updateDesignSystemDraft
returns null on any non-OK response or fetch failure, so a transient error
dropped the typed title with no feedback. The modal now stays open with the
title intact and shows an inline error on failure, matching the existing import
error pattern in this component. Added tests for the flag-as-value rejection
and for the failed-update modal state.

* Gate the rename completion on the active modal session

commitRename mutated the shared modal state after awaiting the PATCH, so a
slow rename for system A could resolve after the user cancelled and opened a
rename for system B, then close B's modal or show A's failure inside B's
dialog.

A monotonic session token (bumped whenever the modal opens or closes) is now
captured before the request and rechecked after it resolves. A stale
completion skips all modal-state updates. The list update for a successful
rename still applies, since that reflects a real server-side change regardless
of which modal is open. Added a regression test that opens a second rename
before the first PATCH settles and confirms the newer modal is untouched.

* Localize the rename-failed error instead of hardcoding English

The inline rename error was hardcoded English on a Settings surface that
otherwise runs through useT(), so non-English users saw English while the
rest of the panel was localized.

Added settings.designSystemRenameFailed to the typed dictionary and all 19
locale files, and the modal now reads it through t(). The translations are
adapted from each locale's existing settings.rescanFailed string ("X failed.
Check the daemon and try again."), swapping the verb to rename, so the daemon
and retry wording matches what those locales already ship.
2026-05-25 03:13:35 +00:00
kami
024e6d86a9
fix: validate plugin connector refs in doctor (#2164)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
ci / Detect CI change scopes (push) Successful in 0s
nix-check / build (push) Failing after 1s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 1s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
* fix: validate plugin connector refs in doctor

Co-authored-by: multica-agent <github@multica.ai>

* chore: refresh pool review queue

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-24 16:28:00 +00:00
Chris79OG
99921e1883
Fix Windows connector CLI tests (#2809)
Co-authored-by: Christian Scherkl <christianscherkl79@gmail.com>
2026-05-24 14:42:28 +00:00
zqyaym
b22c7713db
fix(web): prevent preview iframe from stealing focus on load (#2792)
* Fix preview iframe focus stealing

* Fix preview focus guard for URL-loaded HTML previews

Focus guard was only injected via the srcdoc path, but the default
URL-load path bypasses buildSrcdoc entirely. Add htmlNeedsFocusGuard
detection so focus-stealing HTML is routed through srcdoc where the
guard can suppress window.focus/element.focus calls.

* Widen focus guard detector to cover all .focus() call patterns

The previous regex only matched window.focus() and document.focus(),
missing document.body.focus(), querySelector().focus(), and other
chained focus calls. Broaden to match any `.focus(` so the default
URL-loaded preview path is forced to srcDoc for all focus-stealing HTML.

* Conservatively force srcDoc for HTML with external script references

When the HTML contains <script src=...>, we cannot inspect the linked
file for focus-stealing calls. Force the srcDoc path so the focus guard
intercepts any .focus() calls from external scripts.

---------

Co-authored-by: JoeyZhu <15500388+acthenknow@user.noreply.gitee.com>
2026-05-24 14:37:08 +00:00
leessju
8e3d1360bd
fix(daemon): close # Instructions block with an explicit do-not-echo guard (#2827)
The composed chat prompt prepends a '# Instructions (read first)'
block in front of '# User request' so a single user message carries
both the system rules and the actual request — the shape every agent
CLI (Claude, Codex, OpenCode, Gemini) expects on stdin.

In practice claude-opus-4-7 (and a few other instruction-tuned
models, particularly with --include-partial-messages on the stream)
start their reply by echoing the top of that user message verbatim.
The chat UI then shows the system prompt as a literal block leading
the visible answer, e.g.:

  Instructions
  Always respond in Korean. Use Korean for all explanations…
  …Maintain full orthographic correctness…
  ).네, 완료했습니다. 전달하신 4가지 보강 포인트를 …

(The closing token of the instructions block runs straight into the
real answer without a newline — the telltale of a model-side echo
rather than a UI render bug.)

Close every Instructions block with one trailing line:

  (Do not quote, restate, or echo the # Instructions block above in
  your reply. Begin your response with the answer to the # User
  request below.)

This kills the regression in practice without changing the turn
shape (still one user message), so no agent CLI plumbing has to move.

Tested via tests/chat-route.test.ts — pins the literal guard string
so a future refactor cannot silently drop it.

Co-authored-by: nicejames <nicejames@gmail.com>
2026-05-24 14:30:58 +00:00
张东明
53dfb8808c
fix(#2361): patch both causes that present as blank preview (#2805)
* fix(web): treat external <script src> as needing the sandbox shim (#2361)

Agent-emitted HTML artifacts that read localStorage from an external
boot.js / app.js currently render blank in the preview pane because the
URL-load iframe's sandbox lacks allow-same-origin and htmlNeedsSandboxShim
only scans the HTML string. The "Known limitation" comment already
anticipated this case; #2361 is the reported case justifying the cost.
Conservatively route any HTML with an external <script src=> through the
srcDoc path so injectSandboxShim is in place before the script runs.

* fix(web): stop infinite srcDoc re-activate loop that blanks animated previews (#2361)

The lazy srcDoc transport iframe fires its 'load' event twice for one successful activation: once when the empty transport shell HTML loads, and again when our own document.open()/write()/close() inside the shell finishes. PR #2699 made the onLoad handler unconditionally reset activatedSrcDocTransportHtmlRef.current = null so that switching preview -> source -> preview (which remounts the iframe as a brand new DOM node) would re-activate the new shell. But that reset also fires on the second load of an unchanged frame, which re-triggers activateSrcDocTransport, which re-runs document.open/write/close, which re-fires the load event, ad infinitum. In one local reproduction the dedupe ref was cleared and re-activated 4763 times before the test was stopped.

Each iteration rebuilds the document, which restarts every CSS animation from its 'from' keyframe. Designs that use 'animation-fill-mode: both' with 'from { opacity: 0 }' (very common for editorial hero fades) therefore stay at opacity 0 forever and the preview reads as blank. In React strict mode + HMR (pnpm tools-dev) the symptom is visible high-frequency flashing; in a packaged production build the loop runs cool enough that the user only sees a stable blank — both are the same root cause.

This change keeps PR #2699's remount-after-Source-toggle behavior by tracking which iframe DOM node we last reset for in a new srcDocFrameDedupeResetForRef. The reset runs exactly once per freshly mounted iframe (the first load is the shell HTML) and is skipped on every subsequent load of the same node (those are the document.write loads). Switching source back to preview remounts the iframe as a fresh DOM node, so the reset still happens and PR #2699's regression test still passes; ordinary srcDoc renders no longer enter the infinite loop.

Refs #2361

* chore: re-trigger CI

Upstream's fork-pr-workflow-approval check hit a transient 401 Bad credentials when calling the GitHub API on the previous run; the underlying workflow has nothing to do with the code in this PR. Pushing an empty commit to re-run the workflow chain.

* chore: re-trigger CI (retry transient checkout race)

First re-trigger surfaced a transient race in ci / Build workspaces (actions/checkout failed to fetch refs/remotes/pull/2805/merge with 'could not read Username for https://github.com'). Other concurrent fork PRs' Build workspaces all passed on the same upstream runner, so this is not a token/permission infra issue — likely just a per-PR fetch race after the previous push. Pushing a second empty commit to retry the workflow chain.
2026-05-24 14:29:36 +00:00
Chris Seifert
0afaf3bb7e
feat: pin custom design systems to top and read swatches from color tables (#2817)
* feat: pin custom design systems to top and read swatches from color tables

Two changes to Settings -> Design Systems.

Custom (user-created) systems now sort to the top of the list instead of
sitting under the built-in catalog. A small pure helper
(orderDesignSystemGroups) floats any group that holds an editable system
above the rest; everything else keeps its order.

Swatches now show for systems whose DESIGN.md keeps colors in a markdown
table. extractSwatches only understood inline forms before, so table
palettes came back empty and the cards showed no color squares. Added a
table-row pass that reads the first hex in a row as the value and the
first plain text cell as the name. Inline forms still win when a file
mixes both.

* Sort editable systems first within a category group

The group-level sort floated any category holding a user system to the top,
but items inside a group rendered in their incoming (alphabetized) order. A
user system that shares a category with built-ins (its DESIGN.md can set any
category) still landed below Apple/Airbnb in that group, which misses the
point of pinning custom systems to the top.

orderDesignSystemGroups now also sorts items editable-first within each
group, stable so built-ins keep their alphabetical order. The display order
comes from the helper output, so this covers the import path re-alphabetizing
before grouping without touching it.
2026-05-24 14:25:19 +00:00
Chris Seifert
c5ea97a0e8
feat: add "Use system fonts" dismiss to the missing-fonts banner (#2816)
* feat: add "Use system fonts" dismiss to the missing-fonts banner

The 'Missing brand fonts' banner only offered Upload fonts. Now there's
a 'Use system fonts' button next to it that hides the banner for that
project (saved in localStorage) when you're fine with the fallback. It
does not change how fonts render; it just stops nagging.

Pulled the banner into a shared MissingBrandFontsBanner component and
gave the warning card the bottom margin it was missing.

* Resync the missing-fonts banner dismissal when the project changes

FileWorkspace renders MissingBrandFontsBanner without a per-project key, so
the same instance is reused as the user moves between projects. The dismissal
state was read with useState only on first mount, so dismissing project A left
the banner hidden for project B in the same panel even though only A was
written to localStorage. That broke the per-project scoping this banner adds.

Added a useEffect that re-reads the dismissal whenever projectId changes, and a
rerender test that dismisses p1, switches to p2 (banner returns), and switches
back to p1 (stays dismissed).
2026-05-24 14:25:04 +00:00
icc
8615141f94
fix(web): preserve ingest select chevron (#2733)
Co-authored-by: icc <iccccccccccccc@users.noreply.github.com>
2026-05-24 14:23:30 +00:00
icc
a83e9cf9f1
fix(web): clip inline model switcher labels (#2732)
Co-authored-by: icc <iccccccccccccc@users.noreply.github.com>
2026-05-24 14:22:55 +00:00
kami
94421f9676
fix(web): expose hidden edit targets in layers (#2067)
* fix(web): expose hidden edit targets in layers

Co-authored-by: multica-agent <github@multica.ai>

* fix(web): respect visibility overrides in edit layers

Co-authored-by: multica-agent <github@multica.ai>

* fix(web): keep hidden layout targets editable

Co-authored-by: multica-agent <github@multica.ai>

* fix(web): address hidden layer review followups

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-24 14:22:29 +00:00
kami
1b9caf50a0
fix(packaged): honor OD_DATA_DIR in desktop runtime (#2162)
* fix(packaged): honor OD_DATA_DIR in desktop runtime

Co-authored-by: multica-agent <github@multica.ai>

* fix(packaged): scope OD_DATA_DIR by namespace

Co-authored-by: multica-agent <github@multica.ai>

* fix(packaged): reject relative OD_DATA_DIR overrides

Co-authored-by: multica-agent <github@multica.ai>

* fix(packaged): preserve scoped OD_DATA_DIR overrides

Co-authored-by: multica-agent <github@multica.ai>

* fix(packaged): surface OD_DATA_DIR validation as PackagedPathAccessError

Relative OD_DATA_DIR in packaged mode now throws PackagedPathAccessError
instead of a plain Error. apps/packaged/src/index.ts main() only routes
PackagedPathAccessError to dialog.showErrorBox, so the prior plain Error
made the app exit silently for GUI launches with an invalid override.

Extract PackagedPathAccessError into apps/packaged/src/errors.ts so
paths.ts can throw it without an inter-module value cycle with launch.ts.

Co-authored-by: multica-agent <github@multica.ai>

* fix(packaged): make OD_DATA_DIR absolute-path guard platform-aware

The previous guard ran `win32.isAbsolute(expanded)` unconditionally on
every platform, so on macOS/Linux a value like `C:\Users\Fred\OD` passed
the check (win32 considers it absolute) and silently flowed into
`join(expanded, "namespaces", namespace, "data")`, producing a
cwd-relative POSIX path instead of throwing.

Branch the check on `process.platform === "win32"` so Windows paths are
only accepted on Windows. Update the existing Windows-themed test
fixtures to stub `process.platform = "win32"` (the omission was what
masked this bug) and add a regression that stubs `linux` and asserts
`C:\foo` and `\\server\share` are rejected as PackagedPathAccessError.

Co-authored-by: multica-agent <github@multica.ai>

* fix(packaged): reject mismatched scoped OD_DATA_DIR

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: kami.c <kami.c@chative.com>
2026-05-24 14:21:54 +00:00
Chris Seifert
fb5d4bbd16
Surface GitHub repo connect/import CTA in design system projects (#2820)
* Surface GitHub repo connect/import CTA in design system projects

Design systems built from a GitHub repo used to show a dead-end banner
("Waiting for GitHub connector evidence") when the repo files were never
pulled in. It said nothing actionable and blocked publishing with no way
forward.

That state now reads as a CTA, in both the Design System review banner and
the project's Start-a-conversation empty state. When GitHub is not
connected, both say "Connect your repo to pull aspects of your design
system" with a Connect GitHub button that opens Connectors. Once GitHub is
connected the copy switches to "GitHub is connected" with an Import repo
button that drops the bounded github-design-context intake instruction into
the chat composer, so you review it and send. The agent runs the pull, the
snapshots land, and the prompt clears itself.

The copy lives in one helper so the banner and the chat card never drift,
and ProjectView reads connector status (refreshing on window focus) only
while the CTA is showing.

* Build the import prompt from linked repo URLs, not the manifest

The Import repo prompt hard-coded "read context/source-context.md first,"
but the CTA shows for any incomplete GitHub-backed system, and that manifest
is not always written yet. So clicking Import repo could start the recovery
on a file that does not exist.

buildRepoImportPrompt now builds the instruction from the design system's
provenance.githubUrls, which is always present when the CTA shows (the CTA
gates on a non-empty repo list). It points at context/source-context.md only
when that file is actually present, and otherwise tells the agent to run the
bounded github-design-context intake for the linked repos directly.

* Hold the connect-repo CTA neutral until the status resolves

githubConnected seeded as false, so on first paint every GitHub-backed
project rendered the CTA as Connect GitHub and routed clicks to Connectors
until /api/connectors/status came back. An already-connected user who
clicked fast got bounced to Connectors instead of the Import repo handoff.

githubConnected is now tri-state (undefined while loading). repoConnectCopy
returns a neutral "Checking GitHub..." label for that window, the CTA button
is disabled until the status resolves, and handleConnectRepo guards the
undefined case. Once the fetch lands the button flips to Connect or Import.
Tests cover the pending label, the disabled button, and the no-op click.
2026-05-24 14:20:35 +00:00
bougie-atxp
d28acdc879
Fix Gemini BYOK model URL normalization (#2761)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
ci / Detect CI change scopes (push) Successful in 1s
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 1s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
Co-authored-by: ATXP Earn Clowdbot <bougie-atxp@users.noreply.github.com>
2026-05-24 03:23:36 +00:00
jasonyang365
840019c8e2
Add Trae CLI as an ACP coding-agent adapter (#2729)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
ci / Detect CI change scopes (push) Successful in 0s
nix-check / build (push) Failing after 1s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 1s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 1s
ci / Runtime trace (push) Has been skipped
* Add Trae CLI ACP adapter

* Add Trae CLI binary override support

* Update mature ACP MCP discovery test

* Stabilize Orbit summary tracking test

---------

Co-authored-by: AI Bot <bot@example.com>
2026-05-23 15:17:42 +00:00
Muhammad Adnan R
f8d71fdcb3
fix(web): anchor avatar-popover to left in RTL so it doesn't clip off-screen (#2719) (#2764)
In RTL the settings cog moves to the left side of the header, but
.avatar-popover still used right: 0 from the LTR rule, pushing the
280px popup off-screen to the left. Mirror the existing
entry-help-popover RTL pattern with right: auto; left: 0 so the
popover stays in view.

Fixes #2719
2026-05-23 15:11:31 +00:00
leessju
eb88d41107
feat(web): enable Next.js Turbopack for the web dev server (#2798)
The dev server runs on Next.js webpack by default. On a sizeable
monorepo like this one (19 locale files, many components, the i18n
content surface) webpack dev mode pushes the Node heap past the
default 4 GB ceiling and the process dies with 'Ineffective
mark-compacts near heap limit' after a few hot reloads, leaving the
desktop window pointing at a dead URL.

Switch the dev script to '--turbopack'. Next.js 16 ships Turbopack
as stable for dev, and apps/web/next.config.ts already declares
turbopack.root so the workspace resolution is consistent with the
webpack path. The build script is unchanged on purpose — this PR is
scoped to the dev server, where the OOM repro is.

In practice Turbopack runs the same dev workload with materially
lower steady-state heap usage (Rust-side bundling instead of
JavaScript-side webpack) and recovers faster on HMR, so a long dev
session no longer drifts toward the OOM ceiling.

Co-authored-by: nicejames <nicejames@gmail.com>
2026-05-23 15:09:46 +00:00
Dhruv Rana
ecfe9b9d10
fix: gate chat token params by model family (#1675)
* fix: gate chat token params by model family

* fix: retry azure deployment token params

* fix: retry azure v1 token params

* fix: report azure retry latency

* test: cover azure failed retry latency
2026-05-23 14:58:47 +00:00
Qian Wang
db79db04a9
Forward Node proxy env to packaged sidecars (#2763)
Co-authored-by: Qian Wang <wangqian5730@gmail.com>
2026-05-23 14:56:10 +00:00
Jane
b8cddc421e
fix(landing-page): drop trailing slash from preview iframe URLs (#2790)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 2s
ci / Detect CI change scopes (push) Successful in 1s
landing-page-ci / Validate landing page (push) Failing after 2s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
The detail-page interactive preview iframe pointed at
`/skills/<slug>/example.html/` and `/templates/<slug>/preview.html/`
with trailing slashes. Cloudflare Pages 308-redirects those URLs to
the extension-stripped form, but with the trailing slash present it
fails to map back to the published `out/skills/<slug>/example.html`
file and SPA-falls-back to the homepage. Result: every preview iframe
in production rendered the homepage instead of the skill or
live-artifact preview.

Verified against the deployed site after the #2679 release:

  - /skills/deck-guizang-editorial/example.html       → 4942 bytes (real preview)
  - /skills/deck-guizang-editorial/example.html/      → 163377 bytes (homepage SPA fallback)
  - /skills/deck-guizang-editorial/example            → 4942 bytes (real preview)
  - /skills/deck-guizang-editorial/example/           → 4942 bytes (real preview)

Drop the trailing slash from all six iframe `src` and "Open in new
tab" `href` attributes in `pages/skills/[slug]/index.astro` and
`pages/templates/[slug]/index.astro`, plus the inline comment that
documented the URL shape.

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
2026-05-23 05:22:35 +00:00
icc
caf7bc3d59
fix(web): avoid duplicate active plugin clear (#2653)
* fix(web): avoid duplicate active plugin clear

* test(web): cover active plugin clear branch

---------

Co-authored-by: sssmartmita-source <286839273+sssmartmita-source@users.noreply.github.com>
Co-authored-by: mrcfps <mrc@powerformer.com>
2026-05-23 05:04:21 +00:00
Muhammad Adnan R
de1665d0be
fix(web): sketch save button shows saving and saved feedback (#2500)
* fix(web): ensure sketch save button shows saving state

The save handler completes so quickly that the saving UI state flashes
for less than a frame, making it impossible for users to perceive that
a save is actually in progress. Add a 500ms minimum delay on the save
path so the button's saving state is visible, giving users clear
feedback that their action was registered.

Fixes #109

* fix(web): show saved confirmation after sketch save completes

After a sketch save succeeds, show a checkmark icon on the save button
for 2 seconds so users get clear confirmation that the save completed,
addressing the missing success feedback reported in the issue.

Fixes #109

* test(web): add save feedback tests for SketchEditor

Cover the Save button's default, saving, disabled, and post-save
checkmark states, including the 2-second saved confirmation timeout.

Refs #109

* fix(web): gate saved checkmark on explicit save success

saveSketch now returns false on write failure (daemon down, 4xx/5xx,
disk error, dropped connection) instead of resolving normally.
handleSave checks the return value and skips the green checkmark when
the save did not persist.

Refs #109

* fix(web): make sketch save delay a minimum floor not additive

saveSketch previously added a hardcoded 500ms after writeProjectTextFile completed, making the total perceived save time writeTime + 500ms regardless of how long the real write took.

Now it measures elapsed write time and only sleeps for the remainder of 500ms — fast saves still get the minimum visible duration, but a save that takes 600ms skips the sleep entirely.

Refs #109

* fix(web): remove showSaved from sketch save button disable guard

saveSketch's handleSave already skips the save when onSave returns false, so showSaved was doubling as an accidental debounce that blocked the user from saving again for 2 seconds after each save — even when they had continued drawing during the save animation.

The saving prop from the parent and the dirty/canSave check remain in place, so the button is still disabled while the async write is in flight and when there is nothing new to persist.

Refs #109

* fix(web): clear savedTimerRef on unmount

savedTimerRef schedules a setTimeout that calls setShowSaved(false)
after the save confirmation period. When the component unmounts before
the timer fires (e.g., closing the sketch tab), the pending callback
triggers a state update on an unmounted component. Add a useEffect
cleanup to clear the timer, consistent with the existing ResizeObserver
cleanup in this component.

Refs #109

* fix(web): strip trailing whitespace on save button className line

The className="primary" attribute on the save button accumulated
trailing whitespace in the recent save feedback changes. Remove it.

Refs #109

* test(web): add sketch save minimum visibility test for FileWorkspace

Add a test that verifies the sketch save button keeps the "Saving…"
state visible for at least 500ms before transitioning to the saved
checkmark. Mocks fetchProjectFileText, writeProjectTextFile, and a
stub ResizeObserver to make the SketchEditor render cleanly.

Refs #109

* test(web): update SketchEditor save disable assertion after removing showSaved guard

a6aa82a removed showSaved from the save button disable guard, so the button no longer stays disabled for 2 seconds after each save completes. This test expected the save button to remain disabled after save resolution, which no longer matches the component behavior — the saving prop (false) and dirty/canSave check (true) leave the button enabled immediately after save.

Refs #109

* fix(web): clear saved indicator when sketch save fails

When a save succeeds (shows checkmark), then the user saves again
and the second save fails, the stale checkmark remained visible
until the original 2-second timeout expired. The handler now clears
the timer and hides the indicator immediately on failure.

Refs #109

* fix(web): clear saved checkmark when sketch goes dirty again

After a successful save, the checkmark remained visible for the full
2-second window even if the user resumed drawing during that time.
dirty would become true again while the button still showed "Saved",
contradicting the actual unsaved state.

A new useEffect watches the dirty prop and immediately clears the
timer and hides the indicator when the sketch becomes dirty again.

Refs #109

* refactor(web): fix useEffect dependency array indentation

The closing bracket of the useEffect dependency array was indented
one space too far, breaking the code style consistency in
SketchEditor.tsx.

Refs #109

* refactor(web): remove trailing blank line in FileWorkspace

A blank line with trailing whitespace was left between the useEffect
closing brace and the following if-statement, violating the file's
whitespace conventions.

Refs #109

* fix(web): give sketch save button a stable accessible name

Refs #109

* test(web): add a11y tests for sketch save button aria-label

Refs #109
2026-05-23 05:03:10 +00:00
999axel999
db90cb0bdb
fix(daemon): reject unsafe plugin manifest names (#2757)
Co-authored-by: Zerocracy Assistant <zerocracy-assistant@example.com>
2026-05-23 12:53:39 +08:00
lefarcen
a37d11fe72
Merge pull request #2461 from nexu-io/release/v0.8.0
release: Open Design 0.8.0 — Everything is a plugin. Headless. Plugins create plugins.
2026-05-23 12:38:36 +08:00
arya rizky
f255c93c3e
fix: allow preview toolbar controls to wrap on narrow viewports (#2295)
When the chat panel is widened, the workspace panel shrinks and
the preview toolbar action buttons can overflow. This changes the
fixed toolbar height to a min-height and enables flex-wrap on the
toolbar actions container so controls wrap instead of clipping.

Fixes #2166

Co-authored-by: algojogacor <algojogacor@users.noreply.github.com>
2026-05-23 12:37:01 +08:00
lefarcen
f93c886411 Fix conflict resolution from c14baf07
Two CI failures from PR #2461 root-caused to wrong picks in the
merge:

* apps/web/src/components/plugins-home/PluginCard.tsx — reverted to
  release-side. The release-side version uses `localizePluginTitle
  (locale, record)` / `localizePluginDescription(locale, record)`
  to read each plugin's `titleI18n` / `descriptionI18n` fields,
  driving the 'localizes plugin card titles' test in
  plugins-home-section.test.tsx (which asserts '瑞士国际主义 Deck'
  appears under zh-CN). The main-side version replaced that
  record-level i18n with hardcoded English + `t()` aria-label
  keys — a finer-grained i18n migration but a fundamental loss of
  the record-level localization the test exercises. Taking
  release-side keeps the test functionality; the aria-label i18n
  keys are micro-optimisation we can re-port in a follow-up.

* e2e/ui/settings-local-cli-codex-fallback.test.ts — added the
  `SETTINGS_MENU_LABEL` constant declaration that the menu-
  dismissal helper (kept from main in c14baf07) references at
  line 161. main's diff added the const at the top of the file but
  it didn't carry through auto-merge alongside the helper block;
  this restores it.

Both fixes verified locally:
- PluginCard now grep-finds locale + localizePluginTitle usage.
- fallback test grep-finds SETTINGS_MENU_LABEL declaration.
2026-05-23 12:28:08 +08:00
lefarcen
c14baf07d3 Merge origin/main into release/v0.8.0
PR #2461 sync prep — resolves 14 conflicts merging 84 main-side commits
on top of 58 release-side commits accumulated during the 0.8.0 cycle.

Resolution summary:

Take main (theirs) where main carried deliberate forward progress:
- apps/web/src/components/PluginCard.tsx — 7 hunks, i18n migration:
  hardcoded English aria-labels/titles replaced with t() calls keyed
  on pluginCard.* (all 8 keys verified present in en.ts).
- apps/web/src/components/TasksView.tsx — 1 hunk, source-ingestion
  feature: sortedRoutines (newest-first), sourceIngestionTemplates,
  patchSourceForm, submitSourceIngestion. activeCount/pausedCount
  semantics preserved (now keyed on sortedRoutines, count unchanged).
- e2e/ui/app.test.ts — new node:fs/promises + tmpdir + path + @/timeouts
  imports needed by main-side test helpers.
- e2e/ui/settings-local-cli-codex-fallback.test.ts — menu-dismissal
  helper block added by main.

Keep both sides where each added a different field to the same object
literal:
- apps/web/src/components/ProjectView.tsx (locale + analyticsHints
  spread).
- apps/web/src/components/DesignSystemFlow.tsx (locale + analyticsHints).

Take release (ours) where release carried deliberate work that ships
0.8.0:
- CHANGELOG.md — release-side 0.8.0 entry + PR link refs; main's
  Unreleased section was the same body of work, now finalized.
- apps/landing-page/public/{apple-touch-icon,favicon}.png +
  apps/web/public/app-icon.svg — release-side visual refresh assets
  consistent with 0.8.0 stable ship.
- tools/pack/src/linux.ts — packageVersion const required by line 466;
  taking main's empty line would build-error.
- e2e/ui/project-management-flows.test.ts +
  e2e/ui/settings-api-protocol.test.ts +
  e2e/ui/settings-memory-routines.test.ts — release-side release-smoke
  hardening (shangxinyu1 + PerishFire) takes precedence on overlap.

Closes-issue / unblocks: PR #2461 sync release/v0.8.0 → main.
2026-05-23 12:17:18 +08:00
Yuhao Chen
650f7a5d23
fix(web): keep plugin use menu text readable (#2756) 2026-05-23 11:50:06 +08:00
Chris Seifert
ce68097f6b
feat(web): point .jsx module previews at their HTML entry (#2748)
* feat(web): point .jsx module previews at their HTML entry

Multi-file React prototypes load .jsx modules from an HTML entry via
<script type="text/babel" src>. A module previewed on its own has no
standalone component, so it dead-ended on the React runtime error
"No React component export found".

Modules are now detected (a .jsx/.tsx referenced by a sibling HTML
entry's babel script src) and handled:
- The Preview shows a pointer to the HTML entry(ies) that render the
  module; clicking one opens that page and closes the module tab.
- The Code tab still renders the raw source.
- Such modules no longer auto-open as preview tabs after a write.

* fix(web): bound module-detection cache, ignore commented-out scripts

Review follow-up on the .jsx module preview pointer:
- ProjectView: key the HTML content cache by file name with mtime stored
  alongside, so a rewrite replaces the file's single entry instead of
  leaking a new name@mtime key per revision.
- extractBabelScriptSrcs: strip HTML comments before scanning so a
  commented-out babel script is not collected as a live reference.
- i18n: normalize the three new jsxModule values to single quotes across
  all 19 locales to match each file's existing style.
2026-05-23 11:49:22 +08:00
Matt Van Horn
715ed04f5d
fix(prompt): instruct discovery form to follow user's chat language (#2534)
* fix(prompt): instruct discovery form to follow user's chat language

The discovery form was reaching users in English even when their UI
language was Chinese (#1416). The form is generated by the LLM under
guidance from packages/contracts/src/prompts/discovery.ts, but the
prompt only mentioned that option labels MAY follow the user's
language. The example form embedded English text for title,
description, per-question labels, and placeholders, and the LLM
copied that text verbatim instead of localizing.

Two minimal changes to the prompt:

1. Add a sentence under RULE 1 making the language-match expectation
   explicit before the example forms.
2. Expand the Form authoring rules bullet so it covers every
   user-facing string (title, description, label, placeholder, option
   label) and pins the unlocalized identifiers (id, type, option
   value, branch values) for the runtime branch logic.

Fixes #1416

* fix(prompts): mirror discovery localization rule to daemon prompt copy

Apply the same 'Match the user's chat language' paragraph and the
expanded 'Localize every user-facing string' bullet to
apps/daemon/src/prompts/discovery.ts, which the daemon-backed chat
path uses (it imports ./discovery.js, not the contracts copy).

Also add apps/daemon/tests/prompts/discovery-localization-drift.test.ts,
which reads both prompt copies and asserts each one contains both rules,
so the contracts and daemon files cannot silently drift on this behavior.

Apply-anyway reason: pnpm install / pnpm vitest could not run locally
(registry DNS blocked in sandbox + node v26 vs required v24). Direct
Node content assertion over both files passes. CI will run vitest.

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-05-23 11:48:17 +08:00
YOMXXX
97ed479c6b
fix(web): route chat file links to workspace preview instead of new window (#1239) (#2576)
* fix(web): route chat file links to workspace preview instead of new window (#1239)

Chat-emitted markdown links like `[template.html](template.html)` rendered
as `<a target="_blank">` with no click handler. In Electron that hits
`setWindowOpenHandler` and creates a new `od://` BrowserWindow; relative
hrefs have no base so the new window can't resolve them and the user
lands on the home screen — the file they wanted to preview is never
shown.

Detect in-project file paths in chat markdown via a new
`asInProjectFilePath` helper and route them through the existing
`requestOpenFile` workspace tab opener. External URLs, `mailto:`,
`#anchors`, absolute paths and `..` traversal keep their default
browser-link behavior. The `renderMarkdown(options)` extension is
backwards-compatible: existing callers (file viewer, system reminders)
keep their default `target="_blank"` behavior when the option is
omitted.

Closes #1239.

* fix(web): decode percent-encoded chat file links before workspace open (#1239)

Chat markdown frequently emits links as URL-encoded text — `Mock%20Page.html`
for a file named `Mock Page.html`, multi-byte sequences for non-ASCII
filenames. The workspace tab opener (`requestOpenFile` →
`FileWorkspace`) matches by literal on-disk file name, so handing it
the raw `%20`-encoded form silently misses the existing tab and the
user sees nothing happen on click — the exact regression #1239
reopened against.

Decode after the literal `..` check and re-check `..` on the
decoded form so a `%2E%2E` smuggling attempt cannot bypass the
traversal guard. Malformed encodings fall through to `null` (default
browser link behavior) instead of letting URIError crash the
renderer.

The same gap was flagged on the earlier draft PR #1255 by mrcfps and
lefarcen (P2) but never landed there; this PR now covers it with
five new regression tests (ASCII spaces, nested subdirs, UTF-8 byte
sequences, malformed `%`, percent-encoded traversal).
2026-05-23 11:48:07 +08:00
Nicholas-Xiong
bf699736d4
fix: prevent plugin info collapse button from scrolling with content (#2515)
* fix: prevent plugin info collapse button from scrolling with content

Changed .ds-modal-stage-handle from position: absolute to position: sticky
so the collapse/expand button stays anchored at the vertical center of the
viewport instead of scrolling with the sidebar content.

Before: button moved with scrolling content and could overlap text
After: button remains fixed at 50% viewport height, always accessible

Closes #2209

* fix: use sticky positioning only for collapse button

The original approach broke the expand button because changing the base
class to position: sticky made the expand button's right: 0 ineffective.

Correct fix:
- Base class stays position: absolute (for expand button at stage edge)
- Only .is-collapse overrides to position: sticky (for sidebar scroll)

This ensures:
- Expand button anchors correctly at the right edge when sidebar closed
- Collapse button stays at viewport center when sidebar content scrolls
- No regression in either collapsed or expanded state
2026-05-23 11:47:50 +08:00
Ghxst
f7438b707a
Fix design system new conversation action (#2483)
Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com>
2026-05-23 11:47:42 +08:00
YOMXXX
cb98721b91
fix(daemon): isolate per-agent detection failures so one bad probe cannot blank the picker (#2297) (#2444)
* fix(daemon): isolate per-agent detection failures so one bad probe cannot blank the picker

`detectAgents` ran every adapter probe in a bare `Promise.all`, so a
synchronous throw from any single probe (e.g. a filesystem error
during PATH walking on a packaged Windows daemon, or an unhandled
async rejection from one of the post-launch probes) rejected the
whole batch. The `/api/agents` route's `catch(() => [])` then handed
the UI an empty list and the model picker collapsed to BYOK / Cloud
only, losing every installed CLI option — which matches what users
in issue #2297 observed after one or two app restarts on Windows.

Wrap each probe in `safeProbe` so a single failure degrades just that
adapter to `unavailableAgent(def)` while the rest of the registry
keeps its real availability. The new
`apps/daemon/tests/runtimes/detection-resilience.test.ts` pins both
synchronous failure sites that previously sat outside the existing
inner try/catch blocks (`resolveAgentLaunch` and
`applyAgentLaunchEnv`) so a future code change cannot regress the
isolation contract.

This is a defensive guard rather than a Windows-only diagnosis: it
fixes any scenario where a single probe blows up, including ones we
have not reproduced yet. If a user still hits #2297 after this lands,
the daemon log will identify which adapter failed instead of silently
returning an empty list.

Fixes #2297

* ci: re-run checks (unrelated e2e baseline flake on previous run)
2026-05-23 11:47:32 +08:00
YOMXXX
c85cd8dbd8
chore(assets): optically center title bar icon inner mark (#2401) (#2439)
The Open Design wordmark sits next to the circular app icon in the
macOS title bar (`EntryNavRail` home button), and the inner mark inside
that circle reads as slightly off-center because its bounding box sits
about 16px right and 9px below the circle's geometric center.

Shift the inner mark by (-15.635, -9.135) so its bbox center matches
the circle center at (266.503, 266.503). The change rewrites the two
sub-paths that draw the mark and its interior cut-out; the outer ring
keeps the original coordinates so the gradient mask, blur filters, and
ring weight all render identically. Dock and Launchpad icons live in
`tools/pack/resources/{mac,win,linux}/icon.*` and are not touched, so
this fix is scoped to the title bar / favicon / mask-icon surfaces as
described in #2401.
2026-05-23 11:46:49 +08:00
Yuhao Chen
4d84bbd629
fix(web): wrap local cli status paths (#2283)
* fix(web): wrap local cli status paths

* fix(web): top-align wrapped status details
2026-05-23 11:46:32 +08:00
Ghxst
a8ddebdc81
fix(web): retry failed chat runs without duplicating user message (#2491)
* Fix retry duplicate user turns

* test(web): mock i18n hook in reattach restore test

---------

Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com>
2026-05-23 01:01:42 +08:00
leessju
dbc86777a8
test(web): mock useI18n in ProjectView reattach-restore suite (#2724)
ProjectView calls useI18n() for locale/t, but this suite's `../../src/i18n`
mock only returned useT — so every render threw `No "useI18n" export is
defined on the i18n mock` and 5 of 10 cases failed. Mirror the i18n mock the
other ProjectView suites already use (useI18n + useT) so the suite renders.

Co-authored-by: nicejames <nicejames@gmail.com>
2026-05-23 00:38:27 +08:00
YOMXXX
7a118424b6
feat(mcp): add write_file, delete_file, delete_project tools (#2416)
* feat(mcp): add write_file, delete_file, delete_project tools

External coding agents driving Open Design through MCP can create new
artifacts (create_artifact) but cannot iterate on a file once written
(create_artifact rejects existing targets), cannot remove a stale
file, and cannot tear down a throwaway project they just spun up via
create_project. Close that loop so the same agent can drive the full
file/project lifecycle end-to-end through MCP.

- write_file(path, content, encoding?): POSTs to /api/projects/:id/files
  without `artifact: true`, which the daemon route writes as a plain
  overwrite. Supports nested paths and base64 binaries.
- delete_file(path): DELETEs /api/projects/:id/raw/<path> so nested
  paths work just like create_artifact's nested name argument.
- delete_project(project, confirm:true): DELETEs /api/projects/:id but
  refuses to fall back to the active project and requires confirm:true,
  since the operation purges the SQLite row and on-disk project dir
  irreversibly. Marked destructiveHint:true on the annotation.

Tests cover each tool's success path, the active-context fallback for
write/delete_file, missing-argument rejection before any network call,
the daemon-error mapper, and the two delete_project guards.

* fix(mcp): echo resolvedProject from delete_project and cover the daemon error path

Two follow-ups from review of #2416:

- delete_project accepts a name substring per its inputSchema and the
  server instructions block tells callers to verify which row was
  matched via resolvedProject. write_file/delete_file already honor
  that contract via withActiveEcho(json, active, resolved), but
  deleteProject destructured only `id` and dropped the echo on the
  one irreversible tool. Capture `resolved` and pass it through;
  active is always null here because the active-context fallback is
  intentionally disabled.
- formatDaemonError and the !resp.ok branches in writeFile/deleteFile/
  deleteProject had zero coverage — all nine tests stubbed status: 200.
  Add three regressions covering the structured-error reformat, the
  raw-text fallthrough for non-JSON bodies, and the irreversible
  delete_project surface, so a regression in the parse/fallthrough
  logic will fail in CI instead of reaching agents.
2026-05-23 00:31:04 +08:00
Marc Chan
1c7233ef10
fix(landing-page): speed up landing-page CI builds (#2734)
* fix(landing-page): speed up landing-page CI builds

* fix(landing-page): disable dev-only landing caches

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

* fix(landing-page): reuse previews across CI runs

* fix(landing-page): hash shared preview dependencies

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

* fix(landing-page): skip missing preview html reads

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

* fix(landing-page): rerun previews on cache hits

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

* fix(ci): repair landing-page workflow cache keys
2026-05-23 00:30:31 +08:00
Marc Chan
a3872b97a9
fix(tools-dev): preserve web origin trust on web start (#2715)
* fix(tools-dev): preserve web origin trust on web start

Restart daemon/web when the trusted web port is missing, and reuse the active web port during repeated starts so run web and start web keep app-config origin checks aligned.

Generated-By: looper 0.0.0-dev (runner=worker, agent=opencode)

* fix(plugins): refresh official registry bundled count

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

* fix(tools-dev): preserve daemon/web reserved ports

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

* fix(tools-dev): preserve daemon reuse on web start

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

* fix(tools-dev): preserve running daemon port on web reuse

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

* fix(tools-dev): reserve explicit web port before daemon allocation

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

* test(web): stabilize media provider reload flash timing

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

* fix(web): restore merged reattach workspace coverage

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

* fix(tools-dev): reserve allocated daemon port

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

* test(e2e): wait for artifact manifest persistence

Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode)
2026-05-23 00:25:43 +08:00
soulme
b24168b340
fix conversation title editing (#1926) 2026-05-23 00:25:05 +08:00
Patrick A
ce2e7a0e66
fix(web): chat pane preserves scroll position when todo card grows (#2299)
* fix(web): chat pane preserves scroll position when todo card grows

The PinnedTodoSlot renders outside the chat-log scroll container. When
the todo card grows (new tasks added via TodoWrite), the scroll container's
clientHeight shrinks in the flex layout, drifting the user away from the
bottom. The existing ResizeObserver only observed children of the chat-log
div, so pinned-todo growth was invisible to followLatestIfPinned.

Fix: pass a containerRef to PinnedTodoSlot and observe that element in the
same ResizeObserver. syncPinnedTodo() is called on effect setup and from
the MutationObserver callback so observation stays current as the slot
appears and disappears across TodoWrite snapshots.

Red spec: apps/web/tests/components/chat-todo-autoscroll.test.tsx

* fixup! fix(web): chat pane preserves scroll position when todo card grows

Clarify test comment: the second test confirms followLatestIfPinned
snaps scroll to bottom when fired. The structural guarantee (pinned-todo
element is observed) is separately asserted in test 1, which is the
check that goes red on main without the fix.

* fix(web): correctness extend MutationObserver to pane ancestor for PinnedTodoSlot mount detection

The MutationObserver was only watching the .chat-log element. PinnedTodoSlot
(.chat-pinned-todo) is a sibling of .chat-log-wrap inside .pane, outside the
observed subtree. syncPinnedTodo inside the MutationObserver callback was
therefore dead code for mount/unmount transitions of the slot.

Add a second observation on paneEl (el.parentElement?.parentElement) with
childList-only so the MutationObserver fires when PinnedTodoSlot mounts or
unmounts and syncPinnedTodo can register/deregister the element with the
ResizeObserver.

* test(e2e): chat pane auto-scroll on todo card growth

Add Playwright spec that goes red on origin/main and green on this fix
branch. Scenario A asserts that a chat-log pinned to the bottom snaps
back after the PinnedTodoCard grows (the ResizeObserver-on-pinned-todo
path). Scenario B asserts that a deliberate scroll-up is not overridden.

Also allow OD_WORKSPACE_ROOT env override in next.config.ts so Turbopack
resolves node_modules correctly when the web app is booted from a worktree
whose node_modules symlinks resolve outside the default workspace root.

* docs(agents): note pinned-todo observer coverage in chat UI conventions

PinnedTodoSlot sits outside the .chat-log scroll container, so the
ResizeObserver and MutationObserver coverage that keeps auto-scroll
working when the todo card grows is non-obvious to future implementers.
Document the invariant in the Chat UI conventions section.

* fix(web): validate OD_WORKSPACE_ROOT, harden autoscroll test precondition

* fix(web): validate OD_WORKSPACE_ROOT existence, make autoscroll precondition unconditional

* fix(web): throw on invalid OD_WORKSPACE_ROOT instead of warn-and-fallback

* fix(web): require pnpm-workspace.yaml at OD_WORKSPACE_ROOT, drop dead test branch

Three follow-ups to nettee's review feedback:

1. apps/web/next.config.ts gains a pnpm-workspace.yaml existence check
   after the relative-path validation. Without it, an override like
   '<repo>/apps' or '<repo>/apps/web' passes the relative(resolved, WEB_ROOT)
   check but the resolved path is missing the sibling packages/* directory
   that apps/web imports from (for example @open-design/contracts). Next
   would later fail deep inside file tracing / Turbopack with a much
   harder-to-diagnose error. Now we throw at config load with a clear message.

2. e2e/ui/chat-todo-autoscroll.test.ts drops the redundant
   'if (scrollUpOccurred)' branch. The hard precondition above it already
   guarantees distanceAfterScroll > 80, so the if was dead code that read
   as a false-green path. The body now runs unconditionally.

3. Same test tightens the post-grow assertion. The previous
   toBeGreaterThan(60) would pass even if a regression dragged the log
   most of the way back to the bottom (e.g. before=150, after=61).
   Replaced with Math.abs(distanceAfterGrow - distanceAfterScroll) less than
   SCROLL_PRESERVATION_TOLERANCE_PX (20) — a delta check that actually
   verifies the comment's claim of 'within ~20px of where the user left it'.

* fix(web): canonicalize workspace root with realpathSync and tighten scenario B assertion

- Use realpathSync on both resolved and WEB_ROOT before the ancestor check so
  that symlinked paths (macOS /tmp vs /private/tmp, worktree checkouts) compare
  correctly instead of false-throwing on a physically valid override.
- Add isAbsolute(rel) guard for the Windows cross-drive case where path.relative()
  returns an absolute path instead of a ..-prefixed string.
- Scenario B: replace distance-to-bottom delta assertion with scrollTop preservation
  check. Growing the pinned todo naturally increases distance-to-bottom by ~extraPx
  (clientHeight shrinks while scrollTop is held fixed), so the old Math.abs(after -
  before) < 20 check would fail on correct behavior. asserting scrollTop directly
  catches the real regression: followLatestIfPinned incorrectly snapping a non-pinned
  user back to the bottom.
- Add hard precondition that clientHeight actually changed so the test fails fast
  if the layout stops exercising the non-pinned path.

* test(e2e,web): add clientHeight guard to scenario A and mount-wiring unit test

---------

Co-authored-by: Patrick A <eefynet@users.noreply.github.com>
Co-authored-by: Patrick A <259201958+eefynet@users.noreply.github.com>
2026-05-23 00:19:59 +08:00