Commit Graph
64 Commits
Author SHA1 Message Date
lefarcenandGitHub 4c73aba565 fix(pack): align prebundle better-sqlite3 pin to 12.10.0 so mac daemon starts (#5680)
The packaged macOS daemon fails to start for a large share of users: it
exits code=1 at boot with ERR_MODULE_NOT_FOUND, `missing_module` =
better-sqlite3 (~270 hits / 4 days, dominant across 0.13.0–0.15.0). With
the daemon dead the whole app is unusable and the shell keeps retrying —
the "app keeps crashing / restarting" reports.

Root cause: mac-prebundle.ts / win-prebundle.ts pin the daemon's runtime
externals at `better-sqlite3@12.9.0`, but apps/daemon/package.json and
the pnpm lockfile resolve `12.10.0`. The assembled app declares 12.9.0
as a dependency; electron-builder 26's pnpm node_modules collector keeps
a dependency only when the on-disk version semver-satisfies the pin.
12.10.0 does not satisfy exact `12.9.0`, so the collector DROPS
better-sqlite3 from Contents/Resources/app/node_modules with a non-fatal
warning, and the prebundled daemon's `import Database from 'better-sqlite3'`
can't resolve at runtime. blake3-wasm (pinned 2.1.5, matching disk)
survives, which is why it only occasionally shows the same error.

Fix: bump the pin to 12.10.0 in both prebundle manifests to match the
daemon / lockfile. Unit assertions updated (red on main's 12.9.0, green
here).
2026-07-15 08:34:33 +00:00
Hashim KarimandGitHub f6a891b1f5 [codex] fix linux AppImage launcher startup (#4494)
* fix linux AppImage packaged entry

* fix linux desktop launcher env

* fix linux appimage start env

* fix linux packaged entry keepalive

* fix linux appimage desktop metadata args

* fix linux appimage launcher env wrapper

* fix: preserve appimage lifecycle ownership

* fix: export direct apprun appimage marker
2026-07-14 12:24:30 +00:00
d7151b2b60 feat(analytics): link downloads and first-party browser opens (#5442)
* feat(analytics): attribute desktop downloads

* fix(analytics): initialize attribution routes after HTTP deps

* feat(analytics): bridge first-party browser opens

* feat(analytics): bridge first-party anchor opens

* fix(analytics): make attribution claims idempotent

* fix(analytics): record pending attribution results

* chore(analytics): bind landing attribution KV

* fix(ci): remove landing preview wrangler config override

* fix(landing): resolve download attribution function

* fix(analytics): harden attribution token consumption

* fix(analytics): preserve same-installation claim retries

* fix(analytics): merge recovered attribution retries

* fix(analytics): preserve attributed browser links

* fix(analytics): clear attribution on privacy reset

* fix(analytics): bridge stopped AMR console links

* fix(ci): deploy attribution schema before Pages

* fix(ci): run attribution migrations with Wrangler 4

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
Co-authored-by: PerishCode <perishcode@gmail.com>
2026-07-14 08:46:04 +00:00
edb6292f1c feat: post-update what's new card on home with release feed highlights (#5071)
* add post-update what's new card on home with release feed highlights

After an update, the home surface shows a one-time bottom-right card with
the release highlights (title, copy, optional image) and a "See what's new"
link. Content ships in the release feed metadata.json as an optional
whatsNew block sourced from tools/release/whats-new/<baseVersion>.json;
versions without highlights fall back to generic copy linking the release
notes. The card shows once per version, keyed on a locally stored
last-seen version, and never on fresh installs.

- packages/contracts: WhatsNewContent/WhatsNewResponse DTOs + analytics props
- daemon: GET /api/whats-new (channel feed fetch, 1h cache, stale/null
  degradation) + od whats-new CLI mirror
- web: WhatsNewPopup card (CSS Module, i18n chrome in 19 locales,
  surface_view/ui_click tracking), lastSeen decision logic in lib/whats-new
- release tooling: publish-metadata forwards the optional whatsNew block and
  fails loudly on malformed files; docs in tools/pack/AGENTS.md and
  tools/release/whats-new/README.md

* fix: gate whats-new card on active home view, soften Escape, validate locale overrides

Address review feedback on the post-update what's-new card:

- EntryShell passes `active={view === 'home'}` into WhatsNewPopup, and the
  component defers its fetch/show decision (and `page_name: 'home'`
  analytics) until Home is the active entry view, so direct routes like
  /projects no longer surface the card or emit home-page events.
- The document-level Escape listener now ignores already-handled events
  (`defaultPrevented`) and hides the card without marking the version as
  seen or firing the dismiss analytics event, so a stray Escape no longer
  permanently spends the once-per-version card.
- publish-metadata validates the optional `locales` block at publish time
  (plain-object shape, non-empty `title`/`body`, HTTPS `linkUrl`) so
  malformed locale overrides fail the publish loudly instead of being
  silently dropped by the daemon parser; README documents the contract.

Generated-By: looper 0.9.11 (runner=fixer, agent=claude-code)

* fix(release): validate whats-new URLs by parsing, not prefix check

Replace the startsWith("https://") checks in readWhatsNewBlock with a
shared assertHttpsUrl helper that trims, constructs new URL(...), and
requires protocol === "https:", mirroring the daemon readHttpsUrl parser
so values it would silently drop (e.g. "https://") fail the publish
loudly for imageUrl, linkUrl, and locale linkUrl.

Generated-By: looper 0.9.11 (runner=fixer, agent=claude-code)

* fix(web): stop whats-new card fetch race from swallowing the card on first Home load

The card's fetch effect armed its once-guard when the fetch STARTED, and the
effect teardown re-armed it — so React StrictMode's double-invoke (dev) or a
Home toggle mid-fetch cancelled the first fetch, the second effect run saw the
guard still set and bailed, and the resolved-but-cancelled fetch re-armed the
guard without ever showing the card. The card then never appeared until the
Home view toggled again.

Arm the guard only once a decision is actually REACHED (fetch resolved with a
payload); keep the `cancelled` flag purely as a state-update guard that no
longer re-arms the fetch. A superseded fetch drops and the surviving one
records the decision, so the card shows on first Home activation and a genuine
mid-fetch Home exit still retries on the next activation.

Red spec: apps/web/tests/components/WhatsNewPopup.test.tsx renders under
StrictMode and asserts the card appears — red on the previous effect, green now.

* feat: add silent updater preference

* fix: block posthog in beta dry runs

* refactor(whats-new): drive the home card from a dedicated R2 document

Source the post-update "what's new" card from a single hand-curated document
on a dedicated R2 bucket (https://whatsnew.open-design.ai/whats-new.json,
overridable via OD_WHATS_NEW_URL) instead of per-version release metadata.
Operators edit that one file after a release; there is no publish tooling.

- Show-once is keyed on the document's `id`, not the app version: the card
  shows whenever the id differs from the last one the client recorded, first
  sight on a fresh profile included. This removes the version gating that could
  never tell a fresh install from an existing profile upgrading across the
  feature boundary (the reviewer's blocking finding) -- content identity
  settles it instead.
- Daemon service just fetches the document and parses {id, ...content}; no
  channel feed or release-version matching. Graceful degrade to no card on any
  failure is preserved.
- Drop the now-dead release publish tooling: tools/release/whats-new/*, the
  whatsNew forwarding in publish-metadata.ts, and its tests. Ops flow documented
  in docs/whats-new.md.
- Remove the generic no-content fallback card and its two i18n keys; the card
  shows only when the document has content.

Contracts, `od whats-new` CLI, and analytics updated to the {version,id,content}
shape.

* style(whats-new): align the home card with the marketing design

Match the reference card: brand serif body copy (var(--serif), same face as
the home hero), body + CTA stacked in a left column with the illustration
stretching to the card's full height on the right so there is no dead space,
a subtle CTA, and a slightly wider, softer card. CSS-module + markup only;
no behavior change (test ids and flow are untouched).

* style(whats-new): drop the title row, float the close over the card

Per design feedback the "Design system sync" label row is removed so the serif
body copy starts at the top edge; the ✕ now floats top-right as a bordered chip
that stays legible over the illustration. The title still labels the dialog via
aria-label for screen readers. Test asserts on the body + dialog name instead.

* style(whats-new): version eyebrow + release title as the card copy

Per design feedback: the top row is the "Open Design x.y.z" version eyebrow
(left) with the close button (right); the release title renders as the main
serif line (1-2 lines) and the illustration stretches to the card height; the
long body is dropped from the card. CTA + release-notes action unchanged.

* fix(whats-new): gate the card to release channels so it can't intrude on CI

The R2 rearchitecture dropped the daemon's channel gate, so development/CI
builds fetched the live document and showed the card on Home. That broke the
P0 "settings dialog reachable" smoke (getByRole('dialog') matched both the
settings modal and the what's-new card, which also carries role="dialog") and
would have dirtied every Home visual baseline. Restore the gate: only
beta/prerelease/preview/stable fetch the document; other channels resolve to no
card and never touch the network. OD_WHATS_NEW_URL still opts any channel in
for e2e fixtures that exercise the card on purpose.

* fix(whats-new): use role="complementary" instead of "dialog" for the toast

The card is a non-modal, dismissible announcement — not a dialog. As
role="dialog" it collided with getByRole('dialog') in the P0 Settings smokes
(settings-dialog-reachable, AMR reopen, Local CLI Codex fallback), which
strict-mode-failed the moment the card rendered on Home. A complementary
landmark is the correct a11y role for supporting content and never matches a
dialog query, so those P0s pass regardless of whether the card is showing. The
daemon channel gate (5f17873e0) still keeps the card off development builds.

---------

Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
Co-authored-by: PerishCode <perishcode@gmail.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-07-10 08:38:49 +00:00
Ethan GuoandGitHub 06426ca029 test(tools-pack): cover mac/win paths, registry, manifest, NSIS, config validators (#2425)
* test(tools-pack): cover sanitizeNamespace, macAppBundleName, executable path

* test(tools-pack): cover stripRegistryQuotedValue branches

* test(tools-pack): cover readBuiltAppManifest and writePackagedConfig portable flag

* test(tools-pack): cover writeNsisInclude double-quote and newline escaping

* fix(tools-pack): add required requireVelaCli to ToolPackConfig fixtures

`ToolPackConfig` requires `requireVelaCli: boolean`, but the new
win-manifest / win-nsis test fixtures omitted it, so
`pnpm --filter @open-design/tools-pack typecheck` failed with
`'boolean | undefined' is not assignable to 'boolean'`.

Add `requireVelaCli: false` (the production default, per
`options.requireVelaCli === true` in config.ts) to both `makeConfig`
fixtures. Placed before the `...overrides` spread so TypeScript does not
re-widen it through the Partial (verified with tsc --strict).

Addresses review feedback on #2425.
2026-07-03 02:57:17 +00:00
6132da1d79 [codex] Restore stable release publish mode (#5031)
* fix(release): restore stable publish dispatch mode

* test(e2e): cover stable publish dispatch mode

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-07-02 04:30:58 +00:00
lefarcenGitHubopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
59bca72f7e feat(export): programmatic screenshot-based PPTX/PDF export (#4604)
* feat(export): programmatic screenshot-based PPTX/PDF export

Replace the prompt-driven "ask the agent to run python-pptx" PPTX export
with a deterministic, programmatic pipeline. The daemon renders each deck
slide to a pixel-perfect PNG via the desktop's bundled Electron Chromium
(reused over sidecar IPC — no second headless engine, so the packaged app
does not grow) and assembles a one-image-per-slide .pptx (PptxGenJS) or
raster .pdf (pdf-lib).

- sidecar-proto: RENDER_SLIDES message + DesktopRenderSlides{Input,Result}
- daemon: deck-export.ts assembler (decode + pptx + pdf), POST
  /export/pptx and /export/pdf-image routes, desktopSlideRenderer wiring
- desktop: deck-capture.ts renders the deck off-screen and captures one
  PNG per `.deck > .slide` (skips presenter-mode .mini-slide clones)
- web: exportProjectAsPptx() fetch+blob download; ProjectView swaps the
  prompt path for it
- cli: `od export pptx|pdf` dual-track closure
- remove the now-dead build-pptx-export-prompt lib + test

Tests: deck-export assembler unit tests + exportProjectAsPptx web tests.
Screenshot mode ships first; editable-rebuild modes are follow-up.

* chore(nix): refresh pnpm deps hash

* fix(export): deck PDF blank pages on opacity decks + image export capturing the modal

Two pre-existing deck-export defects surfaced while validating the new export:

- Vector PDF (Print-ready) left blank pages for decks that show one slide at a
  time via opacity: DECK_PRINT_CSS forced each .slide onto its own page but
  never reset opacity/visibility, and CSS transitions animated opacity from 0,
  so inactive slides printed blank. Force opacity/visibility/animation/
  transition in print media (both the web and desktop DECK_PRINT_CSS).

- "Export as image" captured Open Design's own format-chooser modal: the host
  compositor snapshot ran after the modal opened, so the overlay leaked into
  the PNG. Capture the clean preview before showing the modal; the cached
  snapshot is reused so opening the modal never re-captures over the overlay.

* feat(export): dual-mode renderer + reuse screenshot for image/PDF export

Unify all screenshot exports on one off-screen renderer and make them
viewport-independent:

- Renderer (deck-capture.ts) now has two modes: deck → one 1920x1080 PNG per
  slide (optionally just the slide at `index`); page (no `.slide` sections,
  e.g. a website) → a single full-document PNG at natural size. Adds `index`
  to the render input and `mode` to the result.
- Image export now renders through the daemon off-screen renderer (deck → the
  current slide at fixed size; website → the whole page as one long image),
  so the exported size no longer depends on the preview pane and can never
  capture Open Design's own UI. Falls back to the host/iframe snapshot on web.
- "Export as PDF" (UI) now produces a pixel-perfect screenshot PDF that matches
  the preview (same renderer as PPTX/image), falling back to the vector
  print path on web or on failure.
- New POST /export/image route; PPTX on a non-deck returns a clear 422.

* feat(export): smart full-page image capture + PDF back to print-view default

Full-page image export now auto-selects the capture technique (users only see
"full page"):
- captureBeyondViewport (one clean off-screen pass, no fixed-element
  duplication) when the output fits the machine's real GPU texture limit —
  queried at runtime via WebGL MAX_TEXTURE_SIZE, not hard-coded — and
  below-the-fold content actually rendered.
- scroll-segment stitch otherwise (too tall, or blank-below-fold scroll-driven
  pages like parallax landings): scrolls a viewport at a time, captures each
  frame, and stitches by real scroll offset into one long PNG. RAM-bound (a
  plain buffer, not a GPU texture), capped by a memory budget; encoded with a
  tiny dependency-free PNG encoder (node:zlib) so it bundles cleanly into the
  ESM packaged main and has no Skia dimension cap.
- Output scale derives from the window DPR / actual captured chunk, fixing a
  double-scale bug (DPR x clip.scale) that produced 4x-sized images.

PDF "Export as PDF" reverts to the print-ready vector path (instant, selectable
text) as the default; the pixel-perfect screenshot PDF stays available via
`od export pdf`.

* fix(export): PDF defaults to the CJK-safe screenshot path, not vector printToPDF

Chromium's vector printToPDF embeds no fonts in the packaged runtime and drops
CJK glyphs entirely — a Chinese page exported to "PDF" lost all its Chinese text
(only Latin survived). The off-screen screenshot renderer (already used for
image/PPTX) rasterizes the real browser render, so CJK is always correct.

"Export as PDF" now produces a pixel-perfect screenshot PDF that matches the
preview (one page per deck slide, or the whole page for a website), falling back
to the vector/browser print path only on web or on failure. Verified: a Chinese
site that lost its text under vector printToPDF renders fully under the raster
path.

* perf(export): stitch scroll-segments with Electron's native PNG encoder

The scroll-segment path was slow (~100s for a long parallax page) because of a
hand-written PNG encoder: a per-pixel JS BGRA→RGBA loop over tens of millions of
pixels plus zlib.deflate of a ~110MB high-entropy buffer.

Replace both with Electron's native image pipeline: stitch chunks as BGRA with
one Buffer.copy per chunk (capturePage already returns BGRA, which is what
createFromBitmap wants — no channel swap, no per-pixel JS) and encode once via
nativeImage.createFromBitmap(...).toPNG(). createFromBitmap is a CPU bitmap, not
a GPU texture, so it is not bound by the texture limit. Removes the hand-written
PNG encoder (crc32 / chunk framing / node:zlib).

Measured on a long parallax page: image 44s→17s, PDF 101s→19s (~5x), and the
native encoder also compresses better (55MB→26MB PNG).

* fix(export): render the image only on Save, after the format is chosen

Image export was rendering eagerly when the modal opened (a holdover from when
the capture had to run before the modal to avoid catching the overlay). Now the
desktop path renders off-screen and can't see the modal, so capture moves to the
Save click: open modal → pick format → Save → render + encode + download.

The Save button shows the "Saving…" state during the render; for the web-only
host-compositor fallback the modal is hidden during the brief capture so it
can't leak into the image. The snapshot is cached, so switching format after a
render re-encodes without re-capturing.

* perf(export): raster PDF embeds full pages as JPEG (52MB -> a few MB)

The screenshot PDF embedded full-page captures as PNG, so a photo-heavy long
page produced a ~52MB PDF. Full pages now render as JPEG (quality 82) — visually
near-identical for web screenshots but ~10x smaller. Deck slides stay PNG (crisp
text/graphics); image export still uses a lossless PNG source the client
re-encodes to the user's chosen format.

Threads a `pageImageFormat` hint through the render input; the desktop renderer
encodes page mode as JPEG (CDP captureScreenshot format:jpeg / nativeImage
toJPEG) and the daemon assembler embeds with embedJpg vs embedPng per image.

* feat(export): pre-pass for reveal-on-scroll + deck image as one long image

Two improvements found while testing real landing pages:

1. Pre-pass before full-page capture: freeze animations/transitions and scroll
   the whole page once (then back to top) so reveal-on-scroll content
   (IntersectionObserver / AOS / lazy images) is triggered and holds. This is
   the standard full-page-screenshot technique. Result: pages that previously
   came back blank-below-the-fold (and fell to the duplicate-prone scroll-
   stitch) now succeed with a clean single-pass captureBeyondViewport.
   Verified: a reveal-on-scroll landing page renders fully in one shot (no black,
   no duplicates); only pure JS-scrollY parallax (which re-hides at scrollY=0)
   still falls back to scroll-stitch.

2. Image export of a deck now stitches every slide top-to-bottom into one tall
   image (the "whole deck as one picture") via the native BGRA stitch, capped so
   a long deck can't exceed the bitmap limit. Ordinary pages remain a single
   full-page capture; a specific slide index is still honored if given.

* fix(export): show the loading toast at the start (not after) + track real completion

The "Exporting" toast was set inside the promise's .then(), so for a multi-second
screenshot export it only appeared AFTER the export finished — looking frozen the
whole time. Now fireShareExport shows a loading toast immediately, clears it on
success, and shows an error toast on failure. The loading toast TTL is raised to
60s so it survives a long export, and PPTX threads its real promise (was fire-
and-forget) so the toast reflects actual completion. Adds fileViewer.exportFailed
across all locales.

* fix(export): detect scroll-driven pages by comparison, not by color

The blank-below-fold heuristic (flat-color fraction) was unreliable: a dark-
themed page that renders fine (Mindloop, 89% near-black below the fold) looked
"blanker" than a scroll-driven page that genuinely fails (Luxury, 78%), so the
black middle slipped through and exported with a black band.

Replace it with a color-independent comparison: render the document's MIDDLE
band two ways — scrolled into view (real content) vs captureBeyondViewport at
scroll 0 (what the one-shot produces). If they differ significantly, the page is
scroll-driven and we use scroll-segment stitch; otherwise the clean one-shot.
Verified: Luxury now exports full content (stitch), Mindloop stays a clean
one-shot, dark designs are no longer false-flagged.

* fix(export): recognize nested-`.slide` decks (export all slides + show PPTX)

A deck whose slides are `.slide` nested under `.deck-viewport`/`.deck-stage`
(not direct children of `.deck`/`body`) was missed: PDF/image exported only the
first slide and the PPTX option was hidden, even though the slide pager showed
"1 / 9".

- Renderer: find slides via `.slide` anywhere, filtering out presenter-mode
  clones (`.mini-slide`/`.overview`/`.thumb`) in-page, instead of the rigid
  `.deck > .slide` selector. showSlide now also sets `visibility:visible` and
  toggles the common active-slide classes (active/visible/is-active/current) so
  decks that hide via `visibility:hidden` and gate reveals on `.visible` render
  every slide; animations are frozen so reveals reach final state instantly.
- UI: PPTX export shows whenever the artifact is deck-like (incl. the
  content-detected `.slide` decks that drive the pager), matching the pager.

Verified on a nested-`.slide` deck: PPTX = 9 slides, image = 9 stitched, all
with content (previously only slide 1).

* perf(export): cache /raw/ assets + halve per-slide round trips

Two performance wins for screenshot export (covers + live preview benefit too):

1. /raw/ now emits ETag + Last-Modified + Cache-Control: no-cache, and answers
   conditional GETs with 304. Covers, live preview, and the screenshot export
   window all load project HTML + its fonts/CSS/images through /raw/, and in the
   packaged app the hidden export window shares the same Chromium session/cache
   as the web UI — so a second load reuses already-downloaded bytes instead of
   re-fetching every asset. The validators are derived from file size+mtime, so
   any agent rewrite changes them and busts the cache immediately (no-cache keeps
   it always-revalidate, never silently stale). Previously /raw/ sent no cache
   headers at all, so nothing was reusable.

2. Deck slide capture merges the slide-show DOM toggle and its two-frame settle
   into a single executeJavaScript round trip (showSlide returns the settle
   Promise) instead of two separate main<->renderer hops. Output is identical;
   the loop is measurably faster and the saving scales with slide count.

Tests: apps/daemon/tests/project-raw-cache.test.ts covers the validators, 304 on
If-None-Match / If-Modified-Since, cache-bust after rewrite, and the streamed
media path. The merge is correctness-preserving by construction (same DOM ops,
same two-frame settle).

* perf(export): hand rendered images to the daemon as files, not base64 IPC

The desktop renderer used to return every rendered slide/page as a base64 data
URL inside the JSON IPC reply. For large images (photo-heavy decks) that means a
1.33x base64 blow-up plus JSON.stringify/parse of a multi-MB string plus a
multi-MB socket transfer across the desktop->daemon sidecar bridge, with a
matching RAM spike. The pixels only exist in the desktop process (it owns the
Chromium that captures them), so they must cross to the daemon — but they should
cross as a file on the shared filesystem, not as base64 in a JSON message.

Now the daemon picks a unique scratch dir under its data root
(<RUNTIME_DATA_DIR>/export-render/<id>), passes it as `outputDir` in the
RENDER_SLIDES request, the desktop writes the images there and returns their
paths in `slideFiles`, and the daemon reads them back and deletes the dir in a
finally. desktop only ever writes to the absolute path the daemon handed it, so
this works identically in dev and packaged (desktop never infers the data root).
A unique per-request id means concurrent exports never collide. Base64 data URLs
remain a fallback for older desktop builds that don't honor outputDir.

- sidecar-proto: DesktopRenderSlidesInput.outputDir + DesktopRenderSlidesResult.slideFiles
- deck-capture: emitImages() writes files when outputDir is set (all 3 paths:
  deck per-slide, deck stitch, full-page incl. scroll-segment)
- deck-export: readSlideFiles() reads the handoff files (companion to decodeSlideDataUrls)
- import-export-routes: create/own/clean the scratch dir; prefer slideFiles

Tests: readSlideFiles unit tests; a route-level test that asserts the renderer
is handed an outputDir under the data root, the image returns, the scratch dir is
deleted after the response, and concurrent exports each get a unique dir.

* chore(export): one-line per-phase timing logs for screenshot export

A slow export now leaves a diagnosable trail instead of guesswork:

- desktop `[od-export] render`: load / assets(fonts+images) / prepare / render
  phase breakdown + total, plus mode and whether the handoff used files.
- daemon `[od-export] assemble`: renderer(IPC) / read(file handoff vs base64) /
  assemble(pptx/pdf build) + total + byte size.

These immediately surfaced that a slow image export was dominated by the
artifact's own in-browser compile (Babel/Tailwind CDN) and uncacheable external
media — not the export pipeline (file read was ~2ms). One info line per export.

* fix(export): normalize PDF page size to points; honor --title in CLI output name

Addresses review feedback on PR #4604:

- buildScreenshotPdf sized each PDF page by the captured image's pixel
  dimensions, so the nominal page size scaled with the capture's device pixel
  ratio (a 2x retina capture produced a page twice as large as 1x). Normalize
  each page to a fixed longest-side in points (960pt; a 16:9 slide => 960x540pt,
  matching PowerPoint) with the image's aspect ratio. The image still embeds at
  full pixel resolution, only the page's points change.
- `od export pptx --title "X"` forwarded the title to the server but always saved
  the local file under the source HTML's basename. Name the output after the
  slugified title when --output is not given.

Tests: PDF page-size normalization assertion (loads the PDF, checks 960pt not
the 1px capture size); sidecar-proto render-slides IPC validation (outputDir,
enum, boolean, unknown-key rejection, minimal round-trip).

* test(export): cover the server Content-Disposition filename branch

The exportProjectAsPptx happy-path test only exercised the no-header local
fallback name; production always returns a Content-Disposition. Add a test that
pins the branch the desktop download actually uses (server filename wins).

* feat(export): support arbitrary-aspect decks (not just 16:9)

Screenshot deck export no longer assumes every deck is 16:9. The renderer
measures the deck's authored slide box (the rendered rect of the first slide
with layout, so fit-to-viewport decks report the stage they actually paint),
sizes the capture window + pinned stage to it, and clips capturePage to it. The
measured pixel dimensions flow to the PPTX assembler, which derives the slide
layout from the real aspect ratio (13.333" wide, height = width/aspect) instead
of hardcoding LAYOUT_16x9 — so 4:3, square, and portrait decks export
correctly-proportioned slides and PDFs instead of being letterboxed or clipped.

Falls back to 1920x1080 / 16:9 when the slide box can't be measured or is out of
a sane range, so existing 16:9 decks are unchanged.

Verified: demo-deck measures 1920x1080 (16:9, unchanged); a 1024x768 deck
measures 4:3. Tests: PPTX layout follows 16:9 / 4:3 / 9:16 aspect (asserted via
the slide cx/cy in presentation.xml).

* fix(export): capture off-screen carousel slides (translated-strip decks)

showSlide only toggled the active class/opacity, so decks that paginate by
translating a flex-strip container (e.g. html-ppt-zhangzara-grove) left slide 2+
off-screen and capturePage kept grabbing the first viewport region — exporting
the wrong slide or a blank page.

showSlide now reports where the active slide actually landed; if it is off the
top-left capture stage, showDeckSlide restacks just that slide into the viewport
(clears ancestor transforms + pins it fixed at 0,0) and re-settles before
capture. This branch only runs when the slide is genuinely off-stage, so
transform-scaled fit-to-viewport decks (active slide already at 0,0, and which
DO rely on an ancestor scale) are never touched.

Verified: a 3-slide flex-strip carousel — slide 0 stays at 0,0 (untouched),
slides 1/2 detected off-stage (x=1920) and restacked to 0,0 before capture.

* fix(export): gate PPTX on a host runtime; unify + center the image toast

- PPTX export has no web-only fallback (it needs the daemon's Electron-Chromium
  screenshot renderer), so a web-only deployment showed a PPTX button that always
  failed with 501. Gate `showPptxExport`/`canPptx` on `isOpenDesignHostAvailable()`
  so the action only appears where it can succeed. Image/PDF keep their web
  fallbacks and stay shown.
- Image export showed an in-modal spinner and a separate, non-portaled "saved"
  toast that rendered off-center (its `position:fixed` resolved against the
  preview pane's transform). Route image export progress through the same
  portaled, viewport-centered `exportToast` used by PPTX/PDF: close the modal on
  Save, show a loading toast, then success/error — one consistent, centered toast
  style. Removes the now-dead imageExportBusy/imageExportCapturing/savedToast.

* fix(export): screenshot the current deck slide; never drop slides when stitching

Two more review findings on the screenshot export path:

- captureExportImageSnapshot() routed deck snapshots through the daemon without a
  slide index, so /export/image fell into the stitch-whole-deck branch even for
  "Copy screenshot" and "Export as image" — which both promise "the current
  preview". Pass the active slide index for decks so both capture the current
  slide. Stitching the whole deck into one long image is reserved for an explicit
  action (a follow-up modal toggle).
- stitchDeckSlides() capped the output at DECK_STITCH_MAX_H by stopping the loop
  and still returning ok:true, silently dropping trailing slides (~13+ on a 2x
  capture) — partial-success data loss. It now captures slide 0 to learn the
  native size, picks one uniform downscale so all `count` slides fit under the
  cap, and stitches every slide (long decks just get a smaller per-slide size).

* fix(export): drop dead `scale` param; keep deck PDF slides PNG (not JPEG)

Two more review findings:

- The render-slides contract accepted a `scale` field (validated in sidecar-proto,
  forwarded by handleScreenshotExport) that the desktop renderer never read — a
  broken protocol surface on the feature's first release. Remove it from the
  proto, the daemon route, and BuildDeckRenderInputOptions; the capture resolution
  comes from the measured stage size and host DPR. (No scale multiplier is needed
  today; if one is added later it must actually be applied in the renderer.)
- The deck branch derived its image encoding from `pageImageFormat`, so the
  screenshot-PDF path (which sets pageImageFormat='jpeg') made deck slides lossy
  JPEG — contradicting the contract ("deck slides stay PNG; JPEG is a full-page
  page-mode optimization") and adding compression artifacts to text-heavy slides.
  The deck branch now always encodes PNG; only `page` mode honors JPEG.

* fix(export): no silent truncation for tall pages; deterministic deck slide index

- The full-page scroll-stitch path clamped the document height to the RAM budget
  and returned ok:true, silently dropping everything below the cap on very tall
  pages. It now refuses with a clear "page is too tall — export as PDF instead"
  error instead of returning a truncated image as success; pages within budget
  still stitch their full height. (Decks downscale to fit since they are discrete
  slides; a continuous page is failed rather than seam-spliced at reduced scale.)
- Deck screenshots now always send a concrete slide index
  (slideState?.active ?? cached ?? 0) so a fresh open — or a deck detected only
  from `.slide` markup that never emits od:slide-state — captures the current
  slide instead of falling into the stitch-whole-deck branch.

* fix(export): explicit page-vs-deck signal; surface semantic export failures

Two review findings:

- Treating any `.slide` element as proof of a deck was too broad for the generic
  /export/image and /export/pdf-image routes — an ordinary page with carousel or
  testimonial `.slide` markup would skip full-page capture and stitch those
  elements as slides. The caller now passes an explicit `deck` flag (the web
  knows `effectiveDeck`; PPTX is deck-only): `deck:false` forces full-page
  capture, `deck:true` forces slide capture, and the `.slide`-count heuristic
  remains only as the no-signal fallback (e.g. the CLI).
- `exportProjectImageDataUrl()` returned null for every non-OK response, so a
  semantic failure (e.g. the daemon's new "page is too tall — export as PDF")
  was treated as "renderer unavailable" and silently downgraded to a partial
  visible-viewport screenshot. It now returns a discriminated result; the caller
  only falls back to a web capture when the off-screen renderer is genuinely
  unavailable (501/no-host/network) and surfaces the real error otherwise (Copy
  screenshot + Export as image both show the message).

Plumbs `deck` through sidecar-proto, the daemon route/options, exports.ts
(image + pptx + screenshot-pdf), FileViewer, and ProjectView. Proto test covers
deck round-trip + rejection.

* fix(export): harden the file handoff (path confinement) + narrow unavailable

Three security/contract findings on the render-slides file handoff:

- sidecar-proto now rejects a non-absolute `outputDir` (was: any non-empty
  string), so a malformed render-slides request can't make desktop main mkdir +
  write outside the daemon scratch area. Negative proto test added.
- The daemon canonicalizes every returned `slideFiles` path and requires it to
  stay under the canonical `renderOutputDir` before reading — a buggy/malicious
  renderer response can no longer make /export/{pptx,pdf-image,image} read and
  stream back arbitrary files (path traversal / symlink escape). Returns 502 on
  an out-of-scope path; handoff test proves an out-of-tree path is refused and
  its bytes never reach the response.
- exportProjectImageDataUrl wrapped the whole flow in one try/catch, so a 200
  with a corrupt/unreadable payload was reported as `unavailable` and silently
  downgraded to the viewport screenshot. The `unavailable` path is now narrowed
  to transport-level failures (the fetch itself); a bad 200 payload returns a
  semantic `error` so the real failure surfaces.

* fix(export): CLI page/deck flag; reject out-of-range slide index

Two review follow-ups:

- `od export pdf|pptx` now accepts `--deck` / `--page` and forwards the signal in
  the request body, so the CLI hits the route with the same page-vs-deck
  semantics the UI uses (which sends effectiveDeck). Previously the CLI fell back
  to the daemon's `.slide` heuristic, so an ordinary HTML file with carousel
  markup could export as a deck from the CLI but a full page from the UI. (PPTX
  stays deck-only server-side; the flag matters for PDF.) `--deck` and `--page`
  are mutually exclusive; omitting both keeps the heuristic fallback.
- renderDeckSlides rejected nothing for an out-of-range `index`: it fell back to
  range(count) and the daemon returned slide 0 with 200 for image export, so
  asking for slide 99 of a 3-slide deck silently returned slide 0. It now fails
  with a clear "slide index N is out of range" error.

* fix(export): If-None-Match precedence; renderer IPC outage -> 502 not 400

- rawRequestIsFresh fell through to If-Modified-Since even when the request sent
  a non-matching If-None-Match, so a same-second rewrite (ETag changes, but
  Last-Modified is identical at second granularity) could 304 changed bytes when
  both headers were sent. If-None-Match is now authoritative when present
  (RFC 9110 §13.1.3) — freshness is the ETag match alone. Regression test sends a
  stale ETag + the current If-Modified-Since and expects 200.
- A rejection from desktopSlideRenderer (a 600s requestJsonIpc) — missing desktop
  process, broken socket, timeout — landed in the outer catch and became
  400 BAD_REQUEST, making renderer outages look like caller errors to retries /
  monitoring. The IPC call is now wrapped and translated to 502
  UPSTREAM_UNAVAILABLE, matching the !rendered.ok branch; the outer 400 stays for
  real request-validation / assembly errors.

* fix(export): full-page stitch corrupts on fractional DPR (125%/150% scaling)

scrollSegmentStitch rounded the device pixel ratio to an integer
(`Math.round(size.width / PAGE_W)`), so on non-retina display scaling (1.25x,
1.5x) the output width and every row offset were wrong — the stitched full-page
screenshot (/export/image and the raster PDF page path) came back cropped
horizontally or with vertical gaps/overlap even though the page rendered fine.

Derive width/height/placement from the REAL captured device width and its true
(possibly fractional) ratio instead. Extracted scrollStitchGeometry /
scrollStitchRowOffset as pure helpers with a non-integer-DPR regression test
(1x / 1.25x / 1.5x / 2x).

* fix(export): broaden deck slide selector; content ETag for transformed HTML

- The renderer only recognized `.slide`, but shipped decks use other slide
  contracts the print/export path already supports (e.g.
  html-ppt-zhangzara-creative-mode uses `<section data-screen-label=...>`), so an
  explicit deck export of those silently downgraded to a single full-page
  capture. Broaden SLIDE_SELECTOR to the pdf-export family
  (`.slide, [data-screen-label], .deck-slide, .ppt-slide`), and when
  `deck === true` finds no slide surfaces, fail fast with a clear error instead
  of capturing a page.
- /raw/ revalidation used the source file's mtime ETag even when the response is
  substituted by a transform (Vite dev-entry -> dist/index.html, or preview
  bridge injection). A change to dist/index.html with an unchanged source entry
  could then return a stale 304. Compute a content ETag from the actual sent
  bytes for transformed HTML; assets/fonts/images/streamed media keep the fast
  mtime ETag + early 304. Regression: rewriting only dist/index.html returns 200.

* fix(export): gate PPTX on explicit deck; page-mode DOM intact; stitch RAM budget

Four review findings:

- PPTX action was gated on the `.slide` regex (`effectiveDeck`/`looksLikeDeck`),
  so ordinary pages with carousel/testimonial `.slide` markup surfaced PPTX and
  were forced through the deck renderer (hardcoded `deck: true`). Gate
  show/canPptx on the EXPLICIT deck signal (`isDeckArtifact`: deck renderer / kind
  / presentation) instead; real decks keep PPTX, pages don't, and `deck: true`
  is now always correct. Image/PDF stay on the broader signal (they handle pages).
- renderDeckSlides ran prepareDeck (hide chrome + freeze animations) BEFORE
  deciding page vs deck, so page-mode exports rendered on a mutated DOM (content
  using generic `.notes`/`.overview` classes vanished). Split the non-mutating
  slide count from the deck-only DOM prep; page mode now captures the original
  document.
- stitchDeckSlides capped only output height, so a wide/high-DPR deck could still
  allocate >1 GiB (8192px stage @2x => W~16384 * 30000 * 4). Add a RAM byte budget
  (320MB, like the page stitcher): downscale by min(heightScale, byteScale).
- sidecar-proto render-slides test now covers the `index` field (success + reject
  negative / fractional / non-number).

* fix(export): image/PDF deck flag from explicit signal, not .slide heuristic

The image and screenshot-PDF exports still passed `deck: effectiveDeck` (the
`.slide` regex), so an ordinary HTML page with carousel/testimonial `.slide`
markup exported only the current card instead of the full page. Drive both off
the explicit `isDeckArtifact` signal (same as PPTX): a real deck → per-slide, a
page → full-page capture. Extracted `shouldCaptureAsDeck()` as a pure helper with
a regression test (page + slides + deck:false => page, not per-slide).

* fix(export): screenshot PDF download must prompt Save As (.pdf in allowlist)

The default Export PDF flow now streams a .pdf download via
exportProjectScreenshotPdf, but the will-download Save As hook only intercepted
.pptx + image extensions — so PDF silently wrote to the OS Downloads folder
while PPTX/images prompted. Add .pdf to SAVE_AS_EXTENSIONS with a PDF filter,
and extract saveAsDialogOptionsForFilename() as a pure helper with a runtime test
(PDF/PPTX/image prompt; uppercase matched; other extensions pass through).

* fix(export): single-shot guard for image export (no double-click duplicates)

The toast-based image export closes the modal and starts the save without an
in-flight guard (the old in-modal busy/disabled states were removed), so a fast
double-click / Enter-repeat on Save could enqueue two concurrent exports
(duplicate captures, downloads, and fireImageExportResult bookkeeping) before the
modal-close re-render removed the button. Add an imageExportInFlightRef guard
that returns early on re-entry and resets in finally — mirrors the existing
screenshotInFlightRef pattern.

* fix(export): If-Range guard on /raw/ stream; block image-modal reopen mid-export

Two non-blocking correctness issues:

- /raw/ honored Range unconditionally even with the new ETag/Last-Modified, so a
  client resuming a cached font/media download after the file changed could
  splice stale + fresh bytes. Gate Range on If-Range (RFC 9110 §13.1.5): serve
  206 only when the If-Range validator (ETag or date) still matches the current
  file, else fall back to a full 200. Regression test: stale If-Range + Range
  returns 200 with the new full length.
- The image-export single-shot guard covered handleImageExportSave, but reopening
  the modal mid-export reset the shared request/result refs, mis-attributing or
  dropping the in-flight export's analytics result. openImageExportModal now
  no-ops while an export is in flight.

* fix(export): drive image/PDF deck decision off the viewer signal (effectiveDeck)

The desktop screenshot image/PDF paths were gated on isDeckArtifact while the
vector-PDF fallback (and the viewer's own prev/next/Present) use effectiveDeck.
That diverged: a metadata-free `.slide` deck rendered as a deck in preview but
exported as a single full page on a desktop host, yet as a deck via the browser
fallback — same artifact, different output depending on host.

Drive image + screenshot-PDF off effectiveDeck (the viewer's deck decision), so
export matches what the user sees and is host-independent. PPTX keeps the
narrower isDeckArtifact: it is deck-only with no vector fallback, so it can't
diverge, and it must not offer slide export for incidental carousel markup.
Removes the now-dead isDeckForExport binding.

* test(web): update image-export specs for capture-on-Save modal flow

The image-export modal was redesigned in this PR from eager-capture-on-open
(preview + live format re-render + in-modal alert + disabled-until-ready Save)
to capture-on-Save unified with the PPTX/PDF portaled-toast flow: the dialog
just picks a format, and Save closes it and runs the single capture behind the
export toast. The 9 specs in file-viewer-image-export.test.tsx still drove the
old eager flow and failed in CI (Web workspace tests). Updated each to click
Save before asserting capture, pick the format before Save, assert the portaled
toast (role=alert error text unchanged) instead of the removed in-modal alert,
and replaced the obsolete "preparing label" spec with one proving no eager
capture happens on open or on format change.

* fix(cli): od export honors the server Content-Disposition filename

The web download helper prefers the daemon's Content-Disposition filename and
only falls back to a locally derived name. `od export` ignored it and always
synthesized the name from --title/basename, so the two surfaces could write
different filenames for the same export. Parse the header (RFC 5987 filename*
and plain filename, reduced to a hardened basename so an odd header can't steer
the write outside the cwd) and prefer it when --output is not given, keeping the
title-slug/basename fallback. Mirrors apps/web/src/runtime/exports.ts.

* fix(export): detect runtime-managed decks; image=whole deck; de-dup long pages

QA found three blocking export-fidelity issues on this PR:

1. Horizontal decks export only slide 1 (image: all such templates; PDF:
   some). Runtime-managed decks (`<deck-stage>` web component with slotted
   `<section data-screen-label>` children toggled via `data-deck-active`)
   carry no literal `class="slide"`, so the viewer's `looksLikeDeck` regex
   misses them and the UI sent an authoritative `deck:false`. The host then
   force-captured page mode (`mode:'page', slides:1`) — a full-page shot of
   whatever slide was visible. PDF same path: `deck:false` skips the host
   DECK_PRINT_CSS, so decks without their own `@media print` print one page.
   Fix: a broader EXPORT-only signal `sourceLooksLikeExportableDeck` /
   `deckExportSignal` mirroring the host's slide-surface family
   (`.slide`/`[data-screen-label]`/`.deck-slide`/`.ppt-slide`) plus
   `<deck-stage>`. Kept OUT of `effectiveDeck` so the host's deck-stage-
   incompatible prev/next nav is not surfaced as a dead "— / —" control.

2. "Export as image" of a deck returned the current slide only. It now
   stitches every slide into one long image (matching the slide count the
   viewer reports); Copy screenshot / Mark-Draw capture keep the current
   slide via `captureExportImageSnapshot({ wholeDeck })`.

3. Long-page image/PDF export duplicated a fixed/sticky hero down the
   output: the scroll-segment stitch captures the viewport per offset, so a
   pinned element was copied into every segment. `preparePageForCapture` now
   neutralizes `position:fixed`->absolute and `sticky`->static before
   measuring/capturing, so each renders once (captureBeyondViewport already
   de-dupes; applied uniformly for consistency).

Red specs: exports.test.ts (deck detection), neutralize-positioning.test.ts
(fixed/sticky normalization).

* chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714)

* fix(sidecar): decode IPC frames with StringDecoder (multibyte UTF-8 corruption)

Exported CJK artifacts intermittently showed `???` / `◆?` (U+FFFD) in place of a
character — e.g. "拥挤" rendered as "拥���", "交付边界" as "交付���界". The bad
character varied between exports, the source bytes on disk were correct, and the
daemon /raw/ serve was byte-identical, so it was not a font or storage problem.

Root cause is in the generic JSON-IPC transport. Both the server and client
socket readers did `buffer += chunk.toString()` into a STRING. A render request
carries the full artifact HTML over the desktop IPC; when the payload spans
multiple `data` events, a multibyte UTF-8 character (CJK = 3 bytes) straddling a
chunk boundary is decoded per-chunk, turning each partial half into U+FFFD. Small
payloads never hit a boundary (hence "works in my repro, breaks on the real
file"); large real artifacts do, at whichever character lands on the split.

Fix: feed each chunk through a per-connection `StringDecoder("utf8")`, which
holds an incomplete trailing byte sequence until the next chunk completes it.

Verified end-to-end against the QA "Blog Post" artifact in a packaged client:
"拥���" → "拥挤" after the fix. Red spec: a ~1.3 MB CJK payload round-tripped
through createJsonIpcServer/requestJsonIpc (forces multi-chunk delivery) is now
byte-exact; it fails on the pre-fix reader.

* fix(export): vector deck PDF rendered only the first slide

A deck exported via the vector PDF fallback (POST /export/pdf →
exportPdfFromHtml) collapsed to a single page: only the runtime-active slide
appeared. Decks gate visibility with `.slide:not(.active){display:none!important}`
(specificity 0,2,0); the host DECK_PRINT_CSS `.slide{}` rule (0,1,0) cannot win
that cascade, so every non-active slide stayed `display:none` in print.

Fix: before printToPDF, mark every slide surface active (the same class set the
screenshot path toggles in deck-capture's showSlide), so the deck's own
`.slide.active` styling applies to all slides and DECK_PRINT_CSS paginates them
one per page. Shadow-DOM `<deck-stage>` decks are unaffected (their own
`@media print` already lays out every slide).

Verified with an offscreen printToPDF of a 12-slide `.slide`-class deck: 1 page
-> 12 pages, each a distinct centered slide.

* fix(export): screenshot PDF fails fast instead of masking errors as vector PDF

Per review: the raster-PDF path fell back to the vector `exportProjectAsPdf` for
EVERY non-ok screenshot result, so a semantic failure (bad deck routing, a 422,
a renderer-side 502, "page too tall", unreadable output) silently handed the user
a different (vector) PDF — the exact fidelity/CJK-glyph class of bug the
screenshot path exists to avoid.

exportProjectAsPptx now returns the same tri-state as exportProjectImageDataUrl:
`{ok:true}` / `{ok:false,unavailable:true}` (501 or transport — caller may fall
back) / `{ok:false,error}` (semantic — must surface). The PDF action only falls
through to the vector path on `unavailable`; a semantic error throws and is shown
in the export toast (onErr now prefers the export's own user-facing message).

* chore(nix): refresh pnpm deps hash

* fix(export): guard deck capture against stale-frame duplicate pages

QA saw a deck export with duplicate pages (e.g. two identical 目录 pages, a
slide silently missing). Root cause is a compositor race: after showing slide i,
`capturePage()` can return the PREVIOUS slide's frame when the new slide hasn't
painted yet (more likely on slower / loaded machines and slides with heavier
reveal content), so the loop emits an exact duplicate of the prior page. The
source has 12 distinct slides and live navigation is fine — the race is purely in
the offscreen capture loop.

Fix: after each capture, compare a cheap sampled checksum to the previous
slide's; if byte-identical (which can't happen for distinct slides), wait for
more frames and re-capture (bounded, 4 attempts × ~60ms). Two genuinely-identical
adjacent slides exhaust the retries and emit once. Applied to both the per-slide
(PDF/PPTX) and stitch (whole-deck image) loops.

Test: imageSignature distinguishes captures by content and length. (The race
itself is timing-dependent and not reproducible on a fast/idle machine — both
file:// and packaged-http exports of the reported deck render 12 unique pages
here — so the guard hardens the failure mode rather than relying on local repro.)

* fix(export): paginate tall pages for raster PDF instead of refusing

Per review: the single-image RAM/texture guard in capturePage refused any page
taller than the budget with "page is too tall — export as PDF instead". That is
right for /export/image, but /export/pdf-image routes ordinary-page PDFs through
the same branch — and since the screenshot-PDF path now fails fast (no silent
vector fallback), a long landing page exported as PDF hit a self-contradictory
hard error and regressed tall-website PDF export.

Fix: the PDF path (`jpeg`) now paginates a too-tall page into a multi-page raster
PDF — captureBeyondViewport per texture/RAM-bounded chunk, one image per chunk,
which the daemon assembles into one PDF page each. /export/image (png) keeps its
refusal (it has nowhere to paginate to). tallPageChunkHeights extracted + tested.

Verified offscreen: a ~20400px page → PDF path returns 3 paginated pages
(ok/page), image path still refuses.

* fix(export): capture deck slides via CDP (structural fix for duplicate pages)

Replaces the pixel-compare/retry guard (88d21c7) with a structural fix, per
review feedback: comparing each capture to the previous slide is the wrong
abstraction (it can't tell a stale frame from two genuinely-identical adjacent
slides, and wastes retries on the latter).

Root cause: the deck path used `webContents.capturePage()`, which grabs the last
COMPOSITED frame and can return the previous slide's frame when the just-shown
slide hasn't composited yet — emitting an exact-duplicate page. The page path
never had this because it uses CDP `Page.captureScreenshot`, which renders the
CURRENT DOM to a fresh frame.

Fix: deck capture now uses CDP `Page.captureScreenshot` too (attach the debugger
once around the deck loop; fall back to capturePage if it can't attach). The
captured pixels always reflect the slide just shown — no compare, no retry, no
identical-slide edge case. Animations/transitions are already frozen
(prepareDeckStage), so each slide is captured at its final state, never a
mid page-turn frame. Removed imageSignature + the retry loop.

Verified: 12-slide deck still stitches to 12 distinct slides at the correct dims.

* fix(export): current-slide capture of runtime decks uses the visible slide

Per review: deckExportSignal makes runtime-managed decks (<deck-stage> /
data-screen-label) exportable, but the current-slide path (Copy screenshot /
annotation capture) still resolved the slide index as `slideState?.active ?? 0`.
Those decks are deliberately kept out of effectiveDeck, so the viewer never
receives their active-slide bridge and slideState is null — meaning Copy
screenshot always off-screen-rendered slide 0 instead of the slide on screen,
inconsistent with the PPTX/PDF fix on the same templates.

Fix: planDeckImageCapture() decides per capture — whole-deck (Export as image),
ordinary pages, and tracked .slide decks render off-screen (with the active index
when tracked); an untracked deck's current-slide capture skips the off-screen
path and falls through to the visible host snapshot (which IS the current slide).

Tests: planDeckImageCapture unit cases (exports.test.ts) + a FileViewer
regression — Copy screenshot of a data-screen-label deck with no tracked slide
uses the host snapshot and does NOT off-screen-render slide 0.

* fix(export): don't mask post-response failures / debugger-less tall PDF as fallback

Two review edge cases:

- exportProjectAsPptx wrapped resp.blob() + triggerDownload() in the same
  try/catch that maps to `{unavailable:true}`, so a corrupt body or a
  client-side download failure (after a 200) was reported as "renderer
  unavailable" — letting the PDF caller silently downgrade to the vector path.
  Only the fetch (transport) and 501 now map to `unavailable`; post-response
  failures return `{error}` so they surface. Unit test added.

- capturePage's no-debugger fallback still returned "page is too tall — export
  as PDF instead" for the PDF path (jpeg). Pagination needs CDP, and we only
  reach this branch when the debugger can't attach, so it now surfaces a
  distinct retryable error instead of telling the user to switch to the format
  they already chose. (The debugger attaches in normal packaged exports; this is
  a rare transient.)

* fix(export): distinguish CDP attach failure from later CDP command failure

Per review: when the debugger attached but a later CDP command threw (a real
Chromium/GPU/clip error), the broad catch swallowed it and the too-tall PDF
refusal reported "renderer is busy, please retry" — hiding the actionable error
and sending users into a pointless retry loop. The retryable busy message is
only correct when the attach itself failed.

Track the caught CDP error (cdpError) separately: the too-tall PDF branch now
surfaces the real CDP error message when the debugger was available but a command
failed, and reserves the retryable "busy" message for true attach contention.

* fix(export): reject `od export pptx --page`; test the tall-PDF error split

Two review items:

- CLI: `od export pptx --page` advertised a page mode that can never work (the
  daemon forces deck mode for /export/pptx). Reject `--page` for pptx with a
  clear contract error pointing at `od export pdf --page` instead of silently
  ignoring it.

- Lock down the cdpError split with a regression: extract tooTallPdfErrorMessage
  and unit-test both branches — attach failure → retryable "busy" message;
  attached-but-CDP-command-failed → the real Chromium/GPU error surfaces (and
  neither tells the user to "export as PDF", which they already chose).

* fix(export): keep current-view captures viewport-based; reject weak If-Range

Two review items:

- planDeckImageCapture sent ordinary-page Copy screenshot / captureViewport
  annotation through the off-screen renderer (useOffscreen:true, no index), which
  renders the WHOLE document instead of the visible region — a regression for
  screenshot/annotation viewport semantics. Now: Export-as-image (wholeDeck) and
  tracked-deck current-slide still render off-screen; an ordinary page's
  current-view capture (and an untracked deck's) falls back to the visible host
  snapshot. Tests updated.

- ifRangeAllowsPartial accepted weak entity-tags for a 206, but RFC 9110 §13.1.5
  requires a strong validator and our /raw/ ETag is weak (W/"size-mtime"). A
  same-size rewrite / mtime collision could splice stale + fresh bytes under a
  matching weak tag. Now any entity-tag If-Range falls back to full 200; only the
  date form authorizes a range. project-raw-cache.test.ts pins it (weak-ETag
  If-Range → 200, fresh date → 206, stale date → 200).

* fix(export): resolve imported-folder project files via metadata.baseDir

Per review: the new screenshot export routes (and the vector /export/pdf) read
the source with readProjectFile() and no metadata, so it fell back to
<OD_DATA_DIR>/projects/:id and returned FILE_NOT_FOUND for imported-folder
projects (whose workspace lives at metadata.baseDir) even though the file renders
in the UI.

Thread project metadata through: BuildDeckRenderInputOptions and
BuildDesktopPdfExportInputOptions gain a `metadata` field passed to
readProjectFile; handleScreenshotExport and the /export/pdf route load it via
getProject(db, id)?.metadata. HTTP regression added: an imported-folder project
(created through /api/import/folder) hitting /export/image now returns 200 with
the rendered image instead of 404.

* chore(nix): refresh pnpm deps hash

* Show PPTX export for detected decks

* Fix deck export detection for page captures

* Route CLI image export through screenshot renderer

* Route legacy image export through screenshot renderer

* fix(export): per-viewport PDF pagination + parallax-faithful image capture

A long non-deck page exported to PDF came out as one giant page, and the same
page exported as an image dropped its scroll-pinned text. Both stemmed from the
page-capture path: PDF assembled one PDF page from a single tall capture, and
the image path flattened fixed/sticky positioning (fixed->absolute,
sticky->static), which deleted parallax headline/foreground text.

- PDF: add a `paginate` render-slides input. A non-deck page now captures one
  image PER VIEWPORT, top to bottom, and the daemon assembles a multi-page PDF
  (one screen per page). Decks still paginate per slide; page-mode only.
- Image: capture each viewport live at its real scroll offset and stitch into
  one tall image, keeping fixed/sticky CSS as authored -- the SAME capture logic
  as the PDF path, differing only in assembly. Drop the captureBeyondViewport
  one-shot and its isScrollBound heuristic (it rendered the whole document at
  scroll 0 and got parallax/reveal-on-scroll content wrong), and drop the
  fixed-neutralization step (it dropped pinned text).

Adds paginateViewportBand unit coverage and a paginate IPC round-trip/rejection
case; removes the now-unused neutralizeFixedAndStickyPositioning helper and test.

* fix(export): capture deck-stage at authored size; share pptx in contracts

Addresses two review findings on the screenshot export surface.

- deck-stage fidelity (blocking): the <deck-stage> runtime fits its canvas to
  the viewport with `transform: scale(...)` by default and documents that PPTX
  export must set the `noscale` attribute so the DOM is captured at the authored
  slide size. The renderer never set it, so a deck whose authored canvas differs
  from the 1920x1080 capture viewport was measured + captured at the preview-
  scaled size. prepareDeckStage now sets `noscale` on every <deck-stage> (a
  no-op for plain `.slide` decks).

- contract boundary: `pptx` was a first-class CLI/daemon export format but the
  shared `EXPORT_FORMATS` in `@open-design/contracts` still declared only
  `['pdf', 'image']`, so the capability was typed through an ad hoc local union.
  Add `pptx` to the shared contract, import it in the CLI instead of a local
  duplicate, and route `pptx` through the generic `/export` route (to the
  screenshot renderer) alongside `image`.

* fix(export): route CLI --format pdf through the raster screenshot PDF path

`od export --format pdf` still posted to the generic `/export` route, whose
desktopArtifactExporter renders vector PDF via printToPDF() and drops CJK glyphs
in the packaged runtime. The web UI was deliberately switched to the raster
`/export/pdf-image` path for that reason, so the CLI diverged from the UI on the
exact decks/pages this feature targets.

Route all three CLI formats through the screenshot renderer (pdf →
/export/pdf-image, matching the UI). Extract the format→route mapping into a
pure `exportRoutePath` helper so it is unit-testable without executing the CLI
entrypoint, and assert no format falls through to the vector `/export` route.

* fix(export): route generic POST /export pdf through the raster screenshot path

The shared ExportRequest contract advertises `pdf` as part of the screenshot-
rendered export surface, but the generic `/export` route still sent `format:
'pdf'` to desktopArtifactExporter's vector printToPDF() path, which drops CJK
glyphs in the packaged runtime. So a contract caller hitting POST /export got the
lower-fidelity PDF while the dedicated /export/pdf-image route, the UI, and the
CLI all use the raster screenshot PDF — the API surface was internally
inconsistent.

Route every /export format (pdf included) through handleScreenshotExport so the
generic endpoint matches the dedicated routes and the contract; drop the now
unused desktopArtifactExporter / buildDesktopArtifactExportInput wiring from the
route. Add an HTTP-level regression asserting POST /export with format:'pdf'
runs the screenshot renderer and streams back a real (%PDF) raster PDF.

* Restore editable PPTX export

* Clarify authored slide measurement

* Enable PPTX export from browser

* Stabilize large editable PPTX text

* Use workspace root for PPTX export resource

* Let CLI exports auto-detect decks

* Avoid tracking generated PPTX bundle

* Fix generic export deck routing

* Fix deck export routing regressions

* Add CLI page-mode export flag

* Preserve authored deck capture DOM

* Load PPTX vendor bundle from gzip resource

* Harden export CLI and PPTX bundle loading

* Preserve editable PPTX slide background images

* Preserve export render sizing contract

* Classify screenshot export request errors

* Preserve freeform slide deck exports

* Preserve UTF-8 export filenames

* Align export routing and CLI JSON contract

* Preserve export compatibility paths

* Keep PDF export on screenshot renderer

* chore(nix): refresh pnpm deps hash

---------

Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-06-29 13:15:14 +00:00
Tom HuangGitHubCursorxne998808-aiPerishCodeopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>lefarcenSiri-Ray
29b138f7a3 feat(brands): turn any brand into a reusable design system (#4691)
* Implement brand management routes and CLI support

- Added `brand-routes.ts` to handle HTTP endpoints for brand operations: listing, extracting, retrieving details, deleting, and serving logos.
- Introduced `brands-cli-help.ts` for CLI commands related to brand management, including usage instructions for listing, creating, retrieving, and deleting brands.
- Updated `cli.ts` to integrate brand commands into the existing CLI structure, allowing headless management of brands via the command line.
- Created supporting files for brand metadata handling, including `design-md.ts` for rendering brand information in markdown format and `index.ts` for the brand engine API.
- Implemented `prefetch.ts` to fetch and process brand material from specified URLs, ensuring a streamlined extraction process.
- Enhanced server setup in `server.ts` to register brand routes and manage brand-related data effectively.

This commit establishes a comprehensive framework for managing brands within the application, facilitating both HTTP and CLI interactions.

* Enhance memory management and onboarding experience

- Introduced canonical profile labels to ensure consistent handling of user input in profile forms, preventing duplicate entries.
- Updated the `parseProfileBody` and `captureProfileFromForm` functions to utilize the new canonical label matching.
- Added a memory callout section in the onboarding view to highlight the benefits of memory usage, including personalized responses and reduced setup questions.
- Implemented new UI elements in the onboarding view to improve user engagement with memory features.
- Expanded i18n support for new onboarding messages related to memory benefits across multiple languages.

* Refactor onboarding flow and enhance design system integration

- Updated the onboarding process to include a new brand extraction step, replacing the previous newsletter step.
- Adjusted the tracking logic to reflect the new onboarding steps, ensuring accurate analytics for user progress.
- Improved the UI for the onboarding view, including new input fields for email collection during the brand extraction phase.
- Refined the EntryShell component to remove outdated comments and clarify the onboarding renderer's purpose.
- Enhanced CSS styles for the onboarding steps to improve layout and user experience.
- Updated internationalization strings across multiple languages to reflect changes in the onboarding flow and brand extraction messaging.

* Add brand management features and enhance font handling

- Introduced new modules for managing brand assets, including `chrome.ts` for headless Chrome operations and `fonts.ts` for self-hosting web fonts.
- Implemented `prefetch.ts` to streamline the brand material extraction process, allowing for efficient harvesting of colors, fonts, and logos.
- Enhanced the brand system with new schema definitions in `schema.ts` to support brand color and font management.
- Developed the `engine` module to integrate brand building and rendering processes, including token derivation and artifact generation.
- Improved the overall structure and organization of brand-related files for better maintainability and scalability.

* Enhance brand extraction and project management features

- Updated `brand-routes.ts` to include new dependencies for project management, allowing for the registration of brand-related projects.
- Modified the `extractBrand` function to support project ID and system files, improving the brand extraction process.
- Enhanced the CLI commands in `cli.ts` to handle project IDs during brand creation, enabling better tracking of brand projects.
- Updated the server setup in `server.ts` to register new project-related routes.
- Improved the UI components to display project information associated with brands, including buttons for opening projects in the `BrandDetailView` and `BrandsTab`.
- Added new metadata fields in the contracts to support project tracking and management for brands.

This commit establishes a more robust framework for managing brand projects, enhancing both backend and frontend functionalities.

* Enhance onboarding profile management and memory persistence

- Added new canonical profile labels for 'Organization size', 'Use cases', and 'Discovery source' to improve user input consistency.
- Introduced `OnboardingProfileState` type to manage onboarding profile data more effectively.
- Implemented functions to build and persist the onboarding profile body to memory, ensuring user selections are saved accurately.
- Updated the `OnboardingView` component to handle profile persistence during navigation and submission steps.
- Enhanced tests to verify that user selections are correctly persisted to the memory profile.

This commit improves the onboarding experience by ensuring that user inputs are consistently captured and stored, enhancing overall user engagement with the application.

* Reflow brand extraction into an agent-driven, live flow

Replace the deterministic SSE prefetch/preview/system pipeline with an
agent-driven extraction: POST /api/brands now reserves the brand and stands
up a backing project with the target site open in an in-app browser tab plus
a seeded prompt, so the agent measures, synthesizes brand.json incrementally,
and the user can clear anti-bot walls by hand. New /preview and /finalize
routes let the agent render the kit page live and register the resulting
user:<id> design system, so extracted brand facts persist as a structured,
reusable brand kit instead of a one-shot deterministic guess.

Adds the brand-extract skill (SKILL.md + brand-kit.html template), kit-render
engine, brand-extraction-engine tests, brand project covers in the Designs
tab, onboarding extract handoff, and the matching od brand extract/preview/
finalize CLI subcommands and contract updates.

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

* Sediment finalized brands into structured memory

Reflow a finalized brand into the memory store (brandToMemoryEntries +
reflowBrandToMemory) so future chats can ground vague requests in the
brand's palette, type, voice and rules. finalizeBrand now wires through
the runtime dataDir and best-effort persists the brand, MemoryChangeEvent
gains a 'brand' source, and the brand kit render hardens its inline JSON
escaping. Adds brand.previewEmpty / brand.viewDetails across all locales.

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

* Implement logo fallback and imagery support in brand extraction

- Introduced a deterministic logo fallback mechanism to ensure that brand extraction processes can retrieve and save site logos, even when the agent fails to do so.
- Enhanced the `startBrandExtraction` and `finalizeBrand` functions to utilize the new logo fallback, allowing for better handling of logo assets.
- Added support for imagery samples in brand validation, enabling the inclusion of representative images in the brand kit.
- Updated the brand kit rendering to include self-hosted fonts and imagery, improving the overall presentation of brand assets.

This commit strengthens the brand extraction workflow by ensuring that logos and imagery are reliably captured and displayed, enhancing the user experience in brand management.

* Enhance memory management with rule proposal and verification features

- Introduced new functionality for distilling annotations into rule proposals, allowing users to suggest rules based on in-canvas annotations through the `od memory rule suggest` command.
- Implemented a verification system that programmatically enforces compliance with active rules during artifact generation, ensuring that all active rules are covered in the self-verify scorecard.
- Added endpoints for managing verification outcomes, including listing, removing, and clearing verification records, enhancing the transparency of the verification process.
- Updated the memory management system to support the retrieval of active rule entries, ensuring that only linked rules are considered during verification.
- Enhanced tests for both rule proposal generation and verification processes to ensure reliability and correctness.

This commit strengthens the memory management capabilities by integrating rule proposals and verification, improving the overall user experience in managing design rules and ensuring compliance.

* Distill review annotations into memory and enforce self-verify scorecard

Add distillAnnotationsToMemory to mine inline preview comments/highlights/
marks into durable feedback + rule memory via a dedicated distiller prompt,
threaded through the existing extract pipeline with an 'annotation' change
source. Tighten the self-verify prompt (daemon + contracts) to state the
daemon programmatically checks the scorecard, so a missing or uncovered
scorecard on an artifact turn is an enforcement failure. Cover the rule
suggest and verification-history routes with tests.

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

* Apply brand design system through web config on "Use in new chat"

Thread onApplyDesignSystem from the entry shell into BrandsTab so the brand's
registered design system is applied via the web config channel instead of a
bare daemon PATCH that left the Home composer stale. Add a transient
home-intent latch + event so the Brands tab can request the Prototype chip on
the already-mounted HomeView, which consumes it once the plugin catalog loads.

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

* Wire annotation distillation into background memory extraction

Add a background distill pass that mines inline review annotations
(comments / highlights / drawn marks) from a turn into durable memory
alongside the general LLM extraction, surface an `annotation` memory
toast source in the web UI, and cover the flow with a unit test.

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

* Fix brand design system not applying to composer on "Use in new chat"

Selecting a brand's "Use in new chat" applies the brand's design system as
the default and fires the Prototype chip intent in the same synchronous click
handler. HomeView consumed that intent inside the event listener, so `pickChip`
ran before React committed the config change and seeded the composer's
design-system field from the stale (empty) default — the composer showed
"No design system" instead of the brand until a reload.

Split the intent handling: the listener now only bumps a tick, and a separate
effect consumes the chip after the re-render lands, so the seeded design system
reflects the freshly-applied brand. Add the previously-untracked home-intent
latch test coverage.

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

* feat(web): rework Brands into Brand Kit and add Home create entry

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

* feat(brands): harvest real cover/hero images for the Images module

The brand kit's Images gallery only populated when the extraction agent
remembered to save imagery — so a forgetful or bot-blocked agent (and the
pre-imagery "Open Design" brand) left it empty. Add a deterministic,
server-side imagery fallback (imagery-fallback.ts), mirroring the logo
fallback: it parses og:image/twitter:image, large <img> (highest-res
srcset/<picture>), <link rel=preload as=image>, and CSS background-image
hero blocks, fetches candidates with browser-shaped headers, decodes
PNG/GIF/JPEG/WebP dimensions to keep only big representative images
(dropping icons/sprites/logos/tracking pixels), dedupes by content hash,
and saves up to 8 of the largest into imagery/ with labeled samples.

finalizeBrand runs it as a timeout-bounded, failure-tolerant safety net
(injectable so tests stay offline) when the agent captured too few
samples, first adopting any on-disk images. The extraction prompt and
brand-extract SKILL now explicitly direct the agent to harvest the site's
large/cover/hero images, filtered by rendered size.

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

* feat(qa): implement deck layout validation and safety checks

Add a new QA module for validating the layout of generated brand decks to ensure robustness against clipping and truncation issues. The `analyseDeckLayout` function checks for critical layout invariants, including the presence of `.slide` sections, correct container types, and necessary runtime layers. Introduce `assertDeckLayoutSafe` to enforce these checks during brand system rebuilds, preventing the deployment of decks that fail validation. Additionally, create comprehensive tests to verify the functionality of the new layout validation features.

* fix(brands): apply deck shrink-to-fit synchronously so slides never clip

The no-clip runtime scheduled its fit pass through requestAnimationFrame,
whose callbacks are throttled while the deck is offscreen or occluded. A
slide could therefore stay unscaled — and clip its content — until first
paint. Fit synchronously on resize/load/fonts-ready with a trailing
setTimeout settle pass for late reflow, removing the rAF dependency.

Verified at the previously-broken 1024x620 viewport: container-type:size,
zero truncations, runtime auto-applies scale (Problem 0.71, ASK 0.87,
Product 0.97, Competition 0.97) and frame clip count drops to 0.

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

* feat(web): let New Brand modal embed scrollable brand reference picker

Add a fillHeight mode to BrandReferencePicker so the heading, quick-pick
row and controls stay pinned while only the gallery scrolls inside a
bounded-height parent. Wire it into NewBrandModal with a stable, spacious
dialog and refresh the related newBrand/brandPicker copy across all 18
locales.

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

* feat(brands): enhance brand extraction with deterministic seed harvesting

Introduce a new `seed-fallback` module to provide a server-side deterministic palette and typography seed during brand extraction. This ensures that the brand kit's initial display includes a harvested logo, an approximate color palette, and font families, improving the user experience by reducing the all-skeleton appearance during the first paint. Update the `startBrandExtraction` function to utilize this new module, allowing for a more seamless and visually appealing brand extraction process.

Additionally, enhance the `BrandReferencePicker` component to reflect loading states and errors during brand extraction, ensuring users receive immediate feedback on their actions. Update related tests to verify the idempotency of the `finalizeBrand` function, ensuring that re-finalizing a brand correctly reuses the existing design system without duplication.

* feat(brand-extract): enhance BrandReferencePicker and localization updates

Updated the BrandReferencePicker component to reflect loading states and errors during brand extraction, improving user feedback. Added a new localization key for the brand extraction process and updated existing translations in English, Simplified Chinese, and Traditional Chinese to enhance clarity and user experience. Additionally, introduced new styles for better interaction with brand assets in the brand kit template.

* feat(brands): wire in-page lightbox/masonry/asset preview + refine seed

Brand-kit preview improvements for the live extraction kit:
- brand-kit.html: add in-page overlay system (sandboxed iframe has no
  top-nav) — clickable image lightbox with prev/next, a "view all"
  masonry modal, and a full-page asset preview modal that loads
  system/artifacts/<kind>.html in an iframe. Defer auto-reload while an
  overlay is open so it never yanks the modal out mid-interaction.
- seed-fallback.ts: prefer vivid mid-luminance hues for the seeded
  accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.)
  from the typography seed so specimens never render glyph soup.

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

* feat(brands): wire in-page lightbox/masonry/asset preview + refine seed

Brand-kit preview improvements for the live extraction kit:
- brand-kit.html: add in-page overlay system (sandboxed iframe has no
  top-nav) — clickable image lightbox with prev/next, a "view all"
  masonry modal, and a full-page asset preview modal that loads
  system/artifacts/<kind>.html in an iframe. Defer auto-reload while an
  overlay is open so it never yanks the modal out mid-interaction.
- seed-fallback.ts: prefer vivid mid-luminance hues for the seeded
  accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.)
  from the typography seed so specimens never render glyph soup.

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

* i18n(web): add brandPicker.opening across remaining locales + picker test

Completes the brand-reference picker i18n key that was committed only for
en/zh-CN/zh-TW, so every locale satisfies the typed Dict, and lands the
BrandReferencePicker extraction-feedback test left untracked by the
concurrent worker.

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

* feat(EntryShell): enhance AMR cloud card visibility post-detection

Updated the EntryShell component to ensure the AMR cloud card remains visible after detection settles, even when the AMR runtime is unavailable. This change prevents the card from disappearing and allows it to degrade gracefully to fallback content and sign-in flow. Additionally, added tests to verify the new behavior, ensuring a better user experience during onboarding.

* feat(library): OD Library asset registry + OD Clipper extension

Add a global, cross-project asset registry (OD Library) and a Chrome MV3
capture extension (OD Clipper), wiring the full HTTP + CLI + Web UI three-track
loop per specs/od-clipper.md.

- contracts: LibraryAsset/Source/Kind, ingest, search, pairing, task DTOs
- daemon: 6 additive SQLite tables, content-addressed owned storage, the
  idempotent registerLibraryAsset hook (hash dedup + append-source),
  programmatic enrichment (mime/size/image dims/domain/tags), pairing tokens
  with a persisted extension-origin allowlist, /api/library/* routes, and
  /api/tools/library/{search,apply} for in-task agent reuse
- cli: `od library list|get|rm|search|import|pair`
- web: Library tab (grid, source badges, filters, search, live SSE updates,
  extension pairing affordance)
- clipper/: standalone MV3 extension (background SW, content toolbar, popup)
- skills/library-curator: utility skill for agent-driven asset reuse

Origin middleware now honors paired chrome-extension:// origins (seeded from
SQLite on boot) and exempts the pairing-confirm handshake. Enrichment AI stages
(caption/OCR/embedding) are recorded as skipped pending a configured model.

* feat(brands): programmatic-first design system extraction + rename

Make brand extraction two-phase so a usable design system is ready the
moment the user enters a URL — the instant "aha" — instead of waiting on
the AI agent:

1. PROGRAMMATIC-FIRST (synchronous): startBrandExtraction now harvests the
   site deterministically (logo, palette, typography, one-line description,
   cover imagery, source URL) via prefetchBrand, synthesizes a valid design
   system with brandFromMaterial (no LLM), and finalizes + registers it
   before returning. finalizeBrand is refactored into a reusable
   finalizeBrandCore shared by both the programmatic path and the agent path.
2. ASYNC AI ENRICHMENT: the seeded agent prompt is reframed to enrich the
   already-usable design system and re-finalize in place (same user:<id>),
   updating every artifact/template.

Bounded + best-effort: a blocked/unreachable origin skips phase 1 and stays
`extracting` for the agent to drive. Gated on userDesignSystemsRoot so the
legacy agent-only path stays intact for tests.

Also rename the user-facing "Brand Kit" surface to "Design System" across
en + zh-CN strings, project names, and the enrichment prompt.

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

* feat(library): enhance asset import and management features

- Updated the `import` command to allow multiple local files and remote URLs, with restrictions on supported formats.
- Added new commands: `apply` for copying assets into project design files, `edit-as-page` for converting HTML assets into editable projects, and `figma` for exporting Figma captures.
- Introduced sidecar functionality for storing derived data alongside owned assets, including Figma capture IR and element HTML.
- Enhanced server configuration to support larger ingest payloads for asset captures.
- Improved error handling and user feedback during asset import and application processes.

* feat(asset-management): enhance asset dropzone and introduce chat-to-design feature

- Updated the DesignSystemAssetDropzone component to improve file preview handling with new functions for creating and revoking object URLs.
- Adjusted CSS for better layout and spacing in the asset dropzone.
- Added a new "Chat to design" button in the LibrarySection component, allowing users to send selected assets to the Home chat composer for project creation.
- Updated localization strings across multiple languages to reflect changes in asset import terminology.
- Enhanced the HomeView component to handle asset staging from the chat composer.

* feat(library): enhance asset application with element markup support

- Updated the `applyLibraryAsset` function to include an `includeElement` option, allowing the capture of element markup alongside assets.
- Modified related components (e.g., `ChatComposer`, `LibrarySection`, `FileWorkspace`) to handle the new element markup feature, ensuring both asset paths and optional element paths are returned and processed.
- Introduced a new function, `fetchLibraryAssetElementHtml`, to retrieve the captured HTML for element-pick assets.
- Enhanced the UI to display element markup inline within the chat composer, improving user interaction with captured elements.
- Updated API contracts to reflect changes in asset application responses, including optional element markup paths.

* feat(library): enhance asset filtering and preview handling

- Updated the LibraryPicker and LibrarySection components to implement a badge-aware kind filter, allowing for more precise asset filtering based on badge kind.
- Introduced a new `matchesKindFilter` function to streamline the filtering logic across components.
- Enhanced the DesignSystemAssetDropzone to ensure proper handling of image previews, addressing issues with broken thumbnails under React StrictMode.
- Added CSS styles for kind badges to improve asset representation in the UI.
- Implemented tests for the DesignSystemAssetDropzone to ensure correct preview lifecycle management.

* feat(library): hydrate single asset on SSE ingest

Add fetchLibraryAsset(id) so the Library grid can merge just the one
asset an `ingest` SSE event references instead of refetching the whole
list on every capture. Returns null on miss/error.

* feat(clipper): richer in-page image picker

Collect CSS background-image url()s in addition to <img> (so hero/section
art painted as backgrounds is no longer silently missed), defer thumbnail
decode to visible cells via IntersectionObserver, draw downscaled canvas
thumbnails instead of second full-res decodes, and add locate-on-page
highlighting so a picked image can be traced back to its DOM source.

* feat(library): implement lazy loading for thumbnails and enhance asset filtering

- Introduced a `LibraryThumb` component to lazily load heavy content (images, videos, iframes) only when they are near the viewport, improving performance.
- Added a debounced search feature to optimize asset filtering, reducing unnecessary network requests during rapid input.
- Enhanced the asset filtering logic to track active filters using a ref, ensuring efficient updates during live events.
- Updated the `snapshotCardRects` and `cardIdsInBand` functions to support improved hit-testing for drag-and-drop interactions.

* feat(library): lazy picker thumbnails + debounced search

Extend the Library grid's lazy-thumbnail + 250ms debounced-search pattern
to the composer LibraryPicker so opening it no longer fires one full-bytes
request per asset, and tidy the clipper content-script image collection.

* feat(clipper): compress and budget capture inlining

Re-encode large raster images to downscaled WebP and inline smallest-first
within a fixed budget, dropping only the secondary Figma IR past a safe body
size, so an image-heavy page (e.g. a news front page) always saves as an
editable HTML capture instead of 413-failing the ingest.

* test(library): LibraryPicker debounce + lazy-thumbnail coverage

Cover the composer picker's 250ms debounced search and its lazy <img>
mount (deferred until the card is in view), matching the grid's perf test.

* feat(design-system): enhance asset handling and UI for design systems

- Updated the CLI to support additional asset kinds, including 'design-system'.
- Enhanced the DesignSystemProvenance type to include source URLs, improving provenance tracking.
- Modified the design system generation jobs to correctly summarize source links and GitHub repositories.
- Updated UI components to reflect changes in asset handling, including new source link management in the DesignSystemFlow.
- Improved tests to cover new functionality for adding source links and ensuring proper handling of design system assets.

* refactor(library): rename 'design-system' to 'brand kit' and enhance thumbnail loading

- Updated labels and filters in Library components to replace 'design-system' with 'brand kit'.
- Introduced a shimmer skeleton for lazy-loaded thumbnails in the LibraryPicker to improve user experience during asset loading.
- Enhanced the PickerCard component for better performance by memoizing individual asset cards.
- Updated tests to ensure proper handling of brand kit assets and their visibility in the LibraryPicker.

* feat(clipper): implement internationalization for toolbar and popup

- Added i18n support to the clipper, enabling localization of UI elements and tooltips.
- Introduced a new i18n.js file to manage translations for various languages.
- Updated content.js and popup.js to utilize the i18n functions for dynamic text rendering.
- Enhanced accessibility by ensuring aria-labels and tooltips are also localized.
- Improved user experience by providing localized messages for actions and statuses.

* feat(clipper): enhance brand kit extraction and localization support

- Updated the brand kit extraction process to include improved handling of assets and localization for various UI elements.
- Added internationalization support for the brand kit feature, allowing for dynamic text rendering based on user locale.
- Enhanced the user experience by ensuring that all relevant messages and tooltips are localized.
- Updated tests to cover new localization features and ensure proper functionality of the brand kit extraction process.

* feat(clipper): enhance brand color derivation and update localization

- Introduced new functions for color manipulation, including linear interpolation and clamping, to improve brand color derivation.
- Updated the deriveBrandColors function to better map observed palettes to semantic roles, ensuring consistent brand representation.
- Revised localization strings in i18n.js to reflect changes from 'brand kit' to 'design system', enhancing clarity and user experience.
- Improved overall code organization and readability by refactoring existing functions and adding new utility methods.

* refactor(clipper): update terminology from 'brand kit' to 'design system'

- Replaced all instances of 'brand kit' with 'design system' across various components and localization files for consistency.
- Updated UI elements, tooltips, and documentation to reflect the new terminology.
- Enhanced user experience by ensuring clarity in the design system extraction process and related functionalities.
- Adjusted localization strings in multiple languages to align with the updated terminology.

* feat(clipper): enhance image fill handling and normalization

- Introduced functions to normalize image fills by converting non-PNG/JPEG formats (SVG, WebP, GIF, AVIF) to PNG before import, ensuring all images are properly rendered in Figma.
- Updated the UI to report the number of images converted and dropped during the import process, improving user feedback.
- Enhanced the overall image processing workflow to prevent silent failures when unsupported formats are encountered.
- Revised documentation to reflect the new image handling capabilities and supported formats.

* feat(clipper): enhance UI kit and busy state feedback

- Updated the UI kit to include new components such as inputs, selection, and overlays, improving the overall design system representation.
- Enhanced the busy state feedback during capture processes with localized messages and a step-by-step progress indicator, providing users with clearer status updates.
- Revised localization strings to support new UI elements and improve user experience across multiple languages.
- Improved documentation to reflect changes in the UI kit and busy state handling.

* fix(brands): restore design-systems nav entry + reconcile BrandsTab on re-activation

Address review feedback on PR #4260:

1. EntryNavRail dropped the only control that reached view==='design-systems'
   when Brands replaced it in the rail, leaving the still-rendered/routed
   design-system manager deep-link only (the entry-nav-design-systems e2e
   specs assert this). Restore a reachable rail entry (blocks icon, existing
   navDesignSystems key) alongside Brands.

2. BrandsTab only fetched once on mount, but EntryShell keeps sub-views
   mounted and toggles visibility, so a brand finishing extraction in its
   backing project never reconciled until a full reload. Refresh whenever the
   Brands view becomes active again, and poll while any brand is extracting
   (torn down once settled / when hidden).

Red spec: tests/components/BrandsTab.refresh.test.tsx (fails pre-fix:
fetchBrands called once, not twice).

* Update clipper/brand-capture.js

* fix(clipper): improve busy state handling and UI feedback

- Adjusted the spinner CSS to use flex properties for better layout control.
- Enhanced the reclampIfMoved function to preserve user position during busy state transitions.
- Added loading toast notifications for popup-launched captures to ensure progress visibility even when the on-page bar is hidden.

* feat(daemon): add kiwi-schema dependency and enhance Figma API integration

- Added kiwi-schema package to the daemon for improved schema handling.
- Updated FigmaApiNode interface and related functions to support shared functionality with the offline decoder, ensuring consistency in node processing.
- Refactored capture functions in clipper to maintain UI visibility during DOM/IR snapshots, enhancing user experience during capture operations.

* fix(web): surface missing backing projects

* fix(web): re-enable brand actions after use

* fix(daemon): serve brand logos from data roots

* fix(brands): reconcile failed extractions

* feat(daemon): implement offline Figma import and decoding functionality

- Added support for importing `.fig` files directly into the daemon, enabling offline processing without requiring a Figma account.
- Introduced a new `fig-decode.ts` module for decoding `.fig` files, handling both ZIP-wrapped and raw formats.
- Created `figma-import.ts` to orchestrate the import process, generating a canonical snapshot and associated metadata.
- Enhanced the server to handle Figma file uploads and integrate with the new decoding logic.
- Updated package dependencies to include `kiwi-schema`, `html2canvas`, and `jspdf` for improved functionality.
- Added tests for the new Figma import features to ensure reliability and correctness.

* feat(clipper): reload-proof capture progress badge on the extension icon

The on-page progress strip dies if a page reloads itself mid-capture
(aggressive paywall sites like economist.com do this), leaving no
loading signal. Add a per-tab '•••' badge on the extension icon for the
lifetime of any capture message — it lives on the action icon, so a page
navigation can't take it down. Verified end-to-end via a real loaded
extension.

* feat(daemon): add export functionality for Figma and enhance PDF export process

- Introduced `runFigma` command for importing Figma designs, supporting both local `.fig` files and Figma URLs.
- Added detailed usage instructions for the `od figma import` command.
- Implemented `runExport` command for programmatic export of HTML/deck artifacts to PDF, PPTX, or image formats.
- Enhanced error handling and user feedback during export processes.
- Removed obsolete `build-pptx-export-prompt` module and related tests to streamline the codebase.

* feat(daemon): enhance library synchronization and export capabilities

- Implemented `reconcileLibrary` to mirror design systems and agent-produced project deliverables into the Library as referenced assets.
- Added support for programmatic export of artifacts via the `od export` command, including detailed usage instructions.
- Introduced new functions for handling Figma imports and exports, improving integration with design workflows.
- Enhanced error handling and user feedback during synchronization and export processes.
- Added tests for new features to ensure reliability and correctness.

* feat(web): PPTX export for any shareable artifact + Library toolbar tooltips

* chore(nix): refresh pnpm deps hash

* refactor(web): enhance onboarding view and file export progress indicators

- Updated the onboarding view layout for improved accessibility and visual hierarchy, including adjustments to spacing, typography, and button styles.
- Introduced a loading toast for file export progress, displaying elapsed time and estimated time remaining for slide exports.
- Added new translation keys for export progress messages in multiple languages.
- Refactored the export progress handling to provide real-time updates during the export process, improving user feedback and experience.

* refactor(web): streamline export capture bridge and update connector styles

- Removed unused loading logic for html2canvas in the export capture bridge, simplifying the code.
- Updated CSS for the onboarding view connector to improve visual clarity and ensure it does not overlap with node numbers.

* refactor(web): remove html2canvas dependency and enhance Figma URL handling

- Removed the html2canvas package from the project, including its references in the lock file and related components.
- Added functionality to manage Figma URLs within the Design System flow, allowing users to add, remove, and validate Figma file links.
- Improved drag-and-drop handling to prevent unintended file navigation when dropping files outside designated areas.
- Updated UI components to accommodate new Figma URL features, enhancing user experience and accessibility.

* refactor(web): unify brand and design system flows

- Merged the brand extraction process into the design system creation workflow, allowing users to start from a brand directly within the design system wizard.
- Updated routing to redirect legacy brand links to the unified design systems tab.
- Enhanced the onboarding experience by removing the separate Brand Kit tab and integrating brand selection into the design system creation process.
- Improved UI components to reflect these changes, ensuring a seamless user experience across the application.

* feat(web): introduce brand enrichment banner and picker modal

- Added a new BrandEnrichmentBanner component to allow users to refine programmatically-extracted design systems with AI by selecting design-system skills.
- Implemented a BrandPickerModal for selecting brands from a searchable gallery, enhancing the design system creation flow.
- Updated ChatPane to conditionally display the enrichment banner for eligible brand projects, improving user engagement.
- Enhanced the design system flow to support the new brand enrichment features, ensuring a seamless experience for users.

* feat(web): enhance BrandPickerModal and DesignSystemAssetDropzone

- Updated the BrandPickerModal to allow scrolling of the entire picker area, improving user experience by creating a unified scrolling surface.
- Added new props to the BrandReferencePicker for action labels and scroll root reference, enhancing flexibility in brand selection.
- Introduced a new DesignKitView component for rendering design kits consistently across different surfaces.
- Enhanced the DesignSystemAssetDropzone to support a wider variety of file types with appropriate previews, improving asset management during design system creation.
- Updated styles for better visual clarity and responsiveness across components.

* feat(web): update Design Systems tab actions and enhance localization

- Changed the button label in the DesignSystemsTab from "Edit" to "Open" for better clarity in user actions.
- Added a new translation key for 'dsManager.openSystem' across multiple languages to support the updated button label.
- Enhanced the FileWorkspace component to ensure the Design Files tab aligns correctly with the Design System tab, improving UI consistency.
- Implemented a new design system editing feature that allows users to fetch and save design system content from DESIGN.md, enhancing the design workflow.

* fix(merge): repair post-merge regressions after origin/main integration

Follow-up fixes on top of the origin/main merge (886f925cd) addressing
regressions the conflict resolution introduced. main's web suite is the
oracle (100% green); resolution principle was main's engine/backend +
HEAD's UI, unioned.

daemon:
- library-sync.ts: correct design-systems import to ./design-systems/index.js
  (design-systems became a directory module on main).
- tests/server-bootstrap-regression: add LIBRARY_DIR to the PathDeps fixture
  (main-added test x HEAD-added LIBRARY_DIR field).

web:
- WorkspaceTabsBar: union — restore main's onboarding Search-popover close
  behaviour + guards, keep HEAD's library/brands nav entries.
- HomeView: restore main's composer sending-state (await onSubmit, widen its
  return type to Promise<boolean>|boolean|void, pass submitting to HomeHero).
- MemorySection.test: take main's version to match main's two-loop memory
  component.
- i18n: restore dropped settings.onboardingRoleMarketing key across types.ts
  and all locales.
- App/BrandsTab/EntryNavRail/router/home-intent: union fixes restoring main
  features dropped during conflict resolution (needs_input handling, etc.).

Validation: pnpm guard + full pnpm typecheck (all 23 packages) green.
Known-red: EntryShell onboarding step 3 intentionally retains HEAD's "build"
step rather than main's brand-extract step; 8 EntryShell.onboarding /
App.onboarding-amr-e2e tests stay red pending that onboarding decision.

* fix(merge): keep HEAD's unified brand flow (revert main's separate Brands tab)

Follow-up to 688544ff7. Per the chosen product direction (brand creation
unified into the design-system create wizard, not a standalone Brands tab),
revert the brand-flow routing/nav that the post-merge repair had restored
from main:

- router.ts: keep HEAD's brand routing (brands folded into design-systems),
  drop main's standalone /brands and /brands/:id view routing.
- EntryNavRail.tsx: drop main's standalone "Brands" nav button.
- runtime/home-intent.ts: drop main's brand "Use in new chat" confirmation
  notice plumbing (tied to the separate Brands flow).

Kept from the repair commit (additive, non-conflicting): App.tsx
loadedActiveProject correctness, composer Sending… state, WorkspaceTabsBar
onboarding popover behaviour, two-loop memory test, restored i18n keys,
brand needs_input STATUS handling, server.ts plugin-route infrastructure.

* feat(library-ui): implement conditional rendering based on LIBRARY_UI_VISIBLE

- Updated router.ts to conditionally render the library view based on the LIBRARY_UI_VISIBLE flag.
- Modified ComposerPlusMenu.tsx, DesignFilesPanel.tsx, and DesignSystemAssetDropzone.tsx to show the "Select from library" button only when LIBRARY_UI_VISIBLE is true.
- Adjusted EntryNavRail.tsx and EntryShell.tsx to include the library navigation button and section conditionally based on the LIBRARY_UI_VISIBLE state.
- Enhanced HomeHero.tsx to allow starting a blank project directly, improving user experience by providing more options for project creation.

This commit introduces a feature toggle for the library UI, allowing for better control over its visibility during development and testing.

* feat(home-hero): implement edge auto-scroll for horizontal overflow

- Introduced `useEdgeAutoScroll` hook to manage auto-scrolling behavior for horizontally overflowing components in HomeHero.
- Updated `PluginPromptPresets` and `RailGroup` components to utilize the new auto-scroll functionality, enhancing accessibility for users without trackpads.
- Added `EdgeScrollZones` component to provide interactive edge zones for scrolling.
- Enhanced CSS styles to support the new scrolling layout and ensure proper positioning of elements.

This commit improves user experience by making overflow content more accessible and easier to navigate.

* feat(design-systems): add project creation from design system and enhance UI components

- Implemented `handleCreateProjectFromDesignSystem` function in `AppInner` to facilitate project creation directly from a selected design system.
- Updated `DesignKitView` to wrap the iframe in a span for better layout control.
- Refactored CSS for `BrandPreviewCard` and `DesignSystemsTab` to improve styling and responsiveness.
- Introduced a new `TemplatePicker` component in `HomeHero` for selecting project-type templates, enhancing user experience.
- Updated various components to support asynchronous handlers for design system actions, improving overall functionality.

This commit enhances the design system integration and user interface, making project creation more intuitive and accessible.

* feat(brand-routes): enhance brand reservation API and add DESIGN.md support

- Updated the POST /api/brands endpoint to accept optional fields: description and designMd, allowing for more flexible brand reservations.
- Modified validation to require either a URL or designMd for brand extraction.
- Introduced a new design-md-input module to handle parsing and validation of DESIGN.md content.
- Enhanced startBrandExtraction function to support processing of DESIGN.md, improving integration with design systems.
- Added utility functions for managing DESIGN.md input and output, streamlining the brand creation process.

This commit improves the brand extraction workflow by integrating DESIGN.md support, making it easier for users to create and manage brands.

* feat(chat-pane, design-kit-view): enhance chat functionality and design preview features

- Added `handleNextStepPromptAction` to `ChatPane` for setting draft prompts, improving user interaction.
- Introduced `nextStepVariant` to differentiate design system projects in `ChatPane`.
- Updated `DesignKitView` to include a button for previewing design kit covers, enhancing user experience.
- Implemented a modal for displaying design kit previews, allowing users to view content in a dedicated space.

These changes improve the chat interface and design kit interactions, making the application more intuitive and user-friendly.

* feat(brand-extraction): enhance DESIGN.md support and testing

- Added a new test case to validate brand extraction from DESIGN.md input without requiring a website.
- Implemented functionality to register brands directly from DESIGN.md, improving the brand creation workflow.
- Updated the `ChatPane` and `NextStepActions` components to handle design system-specific actions for projects, enhancing user experience.
- Enhanced localization files with new carousel hints and project brief options across multiple languages.

These changes streamline the brand extraction process and improve the overall functionality of the design system integration.

* feat(wireframe-examples): add annotated and greybox wireframe examples

- Introduced new wireframe examples for annotated and greybox styles, enhancing design system capabilities.
- Added HTML and JSON files for both wireframe types, providing templates for low-fidelity design mockups.
- Implemented SKILL.md documentation for each wireframe example, detailing usage and design specifications.

These additions improve the design toolkit, offering users more options for creating wireframes in various styles.

* feat(brand-extraction): refine Chrome fallback and enhance error handling

- Updated the Chrome fallback logic in the prefetch pipeline to clarify its purpose and usage as a diagnostic tool.
- Introduced environment variable checks to enable or disable system Chrome usage, improving control over the extraction process.
- Enhanced error messages in the DesignSystemCreationFlow component to provide clearer guidance on required inputs for creating a design system.
- Added regression tests to ensure that prompts do not instruct the agent to invoke a non-existent `brand-extract` skill, preventing potential failures during brand extraction.

These changes improve the robustness of the brand extraction process and enhance user experience by providing clearer instructions and error handling.

* feat(brand-extraction): enhance DESIGN.md input handling and introduce brand ready prompt

- Updated the BrandFromDesignMdInput interface to explicitly define the description property as optional with undefined.
- Enhanced the brand extraction prompts to clarify the inline brand-extract workflow, preventing confusion during the extraction process.
- Added a new BrandReadyPrompt component to notify users when a design system is ready for preview, improving user experience.
- Introduced CSS styles for the BrandReadyPrompt to ensure a visually appealing and user-friendly interface.
- Updated localization files to support new strings related to the brand ready prompt across multiple languages.

These changes improve the clarity and usability of the brand extraction process, providing users with timely feedback and a more intuitive interface.

* feat(brand-extraction): improve design system focus handling and localization updates

- Refactored the handling of browser tabs in the brand extraction tests to ensure proper validation of tab states.
- Enhanced the AppInner component to refresh design systems alongside templates, ensuring users see the latest updates without page reloads.
- Introduced a pending focus state in the DesignSystemsTab to manage design system selection more effectively after brand extraction.
- Added a BrandReadyPrompt in the ProjectView to notify users when a design system is ready for preview, improving user engagement.
- Updated localization files for Chinese (Simplified and Traditional) to reflect changes in terminology related to design systems.

These changes enhance the user experience by providing timely feedback and ensuring that the design system selection process is seamless and intuitive.

* fix(styles): adjust letter-spacing and enhance plus-menu trigger styles

- Set letter-spacing to 0 in design-system-flow.css for improved text clarity.
- Added styles for plus-menu trigger in plus-menu.css, including background, border, and hover effects to enhance user interaction and visual consistency.

These changes refine the design aesthetics and improve the usability of the plus-menu component.

* feat(tests): add design-system focus handoff tests

- Introduced a new test suite for validating the design-system focus handoff functionality.
- Implemented tests to ensure that the focus ID is correctly set, read, and cleared from session storage, preventing user selection hijacking.
- Added checks for scenarios where no focus ID is pending, enhancing test coverage for the design system's behavior.

These tests ensure the reliability of the design-system focus handling, contributing to a more robust user experience.

* feat(export): restrict image format options to PNG and JPEG

- Updated the image format options in the export functionality to only allow PNG and JPEG, removing WebP to prevent silent downgrades.
- Enhanced error handling to provide clear feedback when an unsupported image format is specified.
- Adjusted related documentation and comments to reflect the changes in supported formats across the application.

These changes ensure consistency in image export behavior and improve user experience by providing immediate validation errors for unsupported formats.

* feat(origin-validation): implement zero-config OD Clipper bypass for library requests

- Added a new function `isZeroConfigClipperLibraryRequest` to validate requests from locally-installed browser extensions targeting the `/library/` path.
- Updated the origin validation middleware to utilize this function, allowing unpaired browser extensions to access the `/api/library/ingest` endpoint while blocking other cross-origin requests.
- Enhanced tests to cover the new bypass functionality, ensuring correct behavior for both valid and invalid origins.

These changes improve the integration of browser extensions with the local daemon, enhancing user experience while maintaining security.

* feat(design-systems): add download functionality for design systems

- Implemented a new command `od design-systems download <id>` to allow users to download design systems as a .zip file, including all system files and a generated SKILLS.md usage guide.
- Updated the CLI help documentation to include usage instructions for the new download command.
- Enhanced the design systems API to support the download feature, ensuring only user design systems are accessible while handling errors for non-existent presets.
- Added localization strings for the new download functionality across multiple languages.

These changes enhance the usability of design systems by providing a straightforward method for users to obtain and share their design assets.

* feat(design-systems): enhance design system management and localization

- Introduced new UI components and styles for managing design systems, including buttons for downloading, refreshing, and resetting edits.
- Updated the DesignKitView to support direct actions for DESIGN.md editing, improving user interaction with design systems.
- Enhanced the DesignSystemDetail component to include download functionality and improved state management for design system edits.
- Added localization strings for new features, ensuring consistent user experience across multiple languages.
- Improved error handling and user feedback for design system operations, including download failures.

These changes streamline the design system management process, making it more intuitive and user-friendly while ensuring robust localization support.

* feat(tests): add comprehensive tests for design system archive functionality

- Introduced a new test suite for validating the `buildUserDesignSystemArchive` and `buildDesignSystemSkillsMarkdown` functions.
- Implemented tests to ensure correct packing of design system files, including the generation of a `SKILLS.md` guide and exclusion of internal metadata.
- Added checks for handling non-user IDs and scenarios where a design system already includes its own `SKILLS.md`.
- Enhanced the overall test coverage for design system functionalities, ensuring reliability and correctness in the design system archive process.

These changes improve the robustness of the design system features by ensuring thorough testing of critical functionalities.

* feat(figma-import): enhance CLI output and add Figma import endpoint

- Updated the CLI to conditionally log detailed import information based on the `--json` flag, improving usability for users who prefer JSON output.
- Introduced a new API endpoint for importing Figma files, handling file uploads and validating project existence, with appropriate error responses for missing files or invalid URLs.
- Added a dedicated route for the Figma import functionality, ensuring seamless integration with existing project workflows.

These changes improve the Figma import experience by providing clearer output options and robust error handling, enhancing overall user interaction with the CLI and API.

* feat(design-files): enhance DesignFilesPanel with new actions and styles

- Added new action buttons for opening a browser and creating a design system in the DesignFilesPanel, improving user interaction in the empty state.
- Updated styles for action buttons to enhance visual distinction and usability.
- Enhanced tests to verify the functionality of new actions in the DesignFilesPanel, ensuring they trigger correctly.

These changes improve the user experience by providing additional functionality and clearer visual cues in the design files interface.

* fix(ci): restore new project modal flow

* fix(ci): align design kit and onboarding checks

* fix(ci): sync bake preview workflow action

* fix(ci): include plugin preview helper scripts

* fix(review): harden brand source and preview flows

* fix(ci): stabilize web workspace tests

* fix(review): address latest blocking feedback

* chore(ci): retrigger validation after label update

* chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714)

* refactor(lightbox): implement portal for overlays to resolve z-index issues

- Updated the lightbox component to use React's createPortal for rendering overlays directly to the <body>, ensuring proper z-index stacking.
- Removed session mode toggle from HomeHero and adjusted related styles and tests accordingly.
- Cleaned up CSS by removing unused styles related to session mode toggle.
- Updated tests to reflect changes in the HomeHero component and its interaction with the design router.

* style(home-hero): remove focus halo from template search input

- Updated CSS to eliminate the global input focus outline and box-shadow for the template search field in the HomeHero component.
- Added a test to verify that the template picker search field maintains a clean appearance when focused.

* feat(design-system): add create design CTA and enhance design kit functionality

- Introduced a new `DesignSystemCreateCta` component to facilitate creating new designs from an active design system, enhancing user experience in the chat interface.
- Updated `ChatPane` to include the new CTA, allowing users to create designs directly from the chat.
- Enhanced `DesignKitView` with sticky header functionality for better accessibility while scrolling.
- Added new CSS styles for the `DesignSystemCreateCta` component to ensure a visually appealing and consistent design.
- Updated internationalization files to include new strings for the design system creation feature.

* feat(upload): enhance file upload handling and error recovery

- Introduced `sanitizePath` to preserve directory structures during file uploads, preventing issues with subdirectory paths.
- Updated `DesignKitView` and related components to utilize the new `sanitizePath` function for improved file name resolution.
- Added `KitErrorBoundary` component to gracefully handle rendering errors in the design kit, providing a user-friendly fallback.
- Implemented internationalization updates for new error messages and action confirmations related to uploads and error handling.
- Enhanced CSS styles for better visual feedback during error states and improved user experience.

* feat(design-kit): add keyboard shortcuts hint and enhance key handling

- Introduced a new keyboard shortcuts hint in the DesignKitView, providing users with quick access to essential actions (E edit, C copy, U upload, R refresh, ⌫ delete logo).
- Implemented a keydown event handler to manage keyboard shortcuts contextually within the design kit, improving user interaction and accessibility.
- Updated CSS for the shortcuts hint to ensure it remains low-contrast until hovered, enhancing the UI experience.
- Added internationalization support for new shortcut labels and hints across multiple languages.
- Adjusted DesignSystemsTab to prefer user logos for their systems, improving visual consistency.

* feat(design-system): introduce DesignSystemExtractionPanel and enhance design system interactions

- Added the `DesignSystemExtractionPanel` component to facilitate user interactions during design system extraction, providing a synthesized conversation view and next steps.
- Updated `ChatPane` to render the new extraction panel when a design system is active, enhancing user guidance.
- Introduced a new utility function `designSystemExtractionSource` to derive human-readable labels for design system sources.
- Enhanced internationalization support with new strings for extraction-related actions and prompts across multiple languages.
- Updated various components and tests to reflect changes in terminology and functionality, improving overall user experience.

* feat(project): add project deletion functionality and enhance design system interactions

- Introduced `onDeleteProject` prop in `ProjectView` to handle project deletion, improving project management capabilities.
- Updated `AppInner` to include the new delete project handler, enhancing user experience in project interactions.
- Enhanced `DesignKitView` and `DesignSystemsTab` with loading states and improved visual feedback during design system resolution.
- Removed deprecated `DesignSystemCreateCta` component and associated styles, streamlining the codebase.
- Updated internationalization files to reflect changes in project management terminology and actions.

* feat(design-kit): enhance internationalization and user feedback in DesignKitView

- Updated various labels and error messages in the DesignKitView to utilize internationalization functions, improving accessibility and user experience.
- Enhanced color input validation messages and added confirmation prompts for design system deletions in DesignSystemsTab and FileWorkspace.
- Introduced new props for handling design system project deletions, streamlining project management.
- Updated internationalization files to reflect new strings and translations for improved user guidance across multiple languages.

* refactor(design-kit): remove keyboard shortcuts hint and streamline header menu

- Eliminated the keyboard shortcuts hint from the DesignKitView, simplifying the header menu.
- Updated the sticky-header overflow menu to exclude upload, full-system preview, and shortcut help actions, focusing on essential project operations.
- Adjusted related tests to reflect the removal of the shortcuts hint and ensure accurate menu item visibility.

* feat(brand-routes): add extract-from-html endpoint for brand extraction

- Introduced a new POST endpoint `/api/brands/:id/extract-from-html` to re-run brand extraction using HTML rendered from the in-app browser after clearing anti-bot walls.
- Implemented error handling for missing HTML and brand not found scenarios.
- Enhanced the `extractBrandFromHtml` function to process the provided HTML and optional CSS, integrating it into the existing brand extraction workflow.
- Updated `prefetch` functionality to support extraction from pre-rendered HTML, improving the overall brand data retrieval process.

* chore(nix): refresh pnpm deps hash

* feat(brand-cli): add extract-from-html command for brand extraction

- Introduced a new CLI command `od brand extract-from-html` to facilitate brand extraction from pre-captured rendered HTML, allowing users to bypass anti-bot walls.
- Enhanced the command to accept optional CSS and base URL parameters, improving flexibility in extraction scenarios.
- Implemented error handling for missing HTML input and invalid brand IDs, ensuring robust user feedback.
- Updated the `BRAND_USAGE` documentation to reflect the new command and its usage details.
- Adjusted server configuration to accommodate larger payloads for the new extraction endpoint.

* feat(design-system): enhance design system extraction and browser tools

- Added a new script to collect CSS styles from rendered pages, improving brand extraction capabilities by capturing computed styles from cross-origin stylesheets.
- Removed the `DesignSystemExtractionPanel` component and its associated styles, streamlining the codebase.
- Updated `ProjectView` and `FileWorkspace` components to enhance design system interactions and improve user experience.
- Introduced new internationalization strings for design system phases and actions, ensuring better user guidance across multiple languages.

* feat(brand-assist): implement browser assist for brand extraction

- Added support for a client-side confirmation mechanism for the brand-browser-assist od-card, allowing users to extract brand information from the unblocked browser DOM.
- Enhanced the `ProjectView`, `ChatPane`, and `AssistantMessage` components to handle the new assist functionality, improving user interaction during brand extraction.
- Introduced new internationalization strings for browser assist prompts and messages, ensuring clarity and guidance across multiple languages.
- Updated the `useBrandReadyPrompt` hook to manage the state of the browser assist, providing a seamless user experience when dealing with anti-bot walls.

* feat(brand-prompt): enhance BrandReadyPrompt with refinement options

- Updated the BrandReadyPrompt component to include options for AI optimization and manual editing, allowing users to refine extracted brand systems.
- Added a new refinement nudge to inform users that automatic extraction may miss details, improving user guidance.
- Adjusted styles for the prompt and dismiss button for better alignment and visual consistency.
- Introduced new internationalization strings for the refinement features, ensuring clarity across multiple languages.
- Removed deprecated PPTX export functionality from the FileViewer component, streamlining the export options.

* refactor(export): remove PPTX export functionality and streamline export options

- Eliminated PPTX export support across various components, including CLI, desktop, and web, to simplify export formats.
- Updated documentation and help messages to reflect the removal of PPTX, ensuring clarity for users.
- Adjusted export-related types and constants to focus on PDF and image formats only, enhancing code maintainability.
- Improved user experience by refining export options and related UI elements.

* refactor(export): remove PPTX references and update export functionality

- Removed all instances of PPTX export functionality from the codebase, including related dependencies and comments.
- Updated export options to focus solely on PDF and image formats, enhancing clarity and maintainability.
- Adjusted UI components and tests to reflect the removal of PPTX, ensuring a streamlined user experience.
- Improved internationalization strings and documentation to align with the new export capabilities.

* chore(nix): refresh pnpm deps hash

* fix(onboarding): preserve selected runtime

* fix(brand): localize generated kit copy

* fix(onboarding): align first-run flow with main

* fix(nav): use palette icon for design systems

* fix(analytics): use design system onboarding step

* fix(ui): remove design system guide toggle

* fix(ui): position design system ready prompt

* fix(ui): space plugin task notice

* fix(web): restore home ask mode and design kit preview

* test(e2e): align onboarding visual capture

* test(e2e): align amr onboarding checks

* fix(brand): remove blocked reference brands

* feat(onboarding): show profile choices as chips

* fix(home): prefer design system cover art in recents

* test(e2e): select onboarding profile chips

* feat(brand-extraction): implement programmatic extraction transcript and UI enhancements for design systems

* feat(brand-extraction): enhance programmatic extraction with transcript agent support and UI improvements

* feat(brand-extraction): add transcript agent resolution and improve message handling in brand extraction

* fix(design-systems): stabilize loading state coverage

* test(e2e): align design system detail visual

* fix(brand-extraction): backfill programmatic transcripts

* fix(web): refresh ready brand design systems

* fix(brands): stabilize extraction handoff and seed colors

* fix(brands): return extraction transcript immediately

* fix(web): open new project modal from entry rail

* fix(editing): expose content edits for plain targets

* feat(file-viewer): implement manual edit draft dirty state tracking and reset logic

* feat(design-system): enhance project creation flow with conversation ID handling

* feat(brands): implement light theme handling for color extraction and seed generation

* feat(brands): add finalizeBrandProject function for brand project completion

* feat(file-workspace): add designSystemBrandId prop and update DesignSystemProjectPanel to use it

* Fix manual editing for brand kits

* fix(design-system): wait for project refreshes

* fix(web): open new project modal from rail

* fix(web): restore home ask mode toggle

* fix(web): sync brand color edits with seeds

* fix(web): stabilize design system workspace tests

* test(tools-pack): relax Windows resource cache timeout

* chore(pr): retrigger review after validation

* fix(web): surface design kit action progress

* fix(web): clarify brand next-step actions

* fix(web): cancel programmatic brand extraction

* fix(web): add design systems tab action feedback

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: xne998808-ai <xne998808@gmail.com>
Co-authored-by: PerishCode <perishcode@gmail.com>
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
Co-authored-by: lefarcen <935902669@qq.com>
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-06-25 03:56:14 +00:00
0bf1b6d6b8 [codex] converge release workflows and stable dry-runs (#4390)
* fix(tools-pack): use junctions for Windows standalone peer deps

* fix(desktop): expose IPC during startup

* fix(tools-pack): preserve Windows inspect diagnostics

* fix(tools-pack): report Windows inspect status errors

* fix(packaged): use Electron net fetch for app protocol

* fix(packaged): load Windows renderer from web sidecar

* fix(desktop): show Windows packaged window during startup

* fix(packaged): disable Windows GPU startup

* fix(tools-pack): keep Windows core smoke observable

* fix(packaged): remove Windows startup probes

* fix(tools-pack): trace Windows desktop IPC status

* fix(tools-pack): add Windows IPC diagnose loop

* fix(release): default beta-s Windows updater feed

* chore: clean merged test eof

* refactor(release): unify prerelease channel model

* chore(release): close prerelease doc escape hatches

* refactor(release): converge release channel workflows

* fix(release): install toolchain in metadata jobs

* fix(release): build release package before contracts

* chore(release): bump development version to 0.10.1

* fix(e2e): seed windows packaged smoke runtime config

* fix(release): install toolchain for metadata publish

* fix(release): materialize betas metadata checkout

* chore(release): bump development version to 0.10.2

* fix(release): allow betas metadata cold start from s3

* fix(e2e): support betas packaged update scenarios

* fix(release): pass betas channel into packaged smoke

* fix(release): set betas channel during self-hosted builds

* fix(release): verify counted channel reservations

* fix(release): use pnpm cmd for betas windows publish

* fix(release): add betas manifest artifact fallback

* fix(release): skip beta-s public metadata fetch

* fix(release): read beta-s manifests from storage

* fix(release): cache beta windows tools-pack builds

* fix(release): inline beta mac tools-pack builds

* fix(pack): deep sign unsigned mac bundles

* docs(pack): document payload-first beta updater validation

* fix(release): align preview tools-pack cache flow

* fix(release): align prerelease tools-pack cache flow

* fix(release): pass github token to prerelease metadata

* fix(release): setup pnpm before feishu notify

* fix(release): add stable dry-run prepublish flow

* fix(release): accept completed prerelease metadata gate

* fix(release): require stable release branches

* fix(release): converge r2 access checks

* fix(updater): use release channel parser for defaults

* fix(updater): harden windows payload relaunch

* fix(release): converge updater smoke fixture contract

* test(e2e): require silent updater fixture output

* fix(release): align stable windows smoke build path

* fix(ci): include release workspace in validation

* fix(ci): repair release validation lanes

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(ci): restore zero-install Feishu notification

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-23 06:13:21 +00:00
d3896fa51a [codex] Clean deprecated launcher payloads on updater startup (#4524)
* fix(packaged): recover stale launcher and sidecar state

* fix(packaged): write launcher cleanup from installer sync

* perf(tools-pack): reuse windows payload caches

* perf(tools-pack): avoid slow windows 7z filters

* fix(tools-pack): bound windows payload archive hangs

* test(packaged): stabilize sidecar tests on windows

* fix(tools-pack): harden launcher runtime sync helper

* fix(tools-pack): invalidate nsis cache for installer helpers

* fix(tools-pack): hash nsis helper source in bundled cli

* fix(packaged): restart when existing desktop has stale sidecars

* chore(release): bump beta base to 0.11.1

* fix(e2e): seed windows smoke onboarding in runtime data

* ci(release): let beta runs use default queueing

* ci(release): restore beta concurrency guard

* ci(release): enable beta mac x64 by default

* ci(release): disable beta mac x64 by default

* fix(updater): clean deprecated launcher payloads on startup

* fix(e2e): align stable dry-run fixture version

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-22 04:21:30 +00:00
lefarcenandGitHub 1b35224471 fix(release): pass RELEASE_COMMIT/RUN_ID to every publish step (follow-up to #4144) (#4147)
* fix(release): pass RELEASE_COMMIT/RUN_ID to every publish step so manifests aren't stale

Follow-up to #4144. Setting RELEASE_COMMIT/RELEASE_RUN_ID only on the
publish-metadata step made publish-metadata.ts validateManifest() reject the
per-platform manifests it aggregates:

  refusing stale mac_arm64 platform manifest for 0.10.0.nightly.39: github.runId=0

The staleness guard compares each platform manifest's github.{runId,commit}
against the publish step's currentRunId/currentCommit. The platform build jobs
write those manifests but never received the RELEASE_* env, so they stamped
runId=0 while the metadata step now expected the real runId.

Mirror release-beta.yml: set RELEASE_BRANCH/REPOSITORY/RUN_ATTEMPT/RUN_ID/
WORKFLOW workflow-wide so platform jobs inherit them, and pass RELEASE_COMMIT to
every publish step (4 platforms + metadata) so manifest github.commit matches.

* fix(release): use resolved branch for RELEASE_BRANCH on every publish step

Address review: a workflow-wide RELEASE_BRANCH: ${{ github.ref_name }} stamps
the caller's ref under workflow_call / inputs.ref, so per-platform manifests
would disagree with the aggregate metadata (which uses needs.metadata.outputs.
branch). Drop the top-level github.ref_name and pass RELEASE_BRANCH:
${{ needs.metadata.outputs.branch }} on each publish step instead, matching the
resolved ref the metadata job built. Keep RUN_ID/RUN_ATTEMPT/REPOSITORY/WORKFLOW
workflow-wide (identical for caller and callee). Regression assertion added.
2026-06-11 18:02:22 +08:00
lefarcenandGitHub 0cb06386cc fix(release): restore nightly stableVersion + github attribution for stable promotion (#4144)
The #3995 unified-publisher refactor dropped two fields the stable promotion
gate (scripts/release-stable.ts) requires on a validated nightly:

- publish-metadata.ts omitted stableVersion for the nightly channel, so no
  nightly carried the base version it is a candidate for.
- release-stable.yml never passed RELEASE_COMMIT/BRANCH/REPOSITORY/WORKFLOW/
  RUN_* to the unified publisher (release-beta.yml does), so metadata.github.*
  shipped empty.

Both made every 0.10.0 nightly un-promotable. Restore the nightly stableVersion
field and the RELEASE_* attribution env, mirroring release-beta.yml.
2026-06-11 16:45:41 +08:00
44350ad03c fix(packaging): serve baked plugin previews in the packaged app (#4035)
* fix(packaging): serve baked plugin previews in the packaged app

The packaged gallery fell back to live, GPU-expensive iframes instead of the
baked hover-pan clips, because of two gaps that only bite the packaged layout:

1. The baked-preview manifest (data/plugin-previews/manifest.json) was never
   bundled — it's absent from the packaged resource trees. The clips live on R2,
   but without the manifest the daemon has no plugin -> clip mapping.
2. Even bundled, the daemon resolved the manifest dir from PROJECT_ROOT. For the
   prebundled sidecar (Resources/app/prebundled/daemon) PROJECT_ROOT resolves to
   Resources/app — two levels up — which has no data/. The bundled resources
   actually live under OD_RESOURCE_ROOT (Resources/open-design), where every
   other tree (skills, plugins, design-systems…) is resolved from.

Fix both: add data/plugin-previews to BUNDLED_RESOURCE_TREES, and resolve
PLUGIN_PREVIEWS_DIR against DAEMON_RESOURCE_ROOT like the other resource dirs
(explicit OD_PLUGIN_PREVIEWS_DIR override + the dev PROJECT_ROOT layout still
win). Verified end-to-end: a freshly built+installed mac app's packaged daemon
now returns bakedPreview blocks for 126 plugins (R2 URLs) instead of none.

* test(tools-pack): assert the plugin-preview manifest is bundled

* test(daemon): cover packaged plugin-previews dir resolution from OD_RESOURCE_ROOT

Address review: extract resolveDaemonPluginPreviewsDir and prove it resolves the
bundled manifest under the resource root in the packaged layout (and falls back
to PROJECT_ROOT in dev). Also seed data/plugin-previews in the win-resources and
mac packager fixtures so copyBundledResourceTrees' new tree doesn't ENOENT.

* fix(daemon): resolve OD_PLUGIN_PREVIEWS_DIR override from the injected env

Address review: the override branch delegated to resolvePluginPreviewsDir, which
re-reads process.env — so an injected env was ignored and the override path was
untestable. Resolve it directly from env.OD_PLUGIN_PREVIEWS_DIR (absolute
passthrough, relative against projectRoot) and add an override test.

* fix(tools-pack): include plugin-previews in the Windows resource-tree cache key

Address review: adding data/plugin-previews to the bundled tree didn't feed the
Windows resource-tree cache key, so a builder with an existing cache entry would
reuse the old bundle without manifest.json. Hash data/plugin-previews into the
key, bump the cache schema version, and add a regression proving a manifest.json
change forces a cache miss.

---------

Co-authored-by: audit <a@b.c>
2026-06-10 07:45:52 +00:00
PerishFireandGitHub f9433a7a07 release: harden packaged launcher validation (#3995)
* Unify release metadata publishing

* Allow empty release asset suffix

* Harden beta-s release publishing

* Clean beta Windows release namespace

* Clean Windows launcher namespace

* Retry transient mac notarization uploads

* Control mac notarization upload mode

* Notarize release mac DMGs after build

* Harden release mac DMG notarization retries

* Make beta-s mac updates payload-first

* Bake mac updater metadata URL

* Add unsafe DMG install helper

* Fix beta-s feed and About version

* Improve release validation observability

* Build launcher proto before packaging

* Notarize release-stable mac artifacts

* Enable mac notarization in tools-pack

* test: update packaged release workflow assertions
2026-06-09 11:50:01 +00:00
2ce9d09f41 Fix Windows launcher beta update lifecycle (#3960)
* fix windows beta updater acceptance harness

* fix: harden windows launcher beta updates

* fix: align launcher payload e2e relaunch stub

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

* fix: gate windows uninstall version sync

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

* fix: verify launcher payload before activation

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

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-09 06:00:59 +00:00
PerishFireandGitHub 10dfd32a3e Revert "feat: add screenshot-based visual validation to critique loop (#3660)" (#3865)
This reverts commit 931780c914.
2026-06-08 06:24:44 +00:00
netteeGitHubopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
931780c914 feat: add screenshot-based visual validation to critique loop (#3660)
* feat(daemon): add visual validation atom

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

* fix(daemon): fail closed in visual validation

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

* fix(daemon): narrow visual validation defaults

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

* fix(daemon): add visual validation to default critique stages

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

* fix(daemon): harden visual validation review fixes

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

* fix(daemon): fail closed in visual validation discovery

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

* test(daemon): tighten visual validation discovery regression

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

* fix(daemon): match visual validation reference viewport

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

* fix(daemon): tighten visual validation capture flow

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

* fix(daemon): fail closed across visual validation refs

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

* fix(daemon): honor metadata entry file in visual validation

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

* fix(daemon): capture visual validation through preview route

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

* fix(daemon): fail closed without preview context

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

* fix(daemon): disable pre-start visual validation worker

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

* fix(daemon): narrow visual validation spec discovery

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

* fix(daemon): stop advertising visual validation as runnable

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

* fix(daemon): restore visual validation critique worker

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

* fix(daemon): delay visual validation until run success

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

* test(daemon): cover post-run visual validation scheduling

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

* fix(daemon): gate post-run visual validation before finish

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

* chore(nix): refresh pnpm deps hash

* fix(daemon): preserve deferred pipeline ordering

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

* fix(packaging): bundle Chromium for visual validation

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

* fix(scenarios): keep visual validation out of default critique loop

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

* fix(packaging): bundle Playwright headless shell

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

* fix(packaging): invalidate Playwright resource cache

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

* fix(daemon): include visual-validation in bundled atom roster

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

* chore(nix): refresh pnpm deps hash

* fix: restore pre-run plugin surfaces and nix hashes

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

* fix: preserve pre-run surfaces and tolerate headed-only chromium bundles

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

* fix: launch visual validation with bundled chromium

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

* fix: avoid Playwright fixture teardown races

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

* fix: fail packaged visual validation on shell-only chromium

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

* fix: isolate synthetic Playwright test bundles

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

---------

Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-06-08 04:03:43 +00:00
7a1ad1a438 feat(packaged): add launcher payload updates (#3812)
* feat(packaged): add launcher payload runtime

* ci: focus launcher payload windows smoke

* ci: publish beta mac x64 launcher payload

* test(e2e): harden payload updater smoke

* fix(pack): inject launch config for installed windows smoke

* fix(daemon): allow packaged payload resource roots

* fix(updater): relaunch after payload update

* test(e2e): refresh windows updater UI status

* test(e2e): ensure windows smoke reaches main shell

* test(e2e): skip onboarding before windows updater smoke

* fix(updater): detach windows payload relaunch helper

* test(e2e): run payload smoke without updater dry run

* fix(desktop): relaunch windows payload updates on quit

* fix(updater): preserve installed payload relaunch target

* fix(updater): require launcher target for payload updates

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

* test(e2e): align mac x64 artifact mode assertion

* test(e2e): canonicalize launcher relaunch root

* fix(updater): validate launcher payload config before activation

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

* fix(updater): require live launcher target for payload updates

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

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-07 07:38:57 +00:00
0582749df5 Refactor beta release workflows (#3741)
* Refactor beta release workflows

* Fix beta workflow runner bootstrap

* Fix self-hosted beta release bootstrap

* Use pnpm cmd in self-hosted beta Windows bootstrap

* Pass mac notarization profile to beta builds

* Allow direct Apple notarization credentials

* Fix beta Windows asset handoff

* Skip Windows report zip in beta publish

* Fix beta metadata workflow cleanup

* Stabilize self-hosted beta metadata checkout

* Retry self-hosted beta manifest downloads

* Use release storage for self-hosted manifests

* Isolate self-hosted beta metadata scripts

* Add Windows pack phase diagnostics

* Disable beta self-hosted Windows security checks

* Remove beta Windows security preflight

* Add release-beta-s version reservation

* Fix beta daily workflow caller inputs

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

* Guard beta latest publish and build-only runs

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

* Tighten beta latest publish guards

* Fix beta publish artifact suffix handling

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

* Fix beta metadata suffix and Windows publish target handling

* Fix self-hosted beta release ref and publish guards

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

* Fix beta release commit identity wiring

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

* Fix hosted beta checkout pinning

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

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-05 13:01:54 +00:00
8f6cdaaba1 Harden release-beta-s self-hosted beta lane (#3641)
* Fix Windows smoke launcher payload layout

* Harden release-beta-s runner probe

* Use cmd shell for release-beta-s probe

* Add win beta build script for self-hosted lane

* Keep release-beta-s checkout lightweight

* Use explicit local beta version for release-beta-s

* Install dependencies in beta build script

* Repair Electron dist before Windows beta build

* Add release-beta-s timings and diagnostics

* Polish release-beta-s observability guardrails

* Trace tools-pack cache materialization

* Reuse materialized workspace build cache

* Trace Windows packaging segments

* Trace Windows archive process details

* Reuse installer unpacked materialization

* Preserve reusable Windows unpacked projection

* Add fast smoke telemetry for self-hosted beta

* Cache Windows archive outputs for beta lane

* Tighten fast Windows smoke lane

* Scope Windows beta artifact reporting by target

* Clean stale Windows target artifacts

* Wire Windows Authenticode signing

* Expose self-hosted beta validation modes

* Add core Windows smoke profile

* Decouple core smoke from updater fixture version

* Trace Windows smoke lifecycle steps

* Skip unpacked materialization on NSIS cache hit

* Add Nexu S3 beta publisher

* Harden self-hosted Windows beta lane

* Use Windows PowerShell for publish probe

* Run publish probe via repo script

* Require explicit self-hosted S3 public origin

* Run real installer acceptance for external feeds

* Skip ready-prompt checks for external feeds

* Relax optional self-hosted update inputs

* Tolerate BOM in self-hosted publish probe

* Converge self-hosted beta metadata and signing flow

* Run self-hosted beta metadata prep on the runner lane

* Use Windows PowerShell for self-hosted beta metadata checks

* Bypass execution policy in self-hosted beta metadata checks

* Split self-hosted beta metadata publish

* Suppress mc output in beta publish helpers

* Split Windows NSIS payload cache layers

* Require fresh tool dist metadata

* Preserve Windows overlay payload paths

* Default beta-s gate to core

* Optimize win beta packaging reuse

* simplify windows full smoke lifecycle

* preserve onboarding state after windows upgrade smoke

* fix windows tools-pack test portability

* recover stale tools-pack locks

* Allow auto signing fallback for beta builds

* Add self-hosted beta mac arm64 lane

* Quote self-hosted signing choices

* Run self-hosted beta metadata on mac lane

* Use shallow metadata checkout on self-hosted beta

* Use sparse beta metadata checkout

* Make mac release profile bash-compatible

* Allow file sparse metadata checkout

* Isolate self-hosted metadata checkout

* Clear sparse skip-worktree before mac build

* Isolate self-hosted mac build checkout

* Run self-hosted beta publish without aws cli

* Use unsigned beta prefix for mixed platform publish

* Sparse checkout self-hosted beta publish

* Publish mac beta platform before metadata

* Pass mac publish release profile

* Probe mac signing keychain import

* Expand mac signing preflight variants

* Use sudo keychain helper for self-hosted mac signing

* Route self-hosted mac codesign through sudo wrapper

* Normalize mac signing identity name

* Harden self-hosted mac notarization

* Optimize self-hosted mac release cache

* Fix mac signing helper cleanup

* Retry transient mac notarization uploads

* Keep mac notarization fail-fast

* Reduce mac symlink diagnostics noise

* Normalize self-hosted beta platform inputs

* chore: bump beta base version to 0.9.1

* chore: tune release tools-pack cache retention

* test: add temporary cache validation marker

* chore: key windows nsis base payload by content

* Revert "test: add temporary cache validation marker"

This reverts commit e8487bfd3e.

* chore: probe windows nsis payload cache before materializing

* chore: align self-hosted beta smoke modes

* chore: skip mac smoke report upload when disabled

* chore: default self-hosted beta to publish both platforms

* chore: make beta signing modes explicit

* Add release report artifacts and metatool metadata

* Fix Windows release report zip publishing

* Remove artifact handoff from release beta self-hosted lane

* Fix release beta merge follow-ups

* Fix release beta install regressions

* Restore web build dependencies

* Route Windows beta downloads through mirrors

* Use PowerShell 7 for Windows beta lane

* Fix release beta preflight gates

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

* Fix Windows beta smoke update reuse

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

* Fix release beta review follow-ups

* Refresh Nix pnpm dependency hashes

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

* Fix Windows signtool fallback resolution

* Reject stale beta platform manifests

* Validate beta platform manifest run identity

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

* Validate beta manifest commit identity

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

* Allow beta metadata publish reruns

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

* Build tools-pack fully in beta Windows bootstrap

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

* Emit r2 metadata for Windows beta publish

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

* Stabilize beta publish browser test

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

* Stabilize Windows beta metadata smoke test

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

* Restore multi-platform Vela CLI package

* Refresh Nix pnpm dependency hashes

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

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-05 03:00:55 +00:00
Marc ChanGitHubopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
b6105a8cab refactor(web): extract shared UI primitives (#2879)
* refactor(web): extract shared UI primitives

* fix(web): include components in packaged builds

* fix(nix): refresh daemon pnpm deps hash

* fix(components): trim unused primitive exports

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

* refactor(components): split primitives into modules

* fix(web): restore finalize cancel link

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

* docs: prefer shared web components

* refactor(web): migrate easy primitives

* fix(web): preserve migrated control styling

* fix(nix): refresh daemon pnpm deps hash

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

* fix(web): keep Continue in CLI action styling

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

* fix(web): keep finalize action styling

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

* chore(nix): refresh pnpm deps hash

* fix(web): preserve MCP row label styling

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

* fix(web): resolve ChatComposer merge conflict

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

* fix(e2e): stop leaked tools-dev runtime before retry

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

* ci: include packages/components in change scopes

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

* fix(pack): include components in linux internal packages

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

* fix(pack): ship components package in packed installs

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

* fix(pack): align win app test with internal packages

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

* fix(components): use global visually hidden class

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

* fix(components): add development export for source builds

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

* fix(components): restore selected custom select state

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

* fix(tools-pack): rebuild components for packaged web builds

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

* test(tools-pack): cover components workspace artifacts

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

* fix(ci): include components in tools-pack scope

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

---------

Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-06-03 03:22:29 +00:00
2ea2c91a92 fix(pack): add missing download and host packages to Linux INTERNAL_PACKAGES (#2837)
* fix(pack): add missing download and host packages to Linux INTERNAL_PACKAGES

The native (non-containerized) Linux AppImage build fails with npm 404
errors because @open-design/download and @open-design/host — runtime
dependencies of @open-design/desktop and @open-design/web — were not
included in the INTERNAL_PACKAGES list. Without tarballs for these two
packages, npm install in the assembled app directory tries to resolve
them from the public registry where they don't exist.

Add both packages to INTERNAL_PACKAGES and their build steps to
buildWorkspaceArtifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pack): apply same download/host fix to mac and win lanes, add regression test

1. Extend the INTERNAL_PACKAGES fix to mac/constants.ts, mac/workspace.ts, win/constants.ts, and win/app.ts so all three pack lanes produce tarballs for @open-design/download and @open-design/host.

2. Add internal-packages-coverage.test.ts that derives required workspace runtime deps from apps/desktop and apps/web package.json files and asserts every pack lane's INTERNAL_PACKAGES includes them. This prevents the same drift from recurring when a new workspace dependency is added.

3. Update win-app.test.ts and workspace-build.test.ts mock directory lists to include the two new packages.

* fix(pack): include runtime packages in workspace build cache

* fix(pack): install platform with desktop prebundle packages

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-29 09:44:03 +00:00
youcef zrandGitHub d6d42c3600 fix(pack): bundle download and host packages in Linux AppImage assembly (#2845)
The Linux AppImage path assembles INTERNAL_PACKAGES as `file:` tarballs
and runs `npm install --omit=dev` in an isolated app directory. `pnpm
pack` rewrites each tarball's `workspace:*` refs to a concrete version,
so any runtime @open-design/* dependency missing from INTERNAL_PACKAGES
is resolved from the public npm registry and 404s.

Linux ships webOutputMode "server" and tarball-installs every
INTERNAL_PACKAGES entry, including @open-design/desktop and
@open-design/web. @open-design/host (dep of web + desktop, added in
#2246) and @open-design/download (dep of desktop, added in #2677) landed
after the Linux package list was written and were never added to it, so
`pnpm exec tools-pack linux build --to appimage` fails with:

  npm error 404 Not Found - GET .../@open-design%2fdownload

mac/win default to "standalone", where desktop/web/packaged/daemon are
prebundled with esbuild and excluded from the tarball install
(shouldInstallInternalPackageFor{Mac,Win}Prebundle). The packages they
do install have no download/host dependency, so those lanes correctly
omit them and need no change — this fix stays scoped to linux.ts and
touches no mac/win or workspace-build code.

Add both packages to the Linux INTERNAL_PACKAGES and build them in
buildWorkspaceArtifacts (download depends on platform). Add a cross-lane
regression test that, for each lane, derives the set it actually installs
(honoring the standalone prebundle exclusion) and asserts that set is
closed under its runtime @open-design/* dependencies. The test is red on
the linux lane without this fix and green with it, while mac/win pass
either way — encoding why only Linux needs these packages.
2026-05-29 07:25:03 +00:00
lefarcenGitHuba1chztAmyMasonCaprikaopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
df8a0faff6 feat(runtimes): register AMR (vela) as an ACP stdio agent (#2355)
* feat(runtimes): register AMR (vela) as an ACP stdio agent

AMR is the vela CLI's ACP runtime mode. `vela agent run --runtime opencode`
speaks ACP JSON-RPC over stdio (see vela's
`specs/current/runtime/manual-agent-run-openrouter.md`); per
`docs/new-agent-runtime-acp.md` we expose it through the same `streamFormat:
'acp-json-rpc'` transport that already powers Hermes, Devin, Kimi, etc.

The new `defs/amr.ts` is the entire wiring — `buildArgs` returns
`['agent', 'run', '--runtime', 'opencode']`, `fetchModels` reuses
`detectAcpModels`, and the fallback list seeds the OpenRouter ids vela's
e2e baseline uses. `executables.ts`/`app-config.ts`/`metadata.ts` get the
matching `VELA_BIN`/`VELA_LINK_URL`/`VELA_RUNTIME_KEY`/`VELA_OPENCODE_BIN`
allowlist + install/docs URLs, so users can configure the per-agent env in
Settings without leaking into other adapters.

Coverage: `tests/fixtures/fake-vela.mjs` is a minimal ACP stub that returns
the documented `initialize` / `session/new` / `session/set_model` /
`session/prompt` shapes; `tests/amr-acp-integration.test.ts` spawns it via
`child_process.spawn` and drives a full turn through `attachAcpSession` and
`detectAcpModels`, so the ACP transport contract for AMR is end-to-end
verified locally even before a real `vela` binary is installed.

Validated:
- pnpm guard
- pnpm typecheck (all workspace projects)
- pnpm --filter @open-design/daemon test (2881/2881)

Deferred: real OpenRouter-backed turn through a built `vela` binary —
the runtime def needs no changes for that path, only `VELA_RUNTIME_KEY`
and `VELA_LINK_URL` in env (or Settings).

* fix(runtimes/amr): pin a concrete default model and bare openai ids

End-to-end validation against a freshly-built `vela` (nexu-io/vela@main)
+ OpenRouter surfaced two contract details the first AMR runtime def
got wrong:

1. vela rejects `session/prompt` with `session/set_model must be called
   before session/prompt`. attachAcpSession in apps/daemon/src/acp.ts
   skips set_model whenever the picked model is the synthetic 'default'
   id, so AMR's fallback list must NOT include DEFAULT_MODEL_OPTION. The
   def now ships a concrete `gpt-5.4-mini` as both `fetchModels`'
   default option and `fallbackModels[0]`, which makes attachAcpSession
   always send a real `session/set_model` for AMR turns.

2. `vela --runtime opencode` auto-prepends `openai/` to whatever modelId
   it forwards to opencode's openai provider. With OpenRouter-style ids
   like `openai/gpt-5.4-mini`, opencode receives the double-prefixed
   `openai/openai/gpt-5.4-mini` and replies `ProviderModelNotFoundError`.
   The new fallback list ships the bare ids opencode's openai registry
   actually knows about (gpt-5.4, gpt-5.4-mini, gpt-5.4-fast, etc.).

Stub + tests:
- tests/fixtures/fake-vela.mjs now enforces the set_model gate the same
  way real vela does, so a regression that silently goes back to
  model: 'default' would surface as a fatal error in tests instead of a
  hidden production failure.
- tests/amr-acp-integration.test.ts pins both contracts: no 'default' /
  no 'openai/' prefix in fallbackModels, and a negative case that
  asserts session/prompt fails when no model is set.

Adds `apps/daemon/scripts/verify-amr-real-vela.mjs` — a small dev-time
runner that drives `attachAcpSession` against a real `vela` binary and
prints the daemon's chat events, so future protocol drift can be checked
against an actual OpenRouter call.

Verified locally: `vela agent run --runtime opencode` + OpenRouter
returns the prompted string ("AMR-E2E-PASS") through the full daemon
pipeline; daemon test suite stays 2883/2883.

* fix(runtimes/amr): substitute concrete model when chat run sends 'default'

A plugin-driven AMR run from the UI surfaced a real-world hole in the
prior commit:

  json-rpc id 3: session/set_model must be called before session/prompt

The Default-design-router plugin (and any caller that doesn't pin a
real model) sends `model: 'default'` straight through, which the AMR
runtime def cannot accept — vela rejects `session/prompt` without
`session/set_model` and attachAcpSession skips set_model whenever
model === 'default'. Just leaving DEFAULT_MODEL_OPTION out of the
adapter's `fallbackModels` is not enough: the chat-run handler in
server.ts still forwarded 'default' verbatim.

This adds `resolveModelForAgent(def, resolved, env?)` as the
single source of truth for the substitution:

  1. If the caller picked a real id, pass it through.
  2. Else, if `def.defaultModelEnvVar` is set and the daemon process
     env has a non-empty value for it, return that (operator escape
     hatch — see below).
  3. Else, if the def's `fallbackModels` does NOT contain a 'default'
     id, return `fallbackModels[0].id`.
  4. Else, return the original value (the historic shape — defs that
     list 'default' themselves are untouched).

AMR sets `defaultModelEnvVar: 'VELA_DEFAULT_MODEL'`, so when
opencode's openai-provider registry deprecates `gpt-5.4-mini`
upstream, an operator can swap the fallback id without a code change
by exporting `VELA_DEFAULT_MODEL=gpt-5.5` before launching tools-dev
/ od. Worth noting the env var must live in the daemon's `process.env`
(Settings-UI per-agent env values only reach the spawned child, not
the daemon's resolver) — the new field's docblock spells this out.

Coverage:
- `tests/runtimes/resolve-model.test.ts` — 8 unit tests covering all
  four resolver branches plus the env-override happy path / fallback /
  ignore-when-user-picked-a-real-id case.
- `pnpm --filter @open-design/daemon typecheck` clean.

* chore(runtimes/amr): move AMR to the top of the base agent list

So `AMR (vela)` shows up first in the agent picker / status views,
ahead of claude / codex. Pure ordering change; no behavior delta.

* feat(amr): Sign-in / Sign-out button on the AMR Settings card

The first half of the AMR work assumed the operator would set
VELA_RUNTIME_KEY / VELA_LINK_URL on the daemon process and never
surfaced login state to users. This adds the missing UX so a fresh
install can drive the full path from Settings:

  - GET  /api/integrations/vela/status   reads ~/.vela/config.json
    for the active profile and returns { loggedIn, profile, user }
    (without leaking the runtime/control keys themselves).
  - POST /api/integrations/vela/login    spawns `vela login` once
    (409 if one is already in flight). The vela CLI opens the user's
    browser to the device-authorization page itself — Open Design
    only needs to kick the subprocess off.
  - POST /api/integrations/vela/logout   removes ~/.vela/config.json
    so the next status read returns logged-out.

`AmrAgentCard` is a dedicated agent-card component for AMR because
the existing `<button>` row can't host an interactive sub-control
(nested interactive elements). It polls /status after a login click
until the daemon reports loggedIn=true (or 5 minutes elapse), and
exposes a Sign-out action on hover. Other adapters (claude, codex,
hermes, …) keep their existing `<button>` card.

i18n: 8 new keys (settings.amrLogin / Logout / LoggingIn / etc.)
added to en + zh-CN. Other locales spread `en` and inherit the
English copy until translations land.

Coverage:
- `tests/integrations/vela.test.ts` pins the config.json reader
  against a tmp HOME — including the negative case where a profile
  has user info but no runtimeKey (still logged-out), and the
  secret-leak guard ("rt-secret-*" must not appear in the projection
  payload).
- `tests/components/AmrAgentCard.test.tsx` covers all four UI
  states (logged-out, logging-in, logged-in, logging-out) plus the
  click-propagation invariant the divergent card was built to keep.

`pnpm --filter @open-design/daemon test` 2901 / 2901 passing.
`pnpm --filter @open-design/web test` 1719 / 1719 passing.
`pnpm typecheck` + `pnpm guard` clean.

Dev script side-effects: `apps/daemon/scripts/verify-amr-real-vela.mjs`
no longer requires both VELA_RUNTIME_KEY and VELA_LINK_URL — if
VELA_PROFILE is set, the vela CLI is allowed to resolve credentials
from `~/.vela/config.json`. Added the two AMR `.mjs` fixtures to
`scripts/guard.ts` allowlist with the executable-fixture / dev-runner
rationale.

* fix(connection-test): substitute model for AMR before attachAcpSession

The chat-run path in server.ts already routes the requested model through
`resolveModelForAgent` so AMR / vela (whose CLI demands an explicit
`session/set_model` before `session/prompt`) gets the def's first
concrete fallback id when the chat run ships `model: 'default'`.
`connectionTest.ts` was wiring `attachAcpSession({ ..., model: model ?? null })`
directly, which made the Test Connection button on the AMR Settings
card deadlock with the same `session/set_model must be called before
session/prompt` error the chat-run path already handles — surfaced as a
permanent "Testing connection…" spinner in the UI.

Reuse the same helper here so Test Connection mirrors chat-run behavior.

* test(amr): three-layer end-to-end coverage for the AMR login + turn flow

The PR up to this point shipped runtime + UI code with unit-level Vitest
coverage. This commit adds the cross-layer regression net the live demo
relied on:

1. apps/daemon/tests/integrations/vela.routes.test.ts (HTTP, Vitest)
   Spins up the real daemon Express app via `startServer({port:0,...})`,
   persists `agentCliEnv.amr.VELA_BIN = <fake>` into app-config.json,
   and exercises every /api/integrations/vela/* endpoint against the
   extended fake-vela stub:
     - status reads ~/.vela/config.json under various states
     - login spawns the fake, waits for config.json to appear, returns
       pid + startedAt + profile
     - 409 already-running guard with the stub's delay knob
     - logout removes the file (idempotent)
     - secrets (runtimeKey / controlKey) never leak in the projection
     - login → status round-trip flips loggedIn=false → true

2. e2e/tests/amr/turn.test.ts (tools-dev orchestrated, Vitest)
   Boots a namespaced daemon + web pair through `createSmokeSuite`,
   inlines a self-contained fake `vela` binary that handles BOTH
   `vela login` (writes ~/.vela/config.json) and
   `vela agent run --runtime opencode` (ACP stdio with the
   `session/set_model must precede session/prompt` gate the real binary
   enforces), then drives a complete /api/runs lifecycle for
   `agentId: 'amr', model: 'default'` and asserts the assistant message
   captures the fake's streamed text. This is the test that would have
   surfaced today's plugin-default-model regression (the `set_model
   before prompt` error) at PR time instead of demo time.

3. e2e/ui/amr-login-pill.test.ts (Playwright)
   Mocks /api/agents + /api/integrations/vela/{status,login,logout}
   to drive the Settings AMR card through the full Sign in → Signed in
   → Sign out cycle. Pins the AmrLoginPill polling contract and the
   aria-label semantics (the pill's accessible name is "Sign out" once
   logged in, regardless of which label the hover-state text shows).

fake-vela.mjs extensions:
   - Handles `vela login` argv by writing
     ~/.vela/config.json for the active VELA_PROFILE and exiting 0 —
     mirrors real vela's on-disk side-effect without the device-auth
     loop.
   - FAKE_VELA_LOGIN_DELAY_MS knob so route tests can observe the
     in-flight state of the spawn lifecycle.
   - FAKE_VELA_LOGIN_USER_EMAIL / _USER_PLAN to assert the surfaced
     user fields end-to-end.

Validated:
   - `pnpm guard` + `pnpm typecheck` (all workspace projects)
   - `pnpm --filter @open-design/daemon test`: 2998 / 2998 passing,
     including the new 8-test integration suite.
   - `cd e2e && pnpm test tests/amr`: 1 / 1 passing.
   - `cd e2e && pnpm exec playwright test ui/amr-login-pill.test.ts`:
     1 / 1 passing (6.7s).

* feat(amr): package native cli and refine login ui

* feat(amr): wire vela cli beta packaging

* docs(amr): document vela ci packaging review

* docs(amr): refine vela ci integration review

* fix(ci): refresh nix pnpm dependency hashes

* fix(pack): clean up Vela CLI packaging

* fix(pack): bundle Vela CLI support files

* fix(amr): recover login attempts from stale auth state

* test: expand AMR and automations coverage

* fix(amr): address review follow-ups

* test(web): align tasks fixtures with contracts

* fix(daemon): type wildcard route params

* fix(ci): refresh PR merge validation

* fix(amr): clear env credentials on logout

* feat(settings): inline local CLI model configuration

* fix(amr): recognize daemon env credentials

* [codex] Fix Vela companion packaging (#2979)

* Fix Vela companion packaging

* Update Nix pnpm dependency hashes

* [codex] Surface AMR account failures (#2980)

* fix: surface AMR account failures

* fix: cover AMR recovery error guidance

* chore: bump beta base version to 0.8.1 (#2990)

* Fix AMR profile and packaged runtime review issues

* Detect packaged AMR OpenCode companion tree

* feat(web): polish AMR frontend flows

* Polish AMR onboarding card

* fix: read AMR login state from dot-amr config (#3048)

* test: tighten AMR credential and packaging coverage

* test: restore AMR executable test env helper

* [codex] Fix packaged mac Dock identity and AMR label (#3076)

* Fix packaged mac sidecar Dock identity

* Rename AMR assistant label

* Fix AMR live models and dot-amr login state (#3073)

* fix: read AMR login state from dot-amr config

* fix: load live AMR models before runs

* fix: point AMR onboarding link to production wallet

* fix: address AMR model review feedback

* fix: persist live AMR model fallback

* [codex] Fix AMR link catalog model ids (#3088)

* Fix packaged mac sidecar Dock identity

* Rename AMR assistant label

* Fix AMR link catalog model ids

* Fix AMR model normalization typecheck

* Use live AMR model for default runs

* fix: polish AMR runtime settings UI

* Accelerate AMR startup defaults (#3092)

* Surface AMR insufficient balance wallet URL (#3099)

* fix(web): polish onboarding controls (#3112)

* fix(web): show CLI scan loading state

* Avoid duplicate AMR wallet recharge links (#3117)

* Avoid duplicate AMR wallet recharge links

* Use Vela CLI 0.0.3 test package

* chore(nix): refresh pnpm deps hash

* Fix AMR wallet guidance display

---------

Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>

* chore(pack): pin Vela CLI 0.0.3-test.1 (#3127)

* chore(nix): refresh pnpm deps hash

* chore(pack): pin Vela CLI 0.0.3

* chore(nix): refresh pnpm deps hash

* fix(web): suppress AMR exit 130 fallback (#3136)

* feat(web): nudge users to hosted AMR on model/auth/quota failures (#3083)

* feat(web): nudge users to hosted AMR on model/auth/quota failures

When a non-AMR agent run fails with an auth / quota / upstream model
error, surface an inline nudge under the error pill linking to Open
Design's hosted AMR gateway (https://open-design.ai/amr). The nudge
fires `surface_view` (element=run_failed_toast) on impression and
`ui_click` (element=go_amr) on the link.

Also teach the daemon to classify CLI-agent auth/quota/upstream failures
(Claude Code, codex, ...) into specific API error codes
(AGENT_AUTH_REQUIRED / RATE_LIMITED / UPSTREAM_UNAVAILABLE) instead of
the generic AGENT_EXECUTION_FAILED, so both the error message and the
nudge key off accurate codes. AMR's own runs are excluded from the
nudge — they keep the dedicated sign-in / recharge affordances.

* feat(web): rework failed-run AMR guidance into per-case error UI

Replace the single inline nudge with a per-case failed-run experience
driven by the run's error code + agent:

- The error card is now neutral gray (was red) and always carries a
  retry button; it is driven by the persisted per-message error event so
  it survives a reload.
- Non-AMR agent hitting a model/auth/quota wall: a theme-color promotion
  card under the error card offers "switch to AMR & retry" — switches the
  run to AMR, opens Settings on the AMR card, and auto-retries once the
  account signs in (ProjectView polls vela login status, independent of
  the Settings pill lifecycle, with success / 5-min-timeout / unmount
  exits).
- AMR agent unauthorized: clearer copy + an "authorize & retry" button.
- AMR agent out of balance: clearer copy + a "top up" button to the AMR
  wallet, with manual retry.
- Settings AMR card: when opened from the nudge, it scrolls into view and
  pulses, and an authorize-button coachmark (a fake hand cursor that
  rises in and dismisses on hover) points at the sign-in control when not
  yet authorized.

analytics: surface_view (run_failed_toast) on the promotion card and
ui_click (go_amr) on its action are retained. i18n adds chat.amrCard.*
and chat.amrError.* (en / zh-CN / zh-TW translated; other locales fall
back to en) and drops the old chat.amrErrorGuidance keys.

* fix(daemon): require status context for numeric service-failure codes

Per review on #3083: the model-service classifier matched bare HTTP
status numbers (`500`, `502`, `429`, `401`), so ordinary CLI output like
`line 500`, `read 502 bytes`, or `exit code 401` could be misclassified
as a provider outage / auth wall and wrongly surface the AMR nudge. Now
a status number only counts when it carries explicit context (`HTTP 500`,
`status 503`, `code: 401`, `502 Bad Gateway`); textual provider phrases
(overloaded, bad gateway, service unavailable, rate limit, …) are
unchanged. Adds fixtures proving unrelated numeric output stays null.

* fix(web): keep error pill for failed runs ChatPane's card doesn't cover

Per review on #3083: the per-message gray error pill was suppressed for
every persisted error status event, but ChatPane only renders the
replacement top-level error card for `retryableAssistantMessage` (the
last failed assistant). So a failed turn that is no longer last (after a
follow-up) or an older failed run in history showed neither the pill nor
the card — its error detail vanished, undercutting reload/history
survival. ChatPane now passes `errorCardOwnerId` (the assistant id whose
error the card represents); AssistantMessage suppresses only that one
pill and keeps rendering StatusPill for all other error events.

* fix(daemon): don't treat a process exit code as an HTTP status

Follow-up to review on #3083: the status-context helper accepted a bare
`code` prefix, so `exit code 401` / `process exited with code 429` still
matched and got classified as AGENT_AUTH_REQUIRED / RATE_LIMITED (the
very `exit code 401` case the comment calls out as noise). `code` now
only counts when qualified (`status code` / `error code` / `response
code`) or punctuation-bound (`code: 401`); bare `exit code N` no longer
matches. Adds fixtures for exit-code lines returning null.

* chore(web): translate AMR card / error keys for 16 remaining locales

PR #3083 added 10 new `chat.amrCard.*` / `chat.amrError.*` keys but only
provided en/zh-CN/zh-TW translations; the other 16 locales fell back to
English. Translate the card title/body, three chips, primary CTA, and
the AMR self-error (auth / balance) messages and buttons for ar, de,
es-ES, fa, fr, hu, id, it, ja, ko, pl, pt-BR, ru, th, tr, uk.

* fix(amr): address review feedback on #2355

Targeted fixes for the unresolved review threads on #2355. Each fix
includes / updates a focused test.

- runtimes/executables.ts: `packagedVelaOpenCodeCompanionTree` now
  verifies the inner `opencode` executable exists + is runnable, not
  just the directory. This closes the false-positive availability path
  that let `detectAgents()` surface AMR as available even when the
  packaged companion was empty / partially copied (mrcfps, 4 threads).

- runtimes/executables.ts: `resolveAmrOpenCodeExecutable` now prefers
  the bundled `<OD_RESOURCE_ROOT>/bin/libexec/opencode/opencode` over a
  stale `opencode` on the user's PATH, so packaged AMR builds can't be
  hijacked by a global installation.

- web/EntryShell.tsx: when the Local CLI scan returns an available
  agent and the previously-selected agent is AMR, switch the selection
  to the first available local agent so the runtime and persisted
  agent agree before Continue.

- server.ts (model-probe branch): for AMR, check `readVelaLoginStatus`
  BEFORE rejecting on an empty live-model catalog — a signed-out user
  was getting `AMR_MODEL_UNAVAILABLE` ("choose a model") instead of
  the correct `AMR_AUTH_REQUIRED` (sign-in affordance).

- server.ts (default model fallback): if the user asked for the AMR
  agent default and the cached id is no longer in the FRESH catalog,
  fall back to `liveModels[0]` from the probe instead of rejecting the
  run as `AMR_MODEL_UNAVAILABLE`.

- integrations/vela.ts: route `vela login` through
  `createCommandInvocation` so an npm/Node-style `vela.cmd` / `.bat`
  shim on Windows gets the correct `cmd.exe /d /s /c …` wrapping with
  verbatim args (matches `execAgentFile` / chat-run spawning).

- tools/pack/src/linux.ts: in containerized Linux builds, bind-mount
  the host directory of `OPEN_DESIGN_VELA_CLI_BIN` and rewrite the env
  to the container-side path. The host path was being passed in as-is
  even though the default container only mounts /project, /tools-pack
  and cache/home — `copyOptionalVelaCliBinary` saw a missing path.

Deferred (out of scope for this PR):
- `od amr status/login/logout/cancel` CLI subcommands (AGENTS.md
  UI/CLI dual-track rule, server.ts:5763) — sizable surface; tracked
  for a separate focused PR.
- Strict `--require-vela-cli` for Windows + mac-x64 beta builds:
  prematurely blocked — `@powerformer/vela-cli` only publishes the
  `darwin-arm64` platform binary today; adding the flag elsewhere
  would fail the builds. Revisit once win/x64/linux binaries ship.

* fix(amr): hoist sendAmrAccountFailure above the AMR catalog preflight (TDZ)

The new signed-out AMR branch in the catalog preflight at server.ts:10875
calls `sendAmrAccountFailure(...)` to emit AMR_AUTH_REQUIRED, but the
const declaration sat ~100 lines below at the outer function scope. Because
`const` is TDZ-aware, that branch would have thrown `ReferenceError:
Cannot access 'sendAmrAccountFailure' before initialization` for the
exact users it tries to help — defeating the original intent.

Hoist the helper to just above the AMR preflight block so it's available
to every AMR code path in this function. Behavior elsewhere is unchanged.

Also rerun the daemon test suite: `launch.test.ts > resolveAgentLaunch
uses packaged built-in Vela for AMR` was creating the
`<resourceRoot>/bin/libexec/opencode/` companion *directory* only, but
this PR's earlier tightening of `packagedVelaOpenCodeCompanionTree`
also requires the inner `opencode` executable. Add it to that fixture
to match the new contract; the test was a sibling of the executables /
env-and-detection fixtures already updated in 13fc4f4.

Addresses #2355 review (mrcfps, 2026-05-28).

* feat(web): add hover cancel for AMR login (#3158)

* feat(web): add hover cancel for AMR login

* fix(web): don't bounce AmrLoginPill back to 'Signing in…' after local cancel

Both codex-connector (P2) and looper (CHANGES_REQUESTED) on this PR
flagged the same race in the new local-cancel path: `handleCancelLogin`
dispatches `notifyAmrLoginStatusChanged('login-canceled')` immediately
after `/login/cancel` returns, but the `AMR_LOGIN_STATUS_EVENT` listener
unconditionally re-enters `refresh()` and then restarts polling
whenever `/api/integrations/vela/status` still reports
`loginInFlight: true`.

That is a real race because the daemon's `cancelVelaLogin()` only sends
SIGTERM (escalating to SIGKILL after `LOGIN_CANCEL_KILL_GRACE_MS` =
2000 ms) and keeps the child in `activeLoginProcs` until it actually
exits — so the first `/status` read after a successful cancel can
legally still come back as in-flight. Under that window the pill flips
back to 'Signing in…' and can later surface the timeout/error path even
though the user already canceled, defeating the behavior promised in
the PR description.

Fix the listener instead of every dispatch site: in the
`login-canceled` branch, after the local reset (stopPolling +
setPending(null) + clear refs), optimistically mark every subscribed
pill instance as not-in-flight (`setStatus((c) => c ? { ...c,
loginInFlight: false } : c)`) and `return` — skip the
refresh-and-reconcile branch below entirely. The next explicit refresh
(component mount, user interaction, or a `status-changed` event) will
pick up the daemon's confirmed state once the child has actually
exited.

Add a focused regression test that holds `/api/integrations/vela/status`
at `loginInFlight: true` even after a successful `/login/cancel`,
asserting that the pill stays at the Canceled → Authorize sequence and
never bounces back to 'Signing in…'. This test fails on the pre-fix
listener and passes on the new behavior; existing
'cancels an in-flight AMR sign-in…' and 'reconciles late AMR browser
completion to Signed in after local cancel' tests continue to pass.

Addresses review feedback on #3158 (chatgpt-codex-connector, nettee).

---------

Co-authored-by: lefarcen <935902669@qq.com>

---------

Co-authored-by: a1chzt <chizblank@gmail.com>
Co-authored-by: Amy <1184569493@qq.com>
Co-authored-by: Mason <jinmeihong0201@gmail.com>
Co-authored-by: Caprika <56862773+alchemistklk@users.noreply.github.com>
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-05-28 05:09:55 +00:00
bfcafc81fd feat(pack): add Windows portable zip target alongside NSIS installer (#2937)
Adds a new `--to zip` (and `--to all`) tools-pack Windows build target that
produces a portable `.zip` from the cached `win-unpacked` tree using the
bundled 7z. The zip lays files at the archive root so users can extract it
anywhere and launch `Open Design.exe` without going through the NSIS
installer, addressing the no-install download request.

Release plumbing is updated to publish the portable zip and its sha256
beside the existing installer on R2 for beta, preview, and stable channels
(default on, gated by `WINDOWS_INCLUDE_ZIP`/`WIN_INCLUDE_ZIP`). The
electron-updater `latest.yml` feed continues to point only at the
installer; the zip is a manual-download convenience and is intentionally
excluded from the in-app updater.

Closes #1121

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

Co-authored-by: libertecode <libertecode@proton.me>
2026-05-26 06:14:44 +00:00
lefarcen c14baf07d3 Merge origin/main into release/v0.8.0
PR #2461 sync prep — resolves 14 conflicts merging 84 main-side commits
on top of 58 release-side commits accumulated during the 0.8.0 cycle.

Resolution summary:

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

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

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

Closes-issue / unblocks: PR #2461 sync release/v0.8.0 → main.
2026-05-23 12:17:18 +08:00
2879aceb3a Fix mac tools-pack Node fallback (#2160)
* Fix mac tools-pack Node fallback

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

* Preserve mac native rebuild stat diagnostics

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-23 11:46:19 +08:00
PerishFireandGitHub 526c7f7c26 Fix packaged auto-update release validation (#2565)
* fix: tighten packaged updater flow

* test: prune noisy extended ui coverage

* fix: hide unpublished release artifacts

* test: validate release updater channels

* fix: align prerelease release namespaces
2026-05-21 18:15:53 +08:00
ebf4a3ffca feat(release): upload browser sourcemaps to PostHog for packaged builds (#2508)
* i18n: add translations for media provider coming soon section (#2415)

* i18n: add translations for media provider coming soon section

- Add 'settings.mediaProviderComingSoonHint' key to all 19 locales
- Replace hardcoded English strings in SettingsDialog.tsx with i18n keys
- Reuse existing 'tasks.comingSoon' and 'settings.agentInstall.docs' keys
- Resolves TODO(i18n) comment at line 5091

* fix: escape single quotes in translation strings

* fix: escape all single quotes in English translation string

* feat(release): upload browser sourcemaps to PostHog for packaged builds

Next.js was emitting minified JS with no browser sourcemaps, so PostHog
Error Tracking surfaces frames like fO / fz / s4 / tD instead of real
file:line locations. This wires up the full pipeline:

- apps/web/next.config.ts: enable productionBrowserSourceMaps so next build
  emits .js.map alongside each chunk.
- tools/pack/src/web-sourcemaps.ts: new helper that runs after next build
  and before any packaging step copies the web output into the Electron
  resources. Uses @posthog/cli to inject chunk IDs and upload sourcemaps
  to PostHog, then ALWAYS strips every .map under .next/static so source
  never ships inside an installer (saves ~14 MB per packaged image too).
- tools/pack/src/{mac/workspace,win/app,linux}.ts: call processWebSourcemaps
  immediately after the @open-design/web build step.
- tools/pack/src/config.ts: read POSTHOG_CLI_API_KEY + POSTHOG_CLI_PROJECT_ID
  (with POSTHOG_PERSONAL_API_KEY / POSTHOG_PROJECT_ID aliases) and expose
  them on ToolPackConfig with the same shape as the existing posthogKey /
  posthogHost fields.
- .github/workflows/release-{beta,preview,stable}.yml: pass the new secrets
  through so all three release channels symbolicate stacks.

When the API key is missing (PR builds, forks, local contributor builds),
the helper logs and skips the upload — but still strips .map files. The
strip step is unconditional because shipping a sourcemap is equivalent to
shipping the source.

Adds tools/pack/tests/web-sourcemaps.test.ts covering: missing chunks dir
silently noop, no-map noop, strip-only path when credentials are absent,
recursive walker for nested subdirectories. CLI happy path is left to the
release workflow itself.

Required follow-up (cannot push from code): add a repo secret named
POSTHOG_CLI_API_KEY (the phx_ personal API key) and a repo var named
POSTHOG_CLI_PROJECT_ID (the numeric project id, 420348 for our project)
in nexu-io/open-design settings before merging.

* fix(web-sourcemaps): use management host for CLI, not ingest host

POSTHOG_HOST is the ingest URL (us.i.posthog.com) used by the runtime SDK
to POST events to /capture/. The @posthog/cli sourcemap upload talks to
the **management** API (us.posthog.com) and gets a 404 on the ingest
host. The two are not interchangeable.

Adds a separate `posthogCliHost` field on ToolPackConfig sourced from
POSTHOG_CLI_HOST (with no fallback to POSTHOG_HOST). When the env is
unset the @posthog/cli defaults to the US Cloud app host on its own,
which is correct for our project — so this PR doesn't need a new repo
variable for it.

---------

Co-authored-by: Nicholas-Xiong <2482929840@qq.com>
2026-05-21 11:48:57 +08:00
PerishFireandGitHub 33b20aa9cb Fix Windows reinstall detection and duplicate plugin nav (#2466)
* Fix Windows silent reinstall detection

* Remove duplicate entry plugins nav
2026-05-20 22:08:44 +08:00
PerishFireandGitHub 899c9fe4d8 Support nightly and preview package identity (#2437) 2026-05-20 19:46:39 +08:00
PerishFireandGitHub 15d08d4158 feat: add windows packaged auto update flow (#2362) 2026-05-20 12:56:14 +08:00
lefarcenandGitHub 80d305858b feat(diagnostics): add one-click log export from Settings → About (#798)
* feat(diagnostics): add one-click log export from Settings → About

Adds a new "Export diagnostics" entry under the About section that bundles
daemon/web/desktop logs, machine info, and recent macOS crash reports into
a zip the user can share when reporting issues.

- Browser hits a new daemon HTTP endpoint and triggers a download.
- Electron uses an IPC bridge with the native save dialog and reveals the
  saved file in Finder/Explorer; the Help menu also exposes it as a
  fallback when the daemon is unresponsive.

Packaging + redaction lives in a new @open-design/diagnostics package so
both surfaces share it. Sensitive JSON keys, URL query secrets, and the
current user's home path are redacted before packaging.

* build(nix): include packages/diagnostics in daemon build targets

The Nix daemon derivation builds workspace siblings in dependency order
before compiling apps/daemon. Without @open-design/diagnostics in that
list, the daemon TypeScript build fails inside the Nix sandbox with
`Cannot find module '@open-design/diagnostics'` because pnpm install
only creates the symlink — the dist output that the package.json
exports point at isn't produced until each sibling's build script runs.

* build(tools-pack): include @open-design/diagnostics in packaged INTERNAL_PACKAGES

Without this, packaged win/mac/linux builds fail with `npm error 404` when
the post-build `npm install --omit=dev --no-package-lock` step in the
assembled app tries to resolve `@open-design/diagnostics@0.2.0` from the
public npm registry. The package is workspace-private, so it has to be
tarballed via `pnpm pack` and file:-referenced from the assembled
package.json like every other internal workspace dep that daemon/desktop
depend on.

Also wires the package's `pnpm --filter ... build` into the pre-pack
workspace build step so the dist/ exists before pnpm pack runs, and
updates the two test fixtures (`win-app.test.ts`, `workspace-build.test.ts`)
that mirror INTERNAL_PACKAGES.

The diagnostics package itself is repinned to exact dependency versions
already used elsewhere in the workspace (`jszip 3.10.1`, `@types/node
20.19.39`, `esbuild 0.28.0`, `typescript 5.9.3`, `vitest 4.1.6`) so it
passes the new `pnpm guard` exact-version rule and produces a minimal
lockfile diff vs main (additions only, no resolution-string churn).

* fix(diagnostics): include `~` in bearer-token redaction char class

RFC 6750 token68 syntax allows `~`, so tokens like `Authorization: Bearer
abcd~efgh` were only partially matched by `HTTP_AUTH_SCHEME_RE`. The
regex stopped at the first `~`, leaving the tail (`~efgh`) un-redacted in
the exported diagnostics zip — a clear leak since this feature explicitly
generates support bundles for external sharing.

Add `~` to the character class and a regression test.

* fix(diagnostics): only collect renderer.log from desktop

`buildSidecarLogSources` unconditionally added `logs/${app}/renderer.log`
for daemon/web/desktop, but only the desktop runtime writes a renderer
log (see apps/desktop/src/main/runtime.ts) — daemon and web are pure
Node services with no Electron renderer. Every export therefore produced
missing-file placeholders and manifest warnings for the two phantom
paths, polluting the bundle.

Gate the renderer.log source on APP_KEYS.DESKTOP so the daemon-side
collector matches the desktop-side collector in apps/desktop/src/main/
diagnostics.ts:63.

* fix(diagnostics): mirror desktop-side renderer.log gate

The previous fix only updated the daemon-side `buildSidecarLogSources`
in `apps/daemon/src/diagnostics-export.ts`. The desktop-side collector
at `apps/desktop/src/main/diagnostics.ts` had an identical copy of the
same bug that I overlooked: it also unconditionally added
`logs/${appKey}/renderer.log` for daemon/web/desktop, producing
missing-file placeholders + manifest warnings for the two phantom paths
on every desktop-initiated export.

Apply the same `appKey === APP_KEYS.DESKTOP` gate here so both export
entry points (browser via daemon HTTP, Electron via native save dialog)
emit the same clean manifest.

* feat(diagnostics): add `od diagnostics export` CLI subcommand

AGENTS.md's dual-track capability-exposure contract requires every
user-facing feature to ship on both the web UI and the `od` CLI. The
diagnostics export was only reachable through Settings → About and the
desktop Help menu; this commit closes the loop with an `od diagnostics
export [<path>] [--json]` subcommand registered in SUBCOMMAND_MAP.

The CLI is a thin shell over the existing GET /api/diagnostics/export
endpoint — same zip output, same redaction, same crash-report scope.
Defaults to writing `open-design-diagnostics-<timestamp>.zip` in the
current directory; `--output <path>` or a positional arg overrides.
`--json` prints `{path, sizeBytes}` for shell pipelines.

Use cases this unlocks:
- A CI script can `od diagnostics export ~/artifacts/bundle.zip` after
  a failed run.
- Bug reporters on headless boxes can grab a bundle without booting
  the web UI.
- `od doctor` follow-ups can collect a full snapshot when a probe fails.

* fix(diagnostics): surface non-sidecar launch in manifest warnings

`buildSidecarLogSources()` returns `[]` when the daemon has no sidecar
runtime context, which is the standard `od` (plain) launch path —
`runDaemonCliStartup()` -> `startDaemonRuntime()` does not pass a
runtime. Settings → About and the new `od diagnostics export` previously
reported success but produced a bundle with only the summary JSONs, so
operators could not tell "no logs because plain launch" from "no logs
because something genuinely broke."

- Extend `DiagnosticsContext` with an optional upstream `warnings:
  string[]` that `buildManifest` merges into the manifest warnings.
- Emit STANDALONE_LAUNCH_WARNING from the daemon handler when
  `options.runtime == null`. The warning names the limitation and
  points the user at the sidecar entry points that DO capture logs.
- Add a regression spec at `apps/daemon/tests/diagnostics-export.test.ts`
  that drives the handler with `runtime: null` and asserts the warning
  surfaces in `summary/manifest.json` (and that `files` is empty so a
  user reading the bundle does not confuse "no log sources" with
  "missing files").
2026-05-20 09:10:51 +08:00
PerishFireandGitHub 2c128e0e91 refactor desktop host bridge (#2246) 2026-05-19 18:27:05 +08:00
lefarcen 22a3b99a47 Merge origin/main into preview/v0.8.0
Sync 49 commits from main. Conflicts resolved:
- .github/workflows/ci.yml: kept v0.8.0 granular per-area gating, added main's
  linux specs + release-stable.yml + release-preview.yml triggers
- .github/workflows/release-preview.yml: kept v0.8.0's full workflow over main's placeholder
- apps/web/src/components/AssistantMessage.tsx: combined v0.8.0 file-ops
  summary with main's stripTodoToolGroups + suppressAskUserQuestionFallbackText
- apps/web/src/components/ChatPane.tsx: kept both new imports
- apps/web/src/index.css: kept both .msg-plugin-chip and .user-copy-btn blocks
- e2e/ui/*.test.ts: kept v0.8.0 openEntrySettingsDialog helper over main's
  inline dialog navigation (UI was redesigned in v0.8.0)
- nix/package-{daemon,web}.nix: kept v0.8.0 pnpmDepsHash; rerun nix build to refresh
2026-05-15 18:23:33 +08:00
Olin HendershotandGitHub 74637f1cb5 Add Linux packaged client parity smoke coverage (#1204)
* docs: plan linux client issue 709

* fix: complete linux headless lifecycle routing

* feat: add linux packaged inspect

* test: add linux headless packaged smoke

* ci: add linux headless packaged smoke

* ci: smoke linux AppImage release artifacts

* docs: document linux packaged client status

* chore: finalize linux client audit remediation

* docs: add linux client publication packet

* test: harden linux client smoke coverage

* ci: preserve linux smoke audit evidence

* refactor: consolidate linux e2e helpers

Move pathExists and the desktop/web/daemon app-key array out of
linux.spec.ts into linux-helpers.ts, where expectPathInside and
linuxUserHome already live. Keeps the spec file focused on tests and
the helpers file as the canonical home for shared Linux e2e utilities.

* fix: move linux e2e helpers to lib

* fix: address linux release review blockers

* fix: drop npm dependency from containerized linux build

writeAssembledApp() previously called runNpmInstall() which executed
`npm install` directly. Inside the containerized build path,
electronuserland/builder:base strips npm/npx/corepack, so the inner
tools-pack build would fail at the assembled-app install step.

Route the install through OD_TOOLS_PACK_PNPM_BIN: buildDockerArgs sets
the env to the standalone pnpm binary it bootstraps, and the new
resolveProductionInstallCommand helper consumes that env to run
`<bin> install --prod --no-lockfile --config.node-linker=hoisted`.
Host invocations with no env set keep the prior npm behavior.
--config.node-linker=hoisted preserves the flat node_modules layout
that electron-builder packs the same way as npm-installed trees.

New tests cover the resolver branches and assert the docker-arg-to-
resolver chain end-to-end so reviewers can see the container's inner
build receives the env that switches its install away from npm.

* fix: harden linux container bootstrap

* fix: validate desktop marker liveness in headless cleanup

cleanup --headless previously skipped on any parseable desktop-root.json, trapping recovery when the AppImage had crashed and left a stale marker. Validate the marker the same way stopPackedLinuxApp does: if the PID is not in the live snapshot list, proceed through cleanup instead of skipping.

Extract the validation into validateDesktopAppImageMarker so the stop and cleanup paths share one definition of live and owned. Tests cover both branches: a stale marker drives cleanup to remove the runtime/output roots, while a live marker drives cleanup to skip and preserve them.
2026-05-15 16:38:29 +08:00
PerishCode 545aed642e Merge remote-tracking branch 'origin/preview/0.8.0' into preview/v0.8.0
# Conflicts:
#	nix/package-daemon.nix
#	scripts/postinstall.mjs
2026-05-14 21:46:05 +08:00
PerishCode 883598f556 Build registry protocol in packaged workspaces 2026-05-14 21:23:45 +08:00
Tom HuangandGitHub 76defffb93 Garnet hemisphere (#1702)
* feat(chat-composer): enhance mention handling and input overlay

- Introduced a new overlay for inline mentions in the chat composer, improving user experience by visually indicating mentions as users type.
- Updated the `ChatComposer` component to manage mention entities and integrate them into the input field, allowing for better context and interaction.
- Enhanced the `AssistantMessage` component to support the display of plugin action panels based on the current project context, facilitating easier plugin management.
- Refactored related components to ensure consistent handling of project files and mentions across the application.

This update significantly improves the chat interaction model, making it more intuitive for users to engage with mentions and plugins.

* feat(plugin-management): enhance plugin action panels and UI components

- Updated the `AssistantMessage` component to include plugin action panels based on the latest project context, improving user interaction with generated plugins.
- Refactored the `PluginsView` to support detailed views for available marketplace entries, allowing users to access more information and actions for each plugin.
- Introduced new CSS styles for improved visual representation of plugin-related UI elements, enhancing overall user experience.
- Enhanced the `listPlugins` function to include an option for fetching hidden plugins, providing more flexibility in plugin management.

This update significantly improves the usability and functionality of the plugin management system, making it easier for users to interact with and manage their plugins.

* fix(assistant-message): refine plugin folder candidate selection logic

- Updated the `pluginFoldersTouchedThisTurn` function to improve the logic for selecting plugin folder candidates based on touched paths and message content.
- Introduced a new helper function, `pathMatchesFolderFileBasename`, to enhance the matching criteria for folder candidates.
- Added a check for explicit folder matches before falling back to a single candidate, improving accuracy in folder selection.
- Modified the `shouldRenderSlotAsText` function in `HomeHero` to include the name parameter, refining the rendering logic for slot text.

These changes enhance the functionality and reliability of the assistant message component in managing plugin folder candidates.

* feat(plugin-folder-actions): implement agent-routed CLI actions for plugin management

- Introduced a new `PluginFolderAgentAction` type to streamline actions related to plugin folders, including install, publish, and contribute.
- Updated the `DesignFilesPanel`, `FileWorkspace`, and `AssistantMessage` components to utilize the new agent action handling, improving user interaction with generated plugins.
- Refactored the action handling logic to send commands to the agent, enhancing the workflow for managing plugin folders.
- Added corresponding tests to ensure the new functionality works as expected and integrates seamlessly with existing components.

This update significantly enhances the plugin management experience by routing actions through the agent, allowing for a more cohesive and interactive user experience.

* Fix PR 1702 CI blockers

* Fix PR 1702 remaining CI checks

* Prebuild AGUI adapter after install

* Restore plugin project snapshot wiring

* feat(marketplace): refactor marketplace URL handling and enhance fetching logic

- Introduced new functions to normalize marketplace URLs and manage fetching of marketplace manifests, improving the reliability of marketplace integrations.
- Updated the server and plugin logic to utilize the new fetching mechanisms, ensuring consistent handling of marketplace data.
- Enhanced tests to cover new URL normalization and fetching scenarios, ensuring robustness in marketplace management.

This update significantly improves the marketplace experience by streamlining URL handling and enhancing data fetching capabilities.

* Fix project auto-send cleanup spec
2026-05-14 21:12:50 +08:00
PerishCode 4f15c33595 Merge remote-tracking branch 'origin/preview/0.8.0' into preview/v0.8.0 2026-05-14 21:10:03 +08:00
PerishCode 9ef4b1c048 Use channel mac executable identity 2026-05-14 19:35:51 +08:00
PerishCode 43b1b94c8e Add preview release channel 2026-05-14 19:15:16 +08:00
PerishCode e0c76a09f2 Fix packaged beta build resources 2026-05-14 18:01:52 +08:00
PerishCode cba8bf151d chore: align namespace lifecycle packaging 2026-05-14 16:35:46 +08:00
lefarcen b268bbe169 Merge origin/garnet-hemisphere (post-9e196d34) — Use Plugin handoff fix
Brings in 11 new garnet commits, most importantly:
- 1a90aef4 feat(plugin-use): implement plugin use handoff functionality —
  fixes the bug QA reported where /plugins Use Plugin would 422 silently
  for template plugins; new flow hands off to HomeView with the plugin
  pre-bound + input form prompted there.
- 2ac58544 feat(plugin-inputs): enhance plugin input handling with file
  upload support — extends PluginInputsForm for file uploads.
- 3b167b69 feat(plugins): registry protocol — new @open-design/registry-protocol
  workspace package (needs build before daemon boot).
- Plus enhancements to plugin metadata, GitHub installer, plugin detail
  view, login/whoami, static HTML preview paths.

Conflicts resolved:
- packages/contracts/src/api/projects.ts: HEAD's skipDiscoveryBrief
  field + garnet's contextPlugins (@-mention plugin context refs) both
  kept on ProjectMetadata.
- apps/landing-page/* (3 files): accepted HEAD — garnet had the older
  single-page landing-page header; main has the multi-page layout
  (/skills/, /systems/, /templates/, /craft/) with dynamic counts. Not
  related to the Use Plugin core fix.

New @open-design/registry-protocol package must be built before daemon
boots; pnpm install does this via postinstall already.
2026-05-14 16:32:35 +08:00
pftom 2ac5854432 feat(plugin-inputs): enhance plugin input handling with file upload support
- Added support for file input fields in the PluginInputsForm, allowing users to upload files with serializable metadata.
- Updated the HomeHero component to improve the layout and interaction of input fields, enhancing user experience.
- Adjusted CSS styles for better visual representation of input fields and their states.
- Modified HomeView to reflect changes in authoring chip IDs for better clarity in plugin actions.
- Enhanced tests to cover new file input functionality and ensure correct behavior in various scenarios.

This update significantly improves the plugin input handling, enabling users to upload files seamlessly and enhancing the overall interaction model.
2026-05-14 15:52:21 +08:00
shangxinyu1andGitHub 2976c76fc3 test: expand Memory and Routines coverage (#1521)
* test: expand settings and packaged coverage

* test: extend memory settings coverage

* test: cover routine settings failure states

* test: cover routine operation failures

* test: fix daemon test typing on CI

* test: decouple packaged smoke from orbit bug

* test: avoid live memory LLM calls in route tests

* test: fix daemon fetch typing in CI

* fix: restore preview comment and inspect toggles

* test: align manual edit flow with current inspector UX

* test: align comment attachment flow with current preview comments UI

* fix: probe resolved Codex launch path during detection

* fix: remove duplicate board activation helper after rebase

* test: update ghost cli detection mock

* test: align FileViewer toolbar expectation

* ci: move full app tests to extended lane

* ci: run app tests by changed scope

* ci: cover shared app inputs in test scopes

* ci: avoid setup-node cache in windows packaged smoke

* test: align extended settings and manual edit flows
2026-05-14 14:48:40 +08:00
PerishFireandGitHub 59ed000903 Fix Windows resource cache for Orbit templates (#1554) 2026-05-14 14:27:29 +08:00
lefarcen 5172e37217 Merge origin/main into release/v0.7.0 to prepare merge-back PR
Resolves 7 conflicts via hybrid strategy:
- apps/web/src/components/EntryView.tsx: take main (Discord+X pills are forward feature)
- apps/web/src/components/Icon.tsx: take main (switch-case refactor)
- apps/web/src/components/NewProjectPanel.tsx: take release (preserve #1514 dropdown UX validated in 0.7.0 acceptance)
- apps/web/src/index.css: take main (project-target-platforms / instructions chip styles)
- apps/web/tests/components/FileViewer.inspect-empty-hint.test.tsx: accept main's deletion
- nix/package-daemon.nix, nix/package-web.nix: take main pnpmDepsHash

Non-conflicting hunks from #1519 (AppChromeHeader), #1428 (PostHog analytics
call sites), and #1540 (release light background) are preserved via auto-merge.
2026-05-13 18:19:47 +08:00
PerishFireandGitHub 61163d6b92 Optimize Windows packaged prebundle flow (#1389) 2026-05-12 12:07:32 -04:00