Commit graph

314 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
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
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
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
张东明
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
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
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
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
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
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
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
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
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
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
shangxinyu1
ac0a9212fe
fix(web): restore HTML preview after source toggle (#2699) (#2710) 2026-05-22 22:51:38 +08:00
kami
586e2a0c3b
Fix Draw Send to chat during streaming (#1961)
* Fix queued draw annotation send while streaming

* Wire draw send while streaming
2026-05-22 19:47:52 +08:00
Ghxst
33b96f184e
fix(web): compact plugin authoring composer (#2492)
* fix(web): compact plugin authoring composer

* fix(web): compact pending plugin authoring composer

* fix(web): compact plugin authoring handoff composer

* fix(web): align plugin composer layout rebase

---------

Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com>
2026-05-22 19:46:47 +08:00
Siri-Ray
8eecf665da
Fix Design Files workspace loading (#2703)
* Fix Design Files workspace loading

Generated-By: looper 0.8.1 (runner=worker, agent=codex)

* Fix Design Files workspace loading

Generated-By: looper 0.8.1 (runner=fixer, agent=codex)

* Fix Design Files workspace loading

Generated-By: looper 0.8.1 (runner=fixer, agent=codex)
2026-05-22 19:46:16 +08:00
Devayan Dewri
1b908a8481
fix(daemon): restore full assistant turn after mid-flight reload reattach (#2383)
* fix(daemon): restore full assistant turn after mid-flight reload reattach

When a daemon run is in progress and the browser reloads, the client
reattaches and the artifact recovers, but the restored chat turn drops
assistant text, thinking events, and producedFiles. Three independent
defects combine to cause this:

1. The reattach onDone never populated producedFiles. The pre-turn file
   snapshot used as the diff baseline lived only in a closure. Now it is
   persisted on the assistant message as preTurnFileNames so the reattach
   path can rebuild the diff after reload.

2. The SSE replay used a strict `>` cursor compare. A client that had
   already persisted lastRunEventId equal to the final event id received
   zero replay events on terminal-run reattach, fell into the status-only
   REST fallback, and never fired a clean onDone. The server now replays
   the final buffered event on terminal-run reattach when the cursor is
   at or past the end, so the client always sees a terminal signal.

3. The text buffer flushed on visibilitychange but not on pagehide.
   Hard reloads on browsers where visibilitychange does not fire before
   teardown could lose the last ~250ms of streamed text from the
   persisted message. A pagehide listener now flushes synchronously.

Refactor: extracted computeProducedFiles helper so the send and reattach
flows share the diff logic and cannot drift apart again.

Tests:
- apps/web/tests/components/ProjectView.reattach-restore.test.tsx
  covers: reattach onDone populates producedFiles from preTurnFileNames;
  reattach reaches succeeded via SSE even when only the end event replays;
  computeProducedFiles unit cases.
- apps/daemon/tests/runs.test.ts adds replay-cursor coverage for both
  the terminal-replay safety branch and the no-duplicate normal branch.

* fix(daemon): persist preTurnFileNames end-to-end on the messages table

Review on #2383 caught that `ChatMessage.preTurnFileNames` (added in
packages/contracts) had no daemon-side persistence: the messages
schema, upsertMessage, and normalizeMessage all ignored the field.
saveMessage() would PUT the field, the daemon would silently drop it,
and a real page reload would read a row without `preTurnFileNames`, so
the reattach onDone fell back to `new Set(nextFiles.map(...))` and
still missed files produced earlier in the turn.

This commit closes the round trip:

- New `pre_turn_file_names_json TEXT` column on the messages table,
  with a forward-compatible ALTER for existing databases (same pattern
  as agent_id / feedback_json / run_status).
- Both upsertMessage branches (UPDATE and INSERT) now serialize
  m.preTurnFileNames into the new column.
- listMessages, the post-upsert readback SELECT, and normalizeMessage
  surface the column back to callers.

Round-trip tests in apps/daemon/tests/db-pre-turn-file-names.test.ts
cover: write+listMessages, the UPDATE upsert path preserving the
baseline, and a legacy-row case returning undefined.

* fix(web): preserve terminal status + full multi-file diff on reattach

Two correctness issues caught in review of the prior reattach commits:

1. The reattach onDone path hard-coded `runStatus: 'succeeded'`, which
   overwrote a 'failed' or 'canceled' status that the replayed terminal
   event had already recorded via onRunStatus. Restored messages would
   come back as success even when the run had actually failed or been
   canceled. Now derives the final status from `prev.runStatus` via the
   existing `resolveSucceededRunStatus` helper, mirroring the send path
   at line 2333.

2. When `findExistingArtifactProjectFile()` recovered an existing
   on-disk artifact, the produced-files list was replaced with that
   single file, dropping any other files the turn had created earlier.
   Now always computes the full diff against `preTurnFileNames`, then
   appends the recovered artifact only if it isn't already in that
   set. Extracted as `mergeRecoveredArtifact(diff, recovered)` so the
   logic is a unit-testable invariant.

Tests in ProjectView.reattach-restore.test.tsx:
- mergeRecoveredArtifact: three cases (recovered appended to pre-files,
  no duplication when already in the diff, passthrough on no recovery).
- reattach failed-status: onRunStatus('failed') → onDone → final
  saveMessage has runStatus 'failed', not 'succeeded'.
- reattach canceled-status: same shape for cancellation.

* fix(web): force keepalive PUT on pagehide so the last buffered chunk survives reload

Review on #2383 caught that onPageHide() only called flush(), which
updates React state then schedules persistSoon() — a 500ms debounce.
On a hard reload the page tears down before that timer fires, so the
final ~250ms of streamed text never reaches the daemon.

Threaded a new flushAndPersistNow() callback through
createBufferedTextUpdates(). Both buffer call sites (send-path +
reattach-path) supply it backed by persistMessageById(id, { keepalive:
true }). saveMessage in state/projects.ts forwards the new
SaveMessageOptions.keepalive flag onto fetch's keepalive option, which
the browser honors specifically for unload-time requests.

onPageHide now calls flush() followed by flushAndPersistNow?.(), so:
- flush() pushes the buffered delta into React state synchronously
- the immediate persistMessageById then PUTs the updated message with
  keepalive:true, surviving document teardown

Regression test in ProjectView.reattach-restore.test.tsx: stream a
delta, dispatch pagehide, assert saveMessage was called with the
flushed content AND { keepalive: true } before the 500ms debounce
would otherwise have fired.
2026-05-22 18:47:12 +08:00
lefarcen
5f939ce601
fix(web): remove Ingest source panel from Automations tab (#2711)
* fix(web): remove Ingest source panel from Automations tab

The Automations tab carried a free-form "Ingest source" composer that
let users paste arbitrary content (URL, repo path, connector event,
chat snippet) and turn it into a source packet plus evolution proposals.
The form was confusing next to the routine/template flow on the same
page, exposed an internal canonicalization concept users don't need to
think about, and shipped before the surrounding evolution-proposal flow
was wired into a coherent end-to-end story.

Drop the UI surface only:

- Remove the <section className="automations-ingest"> block, the
  Template / Source / Compression / Connector selects, the title/source
  ref/content fields, the recent-packets list, and the Ingest button.
- Drop the now-dead local state (sourcePackets / sourceForm /
  ingestingSource), the patchSourceForm and submitSourceIngestion
  helpers, the SOURCE_KIND_OPTIONS / COMPRESSION_OPTIONS constants, the
  SourceIngestionForm type and DEFAULT_SOURCE_FORM, the
  /api/automation-source-packets refresh leg, and the sourcePackets
  side-write inside crystallizeRun.
- Remove the matching .automations-ingest / .automation-ingest-* CSS
  block (plus the two responsive overrides) from tasks.css.
- Delete the test case that drove the form in TasksView.templates.test.

Backend stays intact: apps/daemon/src/automation-ingestions.ts, the
POST /api/automation-ingestions route, `od automation ingest` CLI, the
routine-evolution call site, and the AutomationContentPacket /
AutomationSourceKind / AutomationTokenCompressionMode contracts all
remain, since routine scheduling still depends on them.

* fix(web): drop crystallize test assertion on removed packet list

The crystallize test was asserting that the new content packet's title
shows up on the page. That assertion only passed because the daemon
response was being side-written into the deleted sourcePackets state
and rendered in the Ingest source recent-packets strip. With that UI
removed, the packet title has no surface to land on; the proposal title
(`Skill: Artifact polish loop run`) is still asserted and remains the
real signal that crystallize succeeded.
2026-05-22 17:53:27 +08:00
Eli-tangerine
10e11531a1
Improve deck home previews and plugin gallery performance (#2698)
Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-22 17:47:28 +08:00
Alan Matias
00b3f3e52d
feat(design-files): add directory navigation and localization for folders (#2442)
* feat(design-files): add directory support and localization for folders in design files panel

* feat(directory-navigation): implement directory navigation and selection reset in DesignFilesPanel

* feat(rename): improve draft handling for file renaming in DesignFilesPanel
2026-05-22 17:44:01 +08:00
Siri-Ray
e6da01e998
Add i18n metadata for official content (#2692) 2026-05-22 16:39:32 +08:00
mehmet turac
45c77fac41
fix(web): restore manual source edit tabs (#1854)
* fix(web): restore manual source edit tabs

* test: target active manual edit preview
2026-05-22 16:11:01 +08:00
Neha Prasad
4cd93a54dd
fix(web): Enter to send in project chat composer (#2676) 2026-05-22 15:57:20 +08:00
PerishFire
b4e94b0534
Harden packaged updater downloads and install handoff (#2677)
* Add managed download package for updater resumes

* fix(download): clear stale pid locks

* test(e2e): harden windows updater resume smoke

* feat(updater): make update downloads silent in ui

* fix(updater): keep install handoff prompt visible

* fix(ci): build platform before download in postinstall
2026-05-22 15:44:28 +08:00
Eli-tangerine
72c8e34bc9
Polish home onboarding and community presets (#2658)
Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-22 14:26:56 +08:00
shangxinyu1
cc6edb9afe
Proxy GitHub metadata through the daemon (#2654)
* Proxy GitHub metadata through the daemon

* fix(contracts): share GitHub metadata responses

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* fix(contracts): align GitHub fetchedAt payload types

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* Proxy GitHub metadata through the daemon

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)
2026-05-22 14:06:07 +08:00
shangxinyu1
95bbdbb734
[codex] test(e2e): harden settings and entry regressions (#2578)
* test(e2e): harden settings and entry regressions

* test(e2e): align entry chrome coverage with current UI

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* fix(web): refresh saved media providers from daemon

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* test(web): align media provider reload expectations

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* fix(web): keep daemon media-provider reloads authoritative

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* fix(web): make media-provider reload precedence depend on dialog edits

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* fix(web): preserve pending media-provider edits across stale autosaves

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)
2026-05-22 10:04:12 +08:00
Eli-tangerine
edb736edbf
Preserve home example prompt selections (#2611)
Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-22 01:52:08 +08:00
PerishFire
ec96585a8f
fix: harden Windows packaged updater flow (#2595) 2026-05-21 22:32:02 +08:00
Ghxst
7631539808
Add project bulk delete feedback (#2481)
* Add project bulk delete feedback

* Restart bulk delete toast timer

---------

Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com>
2026-05-21 20:57:19 +08:00
Ghxst
8269b7df90
Keep live artifact preview mounted across tabs (#2482)
Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com>
2026-05-21 20:38:58 +08:00
lefarcen
6690dbd5bb
feat(analytics): PostHog + Langfuse instrumentation for assistant feedback (#1558)
* feat(analytics): PostHog + Langfuse instrumentation for assistant feedback

Re-bases the original three-commit PR onto release/v0.8.0. The web-side
feedback UI instrumentation (surface_view / ui_click / feedback_submit_result)
landed on main while this branch was open, so on this rebase that wiring
is taken from main; the remaining net additions are:

- Contracts: TrackingFeedback* enums and the four dedicated
  assistant_feedback_* event payload types (click, reason_view,
  reason_click, reason_submit), plus normalizeCustomReason helper.
  The new event-name variants are added to TrackingEventName and the
  AnalyticsEventPayload discriminated union next to the existing
  surface_view/ui_click variants — both wire formats coexist.
- POST /api/runs/:id/feedback in apps/daemon/src/chat-routes.ts:
  thin route that validates rating, allowlists reasonCodes through a
  simple string filter, and fire-and-forgets into the daemon's
  reportFeedback hook.
- apps/daemon/src/langfuse-bridge.ts reportRunFeedbackFromDaemon
  forwards the rating + reasonCodes into Langfuse as user_rating
  (NUMERIC ±1) + user_rating_reason (CATEGORICAL, one per code)
  score-create entries. Gates on telemetry.metrics + telemetry.content.
- apps/web/src/providers/daemon.ts reportChatRunFeedback (fire-and-forget
  fetch) and apps/web/src/components/ProjectView.tsx wiring so each
  thumbs-up/down + reason submission posts the side-channel.

Conflicts resolved (release/v0.8.0 vs the branch's old base):
- packages/contracts/src/analytics/events.ts: keep main's
  file_upload_result / feedback_submit_result / settings_* event
  variants alongside the new assistant_feedback_* additions.
- apps/daemon/src/server.ts: keep DNS-aware validateExternalApiBaseUrl,
  add reportFeedback closure wired into registerChatRoutes telemetry.
- apps/daemon/src/chat-routes.ts: keep both /tool-result and the new
  /feedback routes; merge RegisterChatRoutesDeps to include both
  'paths' and 'telemetry'. Drop PR's chat-routes-local
  reconcileAssistantMessageOnRunEnd helper (main has the equivalent in
  server.ts).
- apps/web/src/components/ChatPane.tsx & AssistantMessage.tsx & ProjectView.tsx:
  keep main's projectKindForTracking prop name and its existing
  emission of surface_view / ui_click / feedback_submit_result; the
  PR's analyticsCtx-based reason_view/click/submit emission is dropped
  in this rebase since it would duplicate the existing wire format.
- apps/web/tests/components/*: rename projectKind → projectKindForTracking
  to match ChatPane's current prop name.

Outstanding review feedback (from the pre-rebase round, will be
addressed in a follow-up commit):
- AssistantMessage tests not yet passing the new feedback context to
  the direct render path.
- ProjectView clear-feedback path skips reportChatRunFeedback, leaving
  stale Langfuse user_rating scores.
- buildFeedbackPayload has no deletion path for previously-submitted
  user_rating_reason scores when the user switches thumbs.
- POST /api/runs/:id/feedback always returns {status:'accepted'} even
  when consent is off; needs to surface skipped_consent / skipped_no_sink.
- reasonCodes are filtered to string[] but not allowlisted against
  ChatMessageFeedbackReasonCode or deduped.

* fix(analytics): address review on assistant feedback rebase

Picks up the in-scope correctness items from the prior review round
and the rebase residue without rewriting history:

- chat-routes.ts: `/feedback` now awaits the daemon's preflight
  outcome and echoes it as the response. The contract was already
  shaped as `accepted | skipped_consent | skipped_no_sink`, but the
  previous handler always returned `accepted` because the network
  send was fire-and-forget. The consent + sink decision is local
  (a small file read and an env-var lookup); the actual Langfuse
  upload still runs as a detached promise.
- chat-routes.ts: reasonCodes are now allowlisted against the
  contract's reason-code union and deduplicated before reaching
  Langfuse, so a stale or replayed client can't poison the
  Langfuse score table with unknown categorical values or
  duplicate stable ids in the same batch.
- langfuse-bridge.ts: split the consent + sink resolution from the
  fire-and-forget network send so the route can claim `accepted`
  honestly. The legacy `skipped_no_sink` return on app-config read
  failure is preserved.

Contracts + comment hygiene:
- TrackingFeedbackReasonCode in packages/contracts/src/analytics/events.ts
  drifted from ChatMessageFeedbackReasonCode in packages/contracts/src/api/chat.ts;
  add `followed_design_system` and `missed_design_system` so the
  analytics wire format stays aligned with the persistence shape.
- langfuse-trace.ts buildFeedbackPayload: the docblock claimed the
  raw custom-reason text is bucketed before send. Product reversed
  that on 2026-05-13 (raw text now ships, consent-gated). Replace
  the stale comment with the real semantics + a note that there is
  no tombstone path for reason codes the user removes in a
  follow-up submission (left as scope for a later PR).
- AssistantMessage.tsx: remove the now-unused
  `AssistantFeedbackAnalyticsCtx` interface and a stray blank-line
  delete from the rebase; restore the analytics-context comment
  above the feedback hook.

Left as follow-up (intentional, documented in code):
- Sending a tombstone score when the user clears their rating —
  ProjectView still skips reportChatRunFeedback on `change===null`,
  so Langfuse retains the previous rating until the user re-submits.
  The PostHog event captures the clear separately.
- Removing reason-code scores when the user re-submits with a
  smaller set — buildFeedbackPayload only overwrites the codes
  present in the current payload.

* feat(analytics): wire PR's dedicated assistant_feedback_* events

The four dedicated event types (`assistant_feedback_click` /
`_reason_view` / `_reason_click` / `_reason_submit`) the PR added to
contracts were sitting unused after the rebase because main's
umbrella `surface_view` / `ui_click` / `feedback_submit_result`
emissions covered the same user gestures. Wire the dedicated events
alongside the umbrella ones so both wire formats fire on every
feedback action — dashboards / evals can pick whichever schema they
were built against without losing signal.

Each dedicated event has stricter typing than its umbrella sibling
(`project_id` / `project_kind` / `conversation_id` are non-null), so
the new emissions are guarded behind a presence check and skipped on
test renders that mount AssistantMessage without project context. The
umbrella emissions retain their nullable fallbacks unchanged.

Pairing:
- surface_view (feedback reason panel) ↔ assistant_feedback_reason_view
- ui_click (feedback button)           ↔ assistant_feedback_click
- ui_click (reason submit button)      ↔ assistant_feedback_reason_click
- feedback_submit_result               ↔ assistant_feedback_reason_submit

Reason click + submit share the existing `requestId` so PostHog can
stitch click→result across both schemas, matching the spec.
2026-05-21 19:28:51 +08:00
shangxinyu1
10e2019c59
Fix plugin publish and Open Design PR workflow UX (#2564)
* Fix plugin publish and PR workflow UX

* Update plugin workflow test expectations

* Fix fake gh repo view verification path

* Fix plugin publish headless tests and preserve PATH in shell wrappers.

The publish-repo flow needs real git commits and fake gh auth output that
matches gh auth status parsing. Login shells no longer drop PATH so test
fakes and agent wrappers stay visible to nested gh/git calls.

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

* Restore plugin action card when share-task startup fails.

If startGeneratedPluginShareTask rejects before a task is created, clear
hiddenAssistantPluginActionPaths so the assistant action card reappears.

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

* Make daemon vitest self-contained for publish-github CLI shell-outs.

Build dist/cli.js in tests/setup.ts when missing and set OD_DAEMON_CLI_PATH
before server.ts resolves OD_BIN, so headless plugin tests pass from a clean
checkout without a prior manual daemon build.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 19:21:17 +08:00
Eli-tangerine
255bbc1d06
Show published user design systems on home (#2572)
Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-21 19:21:05 +08:00
Ghxst
93036afa2e
Fix sketch default color in dark mode (#2488)
Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com>
2026-05-21 19:18:32 +08:00
Siri-Ray
3a33a7b475
fix(web): localize quick brief prompt (#2520)
* fix(web): localize quick brief prompt

Generated-By: looper 0.8.1 (runner=worker, agent=codex)

* fix(web): pass locale from design system chat

Generated-By: looper 0.8.1 (runner=fixer, agent=codex)

* fix(web): preserve task-type routing options

Generated-By: looper 0.8.1 (runner=fixer, agent=codex)

* fix(web): preserve task-type routing options

Generated-By: looper 0.8.1 (runner=fixer, agent=codex)
2026-05-21 19:18:13 +08:00
Marc Chan
047d2bdd95
fix(orbit): respect selected app language (#2522)
* fix(orbit): respect selected app language

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

* fix(orbit): respect selected app language

Generated-By: looper 0.8.1 (runner=fixer, agent=opencode)
2026-05-21 19:17:53 +08:00
PerishFire
526c7f7c26
Fix packaged auto-update release validation (#2565)
* fix: tighten packaged updater flow

* test: prune noisy extended ui coverage

* fix: hide unpublished release artifacts

* test: validate release updater channels

* fix: align prerelease release namespaces
2026-05-21 18:15:53 +08:00
Eli-tangerine
a56f559f9e
Merge pull request #2550 from nexu-io/codex/fix-home-example-prompts
[codex] fix home example prompt presets
2026-05-21 17:59:52 +08:00
Siri-Ray
b236b37b7d
Remove resume conversation button (#2562) 2026-05-21 17:55:03 +08:00
qiongyu1999
7fe4c3f581 fix(web): preserve live artifact preset metadata 2026-05-21 17:47:26 +08:00