mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
262 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
48f3404051 | fix home example prompt presets | ||
|
|
81eaf596b0
|
fix(web): show feedback prompt on every successful assistant turn (#2529) (#2532)
* fix(web): show feedback prompt on every successful assistant turn Previously the thumbs-up/down widget only appeared when the turn produced an `<artifact>`, wrote a file via Write/Edit, or emitted a live_artifact event. That left whole categories of completed turns — image/video generation via `generate_image` / `generate_video`, MCP tool runs, plain text answers — without any way for the user to rate them. Drop the `hasArtifactWork` gate from `isFeedbackEligible` and remove the helper functions that fed it. The remaining filters (streaming in progress, empty response, unfinished todos, runStatus !== "succeeded") still suppress the widget for genuinely incomplete turns. The PostHog `has_produced_files` field is still emitted so the analytics side can keep slicing feedback by whether the turn produced artifacts. * test(web): flip artifact-only feedback assertion in AssistantMessage.test.tsx Companion test file to `chat-feedback.test.tsx` — the same artifact-only visibility rule was duplicated here and asserted the text-only success case stays hidden. Flip that case to assert the widget now appears, and update the file-level comment so the new rule reads cleanly. The streaming / failed-run / empty-response cases keep their existing "hidden" assertions; those are still excluded under the new rule. |
||
|
|
e149616dbe
|
fix(web): decouple privacy banner from onboarding and Settings lifecycles (#2525)
* fix(web): decouple privacy banner from onboarding and Settings lifecycles
The first-run privacy banner used to be tightly bound to two unrelated
surfaces: it was hidden whenever Settings was open, and the onboarding
panel only navigated in after the user had resolved the banner. The
coupling existed because the banner's z-index sat below modal backdrops,
so showing both at once collided visually, and the banner+onboarding
were linearized to avoid a "two unfinished things on screen" feel.
This change makes the three surfaces independent:
- Lift `.privacy-consent-banner` z-index above the modal-backdrop layer
so the banner stays visible (and clickable) when Settings is open. The
banner is already `pointer-events: none` with opt-in on its actionable
children, so it does not steal clicks from the layer below.
- Drop the `!settingsOpen` guard from `showPrivacyConsent`.
- Drop the `privacyDecisionAt != null` guard from the bootstrap
onboarding route; first-run users land on `/onboarding` purely based
on `!onboardingCompleted`, and the banner sits on top in parallel.
- Drop the `navigate(... onboarding)` side effect from the banner's
`onAccept` — the banner only persists the privacy decision now.
Bootstrap also had to be reshaped: the merged config is now computed
outside the `setConfig` updater so navigation can happen synchronously
after the state update. Calling `navigate` inside the updater triggered
a React "setState while rendering" warning, and reading a captured flag
after `setConfig` was unreliable because React 18+ batches the updater
to the next render — the navigate condition was never observed.
Existing test that asserted the old coupling ("banner unmounts while
Settings is open") is inverted to lock in the new contract.
* fix(web): defer privacy banner until onboarding is done and user lands on home
Product feedback on the previous lifecycle change: the banner should not
appear during the welcome panel. It should surface only:
- immediately after the user Skips onboarding (lands directly on home), or
- after the user finishes the design-system step and later returns to a
home view from the project view they were dropped into.
To capture both paths with a single rule, the banner now requires:
1. Daemon config hydrated (unchanged).
2. No privacy decision recorded yet (unchanged).
3. onboardingCompleted === true.
4. The current route is a home route (route.kind === 'home').
The Skip path already routes through finishOnboarding, which calls
onCompleteOnboarding() + changeView('home') — that satisfies all four
gates the moment Skip is clicked.
The finish path (step 2: create design system) previously navigated to a
project view without marking onboardingCompleted. This commit mirrors the
Skip path by calling handleCompleteOnboarding() from the App-level
renderDesignSystemCreation onCreated callback (the onboarding-specific use
of DesignSystemCreationFlow). The shared DesignSystemFlow component is left
untouched so the create-from-Settings entry point keeps its existing
semantics.
The route gate keeps the banner suppressed while the user is reading their
just-created design system project. As soon as they navigate back to the
entry shell (home route), the banner appears.
Tests:
- "withholds the privacy banner until onboarding completes" — covers
gate 3 (onboardingCompleted=false while still on onboarding/home).
- "withholds the privacy banner outside the home route" — covers gate 4
(user is on a project route, onboardingCompleted=true).
- Existing "keeps the first-run privacy banner mounted while settings is
open" still passes; the Settings/banner z-index relationship is
independent of these gates.
* fix(web): allow privacy banner to surface on non-home routes after onboarding
Follow-up to the previous lifecycle change. After exercising the design-
system finish path end-to-end, product wants the banner to appear in the
project view the user is dropped into — the first generation is running
in the background and the user is already waiting, so the disclosure can
be acknowledged inline rather than being held back until they navigate
back to a home view.
The Skip path is unchanged: Skip routes the user to home and the banner
appears there.
This drops the `route.kind === 'home'` guard and the matching test, and
adds a contract test that locks in banner visibility on a project route
when `onboardingCompleted=true` and no privacy decision has been made.
|
||
|
|
50a4dc8a62 | Merge origin/main into release/v0.8.0 | ||
|
|
10940ab7b6
|
[codex] Update home starter template categories (#2501)
* Update home starter template categories * fix(web): address PR #2501 review follow-ups - usePluginFacets.clearFacets() now also resets `mode` to 'all' so the empty-state "Clear filters" CTA escapes Saved mode in one click. A fresh browser clicking Saved (or Saved combined with a zero-match search) was previously stranded on the empty view because the CTA only cleared `selection`/`query`. - Restore the "Link code folder" item in the composer Tools -> Import panel. The earlier refactor dropped its `onLinkFolder` wiring and left every remaining ImportItem disabled, so chats without linked dirs lost the only enabled entry point to `openFolderDialog()` / `patchProject({ metadata.linkedDirs })`. Adds regressions: - plugins-home-section: Clear filters from the Saved empty state - ChatComposer.import-menu: folder-link click-through hits the folder dialog and patches `linkedDirs` --------- Co-authored-by: qiongyu1999 <2694684348@qq.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
c4a891b184 | Merge origin/main into release/v0.8.0 | ||
|
|
6359132d04
|
Polish Design Systems settings gallery controls (#2392)
* Polish design systems gallery settings * Address design systems settings review --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> |
||
|
|
f5f8937421 |
Merge origin/main into release/v0.8.0
Conflict resolved by taking origin/main: - apps/web/src/components/EntryNavRail.tsx design-systems rail button icon name palette-filled (release-side) -> blocks (main); main's icon swap is part of the more recent design-systems rail pass. |
||
|
|
ce95266586
|
[codex] Polish home composer working-directory controls (#2468)
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 3s
ci / Preflight (push) Failing after 2s
ci / Core package tests (push) Failing after 1s
ci / Tools workspace tests (push) Failing after 1s
ci / Daemon workspace tests (1/2) (push) Failing after 1s
ci / Daemon workspace tests (2/2) (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / E2E vitest (push) Failing after 1s
ci / Playwright critical (starters) (push) Failing after 1s
ci / Playwright critical (core) (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / App workspace tests (push) Failing after 0s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
* Polish design system home flows * Polish home prompt presets * Polish home working directory controls * test: align home hero chrome smoke * fix: stabilize home composer ci checks --------- Co-authored-by: qiongyu1999 <2694684348@qq.com> |
||
|
|
722ddfa235 |
Merge origin/main into release/v0.8.0
Conflicts resolved by taking origin/main on both files. Root cause: main's PR #2460 (fix(landing): align logo.webp with brand icon) changed HomeHero.tsx's .home-hero__brand-mark to render <img src=/app-icon.svg> instead of an inlined <HeroBrandIcon /> SVG, and bundled the matching CSS (26px round badge with bg-panel + border + padding 2px) plus a gap/font-size tune. The release-side visual-refresh CSS still targeted the SVG layout (38px square, transparent, inset SVG selector). Keeping release's CSS would leave main's <img> unstyled. - apps/web/src/styles/home/home-hero.css three blocks, all taken from main: .home-hero__brand gap 8px, .home-hero__brand-mark redesigned for <img> child, .home-hero__brand-name font-size 16px. - apps/web/src/index.css two blocks, both taken from main: workspace tab close column 22px and .workspace-tab__close 18x18 (paired tune-down of tab UI spacing). |
||
|
|
8193981511
|
Keep PR 2400 changes without folder pickers (#2462)
* feat(daemon): add project working directory management and editor hand-off functionality - Introduced new flags for project commands to manage working directories, including `--working-dir` and `--dir`. - Implemented API routes for listing available editors and opening projects in selected editors. - Added a hand-off button in the ChatPane header to facilitate opening project folders in local applications. - Enhanced the HomeHero component to include working directory and design system settings, improving user experience in project creation. - Created HomeHeroSettingsChips component for inline management of working directory and design system selection. * feat(chat): implement voice transcription proxy and enhance UI components - Added a new API route for voice transcription using OpenAI's `/audio/transcriptions` endpoint, allowing users to send audio blobs directly for transcription. - Integrated multer for handling audio file uploads in memory, ensuring efficient processing without disk storage. - Updated the HomeHero component to include example prompt suggestions for plugins, enhancing user interaction. - Introduced the EditorIcon component to visually represent different editors in the hand-off menu, improving the user experience. - Refined the HandoffButton component to utilize the new EditorIcon, providing a more cohesive interface for selecting editors. - Enhanced CSS styles for various components to improve layout and responsiveness, including adjustments to tab and button sizes for better usability. * style(workspace-shell): enhance layout and overflow handling - Updated CSS for .workspace-shell to ensure full viewport width and height, with proper overflow management. - Adjusted grid layout to prevent content overflow and maintain responsiveness. - Modified styles for .workspace-tabs-chrome to improve width handling and prevent overflow issues. * refactor(chat): remove voice transcription proxy and related components - Deleted the voice transcription proxy implementation, including the associated API route and multer configuration. - Removed the MicButton component from the ChatComposer and HomeHero components to streamline the UI. - Updated HomeHero to include example suggestions without the voice input functionality. - Adjusted CSS styles for various components to maintain layout consistency after the removal of the MicButton. * feat(daemon): implement minting of HMAC tokens for working directory management - Added a new function `mintImportTokenFromCurrentSecret` to generate HMAC tokens bound to a specified base directory, enhancing security for working directory operations. - Updated the `desktop-auth.ts` file to include the new token minting functionality, which returns structured errors when the desktop auth secret is cleared. - Introduced new IPC message types for minting import tokens in the sidecar protocol, allowing seamless integration with the daemon's working directory management. - Enhanced the `WorkingDirPill` component to utilize the new token minting flow for secure directory selection in desktop builds. - Updated CSS styles for the HomeHero component to accommodate new example suggestion features and maintain layout consistency. * fix(HomeView): import HOME_HERO_CHIPS constant for improved chip management - Updated the HomeView component to import the HOME_HERO_CHIPS constant from the chips module, enhancing the management of hero chips within the component. * feat(daemon): implement mintImportTokenViaSidecar for secure working directory management - Introduced the `mintImportTokenViaSidecar` function to facilitate the minting of HMAC tokens for desktop-import operations via the daemon's sidecar IPC. This allows CLI commands to bypass authentication when the desktop-auth gate is active. - Updated the CLI to utilize the new token minting function when setting the working directory, ensuring secure access to trust-gated API endpoints. - Enhanced the sidecar server to handle minting requests and return structured error messages for improved user feedback. - Added tests to validate the new token minting functionality and its integration with the working directory management process. - Refactored related components to support the new token flow, improving overall security and user experience. * feat(HomeHero): enhance UI components and styles for improved user experience - Updated HomeHero component to replace active dot indicators with Plug icons for better visual representation of active plugins. - Adjusted CSS styles for various elements, including padding and dimensions, to enhance layout consistency and responsiveness. - Introduced new styles for active type icons and improved hover effects for buttons. - Updated HomeHeroSettingsChips to change button titles and icons for clarity. - Added tests to ensure proper rendering and functionality of updated components. * feat(ProjectDesignSystemPicker): enhance design system selection with preview functionality - Updated the ProjectDesignSystemPicker component to include a preview feature for design systems, allowing users to see a preview of the selected design system. - Implemented hover functionality to update the preview based on the hovered design system. - Added fullscreen preview capability for a more immersive experience. - Enhanced CSS styles for the design system picker to improve layout and responsiveness. - Introduced tests to validate the new preview functionality and ensure proper interaction within the component. * feat: refactor project metadata handling and enhance design system picker - Updated the default scenario plugin ID retrieval to use project metadata, improving the logic for determining the appropriate plugin based on project intent. - Enhanced the ProjectDesignSystemPicker and related components to support localized design system summaries and categories, improving user experience. - Introduced new translations for working directory and design system picker components, ensuring better accessibility and usability across different locales. - Added a new 'live-artifact' project type to the HomeHero chips, expanding the functionality for users creating refreshable artifacts. - Updated tests to validate the new project metadata handling and design system picker functionalities. * feat: enhance localization and styling for design system components - Added French translations for working directory and design system picker components, improving accessibility for French-speaking users. - Updated CSS styles for the pet task item to ensure consistent padding and layout. - Introduced a new test suite for HomeHeroSettingsChips to validate localization and design system selection functionality. - Enhanced ProjectDesignSystemPicker tests to ensure proper localization and interaction with design system categories. * fix: update .gitignore to include all claude-sessions directories and remove specific session files - Modified .gitignore to ensure all claude-sessions directories are ignored by using a wildcard pattern. - Deleted two specific claude-sessions markdown files to clean up unnecessary session data. * fix: repair home automation ci regressions * fix: stabilize artifact consistency e2e * Remove folder picker changes from PR 2400 --------- Co-authored-by: pftom <1043269994@qq.com> Co-authored-by: qiongyu1999 <2694684348@qq.com> |
||
|
|
1cfe274a90 |
Merge origin/main into release/v0.8.0
Conflicts resolved by taking origin/main on all six points:
- apps/web/src/components/HomeHero.tsx:479-487 brand div removed
(main dropped the .home-hero__brand wrapper; the release-side visual
refresh still had it).
- apps/web/src/components/HomeHero.tsx:894-898 attach Icon size
18 (main's update) replaces 20 from release.
- apps/web/src/components/HomeHero.tsx:913-927 submit button uses
<Icon name="arrow-up" size={22} /> (main's component refactor)
instead of the release-side inline SVG.
- apps/web/src/components/EntryShell.tsx:578-582 Discord Icon size
14 (main) instead of 16 (release).
- apps/web/src/styles/home/home-hero.css drop .home-hero__brand /
__brand-mark / __brand-name rules — main removed both the component
div and these CSS rules together; keeping the CSS would be dead code.
- apps/web/src/styles/home/entry-layout.css Discord badge icon color
#5865f2 (main, the brand color introduced by PR #2386) instead of
release's neutral var(--text-strong).
|
||
|
|
0ee363f23e
|
Polish design systems library layout (#2421) | ||
|
|
41a33aed9e
|
Reapply "fix(web): demote Plugins and Integrations to nav rail footer (#1806)" (#2360) (#2397)
* Reapply "fix(web): demote Plugins and Integrations to nav rail footer (#1806)" (#2360)
This reverts commit
|
||
|
|
59c8d72ae4
|
feat(tweaks): bind toolbar toggle to artifact panel (#2348)
* feat(tweaks): bind toolbar toggle to artifact panel
Wires the host viewer's Tweaks toggle to artifact panels via two
protocols: postMessage __edit_mode_* for agent-generated .twk-panel
artifacts, and the existing class-based bridge for tweaks-skill
.tw-panel artifacts. Toggle disables itself when the artifact exposes
no panel, syncs state when the user closes the panel locally, and no
longer regenerates srcdoc on toggle (no iframe remount).
- bridge: emit od:tweaks-available + MutationObserver-driven
od:tweaks-panel-state so host learns availability and mirrors local
closes; transform-based hide preserves the artifact's transition
- host: listen for both od:tweaks-* (bridge) and __edit_mode_*
(artifact) dialects, send both on toggle, disable button when no
panel, reset state on file change
- skill: document the two protocols as a host integration contract
- i18n: add fileViewer.tweaksUnavailable for all 13 locales
* fix(tweaks): keep srcDoc path for `.tw-panel` artifacts
The class based tweaks template (`.tw-panel` / `.tw-hidden`) needs the
bridge `injectTweaksBridge` emits, but the default plain HTML preview
URL loads the iframe, which bypasses buildSrcdoc entirely. Without the
bridge there is no `od:tweaks-available` ping, so the toolbar toggle
stays disabled on first load of a tweaks template artifact unless an
unrelated mode (palette, inspect, etc.) coincidentally forces srcDoc.
Add a `tweaksBridge` flag to `shouldUrlLoadHtmlPreview` and detect the
fixed `.tw-panel` / `.tw-hidden` template selectors in the artifact
source via a new `hasTweaksTemplate` helper. FileViewer passes the
detected flag through so tweaks template artifacts pick the srcDoc
render path on first load.
Tests in `file-viewer-render-mode.test.ts` cover the new disqualifier,
the helper positive and negative cases, and combinations with the
existing flags.
* fix(tweaks): resolve v0.7 UI ambiguity between toolbar toggle and palette
After rebasing onto v0.7, three problems surfaced. v0.7 ships a palette
popover button also labeled `Tweaks` with the same sliders icon as the
new toolbar toggle. Toggling that popover flipped render mode (URL load
to srcDoc) and reloaded the iframe, flashing the preview. The resulting
iframe remount caused agent protocol artifacts to re-announce
`__edit_mode_available`, which flipped the toolbar toggle back on
without user input.
Rename the palette popover button to `Themes` (button label, dialog
title, `aria-label`) and swap its icon to a new `paint-bucket` glyph.
The artifact tweaks toggle keeps the `tweaks` sliders icon. Internal
identifiers (`data-testid="palette-tweaks-toggle"`, CSS classes, the
`PaletteTweaks` component name) stay stable so existing tests and
styles still target the same elements.
Drop the auto set true on `__edit_mode_available`; that signal now
flips `tweaksAvailable` only. `syncBridgeModes` posts the current
`tweaksMode` to the artifact (both the bridge dialect and
`__activate_edit_mode` / `__deactivate_edit_mode`) on every iframe
load so the panel matches the toolbar.
Mount both URL load and srcDoc iframes simultaneously, absolutely
positioned and overlapping, with CSS visibility flipping between
them. Toggling render mode no longer reloads the iframe so there is
no flash. `isOurIframe(source)` accepts messages from either iframe
so startup announcements from the hidden iframe are not lost; six
receive filter sites switch from `iframeRef.current?.contentWindow`
to the helper. Sends still target `iframeRef.current`, kept aligned
with the active iframe via a `useEffect`, and a `syncBridgeModesRef`
pushes current bridge state to the now visible iframe whenever the
render mode flips.
Tests that previously asserted exclusive render mode (`url-load`
vs. `srcdoc` presence) now assert the active `data-testid` sits on
the expected iframe via a co-attribute regex. The Draw bar element
picking test switches from a cached frame reference to a `getFrame()`
helper since `data-testid` follows the active iframe across toggles.
Add a `paint-bucket` entry to `Icon` (Lucide style stroke icon).
* fix(tweaks): scope `od:tweaks-available` to the active iframe
The dual iframe setup mounts both the URL load and srcDoc iframes at
once and accepts postMessage events from either via `isOurIframe`. The
srcDoc iframe always carries the always injected tweaks bridge, which
runs `document.querySelector('.tw-panel')` on mount and posts
`{ type: 'od:tweaks-available', available: false }` for any artifact
that does not ship the class based panel. For an agent protocol
artifact (`.twk-panel`, `__edit_mode_*`), the URL load iframe correctly
announces `__edit_mode_available` and the host sets
`tweaksAvailable = true`. The hidden srcDoc iframe's `available: false`
ping arrives shortly after and overrides that to false, silently
disabling the toolbar button.
Scope `od:tweaks-available` to the active iframe only by re-checking
`ev.source === iframeRef.current?.contentWindow` before applying it.
`__edit_mode_available` and `__edit_mode_dismissed` stay accepted from
either iframe so the artifact's own announcement still drives the
toolbar toggle across render mode flips.
Spotted by Siri-Ray on PR #1643.
* fix(tweaks): start the toolbar toggle ON when the artifact mounts its panel visible
Both tweaks dialects (the class-based `.tw-panel` skill template and the
`.twk-panel` agent-generated edit-mode protocol) mount their panel visible
by default. Before this change the toolbar `Tweaks` toggle started in the
OFF state regardless, so the user saw the panel but had to click
toggle-on → toggle-off to actually hide it — confusing because the toggle
disagreed with what they could plainly see in the preview.
Two changes wire the initial state through to the toolbar:
- `srcdoc.ts` (class-based dialect): the tweaks bridge's `onReady` now
fires `postState()` alongside `postAvailability()`. `postState()` reads
`!panel.classList.contains('tw-hidden')` and posts the artifact's actual
initial visibility, so the host's existing `od:tweaks-panel-state`
handler picks it up and mirrors it into `tweaksMode`. Previously only
MutationObserver-driven changes were posted, so the host never learned
the artifact's initial state.
- `FileViewer.tsx` (twk-panel dialect): the agent dialect's
`__edit_mode_available` carries no visibility payload, so we infer
default-open from the fact that the artifact bothered to announce
availability at all (the SDK pattern is `useState(true)`). Mirror that
into `tweaksMode` exactly once per file (tracked by
`firstEditModeAvailableSeenForFileRef`), so an iframe remount triggered
by, e.g., flipping render mode through the Themes popover does not snap
a user-driven OFF back to ON.
Also fix a runtime `ReferenceError: panel is not defined` regression
this same change introduced when first written with backticks inside the
new code comment — the comment lived inside a `\`...\``-delimited script
template literal, so the embedded backticks closed and reopened the outer
literal and broke the bridge's JS body. Replaced with plain text.
Validation: web typecheck clean, 1597/1597 tests pass. Manually verified
with a `.twk-panel` artifact: open file → tweaks toggle is ON, panel
visible → one click hides both.
* fix(tweaks): seed bridge state from the panel's authored class, not from the host hidden attribute
The bridge installs `data-od-tweaks-hidden` on `<html>` synchronously in
`<head>` so the panel never flashes on initial paint. That attribute is
therefore *always* present by the time `onReady()` fires, which meant the
previous `applyClassesToPanel(!hasAttribute(...))` call unconditionally
forced `.tw-hidden` onto the panel, the follow-up `postState()` read that
forced-hidden class, and the host saw `visible: false` even when the
artifact had authored the panel as default-visible. The PR-1643 attempt
at "start ON when the artifact mounts visible" therefore still reported
OFF for the class-based template path.
Read the panel's authored class state first (the artifact body has just
parsed, so the panel's class is what the artifact wrote and nothing
else has touched it yet), then drive the attribute, the applied class,
and the `od:tweaks-panel-state` post from that captured value:
```ts
var panel = panelEl();
var initialVisible = !!panel && !panel.classList.contains('tw-hidden');
document.documentElement.toggleAttribute('data-od-tweaks-hidden', !initialVisible);
applyClassesToPanel(initialVisible);
attachObserver();
postAvailability();
postState();
```
A default-visible `.tw-panel` now reports `visible: true` on mount, the
host mirrors that into `tweaksMode = true`, and the toolbar Tweaks toggle
starts in the ON state instead of disagreeing with what the user sees in
the preview. The `.twk-panel` agent-protocol path is unaffected; its
initial-state mirror still goes through the
`firstEditModeAvailableSeenForFileRef` guard in `FileViewer.tsx`.
Surfaced by Siri-Ray in https://github.com/nexu-io/open-design/pull/1643#discussion_r3263571196.
Validation: web typecheck clean, 1597/1597 tests pass.
* fix(tweaks): re-mirror __edit_mode_available default-open state when switching .twk-panel files
The once-per-file guard that mirrors a `.twk-panel` artifact's
default-open state into the toolbar `tweaksMode` lives inside a
`window.addEventListener('message', ...)` handler installed in a
`useEffect(..., [])` with an empty dep list. The handler therefore
closed over the first-render `file.name`. After opening one
`.twk-panel` artifact, `firstEditModeAvailableSeenForFileRef.current`
got set to that first file; switching to a second `.twk-panel` file
left the message listener still comparing the new artifact's
`__edit_mode_available` against the stale captured name, so the
guard never re-fired and the toolbar stayed OFF while the new
artifact's panel was clearly visible — exactly the mismatch the
guard was supposed to prevent on initial load.
Add `file.name` to the listener effect's dep list so the handler
gets a fresh closure on every file switch. The bridge-message setters
(`setTweaksAvailable`, `setTweaksMode`), `isOurPreviewIframeSource`,
and `firstEditModeAvailableSeenForFileRef` are stable across renders,
so re-binding the listener has no other side effects beyond updating
the captured `file.name`.
Surfaced by Siri-Ray in
https://github.com/nexu-io/open-design/pull/1643#discussion_r3266838151.
Red-spec regression test added: `FileViewer tweaks toolbar > mirrors
__edit_mode_available default-open state for each switched-to
.twk-panel file`. Verified to go red on the bug (deps `[]`) and green
on the fix (deps `[file.name]`).
Validation: web typecheck clean, 1598/1598 tests pass (was 1597).
* i18n(tweaks): add fileViewer.tweaksUnavailable to the remaining 6 locales
The toolbar's disabled-Tweaks tooltip key landed in 13 locale files but
6 were missed (ar, fr, id, it, th, uk). Those locales were still falling
through to the English string via the `...en` spread, which contradicts
the repo convention that every key be defined explicitly in each locale.
Add the translation alongside the existing `fileViewer.tweaks` entry so
the full set of 19 locales now ships native copy for the disabled state.
Surfaced by Siri-Ray in
https://github.com/nexu-io/open-design/pull/1643#discussion_r3267654385.
* fix(tweaks): respect default-closed dynamic panels in __edit_mode_available
Protocol A in `design-templates/tweaks/SKILL.md` documents that the
artifact may default the panel to either open or closed and the host
should sync its toolbar toggle to whichever state the artifact reports.
The previous handler ignored that and unconditionally mirrored
availability into `tweaksMode = true`, so a default-closed dynamic
artifact would be force-opened the moment `syncBridgeModes` ran and
fired `__activate_edit_mode` — the artifact could not stay closed even
though the contract said it could.
Extend the message shape so the artifact can report its initial state
on the same payload:
{ type: '__edit_mode_available', visible?: boolean }
The host now reads `data.visible`:
- omitted → treat as `true` (back-compat: existing artifacts
emitting the legacy zero-arg shape mount with the
panel already on screen, which is the SDK pattern
`useState(true)`).
- `visible: true` → toolbar starts ON.
- `visible: false` → toolbar starts OFF, panel stays closed; the user
opts in by clicking the toggle, which then fires
`__activate_edit_mode` via the existing
`syncBridgeModes` path.
Update `design-templates/tweaks/SKILL.md` to document the new optional
field alongside the legacy shape.
Surfaced by Siri-Ray in
https://github.com/nexu-io/open-design/pull/1643#discussion_r3269955351.
Red-spec regression test added: `FileViewer tweaks toolbar > respects
__edit_mode_available { visible: false } for default-closed dynamic
artifacts`. Verified red without the fix (always-true mirror) and green
with the fix (`data.visible !== false`).
Validation: web typecheck clean, 1599/1599 tests pass.
|
||
|
|
1855b22d24
|
Improve desktop updater ready UI (#2403) | ||
|
|
f4b8fbece2
|
Fix template project creation flow (#2399) | ||
|
|
a5e43ae2a4
|
add discord feedback entry points (#2386) | ||
|
|
71044bd3d6
|
test(e2e): harden extended coverage state assertions (#2245)
* test(e2e): harden extended coverage contracts * docs(testing): add e2e hardening status * fix(web): persist artifact chips after daemon runs * ci: install playwright browsers for e2e vitest * Fix daemon run recovery across reloads Pin daemon-created runs to assistant messages immediately so hard reloads before the create response can reattach. Replay terminal and active run events from the beginning on reload so restored turns keep assistant text, thinking events, produced files, and artifacts. Fixes #2366 Fixes #2368 Fixes #2371 * test(e2e): preserve fake runtime selection across reload * fix(web): scope daemon run recovery to daemon mode * fix(e2e): remove duplicate delayed smoke flag * fix(web): scope replay artifact recovery to current run * fix(daemon): remove duplicate run-create pin |
||
|
|
d03b8fd095
|
fix(plugins): stop recommending raw publish CLIs from authoring summary (#2380)
QA repro (transcript shared on PR #2363): the agent finishes the Home "Create plugin" → plugin-authoring chip flow, validates / packs / installs the generated plugin, then in its summary turn freeform- recommends shell commands like: od plugin publish ./generated-plugin --to open-design cd generated-plugin && git init && git add . && git commit -m "..." gh repo create lefarcen/<name> --public --source=. --push Two things go wrong: 1. The summary recreates the exact flows the plugin-folder card buttons already drive end-to-end (PR #2363 wired the buttons through the right gh + git sequences with auth gates, jq fallback, and retry rules baked in). The freeform suggestions drop those guarantees — they're the source of bug #2332 where `od plugin publish --to open-design` was the recommended action even though it produces an issue URL rather than creating a public repo. 2. The prompt explicitly hinted at `od plugin publish` in step 3 ("Then run or prepare the CLI path: ... and `od plugin publish` when the user is ready to open a registry PR"). The agent dutifully repeated that as the next step, even though the publish work belongs to the plugin-folder card button prompts now. Rewrites `PLUGIN_AUTHORING_PROMPT_TEMPLATE` to: * Keep the original scaffolding instructions (generated-plugin/ + SKILL.md + open-design.json + plugin.repo + inputs). * Replace the loose "od plugin whoami/login through gh, and od plugin publish when the user is ready" sentence with a precise local- validation chain: `od plugin validate` → `od plugin pack` → `od plugin install --source <abs-path>`. * Tell the agent to STOP after the summary, and explicitly ban follow-up suggestions of `od plugin publish`, `od plugin publish --to open-design`, `gh repo create`, `git init` / `git remote add` / `git push`. The ban names the workarounds it has actually emitted in QA transcripts. * Point the user at the plugin-folder card buttons (Add to My plugins / Publish repo / Open Design PR) and call out that those prompts encode the auth gates and fallback rules the summary suggestions would skip. * Same jq guidance carried over from PR #2363: do not assume the standalone `jq` binary, prefer the built-in Read tool / cat / node -e, and disambiguate from `gh ... --jq` (which is fine because gh ships its own embedded library). Tests: * `apps/web/tests/components/home-hero/plugin-authoring-prompt.test.ts` (new) — nine assertions covering: goal-placeholder interpolation, generated-plugin scaffolding mentions, local validation chain, the follow-up CLI ban list (`od plugin publish --to open-design`, `gh repo create`, `git push`), the plugin-folder card hand-off, the jq fallback + gh-flag carve-out, and the round-trip helpers (`buildPluginAuthoringInputs` / `buildPluginAuthoringPromptForInputs` / `createPluginAuthoringHandoff`). Validation: * `pnpm --filter @open-design/web exec vitest run tests/components/home-hero/plugin-authoring-prompt.test.ts` → 9/9 passed * `pnpm --filter @open-design/web exec vitest run tests/components/HomeView.prefill.test.tsx` → 11/11 passed (no regression in the adjacent HomeView prompt-prefill suite; that test references the template literal directly so a silent shape change would surface here) Related: PR #2363 fixes the same source-of-bug shape on the plugin-folder card "Publish repo" button. That PR is the canonical home for the publish flow; this PR makes sure the authoring summary hands off to it rather than re-implementing it inline. |
||
|
|
c617e30e27
|
fix(plugins): make Publish repo actually create the author's repo (#2332) (#2363)
* fix(plugins): make Publish repo actually create the author's repo (#2332) QA repro from 0.8.0 preview: clicking "Publish repo" on a generated plugin's `DesignFilesPanel` card ran the agent down a path that produced an Open Design registry-submission URL but never created the author's GitHub repo. After the action finished, `gh repo view nuomi/cat` still returned 404 and `git ls-remote https://github.com/nuomi/cat.git HEAD` failed with "Repository not found". Root cause is the action prompt at `apps/web/src/components/design-files/pluginFolderActions.ts:11-12`: publish: 'Use the supported `od plugin publish` or repository-publish flow after confirming the manifest.' That sentence let the agent pick the legacy registry-link CLI (`od plugin publish --to open-design`), which mirrors the path the "Open Design PR" button takes and emits an issue URL instead of creating a public repo. The button label said "Publish repo" but the behavior collapsed onto the registry-submission flow. This PR rewrites the `publish` prompt in the same shape PR #2182 used for `contribute` — a numbered gh + git sequence that drives the real action end-to-end: 1. Pre-flight `gh --version` / `gh auth status`. Invalid / expired tokens are treated the same as not-logged-in (the bug-report agent kept going past an "invalid token" warning). 2. Read manifest, capture `name`, `version`, `description`, `plugin.repo`. Fall back to `https://github.com/<gh-login>/<name>` when `plugin.repo` is missing and write it back into the manifest. 3. `gh repo view <owner>/<name>` to decide create-vs-update. 4a. Repo does not exist → `git init` + commit + tag + `gh repo create <owner>/<name> --public --source . --push`. 4b. Repo exists → reuse the remote, `git add -A` + `git commit -m "Update: <name> v<version>"` (skip if working tree is clean), `git tag v<version>` (skip if already published), `git push`. 5. Verify with `gh repo view <owner>/<name> --json url,nameWithOwner`. 6. Hand off the resolved `https://github.com/<owner>/<name>` URL to chat. End the turn. Hard constraints encoded in the prompt: * Do NOT call `od plugin publish --to open-design` (or any `--to <catalog>` variant). That is the registry-submission flow. * Do NOT call `AskUserQuestion` — fire-and-forget, same as the `contribute` flow's stall fix. * Do NOT auto-install gh/git. Detect-and-instruct only. * Do NOT force-push or overwrite a published tag. * Do NOT retry a failed step. Report and stop. Refactor: pulled `publish` out of the shared `ACTION_TITLES`/ `ACTION_NOTES` template into its own `buildPublishPrompt(folderPath)` function (mirrors `buildContributePrompt` from PR #2182). `install` keeps the simple shared template — that action stays inferrable from the manifest and doesn't need the same blast radius. Tests: * `apps/web/tests/components/pluginFolderActions.test.ts` — extends the existing contract suite with seven new `publish` assertions: targets `plugin.repo` not the registry catalog, drives the full gh + git command list, handles both new-repo and existing-repo branches, explicit ban on the registry-submission CLI, hard-bans on AskUserQuestion / auto-install / force-push / retry, "invalid token" treated as STOP, `${folderPath}` interpolation guard, ends by handing the repo URL back to chat. Validation: * `pnpm --filter @open-design/web exec vitest run tests/components/pluginFolderActions.test.ts` → 16/16 passed (was 9/9 before this PR; +7 new publish-flow assertions, the old generic "mentions od plugin publish" assertion replaced with the precise contract above) (Local `pnpm --filter @open-design/web typecheck` fails on `tests/runtime/exports.test.ts` because `packages/host/dist/testing` isn't built in this checkout — pre-existing breakage from `2c128e0e refactor desktop host bridge` on main, unrelated to this prompt change. CI runs a fresh install and was green on the four previous prompt-only PRs that touched the same module.) Closes #2332. * fix(plugins): don't assume standalone jq when reading the manifest QA repro from the Open Design PR button (transcript shared with the PR #2363 thread): the agent reached step 2 of the contribute prompt, ran `jq '{name,title,description,version}' generated-plugin/open-design.json`, got `zsh:1: command not found: jq`, and stopped per the prompt's "stop on first hard failure" rule. No fork, no branch, no PR. jq is not part of the OD agent runtime baseline — default macOS and Windows shells don't ship it. The agent reached for it first because "jq" is the default JSON tool in claude/codex's shell training distribution, not because the prompt asked for it. The prompt just said "Load and capture", which the agent interpreted as "shell out to the most common JSON parser". Updates both step-2 instructions (contribute + publish prompts) to: - List portable manifest-read alternatives in priority order: the built-in Read tool (always available); `cat` + manual JSON parsing; `node -e 'JSON.parse(...)'` as the shell-only fallback. - Add an explicit "Do not assume the standalone `jq` binary is installed" guard with the macOS / Windows shell rationale. - Disambiguate the standalone `jq` CLI from `gh ... --jq`. The gh flag uses an embedded library and is fine — without the disambiguation the agent reads the ban literally and stops using `gh api user --jq .login` at step 3. Tests: * `apps/web/tests/components/pluginFolderActions.test.ts` — two new contract assertions: - publish prompt: warns against assuming standalone jq is installed; lists cat and node -e as alternatives. Closes the regression on its own surface. - shared block: both contribute and publish prompts disambiguate standalone jq from `gh ... --jq`. One assertion guards both flows so a future prose edit can't drop the carve-out on one side. Validation: * `pnpm --filter @open-design/web exec vitest run tests/components/pluginFolderActions.test.ts` → 18/18 passed (was 16/16 on the previous PR #2363 commit; +2 new jq-guidance assertions) Continues PR #2363. Same source-of-bug shape as the registry-submission fallback issue this PR was opened to fix: agent picks a tool the prompt didn't actually ask for because the prompt was loose. |
||
|
|
25f977c84c
|
fix(web): rename FileViewer Share button label to Export (#2233)
* fix(web): rename FileViewer Share button label to Export The toolbar button in FileViewer triggers a menu where 5 of 8 items are Export/Download actions and only Deploy to Vercel + Copy link are real "share" semantics. The previous "Share" label mismatched user mental model (Share = send a link to someone, Export = save a file locally), which likely contributed to a steep studio_view -> studio_click drop in the artifact-export funnel (~18% step conversion). Rename only the i18n value for fileViewer.shareLabel across all 18 locales so the button reads "Export" (and the locale-equivalent). Keys, component code, icon, and menu contents are unchanged. The unused fileViewer.share key and the unrelated examples.shareMenu / preview.shareMenu keys are left intact. Co-authored-by: Cursor <cursoragent@cursor.com> * Improve export CTA and BYOK test feedback * Fix BYOK provider review regressions * Fix settings locale regressions * Fix design system import header layout --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5fc27f8923
|
Fix daemon run recovery across reloads (#2374)
* Fix daemon run recovery across reloads Pin daemon-created runs to assistant messages immediately so hard reloads before the create response can reattach. Replay terminal and active run events from the beginning on reload so restored turns keep assistant text, thinking events, produced files, and artifacts. Fixes #2366 Fixes #2368 Fixes #2371 * Fix ProjectView daemon run recovery tests |
||
|
|
38b59fa4d9
|
fix(web): confirm before deleting saved template in New Project (#2330)
* fix(web): confirm before deleting saved template in New Project EntryShell and NewProjectModal were dropping the onDeleteTemplate prop, so the trash button in the template picker was inert; thread it through and gate it behind an alertdialog confirm. Fixes #2237 * fix(web/i18n): translate deleteTemplate keys across 18 locales The initial commit shipped English placeholders for the 4 new newproj.deleteTemplate* keys; replace with localized strings matching each locale's existing designs.deleteConfirm style. * fix(web): gate confirm-dialog backdrop on in-flight delete + style error message Backdrop now no-ops while `deleting` is true (matching the Cancel and Delete button gating), preventing a false-cancellation race where the backgrounded DELETE keeps running and leaks a stale `deleteError=true` into the next dialog open; also adds the missing `.modal-confirm-error` rule so the inline failure copy renders as a destructive state instead of default body styling. |
||
|
|
c530d163f8
|
feat(web): "Resume conversation in new chat" UI — #462 Commit B (companion to #1718) (#2264)
* feat(contracts): add handoff request/response DTOs
Adds HandoffRequest, HandoffResponse, and HANDOFF_SCHEMA_VERSION for
the upcoming POST /api/projects/:id/handoff synthesis endpoint. Mirrors
the finalize.ts subpath pattern (package.json#exports + esbuild entry +
index re-export) so daemon and web can import
@open-design/contracts/api/handoff.
Refs nexu-io/open-design#462.
* feat(daemon): add handoff synthesis pipeline (buildHandoffPrompt + synthesizeHandoffPrompt)
Adds `apps/daemon/src/handoff-design.ts` exposing the resume-conversation
synthesis primitives the upcoming `POST /api/projects/:id/handoff` route will
call into.
- `buildHandoffPrompt({ projectId, transcriptJsonl, transcriptMessageCount,
now })` returns the system + user prompts. System prompt asks Claude to
emit a structured Markdown body with Context / Decisions made / Open
questions / Current focus / Provenance, with Provenance bullets explicitly
flat (no Markdown emphasis on labels) to preempt the PR #1584 round-2
parser bug.
- `synthesizeHandoffPrompt(db, projectsRoot, projectId, options)` reuses the
existing finalize-design pipeline pieces: `exportProjectTranscript` →
`truncateTranscriptForPrompt` → `buildHandoffPrompt` →
`callAnthropicWithRetry` → `extractDesignMd`, but without the lockfile,
disk write, design-system, or artifact-resolution paths.
- Promotes `DEFAULT_TIMEOUT_MS` in finalize-design.ts to `export const` so
handoff shares the same 120s upstream-call bound.
Refs nexu-io/open-design#462.
* feat(daemon): wire POST /api/projects/:id/handoff route
Adds the handoff HTTP route and registers it in server.ts. Validation
block + error-mapping shape mirror registerFinalizeRoutes (BYOK payload,
upstream-error → ApiErrorCode mapping, redactSecrets on the raw upstream
body). Handoff has no lockfile, so the CONFLICT branch is omitted.
`res.on('close')` is wired to flip an AbortController whose signal is
threaded into synthesizeHandoffPrompt, so a UI-side cancel actually
aborts the daemon-side Anthropic call rather than letting it keep
running after the client walks away (mirrors the PR #974 fix for
finalize).
- `apps/daemon/src/handoff-routes.ts` — new, exports registerHandoffRoutes
+ RegisterHandoffRoutesDeps.
- `apps/daemon/src/server-context.ts` — adds handoff slot to ServerContext.
- `apps/daemon/src/route-context-contract.ts` — adds RegisterHandoffRoutesDeps
to the compile-time coverage assertion.
- `apps/daemon/src/server.ts` — imports synthesizeHandoffPrompt +
registerHandoffRoutes, builds handoffDeps, registers the route next
to finalize.
- `apps/daemon/tests/handoff-route.test.ts` — 12 HTTP-layer tests:
validation (400/403/404), happy path, upstream error mapping
(401/429/502/502 non-JSON), api-key redaction.
- `apps/daemon/tests/handoff-route-abort.test.ts` — client-disconnect
aborts the daemon-side controller.
Refs nexu-io/open-design#462.
* fix(daemon): map TranscriptExportLockedError to 409 CONFLICT on handoff route
`exportProjectTranscript` acquires a per-project `.transcript.lock`
internally (apps/daemon/src/transcript-export.ts:131-163) and throws
`TranscriptExportLockedError` on EEXIST. Concurrent handoff requests —
or a handoff that races `/api/projects/:id/finalize/anthropic` — lost
that lock and surfaced as 500 INTERNAL_ERROR through the route's
generic catch.
- `apps/daemon/src/handoff-routes.ts` — catch `TranscriptExportLockedError`
and return `409 CONFLICT` ahead of the generic 500 branch, mirroring
the existing `FinalizePackageLockedError → 409 CONFLICT` mapping at
`apps/daemon/src/import-export-routes.ts:603-605`.
- `apps/daemon/src/server.ts` — thread `TranscriptExportLockedError`
through `handoffDeps` so the route can match without a direct import.
- `apps/daemon/src/handoff-design.ts` — correct the module header
comment that incorrectly claimed "no lockfile (concurrent handoff
calls are safe)" — handoff does not add its own lock, but it does
transitively acquire `.transcript.lock` via the transcript-export
call.
- `apps/daemon/tests/handoff-route.test.ts` — regression test that
pre-acquires `.transcript.lock` on disk via `fs.openSync(lockPath, 'wx')`
before firing a handoff request, asserts 409 CONFLICT.
Refs nexu-io/open-design#462 — addresses @nettee's blocking review on
PR #1718 (comment 3242251338).
* fix(daemon): keep handoff request timeout armed through the response body read
`synthesizeHandoffPrompt` cleared the upstream-call timeout in a `finally`
that ran as soon as `callAnthropicWithRetry` returned. But `fetch()`
resolves once the upstream sends *headers* — so the subsequent
`await response.json()` body read ran with no timeout. A response that
sends headers and then stalls its body could hang `/api/projects/:id/handoff`
indefinitely instead of failing.
- `apps/daemon/src/handoff-design.ts` — move `clearTimeout(timeoutId)` into a
single outer `finally` spanning both the call and the `response.json()`
body parse, so the timeout stays armed until the body is fully consumed.
- `apps/daemon/src/handoff-design.ts` — the body-parse catch now re-throws
`AbortError` as-is, mirroring the call-phase catch. Without this a
body-phase timeout would surface as `502` "non-JSON body"; re-throwing
lets the route map it to the intended `503` "handoff timed out"
(`handoff-routes.ts:122-124`).
- `apps/daemon/tests/handoff-design.test.ts` — regression test: a `fetchImpl`
returning a `Response` whose body never closes after headers, raced
against a 500ms deadline, asserts the call aborts (not hangs) and rejects
with `AbortError`.
Refs nexu-io/open-design#462 — addresses @nettee's round-2 blocking review
on PR #1718 (`handoff-design.ts:196`).
* fix(daemon): map upstream 400 to 400 BAD_REQUEST on handoff route
`callAnthropicWithRetry` preserves a non-retryable upstream status, so an
Anthropic HTTP 400 (`invalid_request_error` — unknown model, invalid
maxTokens, malformed body) reached the route's `FinalizeUpstreamError`
branch and fell through to `502 UPSTREAM_UNAVAILABLE`. That reported
deterministic caller input as a transient server outage, inviting
pointless retries and hiding which field was wrong.
- `apps/daemon/src/handoff-routes.ts` — special-case `err.status === 400`
to `400 BAD_REQUEST` with the redacted upstream detail, ahead of the
generic 502. Also refresh the route docblock: it claimed the 409 branch
was omitted (stale since the R1 TranscriptExportLockedError fix) and
that error mapping fully mirrors finalize (now diverges on 400).
- `apps/daemon/tests/handoff-route.test.ts` — route test driving an
Anthropic `400 invalid_request_error`: asserts 400 BAD_REQUEST, the
upstream detail is surfaced, and an echoed key is redacted.
- `packages/contracts/tests/package-runtime.test.ts` — import
`@open-design/contracts/api/handoff` through the package `exports` map
and assert `HANDOFF_SCHEMA_VERSION`, covering the built publish surface
(esbuild entry + exports map + root re-export) that the source-only
`handoff-contract.test.ts` does not exercise.
Refs nexu-io/open-design#462 — addresses @nettee's round-3 blocking
review on PR #1718.
* fix(daemon): await the now-async external base-URL validator on handoff route
Main's #1176 (`9a64fccd`) made `validateExternalApiBaseUrl` DNS-aware and
asynchronous (`validateBaseUrlResolved`) and updated the proxy and finalize
callers to `await` it. The handoff route — added on this branch in parallel,
against the old synchronous validator — still called it without `await`, so
`validated` was a Promise: `validated.error` / `validated.forbidden` were
`undefined`, the SSRF / malformed-URL guard silently no-opped, and a bad
`baseUrl` fell through to the upstream call and surfaced as 502.
A semantic merge break — no textual conflict, green on the branch in
isolation, red once CI re-merged latest main.
- `apps/daemon/src/handoff-routes.ts` — `await validateExternalApiBaseUrl(...)`,
mirroring the finalize route (`import-export-routes.ts:561`). The handler
is already `async`.
The existing `handoff-route.test.ts` cases "400 BAD_REQUEST when baseUrl is
not a valid URL" and "403 FORBIDDEN when baseUrl points at a private internal
IP" already encode this — red against branch + latest main, green now.
Refs nexu-io/open-design#462 — PR #1718 CI fix.
* chore(daemon): list handoff in the assertServerContextSatisfiesRoutes literal
The `assertServerContextSatisfiesRoutes({...})` call in `server.ts` enumerates
every route registrar's deps but omitted `handoff`. Adding `handoff: handoffDeps`
makes the literal complete and consistent with the other route deps.
This was not a typecheck break: route-dep coverage is guaranteed by the
`Assert<ServerContext extends AllRegisteredRouteDeps>` type in
`route-context-contract.ts` — and `AllRegisteredRouteDeps` already includes
`RegisterHandoffRoutesDeps` — not by this assertion-call literal. The literal
has omitted `handoff` since this branch's first push (`806db576`) through green
CI throughout; `tsc -p tsconfig.json --noEmit` is clean before and after.
Refs nexu-io/open-design#462 — addresses @nettee's round-4 review note on PR #1718.
* feat(web): add "Resume conversation in new chat" action (#462)
Adds a Resume control to the chat header, next to "New conversation".
Clicking it synthesizes a handoff prompt from the current transcript
via POST /api/projects/:id/handoff, opens a fresh conversation, and
auto-sends the synthesized prompt as its first user message — so a
drifted session resumes without the user replaying context by hand.
The old conversation is preserved.
- synthesizeHandoff() web-state wrapper in apps/web/src/state/projects.ts
- resume-conversation icon button in ChatPane (onResumeConversation /
resumeConversationDisabled props)
- handleResumeConversation + pendingResumeRef + auto-send effect in
ProjectView; effect gates on messagesConversationId so the prompt
cannot fire before the new conversation's message read settles
- chat.resumeConversation i18n key across all 19 locales
Commit B of #462; Commit A is the daemon endpoint (PR #1718). This
branch is stacked on feat/handoff-endpoint so the web code resolves
@open-design/contracts/api/handoff.
* fix(daemon): scope handoff to one conversation + reject empty transcripts (#462)
Addresses the review on #1718 and #2264:
- mrcfps (#2264): the handoff endpoint exported the whole project's
transcript, so a multi-conversation project blended unrelated chats
into the synthesized prompt. HandoffRequest now carries a required
conversationId; the route validates it belongs to the project
(404 CONVERSATION_NOT_FOUND), and exportProjectTranscript takes an
optional conversationId filter so only that conversation is exported.
- nettee (#1718): a zero-message conversation still called Anthropic and
fabricated a handoff. synthesizeHandoffPrompt now throws
EmptyTranscriptError on messageCount === 0; the route maps it to
400 EMPTY_TRANSCRIPT before any BYOK tokens are spent.
HANDOFF_SCHEMA_VERSION bumped to 2 (conversationId is a new required
request field). Regression tests: a two-conversation scoping test, an
empty-conversation route + pipeline test, and a transcript-export
conversationId-filter unit test.
* feat(web): send conversationId with the resume handoff request (#462)
Follows the handoff endpoint becoming conversation-scoped. The resume
flow now passes the active conversationId to POST /handoff so the
synthesized prompt summarizes only the conversation being resumed.
handleResumeConversation bails when there is no active conversation;
synthesizeHandoff and the resume tests carry the new field.
* feat(daemon): add `od project handoff` CLI + register handoff error codes (#462)
Addresses the second-round review on #1718 and #2264:
- mrcfps (#2264): per AGENTS.md "Capability exposure (UI/CLI dual-track)",
a user-facing capability must be reachable through the `od` CLI, not
only the web UI. Adds `od project handoff <id> --conversation <id>
--api-key <key> --model <model> [--base-url] [--max-tokens] [--json]`,
driving the same POST /api/projects/:id/handoff endpoint. The logic
lives in a testable handoff-cli.ts sibling module (mirrors
artifacts-cli.ts) so cli.ts's import-time dispatch stays out of tests.
- nettee (#1718): the route emitted CONVERSATION_NOT_FOUND and
EMPTY_TRANSCRIPT, which were absent from the shared API_ERROR_CODES
union. Both are now registered in packages/contracts/src/errors.ts,
with a contract test pinning them so the route and contract cannot
drift again.
A CLI contract test covers the conversation-scoped request shape,
--json output, flag validation, and daemon-error surfacing.
* fix(daemon): fail `od project handoff` on a malformed 2xx response (#462)
Addresses nettee's review on #1718: runProjectHandoff treated any 2xx
response as success, so a broken daemon/proxy 200 with malformed or
shape-invalid JSON would print `undefined` (or `{}` under --json) and
still exit 0 — breaking the fail-fast contract scripts rely on. It now
validates the body is a well-formed HandoffResponse via an
isHandoffResponse type guard and fails fast otherwise. Regression tests
cover a shape-invalid and an unparseable 200 body.
* feat(web): surface the daemon's classified handoff error in the resume toast (#462)
Addresses mrcfps's non-blocking note on #2264: synthesizeHandoff returned
null for every non-2xx response, so RATE_LIMITED, EMPTY_TRANSCRIPT, and an
upstream 400 with provider detail all collapsed into one generic "check
your API key" toast — even though handoff-routes.ts had already classified
and sanitized them.
synthesizeHandoff now returns the daemon's structured `{ error }` on a
classified failure; `null` stays reserved for a transport failure or an
unparseable body. handleResumeConversation surfaces error.message plus
redacted details for the `{ error }` case, and a distinct
daemon-unreachable message for null.
* fix(web): omit empty baseUrl from the resume handoff request (#462)
Addresses mrcfps's review on #2264: the default Anthropic config
normalizes baseUrl to '' (config.ts), and the handoff route 400s an
explicit empty baseUrl — so the Resume action failed before synthesis
for every user who never set a custom base URL.
handleResumeConversation now forwards baseUrl only when config.baseUrl
is a non-empty string, matching the contract's optional-field semantics.
Tests: the default-config path asserts baseUrl is absent from the
request, and a new case covers a custom baseUrl being forwarded.
* refactor(daemon): dispatch `od project handoff` before the generic project parser (#462)
Addresses nettee's non-blocking note on #1718: runProject ran the shared
parseFlags(PROJECT_*) before reaching the handoff switch case, so a
malformed `od project handoff` invocation (`--unknown`, `--max-tokens`
with no value) threw out of the generic parser instead of hitting
handoff-cli's structured fail() — the entrypoint behaved differently
from the unit-tested runProjectHandoff helper.
The handoff sub now short-circuits before parseFlags / projectDaemonUrl,
so `od project handoff` runs exactly runProjectHandoff with no
intervening parsing. handoff-cli.test.ts gains unknown-flag and
missing-value cases covering the structured fail path.
---------
Co-authored-by: DevForgeAI CI/CD Engineer <devforge-ai@development.ai>
|
||
|
|
15d08d4158
|
feat: add windows packaged auto update flow (#2362) | ||
|
|
1ab8758045
|
Revert "fix(web): demote Plugins and Integrations to nav rail footer (#1806)" (#2360)
This reverts commit
|
||
|
|
41e0e26d86
|
fix(web): stop re-syncing composer scroll on every keystroke (#2282)
The `draft` dep on the overlay scroll-snap effect triggered setComposerScrollTop on every keystroke, looping through the v0.8.0 mention overlay state and crashing the chat composer with Maximum update depth exceeded. Fixes #2097 |
||
|
|
d72d689430
|
fix(web): preserve daemon run reattach across project switch (#2317)
Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
dea07840f3
|
fix: stop stale pinned todos after terminal runs (#2321)
Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
b426f614f5
|
fix(web): scope design-systems surface chip counts to active style filter (#2141)
On the Design systems page the built-in library surface chips (All / Web /
Image / …) counted the whole catalog, so applying a style category left
them showing stale totals while the grid shrank. Clicking a chip also
called setCategory('All'), discarding the user's style filter instead of
refining within it.
Surface chip counts and the grid now derive from a query-scoped set
(style category + search) over librarySystems; the chip click only
switches surface. An unscoped surfaceTotals count still backs the
"surface is empty" effect guard so it reacts to the catalog, not a
transient filter. The chip row always keeps "all" and the active surface
chip rendered, so a transient search can never hide the active filter.
Re-applied onto the DesignSystemsTab restructure from #2187. Adds 4 tests
covering scoped counts, category preservation on chip click, empty-chip
hiding, and active-chip persistence under a zero-result search.
Fixes #2062
Co-authored-by: DevForgeAI CI/CD Engineer <devforge-ai@development.ai>
|
||
|
|
431a5e2d79
|
[codex] Add global onboarding flow without AMR (#2272)
* Add global onboarding flow * Remove AMR from onboarding variant * Add onboarding role question |
||
|
|
ad37fd30cf
|
Add desktop updater UI flow (#2270) | ||
|
|
e94663bfbd
|
Add connector memory extraction flow (#2265) | ||
|
|
e6d0f4eeab
|
fix(web): restore scrolling in New project modal for tall tabs (#2032) | ||
|
|
c6b7c44424
|
confirm before clearing memory extraction history (#2241) | ||
|
|
86ec951fb9
|
[codex] Add automation templates and proposal workflows (#2193)
* feat(web): introduce Automations tab with dual-track capability for routines This commit adds a new Automations tab that consolidates routines, schedules, and live artifacts, allowing users to manage automations seamlessly. The tab features a modal for creating and editing automations, which supports various scheduling options (hourly, daily, weekdays, weekly) and project modes (create_each_run, reuse). The CLI is also updated to expose automation commands, ensuring consistency between the web UI and CLI interfaces. Key changes include: - New `NewAutomationModal` component for automation creation and editing. - Updated `TasksView` to integrate the new Automations functionality. - Enhanced styling for the Automations tab to improve user experience. This implementation aligns with the dual-track capability exposure policy, ensuring all features are accessible via both the web UI and CLI. * feat(daemon): enhance automation context handling and CLI commands This commit introduces several improvements to the automation context management and updates the CLI commands accordingly. Key changes include: - Added support for new context fields (`plugin`, `mcp`, `connector`) in automation commands. - Updated the CLI to reflect new target options (`new-project`). - Enhanced error messages for invalid target inputs. - Introduced functions to handle context selection and normalization for routines, including the ability to parse and store context data in the database. - Updated the database schema to include a new `context_json` field for routines. - Improved the handling of context in routine routes and the web interface, ensuring that selected contexts are properly managed and displayed. These changes aim to provide a more robust and flexible automation experience, aligning with the recent enhancements in the web UI. * feat(web): enhance TasksView with automation run history and status indicators This commit introduces several new features to the TasksView component, including: - Added functionality to display automation run history for each routine, showing metadata such as status, timestamps, and project details. - Implemented status indicators for routine runs, providing visual feedback on their current state (succeeded, failed, running, queued). - Enhanced the UI to allow users to expand and view detailed run history, including the ability to open the corresponding project conversation. - Updated styles to improve the presentation of automation statuses and history. These changes aim to provide users with better insights into their automation routines and improve overall usability. * feat(daemon): implement automation ingestion and proposal management This commit introduces several new features related to automation ingestion and proposal management within the daemon. Key changes include: - Added new modules for handling automation source packets and proposals, allowing for the storage, retrieval, and management of automation-related data. - Implemented functions to list, create, and apply automation proposals, enhancing the automation workflow. - Introduced new CLI commands for interacting with memory entries and automation sources, providing users with more control over their automation processes. - Enhanced the server routes to support automation source and proposal APIs, enabling seamless integration with the existing system. These changes aim to improve the overall automation experience, making it easier for users to manage and utilize automation proposals and ingestions effectively. |
||
|
|
eb127e0f79
|
Remove live artifact home chip (#2221) | ||
|
|
4376d8a8ec
|
[codex] Add pet task center and desktop pet (#1833)
* feat: add pet task center and desktop pet * Fix pet task center review regressions |
||
|
|
387dc83b27
|
fix(plugins): wire Open Design "Open Design PR" button end-to-end (#2182)
Some checks failed
ci / Detect CI change scopes (push) Failing after 2s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 1s
ci / Preflight (push) Has been skipped
ci / Core package tests (push) Has been skipped
ci / Tools workspace tests (push) Has been skipped
ci / Daemon workspace tests (push) Has been skipped
ci / Web workspace tests (push) Has been skipped
ci / E2E vitest (push) Has been skipped
ci / Playwright critical (push) Has been skipped
ci / Build workspaces (push) Has been skipped
ci / App workspace tests (push) Has been skipped
ci / Validate workspace (push) Failing after 0s
Two-part fix for the Plugin folder card's "Open Design PR" button. The
flow was broken end-to-end: the CLI emitted a 404 URL, and the agent
prompt under-specified the contribution steps so the agent stalled
mid-turn or fell back to the legacy issue-URL path.
Catalog target — `apps/daemon/src/plugins/publish.ts`:
`buildPublishLink({ catalog: 'open-design' })` hardcoded a submission URL
at `github.com/open-design/plugin-registry`, the dedicated registry repo
proposed in docs/plans/plugin-registry.md §1.2. That repo doesn't exist
yet (P3.1 notes "creating the external GitHub repo is an operational
launch step, not a code blocker"), so every generated URL 404'd. Retarget
the catalog at the live `nexu-io/open-design` monorepo and update the
target-path hint in the PR body to `plugins/community/<plugin-name>/`
(the actual layout under main). Plan §1.2 stays the long-term goal — see
the code comment.
Also updates the matching `od plugin yank` issue URL in cli.ts.
Agent prompt — `apps/web/src/components/design-files/pluginFolderActions.ts`:
The `contribute` action prompt only said "use the supported `od plugin
publish` Open Design registry flow", which produces an issue URL (the
legacy path) and left the agent to invent the remaining steps. The agent
ended up calling `AskUserQuestion` mid-turn waiting for input that the
DesignFilesPanel buttons couldn't satisfy, then stalled for 600s.
Rewrite the `contribute` prompt to drive the full PR flow via raw `gh`
commands:
1. preflight `gh --version` / `gh auth status` (detect-and-instruct on
missing CLI, never auto-install anything)
2. read manifest, resolve author login
3. `gh repo fork nexu-io/open-design --remote=false`
4. clone fork, branch, cp plugin into `plugins/community/<name>/`,
commit/push using author's git identity (not a hardcoded bot)
5. `gh pr create ... --web` so the author reviews and clicks Create
Hard bans on `AskUserQuestion`, retry-on-failure, and the legacy
`od plugin publish --to open-design` CLI keep the turn fire-and-forget.
The `install` / `publish` action prompts are unchanged.
Tests:
* `apps/daemon/tests/plugins-publish.test.ts` — assert URL host +
catalogLabel match `nexu-io/open-design`, body contains the new path
hint.
* `apps/web/tests/components/pluginFolderActions.test.ts` (new) — lock
the contribute prompt's command surface, the `--web` review window,
the `AskUserQuestion` ban, the install-tool ban, and folder-path
interpolation. Nine assertions covering the prompt contract without
coupling to exact wording.
Validation:
* `pnpm --filter @open-design/daemon exec vitest run tests/plugins-publish.test.ts`
→ 11/11 passed
* `pnpm --filter @open-design/web exec vitest run tests/components/pluginFolderActions.test.ts`
→ 9/9 passed
* `pnpm --filter @open-design/web typecheck` clean
* E2E: button click in DesignFilesPanel under namespace=e2e-pr-test ran
`gh --version` → fork → clone → branch → commit/push → ended at
`gh pr create --web` opening the GitHub PR draft in browser. No
AskUserQuestion stall this time.
Doesn't touch: `5f71968f` server-side endpoints (`/contribute-open-design`
+ `/publish-github` still in main as dead code — separate cleanup),
plan/spec docs (per maintainer call to keep the dedicated-repo as the
long-term goal), or `registry-backends.test.ts` (plan terminal-state
fixture; no production caller).
|
||
|
|
9596a0ccd5
|
feat(privacy): collapse first-run consent banner to a single "I get it" button (#2202)
* feat(privacy): collapse first-run banner to a single "I get it" button
Replaces the first-run privacy disclosure's two-button decision picker
("Share usage data" / "Don't share") with a single "I get it"
acknowledgement. Clicking it accepts the same default telemetry surface
the previous "Share usage data" path enabled — the banner shifts from
binary consent picker to informed disclosure.
To keep the surface honest, the banner footer is rewritten to spell out
the new default and point at the off switch:
"Data sharing is on by default. You can turn it off any time in
Settings → Privacy. We never upload the contents of your generated
artifact files."
Settings → Privacy (PrivacySection.tsx) is unchanged — that surface still
exposes both Share and Don't share buttons so users who arrive there
later (or come back to flip the choice) keep the explicit picker.
Mechanics:
* `PrivacyConsentModal.tsx`: drop `onDecline` prop and the second button;
rename `onShare` → `onAccept` to match the new semantic. Footer hint
now reads from `settings.privacyConsentBannerFooter` (new key) so the
banner copy can speak in single-button voice without disturbing the
reused `settings.privacyConsentFooter` that PrivacySection still
displays.
* `App.tsx`: drop the `onDecline` handler. Single `onAccept` handler
applies the same opt-in payload as the previous `onShare` branch
(`telemetry.metrics = true`, `telemetry.content = true`, fresh
`installationId`), so the wire format daemon-side is unchanged.
* `i18n/types.ts` + `locales/en.ts`: two new keys —
`settings.privacyConsentAccept` ("I get it") and
`settings.privacyConsentBannerFooter` (the default-on disclosure copy).
* `i18n/locales/*.ts` (all 18 non-en dictionaries): added the two new
keys. zh-CN and zh-TW are translated; the remaining 16 locales follow
the project convention of leaving the EN string as a fallback for
later contributor passes (same shape used by privacyConsentShare /
Decline today).
* `tests/components/PrivacyConsentModal.test.tsx`: rewritten. The four
new tests lock the new contract — single "I get it" button, no
Share/Decline labels, default-on disclosure text in the footer, the
external privacy-policy link, and onAccept firing on click. Replaces
the prior "equal-prominence" tests, which only made sense for the
two-button shape.
Validation:
* `pnpm --filter @open-design/web exec vitest run tests/components/PrivacyConsentModal.test.tsx`
→ 4/4 passed
* `pnpm --filter @open-design/web exec vitest run tests/i18n/locales.test.ts`
→ 5/5 passed (every locale aligned with English keys + placeholders;
the new keys ship to all 19 dictionaries)
* `pnpm --filter @open-design/web typecheck` clean
* test(privacy): update App.connectors fixture to match single-button banner
`App.connectors.test.tsx > does not show first-run privacy consent until
daemon config hydration finishes` hardcoded the previous "Share usage
data" affirmative button label. The single-button banner now renders
"I get it", so the assertion was looking for a button that no longer
exists.
CI signal: 26081467446 → Web workspace tests → `× does not show
first-run privacy consent until daemon config hydration finishes`. The
App workspace tests + Validate workspace failures cascaded from this
one — both are aggregator jobs.
Local: vitest run tests/components/App.connectors.test.tsx → 5/5 passed.
|
||
|
|
3cecb1c881
|
Polish project workspace UI (#2201) | ||
|
|
a38e09f931
|
fix(web): demote Plugins and Integrations to nav rail footer (#1806)
* fix(web): demote Plugins and Integrations to nav rail footer
Plugins and Integrations are platform-configuration surfaces, not
daily-use destinations. Moving them to the footer section of the
left nav rail — separated from primary items by a thin divider —
keeps them reachable while giving the primary four items
(Home, Projects, Automations, Design Systems) the visual weight
they deserve.
- EntryNavRail: remove Plugins/Integrations from the main __group
and place them in the __footer above the help launcher
- entry-layout.css: add __divider rule (1 px separator) to visually
mark the boundary between primary and secondary nav regions
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): remove settings dropdown, gear opens settings directly
The gear/cog button previously opened a dropdown that mixed three
unrelated concerns: community links (X, Discord), preference quick-
access (Language, Appearance), a feature shortcut (Use everywhere),
and a redundant Settings entry — creating two separate paths to the
same Settings dialog and duplicating Language/Appearance relative to
the Settings sidebar.
Changes:
- Gear button now directly opens the Settings dialog (no intermediate
dropdown layer)
- Follow @nexudotio on X and Join Discord moved to the Help menu at
the bottom of the nav rail, where community/external links belong
- Language and Appearance remain exclusively in the Settings dialog
- Use everywhere remains exclusively in the topbar chip
- Remove dead state (avatarMenuOpen, languageExpanded,
appearanceExpanded), ref, outside-click effect, and module-level
constants (APPEARANCE_THEMES, APPEARANCE_LABEL, describeModelChip)
that were introduced solely to support the dropdown
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(web): move output-type chips above chat input as tab bar
The home hero previously had two chip rows below the chat input that
mixed two unrelated dimensions: output type (Prototype, Slide deck,
Image…) and workflow source (Create plugin, From Figma, From folder,
From template). Users had no visual cue that these were different
categories.
This change separates the two dimensions clearly:
- Output type (create group) becomes a tab bar positioned above the
input card. Tabs share the same chip data and onPickChip dispatch,
so plugin selection, active state, and pending state are unchanged.
Active tab shows a colored underline; the bar border visually
connects to the input card below.
- Workflow source (migrate group) stays as the chip row below the
input card, now standing alone with unambiguous "how to start"
semantics.
- Subtitle updated from "Pick a plugin below" to "Pick a type"
to match the new placement.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): skip replace-prompt confirmation when switching output-type tabs
Output-type tab clicks (create group) are mode-selection gestures;
the user expects the prompt to update immediately without a dialog.
The confirmation is still shown for migrate-group chips (From Figma
etc.) where the replacement carries meaningful user-provided content.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): use brand logo as Home destination, drop redundant brand mentions
The entry nav rail previously rendered the brand logo and a separate
Home icon back-to-back; both invoked `onViewChange('home')`, so the
Home button was pure duplicate affordance. The hero pane also
displayed a third "Open Design" lockup that competed with the rail
logo and the rebranded title.
This collapses those affordances:
* `EntryNavRail` drops the dedicated Home `NavButton`. The brand
logo button now carries `aria-current="page"` and an `is-active`
visual state when the home view is showing, and its tooltip reads
"Open Design · Home" off-home so the navigation behavior stays
discoverable for new users.
* `entry-layout.css` adds the matching `.entry-nav-rail__logo.is-active`
accent treatment so the "you are here" cue reads at parity with
primary rail buttons.
* `HomeHero` removes the inline `home-hero__brand` lockup and the
associated CSS, then retunes the title/subtitle/type-tab spacing
so the headline group still pairs tightly with the type tabs and
input card below.
* `entry-chrome-flows` is updated to assert the logo carries the
active page treatment and that no `entry-nav-home` test id
resurfaces by mistake.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): auto-grow home chat input, disable internal scroll and drag-resize
The home hero textarea was capped at a fixed `min-height: 84px` with
`resize: vertical`, so users had to either drag the bottom-right
corner to enlarge it or scroll the textarea internally to read
longer prompts. That hid context (loaded plugin templates routinely
overflow three lines) and exposed a manual grip whose state was easy
to leave in an awkward height.
This change makes the chat box grow with its content:
* `HomeHero` adds a `useLayoutEffect` that, on every prompt change,
resets the textarea height to `auto` so the browser can measure a
smaller content, then writes back `scrollHeight` in pixels. The
effect uses layout phase (not effect phase) to avoid a one-frame
flash at the previous height when a plugin loads a long example
prompt.
* `home-hero.css` swaps `resize: vertical` for `resize: none` and
adds `overflow: hidden`, so the manual drag grip disappears and
the textarea never scrolls internally. `min-height: 84px` is kept
so the empty input still reads as a chunky chat box. The outer
page handles overflow when the prompt is genuinely very long.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): make output-type tabs read as folder-tabs attached to chat box
The previous tab bar used a small icon + label per tab and signaled
the active tab with a 2px accent underline sitting on a horizontal
divider line. That divider visually broke the relationship between
the tabs and the input card below — the active tab looked like a
free-floating header, not a flap of the chat surface, so users had
to do extra work to mentally connect "I picked Prototype" with the
prompt area immediately underneath.
This switches the tab bar to a folder-tab pattern:
* `HomeHero` drops the per-tab `Icon` element. The seven labels
(Prototype, Live artifact, Slide deck, Image, Video, HyperFrames,
Audio) already disambiguate at the type sizes used here, and the
icons were primarily decorative.
* `home-hero.css` rewrites the tab styling:
- The bar no longer paints its own baseline border; the input
card's top edge serves as the baseline.
- Each tab is a rounded-top container with a 1px transparent
border and a `-1px` bottom margin, so its bottom edge overlaps
the card's top border by exactly one pixel.
- The active tab borrows the card's panel background and border
color, and overrides its bottom border with the panel color
so it visually erases the card's top border for the tab's
width. The result reads as one continuous "Prototype is this
chat box" surface.
- The card's prior `margin-top: 8px` is removed so the tab bar
bottom and card top sit at the same y coordinate, which is the
geometric precondition for the 1px overlap to land cleanly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): enlarge home chat attach and submit buttons for legibility
The attach (paper-clip) and submit (arrow-up) controls on the home
chat input rendered at 32px diameter with 14px and 18px glyphs
respectively. After stroke antialiasing the icons read at roughly
11–12px on typical displays — small enough that users reported the
glyphs were illegible and had to be discovered by trial-and-error.
This bumps both controls to 38px circles and grows their glyphs
(attach 14 → 18, submit 18 → 22). The primary call-to-action now
has clearly more visual weight than the surrounding muted hint
text, and the paper-clip is recognisable at a glance.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): paint a chevron on inline select slots in the home prompt
Inline select slots in the home prompt overlay (e.g. the
`high-fidelity` / `wireframe` fidelity picker on the Live artifact
template) rendered as plain pill highlights, visually identical to
free-text and read-only text slots. The slot's `appearance: none`
strips the browser's default chevron, so users had no affordance
hinting the value was switchable.
This wraps select-type slots in an `inline-flex` span and overlays
an explicit chevron-down `Icon` against the slot's trailing padding
(bumped from 18px to 22px to make room). The chevron inherits the
accent color via the wrap's `color` property so it themes correctly
in light and dark modes. Click semantics are unchanged because the
chevron is `pointer-events: none`.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): size inline number slots to their value, not the whole row
Number-type inline slots in the home prompt (e.g. the slide-count
spinner on the Slide deck template) inherited the generic slot
`min-width: 8ch` and then stretched to the browser's default
`<input type=number>` width, so a two-digit value like "10" ate
the entire remaining line and pushed the native spinner buttons
to the far right edge — far away from the value they control.
This sizes the number input to its actual content plus four
characters of trailing room for the spinner buttons (clamped to
6–14ch), and adds a `home-hero__prompt-slot-input--number`
modifier that overrides the slot `min-width` to 4ch. The
spinner now sits flush against the value and the slot reads as
a compact inline pill matching the surrounding text/select
slots.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): widen prompt line-height so slot pills do not collide vertically
Each interactive slot and mention in the home prompt paints a 2px
outline ring via `box-shadow`. At the previous `line-height: 1.55`
on a 15px font, two lines were ~23px apart while a single pill
occupied ~20px of vertical space (text box + ring on both sides),
leaving roughly 2px of clear space between rows. When the prompt
wrapped onto multiple lines — common for the Image template's
"Generate a {kind} of {subject}. Style: {style}. Aspect: {ratio}."
example — the rings from line N and line N+1 visually merged into
a single bar, making it ambiguous which pill the user was about to
click or edit.
This bumps the line-height of both the highlight overlay and the
underlying editable textarea to 1.85 (~28px per line), restoring
~8px of clear space between pill rows. The two values must stay
identical so the overlay glyphs continue to track the textarea
caret positions exactly; a brief comment in each rule documents
this coupling for future edits.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): paint dropdown chevron on select element with neutral color
The previous chevron treatment wrapped each select slot in an
inline-flex span and laid an accent-orange `<Icon>` over the
trailing padding. At the prompt's normal display size the orange
chevron blended into the orange pill ring corners, so users
perceived "a bunch of dropdown arrows" across every highlighted
slot, even though only the actual select rendered one.
Move the chevron onto the `<select>` itself as a small neutral-
gray `background-image` SVG. The grey contrasts clearly with the
pill's accent ring and the chevron lives inside the select's own
padding, so it can never visually overlap the value text and can
never appear on non-select slots.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): shrink select-slot dropdown caret so it stops reading as a check-mark
The 8×6 stroked chevron looked like a check-mark in zoomed views
because the stroke width was a large fraction of the glyph. Swap
for a small (9×5) filled triangle drawn as a `background-image`,
with `background-size` pinned so browsers can't scale it to its
intrinsic SVG box. The caret is now unambiguously a dropdown
indicator without crowding the value text.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): make read-only prompt slots visually distinct from editable pills
Multi-word context values are rendered as plain <span>s with
pointer-events: none, but they inherited the same orange pill +
ring treatment as the truly editable <input>/<select> slots.
Users couldn't tell which pills they could click into.
Strip the pill background, the ring, the radius, and the padding
from `.home-hero__prompt-slot-text` and replace them with a
subtle dashed bottom border. The orange foreground keeps the
slot family link, but read-only highlights now clearly read as
"context value spliced into the prompt" rather than as
"interactive control".
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): scale select-slot dropdown caret up to 12px wide for legibility
The 9×5 caret was too quiet at the prompt's font size to read as
a dropdown affordance. Bump to a 12×6 filled triangle and pad the
select's trailing space (24px) so the value text still has clear
breathing room from the caret.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): collapse plugins search and filter strips into one bar
The plugins-home gallery laid out search, count, mode (Featured),
total-in-catalog, clear-filters, the main category row, and the
sub-category row as four floating clusters. Search lived up in
the section header, far from the chips it actually scopes; the
mode strip wedged "Featured + 386 in catalog + Clear filters"
between the header and the category row with no obvious
ownership; and the two clear-filter affordances duplicated each
other (chip strip + empty-state).
Fold the mode strip into the category row: Featured is now the
leading chip on the same line as the category pills, and the
search field, the result count, and a compact Clear link sit as
a right-aligned tools cluster on that same row. The header
shrinks back to just the title, subtitle, and Browse registry
link. The sub-category row stays as the contextual second line
when a category is active.
Tests: keeps the existing data-testids (plugins-home-chip-featured,
plugins-home-row-category, plugins-home-clear, plugins-home-count,
plugins-home-search) so the existing 11-case section spec passes
without modification.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): hide Recent projects rail on first-run home when empty
A brand-new user landing on Home saw an empty dashed box with the
copy "No projects yet — type a prompt to start one." sitting
above the plugin gallery. The hero already prompts the user to
type, so the empty rail just adds vertical noise without telling
them anything new.
Return null from RecentProjectsStrip when the recent list is
empty (loading or not) so the rail appears only when there is
actually something to show. Update the entry-chrome e2e to
assert toHaveCount(0) on first run — codifying the new contract
that the rail is conditional, not always-mounted.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): hide structured inputs form when every field is in the prompt
The PluginInputsForm below the chat textarea surfaced every plugin
input — even the ones the prompt template already substitutes
inline as highlighted slot pills. For a plugin like Prototype
that's five identical labelled inputs duplicating the five slot
pills above them, making the chat box look like it has grown a
second composer.
Compute the set of placeholder keys actually referenced in the
prompt template (via INPUT_PLACEHOLDER_PATTERN) and skip those
when deciding what the structured form needs to render. The form
still appears for plugins that expose inputs not referenced in
the prompt text (e.g. a "Run in background" toggle), but
template-only plugins collapse the redundant second editor.
Update the picker spec accordingly: when every field is in the
template the form is now expected to be absent rather than to
render a duplicate of the inline slot.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): drop redundant filtered-count and Clear link beside the search
The combined plugins filter bar trailed the search field with
"59 / 386" and an inline "Clear" link, but both repeat what the
chip strip already shows: every chip carries its own count
(Slides 59, All 386, …) and clicking the All chip already resets
the filters. Users had no way to know what the bare "59 / 386"
fraction or "Clear" referred to without inference.
Strip the trailing tools cluster down to just the search input.
The empty-results message at the bottom of the gallery still
exposes a contextual "Clear filters" button when a stacked
filter yields no matches, so the affordance isn't lost — just
removed from a position where it didn't read as actionable.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): drop default subtitle copy from the Official starters strip
The Home gallery rendered a two-line explanatory subtitle under
"Official starters" — "Ready-to-use Open Design workflows bundled
with this runtime. Pick one to load a starter prompt, or browse
the registry for more." — every visit. Returning users skip it,
new users get the same message from the section title plus the
Browse registry link plus the visual card grid itself; the prose
read as filler chrome above the chip strip.
Default the subtitle prop to undefined and only render the <p>
when a caller passes an explicit string. Other surfaces that
mount PluginsHomeSection with their own copy keep their
subtitle; the bare Home gallery loses one row of vertical noise.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fade out empty workflow lanes in Plugins home filter
Empty top-level lanes (Deploy 0, Refine 0, etc.) and empty
sub-categories (Vercel 0 under Deploy, Figma 0 under Import, etc.)
used to look identical to populated ones, so users couldn't tell at
a glance which chips were real catalog buckets and which were
"contribute a plugin" invites. The strip kept all lanes visible by
design — the workflow shape (Import / Create / Export / Share /
Deploy / Refine / Extend) is part of what we want users to see —
so we keep them clickable, but tag count-zero chips with
data-empty='true' and give them a faded, dashed-border treatment.
"All" pills stay solid since their count reflects the parent lane,
not their own emptiness.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): update home nav test expectations
* fix(e2e): align critical smoke with entry chrome
---------
Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
18b947c25f
|
[codex] Land design system GitHub intake handoff (#2187)
* Add Claude-style design system workflow * Merge design system workflow into main * Restore design system workflow UI styles * Fix design system setup scrolling * Fix design system setup connector button * Preserve connector auth link after popup block * Simplify connected GitHub setup state * Open generated design system workspace project * Summarize design system auto prompt in chat * Add bounded GitHub connector design intake * Prefer path-scoped GitHub intake tools * Restore branch GitHub design context intake * Restore design system review workspace * Restore design system manager tab * Let design system workflow routes own details * Open editable design systems as projects * Restore design system workspace coverage * Fix bounded GitHub connector intake * Hide design system review while generating * Suppress design system generation questions * Constrain GitHub design intake to bounded command * Tolerate oversized GitHub metadata during intake * Rebuild daemon CLI when sources change * Fallback when GitHub connector snapshots are rate limited * Allow GitHub intake without Composio * Use native GitHub auth for design intake * Remove design system review group heading * Improve design system extraction evidence * Align design system scaffold with Claude output * Add evidence inventory for design system intake * Add local design system evidence intake * Add design system package audit gate * Allow auditing Claude Design reference packages * Audit design system package content quality * Migrate legacy design system artifacts * Clean migrated design system artifacts * Require modular design system UI kits * Reject thin design system UI kits * Prioritize core design evidence intake * Require role-based design system UI kits * Clean stale design system manifest references * Require representative preserved design assets * Warn on generic design system visuals * Enforce design system quality warnings * Audit connected design system UI kits * Require mounted design system UI kits * Require composed design system app shells * Require runnable JSX design system kits * Require browser globals for design system components * Infer design system names from source URLs * Require source examples in design system packages * Bind preserved fonts in design system tokens * Require skill frontmatter in design system packages * Preserve build icons in design system packages * Require real assets in brand previews * Require substantive source examples * Require product overview in design system README * Require reusable UI kit README * Require reusable design system skill docs * Seed Claude-style UI kit entry contract * Preserve runtime build assets in design packages * Audit design system packages after generation * Audit design system first-run output * Audit source-backed preview cards * Align design system UI kit scaffolds * Materialize design evidence package artifacts * Show project chat during design system setup * Hand off design system setup to project chat * Auto-repair design system audit failures * Harden design system evidence preservation * Tighten design system package guidance * Add targeted design system repair guidance * Bound design system audit auto repair * Use connector statuses in design system setup * Audit design system preview manifests * Require README preview manifests for design systems * Fix design system GitHub intake handoff * Fix daemon prompt CI assertions |
||
|
|
bd48c597b0
|
chore: pin dependency versions and harden CI caches (#2189)
* chore: pin dependency versions * ci: enforce pinned dependency specs * ci: fix pnpm executable invocation |
||
|
|
b838e94b88
|
fix(web): expand skill mention picker (#2170) | ||
|
|
34f66113a0
|
Implement Home audio essential workflow (#2104)
Some checks failed
ci / Packaged mac smoke (push) Blocked by required conditions
ci / Packaged windows smoke (push) Blocked by required conditions
ci / Detect PR change scopes (push) Failing after 1s
ci / Validate workspace (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 1s
ci / Packaged linux headless smoke (push) Has been skipped
* fix(web): align Home prompt overlay with textarea so caret lands on click
Picking a chip such as Slide deck or Image loaded a default prompt
into the Home textarea and rendered an overlay with `{{key}}`
placeholders as interactive `<input>` / `<select>` controls. The
overlay controls and the underlying textarea text were laid out
independently:
- Inputs declared `min-width: 8ch` and `Math.max(displayValue.length
+ 1, 10)ch` of width.
- Selects added 18px of right-padding for the dropdown arrow.
- The textarea kept the raw substituted string in a proportional
font.
The two layouts no longer matched column-for-column, so every slot
shifted the textarea text to the left of where it appeared in the
overlay, compounding across the line. Clicking on visible prose to
position the caret hit a different character offset in the textarea
and subsequent typing or deletion landed in the wrong place.
For example, the Slide deck template
Create a {{slideCount}}-slide {{deckType}} for {{audience}} about
{{topic}}. ...
renders with slideCount=10 (~2 ch in the textarea) under a slot
input forced to 10 ch in the overlay — clicking right after the
literal `-slide` placed the caret several characters into `pitch
deck`. The Image / Video / Audio chips with their pre-filled
subject, style, aspect values reproduced the same drift.
Render the inline pills as read-only `<span>`s carrying the exact
substring the textarea shows at that position, mark them
`aria-hidden` so the textarea remains the single labelled control,
and surface every plugin input field — including the ones referenced
inline — in `PluginInputsForm` underneath. Editing flows through the
form, the parent's `updateActiveInputs` already re-renders the
prompt, and the pills stay aligned with the textarea on every
keystroke.
Also drop the now-unused inline helpers (updatePluginInput,
getTemplateInputNames, shouldRenderSlotAsText, inlineFieldType,
fileInputLabel, fileMetadata) and the dead
`.home-hero__prompt-slot-control/input/select/toggle/file/text` CSS
rules.
Verified:
- pnpm --filter @open-design/web typecheck
- vitest run on HomeHero.plugin-picker, HomeHero.rail, and
HomeView.prefill (29/29 pass; tests updated to reflect the new
read-only span + always-on form contract)
- Manual click-to-edit on Slide deck and Image chips in the
pnpm tools-dev web runtime — caret now lands where the user
clicked.
* Implement home audio essential workflow
* Fix Home media composer review issues
* Guard stale Home media apply results
---------
Co-authored-by: hahaplus <zmjdll@gmail.com>
|
||
|
|
1523defad1
|
feat(web): add plugin registry detail drawer (#2087)
Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
8a629eb999
|
fix: discover codex models from cli (#2082) | ||
|
|
ec53959601
|
feat(web): unify plugin trust badges (#2088)
Normalizes plugin trust badge rendering across Home, plugin details, registry entries, sources, and plugin share/install surfaces while mapping bundled and official sources to the user-facing Official tier.
Validation: CI and nix-check were green on PR head
|