mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
chore/plugin-previews
96
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
20a077f66c |
fix(desktop): avoid native file dialog AppHang on Windows Downloads (#5314)
Native Windows file dialogs enumerate the shown folder synchronously via the shell. On OneDrive-backed Downloads folders that stalls the dialog's UI thread long enough for Windows to flag the app 'not responding' (AppHangB1, reported against 0.14.0.0). The diagnostics Save dialog also forced its defaultPath into the Downloads folder, opening the dialog directly in that slow location. Seed the diagnostics Save dialog with a bare filename (matching the PDF/PPTX flows) and add dontAddToRecent to every native Save/Open dialog to trim shell recent-items work against the browsed folder. Adds a regression test locking the filename-only + dontAddToRecent behavior. |
||
|
|
39c8eea0c5 |
test: cover recent daemon and e2e follow-ups (#5354)
* test(e2e): cover recent main follow-ups * test: cover recent main automation gaps |
||
|
|
87a6a1a71a |
feat(desktop): observe abnormal exits (runtime 闪退) + child-process crashes (#5333)
* feat(desktop): observe abnormal exits (runtime 闪退) + child-process crashes The crash telemetry only covered renderer crashes (desktop_renderer_crash) and startup failures (packaged_runtime_failed). The most common "闪退" a user reports — the app was running fine, then the whole window vanished — is a MAIN-process crash / OS kill / force-quit after a hang, and none of it reaches analytics: a dead process can't report itself, and uncaughtException/unresponsive were log-only. Add abnormal-exit detection via a session marker: beginDesktopSession stamps a `clean: false` marker at launch; a graceful shutdown flips it clean. If the next launch finds the previous marker still dirty, the previous run died without a graceful quit — emit `desktop_unclean_exit` (previous version/session/started-at) once the daemon is up to relay it. This catches the whole class regardless of cause. Also emit `desktop_child_process_crash` on `child-process-gone` (GPU / utility crashes — a common cause of a window that then goes blank). Both events ride the existing free-form POST /api/observability/event (no contract change), same as desktop_renderer_crash. The lifecycle logic is a pure, unit-tested module; all fs/telemetry is best-effort and can never block startup or shutdown. * fix(desktop): only report unclean exit for runs that reached running Review (nettee): stamping the dirty marker at startup meant a bootstrap failure AFTER beginDesktopSession but before the app was up would leave the marker dirty and be reported as desktop_unclean_exit next launch — but that's a startup failure (already covered by packaged_runtime_failed), not a 'while running' crash. Add a reachedRunning flag: the marker is written not-yet-running at startup, markDesktopSessionRunning flips it true only once the runtime is up (after onDesktopReady), and a previous run is reported ONLY when it reached running and never went clean. This also makes each previous state reported at most once (begin overwrites the marker). * fix(desktop): mark session clean at end of shutdown, not start Review (nettee): endDesktopSessionCleanly ran at the very start of shutdown(), before the awaited teardown (beforeShutdown / IPC close / desktop.close / app.quit). A quit that then hangs in one of those steps and is force-quit or OS-killed would already have flipped the marker clean, hiding that abnormal exit. Move the clean-mark to just before app.quit(), after teardown completes. * fix(desktop): mark session running on actual reveal, not runtime creation Review (nettee): createDesktopRuntime() starts async bootstrap with void tick() and returns before the first window load/reveal, so calling markDesktopSessionRunning right after it fired too early — a crash during the first load would be mislabeled a runtime exit. Add an onRevealed callback fired from revealMainWindow (the real 'app is up' moment: web app mounted + window shown) and mark running from there instead. * fix(desktop): persist unclean-exit until its report is acked Review (nettee): beginDesktopSession overwrote the marker (dropping the crash from disk) while the crash was only held in memory and reported via a fire-and-forget POST that swallows failures — so a report that fails (daemon not reachable yet, transient network) lost the signal permanently. Carry the crash forward in the marker's unreportedCrash field; report returns whether it was acked; clearReportedCrash removes it only on success, so a failed report retries next launch. markRunning / clean preserve the carried-forward crash. * fix(desktop): queue unreported crashes so consecutive failures lose none Review (nettee): with a single carried-forward slot, a launch that is only retrying crash A and then itself crashes replaced A with B on the next launch, losing A. Carry a bounded queue (unreportedCrashes[], cap 20, oldest dropped) instead: begin appends the previous run's crash to the carried list, each is reported and cleared independently by sessionId, so a consecutive crash+failed- report sequence keeps every crash. markRunning/clean preserve the queue. |
||
|
|
e4e127d058 |
fix(desktop): recover from first-launch document-level 502 (#5186)
On initial packaged desktop launch the window could be covered by a raw 502 page near the end of the splash. Two root causes: 1. The od:// protocol handler proxied the top navigation to the web sidecar with a single fetch; a transient undici socket throw (setTypeOfService EINVAL, #895) on that one attempt became the 502 document the window rendered. 2. An HTTP 502 response with a body is a successful load to Electron (did-fail-load only fires for net::ERR_*), so the existing renderer- recovery loop never retried it and the splash revealed the raw 502 after its 15s ceiling. Fixes: - protocol.ts: retry idempotent (GET/HEAD) od:// fetches up to 3x with short backoff before surfacing the 502; resolved responses (incl. upstream 5xx) and non-idempotent methods keep single-attempt semantics. - runtime.ts: route 5xx main-frame documents from did-navigate into the markRendererFailed reload path, clear rendererFailed before awaiting loadURL (so a mid-load did-navigate flag survives), and re-poll a still- failed renderer at the prompt cadence. The window now self-heals without a manual reload. Red specs added first (protocol retry contract; 5xx-document recovery wiring), verified red on main and green on this branch. Co-authored-by: 박세원 <s-w.park@samsung.com> |
||
|
|
5b5cc6b89c |
feat(updater): route to installer when a release outgrows this build (#5235)
Add the installed-base escape hatch: the desktop updater now refuses an in-place payload update and routes to the full installer when a release declares capabilities beyond what this build can adopt. Two orthogonal, fail-open guardrails read from the release feed: - launcher.schema (ABI axis): feed.launcher.schema > LAUNCHER_SCHEMA_VERSION means this build cannot interpret the launcher contract the payload expects. - control.launcher.version.min (recency axis): min > currentVersion means the release requires a newer launcher/build. Either trip forces preferPayload=false, reusing the existing installer path; missing/malformed fields are ignored so older feeds keep updating seamlessly. This lets already-shipped builds self-route to a reinstall once a future launcher-based release lands, instead of attempting an update they cannot run. remoteRequiresReinstall is a pure, exported function; the same schema floor will later be enforced locally by the Go launcher against on-disk manifests. The tools-serve updater fixture can now emit both fields for verification. |
||
|
|
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>
|
||
|
|
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 ( |
||
|
|
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> |
||
|
|
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> |
||
|
|
ecb1f3ac00 | fix(desktop): treat payload updates as in-app installs (#4471) | ||
|
|
8ff19c6b41 |
[codex] Add main-risk smoke and E2E coverage (#4322)
* test(e2e): cover daemon reload artifact recovery * test(e2e): expand critical flow coverage * test(daemon): add startup and recovery smoke coverage * test(e2e): add visual and desktop chrome smoke coverage * ci: gate packaged onboarding smoke to nightly * fix(web): repair artifact replay type narrowing Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): repair preflight smoke coverage checks Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): tighten artifact recovery polling Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): keep error recovery retries alive Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): relax brittle UI P0 assertions Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): recover pointer artifact outputs Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): stabilize manual edit reload smoke Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): avoid noisy win onboarding cleanup Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): stop failed artifact recovery polling Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(daemon): isolate startup smoke data root Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): recover standalone html artifacts Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): guard pointer artifact recovery by run time Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): narrow strict visual gate to critical smoke Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): limit PR visual capture to critical cases * fix(ci): keep PR visual capture complete * fix(web): keep reload artifact recovery retryable * fix(ci): clarify PR visual gate layering * fix(e2e): stabilize visual home P2 capture * fix(e2e): align visual gallery detail clicks * fix(e2e): preserve packaged install during onboarding cleanup * fix(ci): shard PR visual capture * fix(ci): remove duplicate nix validation from ci * fix(ci): keep visual capture screenshots flat * fix(ci): point nix hash autofix at ci-nix * fix(ci): stabilize PR P0 recovery gate * fix(web): ignore stale artifact pointers during recovery * fix(web): reset BYOK model after clearing key * ci: run critical UI P0 subset on PRs * ci: combine PR UI P0 critical shards * ci: combine PR visual capture shards * ci: preserve visual capture shard manifests * fix(web): clear ProjectView delayed timers on unmount * test(e2e): harden onboarding connect gate coverage * test(e2e): align AMR auth recovery and visual catalog timeout |
||
|
|
811ad9d118 |
Show boot stage progress on the desktop splash screen (#4223)
* Show boot stage progress on the desktop splash screen The splash video plays ~1.7s and then freezes on its final frame while the packaged cold boot (daemon, web sidecar, Next.js standalone server, web mount) can take many seconds on slow machines, reading as a hang. Add a muted status line under the logo with continuously pulsing dots and stage labels that advance through the real boot phases: - Starting Open Design (baked into the splash HTML) - Starting the local engine (daemon sidecar spawn) - Preparing the interface (web sidecar spawn) - Opening your workspace (hidden main window loading + mount gate) Stage transitions follow the repo animation philosophy (140ms ease-out exit, 200ms ease-out enter). The packaged entry forwards phases through a new optional onPhase hook on startPackagedSidecars; the runtime re-asserts the final stage at its reveal gate so the tools-dev splash path is covered too. setSplashStage is safe on destroyed windows and idempotent per label. https://claude.ai/code/session_01MHt6dRc7GMAcu9Npo5Bgdx * Guard splash stage updates against the page-load race The first boot-stage update (the daemon phase) is fired from the packaged entry immediately after the splash window is created, which can be before the splash data-URL has finished loading and defined window.__odSplashSetStage. The previous setSplashStage ran the update unconditionally, so on a fast-spawning cold boot the renderer had no setter yet and the 'Starting the local engine' label was silently dropped — on exactly the slow-PC path this feature targets. Track per-splash load readiness: registerSplashStageTracking arms a did-finish-load listener before loadURL, and stage updates that arrive before load are stashed (latest wins) and replayed once the page reports it has loaded. Updates after load apply immediately as before. setSplashStage / registerSplashStageTracking now take a narrow SplashStageSurface so the replay logic is unit-testable without a live Electron renderer; BrowserWindow satisfies it structurally. Red spec: tests/main/splash-stage-replay.test.ts — fails when the deferral is removed (update fires before load), green with the guard. https://claude.ai/code/session_01MHt6dRc7GMAcu9Npo5Bgdx * desktop(splash): granular boot stages with step counter and progress bar The first-run cold boot on a slow machine sat on the brand splash with only four coarse status labels and no sense of how far along it was, so the wait read as a possible hang. Make the boot progress finer-grained and quantified: - Expand the splash stage set from 4 to 7, marking both the "starting X" and "X ready" edges of the two long native waits (local engine online, web interface online) plus a final "Almost ready" step, so the status visibly advances right after each long wait clears. - Show a "N/total" step counter and a thin progress bar under the logo that fills as stages advance; the bar is monotonic so an idempotent re-asserted stage cannot make it jump backwards. - Emit the daemon-ready / web-ready edges from the packaged sidecar bring-up via the onPhase hook so the new stages map to real boot transitions, and assert the final "finishing" stage once the web bundle reports it mounted. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: 박세원 <s-w.park@samsung.com> |
||
|
|
935034cc20 |
fix(diagnostics): desktop export delegates to daemon so run/AMR logs are captured (#4405)
The desktop "Export diagnostics" path rebuilt the support bundle in the Electron main process and had to GUESS the daemon data dir from the sidecar namespace layout (`<namespaceRoot>/data`). That guess is wrong whenever the daemon's real RUNTIME_DATA_DIR differs: in tools-dev it is `<projectRoot>/.od` (OD_DATA_DIR unset), and under any OD_DATA_DIR override it is elsewhere. The exporter then swept the wrong `runs/` and `amr/opencode-home` trees and silently dropped the per-run `events.jsonl` (which already records every agent stderr chunk, tool call, and error) and the AMR OpenCode logs — the exact data needed to explain a stuck/looping run that produced nothing. The daemon already exposes a complete, correctly-pathed bundle at `GET /api/diagnostics/export` (it knows its own RUNTIME_DATA_DIR and collects run events, agent CLI logs, sidecar daemon/web/desktop logs incl. renderer.log, and host crash reports). So delegate to it instead of duplicating the assembly: - desktop: new electron-free `diagnostics-fetch.ts` fetches the daemon bundle; `diagnostics.ts` keeps only the save dialog + reveal and saves those bytes. Removes the data-dir guessing entirely (net -141 lines in diagnostics.ts). - index.ts: share one `resolveDaemonBaseUrl(runtime, options)` (daemon URL → web URL → sidecar web discovery) across app-config menu actions and the diagnostics IPC, so the export targets the same daemon as everything else. - daemon: emit a manifest warning when `runsDir` is set but no per-run event logs were found, so an empty bundle reads as "collection gap / persistence off" rather than "clean run with nothing to report". Tests: desktop `fetchDiagnosticsBundle` hits the daemon export endpoint and surfaces non-200 as an error; daemon export warns on an empty runs dir. Both red before the change. |
||
|
|
fbc5fe3458 |
fix(desktop): scope window-chrome spec matching to the main app window block (#4216)
* fix(desktop): scope window-chrome spec matching to the main app window block The backgroundThrottling and autoHideMenuBar specs extracted the BrowserWindow options with a non-greedy whole-source regex anchored on title: "Open Design" / width: 1280. Since the packaged brand splash window (createSplashWindow) shares both markers and appears earlier in runtime.ts, the regex locked onto the splash block: the backgroundThrottling spec failed (splash intentionally has no backgroundThrottling: false) and the autoHideMenuBar spec passed only coincidentally against the wrong window. Split the source into per-constructor option blocks and select the main app window by the marker pair unique to it (app title + preload), asserting exactly one match so a future fourth window fails loudly instead of being checked ambiguously. https://claude.ai/code/session_016MqkcZLEz3NzgNKgU2ARLd * test(desktop): anchor window-chrome selector on the unique main-window declaration Adopt the clearer anchor from the upstream fix: select the main app window by the `const window = new BrowserWindow({` declaration, which is unique to it, instead of the indirect title + preload marker pair. Keep the exactly-one-match assertion so a rename or a duplicate declaration fails loudly rather than silently inspecting the wrong window. https://claude.ai/code/session_016MqkcZLEz3NzgNKgU2ARLd --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a1742e2fa9 |
fix(desktop): recover the window when the renderer dies or a load fails (#4179)
* fix(desktop): recover the window when the renderer dies or a load fails The main window's poll loader only calls loadURL when the discovered web URL changes, so a renderer that dies after a successful load is never reloaded. The render-process-gone handler only reported the crash to analytics, and there was no did-fail-load handler, so a failed main-frame load left the renderer parked on chrome-error:// (blank white) with no retry. That is the blank window after minimizing and restoring the app: the OS reclaims the backgrounded renderer or its connection drops, and nothing brings it back. Add a rendererFailed flag set by did-fail-load (ignoring -3 aborts) and by a non-clean render-process-gone, and have the poll loop reload the current URL while the flag is set, clearing it on a successful load. When the web server is still unreachable, discovery returns null and the loop backs off to its normal interval until it returns. Verified in the running app: forcing the renderer to a refused port parks it on chrome-error, then the next poll reloads it back to the mounted app. * fix(desktop): keep a single poll loop when a renderer load fails mid-tick markRendererFailed scheduled its own prompt retry, but a loadURL that rejects inside tick fires did-fail-load AND falls into tick's catch, so both queued a poll timer and the loop forked into two (multiplying on each repeated failure). Track whether a tick is in flight and have the failure handler only flip the rendererFailed flag while one is running, leaving tick as the sole rescheduler. An idle renderer death still reschedules promptly. Adds a source-assertion regression test in the window-chrome.test.ts style, since the loop lives inside createDesktopRuntime and has no behavioral seam under vitest. Addresses review feedback on #4179. |
||
|
|
7e3b8a3b32 |
Fix blank/clipped desktop PDF export by sizing the page to artifact content (#4067) (#4086)
* Fix blank/clipped desktop PDF export by sizing the page to artifact content (#4067) The desktop PDF export wraps the artifact in a sandboxed-preview iframe (sandbox="allow-scripts", no allow-same-origin) inside an overflow:hidden wrapper. inferPageSize() measured the wrapper document, which is pinned to the window viewport, so taller artifacts were clipped and content sitting below the fold exported as a blank white page. The wrapper can't read the cross-origin iframe, so the artifact now reports its own content dimensions through the existing print-ready handshake. The parent caches them as window.__odPrintSize (nonce + source checked, validated as a positive finite size) and inferPageSize() prefers that over measuring the wrapper, falling back to direct measurement for the daemon path that runs no handshake. Purely additive: other export paths never set __odPrintSize and are unchanged. * Reject non-finite reported print sizes in the #4067 handshake guard The __odPrintSize validation accepted any `typeof === 'number'` that was `> 0`, so `Infinity` slipped through (`Infinity > 0` is true) despite the guard's intent — and its comment — being "positive finite numbers". A malformed `{ width: 756, height: Infinity }` then sized the desktop PDF page to a Frankenstein 756x900 (reported width kept, non-finite height dropped to 900) instead of falling back cleanly to direct measurement. Tighten both the parent write guard (apps/web/src/runtime/exports.ts) and the mirrored consumer guard (apps/desktop/src/main/pdf-export.ts) to `Number.isFinite(x) && x > 0`, and add a desktop regression that feeds a non-finite height and asserts the whole reported size is rejected. |
||
|
|
d1bc38478a |
fix(desktop): cap idle updater polling (#4143)
Generated-By: looper 0.9.7 (runner=worker, agent=codex) |
||
|
|
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 |
||
|
|
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> |
||
|
|
a58aafa1e6 |
feat(desktop): add hidden AMR profile menu (#3899)
* 1 * 2 * spec * feat(desktop): add hidden AMR profile menu * fix(desktop): rename AMR profile menu * fix(amr): scope cached models and sync desktop profile changes Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(amr): preserve scoped cache and settings draft Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(amr): reuse scoped models after empty probes Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(web): clear stale AMR cache on profile refresh Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(web): scope AMR recharge links by profile Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(settings): preserve draft env entries on AMR sync Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(web): preserve AMR profile context in side chat Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(web): clear stale AMR selection on profile change Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(daemon): scope AMR connection test model fallback Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(settings): clear stale AMR draft model on profile change Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(amr): align profile switch model resets Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(web): preserve AMR profile links across chat entry points Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(desktop): serialize AMR model clears Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(web): ignore stale AMR poll results after profile changes Generated-By: looper 0.9.6 (runner=fixer, agent=codex) * fix(desktop): preserve AMR model on same-profile selection Generated-By: looper 0.9.6 (runner=fixer, agent=codex) |
||
|
|
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> |
||
|
|
3a21392c46 |
Fix Electron composer lag and stale generation failures (#3593)
* Keep chat typing and web CSS resolution responsive Cache the normalized mention index by entity array and scan only concrete @ positions so the composer does not rescan every file/plugin/skill token on each draft update. Link the shared components package at the root importer so Turbopack/PostCSS can resolve the exported component stylesheet from the web CSS entrypoint. Constraint: Chat composer typing updates run on every keystroke in Electron, and tools-dev/Next evaluates global CSS from the workspace root. Rejected: Debouncing textarea state updates | would make text entry feel stale and would not remove the expensive synchronous parser path. Rejected: Relative-importing package source CSS from apps/web | would bypass the package export contract and hardcode package layout into the app stylesheet. Confidence: high Scope-risk: narrow Directive: Keep inline mention parsing proportional to draft mention markers, and keep package CSS imports resolvable through workspace package links. Tested: pnpm install Tested: pnpm --filter @open-design/web test -- tests/utils/inlineMentions.test.ts Tested: pnpm --filter @open-design/web typecheck Tested: pnpm --filter @open-design/web build Tested: pnpm guard Not-tested: Full web Vitest remains blocked by unrelated CSS selector tests observed before this commit. * Keep composer typing off the render path Move keystroke-time draft updates into the textarea/ref path while deferring React draft rendering, mention overlay work, picker filtering, and localStorage persistence so Electron input echo stays within frame budget. Constraint: Electron chat input was visibly lagging for plain text and mention text while the right iframe stayed responsive. Rejected: Keep the textarea fully controlled | every keystroke still synchronizes the largest ChatComposer render path. Confidence: high Scope-risk: moderate Directive: Keep prompt submission, slash commands, and mention insertion reading draftRef when draft rendering is deferred. Tested: pnpm guard; pnpm --filter @open-design/web typecheck; pnpm --filter @open-design/web build; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.context-pickers.test.tsx; pnpm exec vitest run -c vitest.config.ts tests/utils/inlineMentions.test.ts; Electron inspect eval plain text avg 0.03ms max 0.5ms; Electron inspect eval @ path avg 0.03ms max 0.1ms; HTTP smoke status 200 with no components CSS resolve error. Not-tested: Full web Vitest suite and Playwright UI suite. * Keep Electron chat input responsive under heavy projects Constraint: Electron project views can carry large chat/file DOM and Chromium can throttle or defer visible renderer work under tab/window visibility edge cases. Rejected: Re-rendering the composer draft on every plain keystroke | it couples textarea input to mention overlay, resize, storage, and project layout work. Confidence: medium Scope-risk: moderate Directive: Keep plain text input on the textarea/ref path; only commit React draft state immediately for mention, slash, chip, or imperative draft mutations. Tested: pnpm guard; pnpm --filter @open-design/web typecheck; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx; pnpm --filter @open-design/desktop test; pnpm --filter @open-design/desktop typecheck; desktop inspect actual project composer with OS SendKeys. Not-tested: Push/PR remains blocked by GitHub 403 for mimicryluden on nexu-io/open-design. * Keep heavy project typing isolated from preview layout Constraint: Electron project panes can carry large DOM histories and preview frames that make textarea paint sensitive to parent layout. Rejected: Further debouncing ChatComposer input state | app-side handler costs were already low while native textarea updates still lagged. Confidence: high Scope-risk: moderate Directive: Keep the composer mounted through the fixed layer/slot pair so future layout changes do not pull typing back into the split-pane flex reflow path. Tested: pnpm --filter @open-design/web typecheck; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx tests/components/ChatComposer.context-pickers.test.tsx; pnpm guard; pnpm --filter @open-design/web build; desktop inspect fixed-layer DOM and SendKeys probe avg beforeinput->input 1.4ms max 2.5ms Not-tested: PR publish blocked by GitHub repository permissions if remote still rejects mimicryluden * Keep stale run recovery from hiding generated previews Constraint: Completed daemon runs can outlive the in-memory run registry after a restart. Rejected: Replaying succeeded rows with empty producedFiles | It turns completed messages into failed messages when the registry is gone. Confidence: high Scope-risk: narrow Directive: Only active run statuses should enter daemon reattach recovery. Tested: vitest generation-preview; vitest ProjectView reattach restore; web typecheck; pnpm guard; web build; desktop DOM eval failureText=false iframeCount=2 Not-tested: Fresh provider generation against an external model * Keep composer contracts aligned with latest main Rebase conflict resolution restored the current composer contract while leaving the lower-risk latency fixes in separate layers. Constraint: The branch was rebased over newer workspace/session context composer contracts. Rejected: Keeping the older composer conflict side because it removed current ChatComposer props and broke web typecheck. Confidence: high Scope-risk: narrow Directive: Preserve upstream ChatComposer contracts when rebasing input-latency work. Tested: web typecheck; targeted ChatComposer, inlineMentions, ProjectView generation-preview tests; desktop window-chrome test Not-tested: Full browser interaction after fork push * Fix daemon Nix workspace symlink pruning --------- Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
fd1c0ecb93 |
Feat/optimize style (#3624)
* Refactor ChatComposer and DesignBrowserPanel components for improved functionality and localization - Introduced new functions in ChatComposer for managing attachment orders and appending staged attachments, enhancing user experience during file uploads. - Updated ChatPane to clarify the search button's aria-label for better accessibility. - Enhanced DesignBrowserPanel with new localization keys for browser use inputs and actions, improving clarity across multiple languages. - Adjusted styles in various components for better layout and user interaction, including updates to the chat input and staged context elements. * Update AvatarMenu and Chat components for improved functionality and localization - Modified AvatarMenu to include an optional section parameter for the onOpenSettings callback, enhancing flexibility in opening settings. - Updated tests for AvatarMenu to verify the correct section is passed when opening settings. - Adjusted styles in various components, including the ChatComposer and FileViewer, to improve layout and user interaction. - Enhanced localization strings across multiple languages to reflect changes in chat mode terminology from 'Chat' to 'Ask', improving clarity for users. |
||
|
|
18169ee52a |
Improve export fidelity contract for editable PPTX and PDF readiness (#3628)
* Improve export fidelity contract * Improve export fidelity contract * Improve export fidelity contract * Improve export fidelity contract * Wait for CSS images in desktop print handshake * Wait for CSS images in desktop print handshake |
||
|
|
6408937564 |
feat(web): redesign Design Files panel + pick working directory from Home (#3358)
* feat(web): align Design Files panel with Claude reference UI Redesign the Design Files panel into a continuous grouped table that mirrors Claude's own Design Files surface: uppercase gray section bands (Folders / Stylesheets / Scripts / Documents / Images …), flush full-width rows with a colored icon block + bold name + type subtitle + right-aligned timestamp, a clean single-line toolbar (breadcrumbs left, New sketch / Paste / Upload right), a right preview card with a centered Open action and `Modified … · size · EXT` stats, and a static USEFUL INFO footer. Removes the old table/sort-header/kind-filter/pagination/group-toggle chrome and the conflicting legacy `.app .df-*` overrides in routines.css that were trumping the panel's own stylesheet via specificity. Preserves batch select/download/delete, rename, the row context menu, live-artifacts, plugin-folders, drag-upload, and the reloading overlay. Adds the designFiles.* section/kind/useful-info i18n keys across all locales. * feat: pick a working directory from Home before the project exists Let users choose a code folder on the Home screen up front, instead of only being able to replace it after a project is created. The HomeHero composer gains a folder pill (pick / clear); the chosen path is threaded through project creation and applied via the working-dir API once the project id exists. On desktop the working-dir POST is gated behind a host-minted token, so add a `dialog:pick-working-dir` IPC handler that shows the native picker and mints a single-use token bound to the folder, plus a `pickHostWorkingDir()` bridge + types/normalizer in `@open-design/host`. The renderer feature-detects the bridge and falls back to the web folder dialog (no token) otherwise. Native dialogs now allow creating a new folder inline (`createDirectory` / `ShowNewFolderButton`), and macOS uses `choose folder` so the panel reliably takes key focus. Wire the WorkingDirPill into the project composer footer and surface a reloading state + breadcrumb root label to the Design Files panel while a replace reindexes. Drop the WorkingDirPill "recent directories" localStorage list in favor of a single Replace action. * fix(web): preserve staged Home attachments when picking a working dir Move the working-dir handoff ahead of the initial upload so staged attachments land in the external folder instead of the temporary managed root, and fall back to the browser folder dialog when the desktop host build does not expose pickWorkingDir. * fix(web): surface host pick errors and normalize Windows dir basenames When the desktop host is present but cannot mint a working-dir token, surface the error instead of falling back to the browser folder dialog — the tokenless raw path would be rejected by the desktop auth gate as a late create-time failure. Only the pure web path (no token gate) keeps the dialog fallback. Also split the Home pill basename on both path separators so Windows paths show the folder name, not the full path. * fix(web,desktop): harden Home working-dir handoff against auth/token failures Run the desktop-auth handshake before minting the Home working-dir token so a missed startup registration fails up front instead of as a rejected deferred POST. Surface a create-time error toast when the working-dir handoff fails (expired ~60s token or daemon rejection) instead of silently leaving the project in the managed root while the user believes their folder was applied. * fix(web): abort create on failed working-dir handoff and always show root crumb When the pre-create working-dir handoff fails, short-circuit the rest of the Home submit path (skip the staged-attachment upload and auto-send) so the first run cannot proceed against the managed `.od/projects/<id>` tree the user did not choose, and reword the error toast accordingly. Always render the Design Files root breadcrumb, falling back to the `designFiles.crumbs` label when no rootDirName exists, so the toolbar is not blank on the default managed-storage view. Adds regressions for the short-circuit and default-root crumb cases. Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code) * fix(web): normalize Windows separators in WorkingDirPill basename shortPath() split only on '/', so a Windows working dir like C:\work\repo rendered as the full absolute path in the footer pill instead of the folder name. Split on both separators to match HomeHero/ProjectView, and add a regression covering the backslash path. Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code) --------- Co-authored-by: qiongyu1999 <2694684348@qq.com> |
||
|
|
c6722d2671 |
feat: Lexical composer, interactive terminals, comment attachments & browser reference board (#3516)
* feat(daemon): add interactive terminal support with node-pty - Introduced a new terminal service to manage interactive terminal sessions. - Added routes for creating, streaming, and managing terminal sessions in the daemon. - Integrated terminal functionality into the CLI, allowing users to open interactive shells. - Updated project routes to support conversation seeding for side chats. - Enhanced the web application to include terminal tab functionality and UI components. This feature enables users to interact with a terminal directly within the application, enhancing the overall user experience and providing a more integrated development environment. * feat(web): implement embedded browser module in Design Files workspace - Added a `+` icon to the Design Files tab for opening a new Browser module. - The Browser module supports navigation features including back, forward, refresh, and address input. - Integrated a curated list of design reference URLs for user convenience. - Implemented browser data clearing functionality via IPC. - Enhanced desktop runtime to support embedded browser with appropriate security measures. - Added tests for browser functionality and URL handling. This commit establishes a new workspace for browsing and referencing design resources directly within the application, improving user experience and accessibility to design tools. * refactor(web): enhance add tab menu functionality in FileWorkspace - Updated the add tab button to toggle the visibility of the menu with improved accessibility attributes. - Refactored the add menu to be portaled to the document body for better positioning and visibility. - Adjusted CSS styles for the add menu to use fixed positioning and increased z-index for proper layering. - Minor CSS adjustments in entry layout for consistent padding. These changes improve the user experience when adding new modules in the FileWorkspace, ensuring the add menu is more accessible and visually consistent. * feat(daemon): introduce session mode for conversations - Added a new `session_mode` column to the `conversations` table with a default value of 'design'. - Implemented logic to handle `session_mode` in conversation creation, updates, and retrieval. - Enhanced the API to support `session_mode` in conversation requests, allowing for 'chat' or 'design' modes. - Updated the web application to include a session mode toggle, enabling users to switch between chat and design modes seamlessly. - Adjusted system prompts to reflect the current session mode, providing context-aware responses. This feature enhances the user experience by allowing for more flexible conversation management, catering to different interaction styles. * feat(web): enhance navigation and settings functionality in DesignBrowserPanel and EntryShell - Introduced a navigation stack in DesignBrowserPanel to manage back and forward navigation states. - Updated the browser navigation logic to handle URL history and improve user experience. - Added a settings menu in EntryShell for quick access to language and appearance options. - Implemented CSS styles for the new settings menu, ensuring a consistent and user-friendly interface. - Enhanced tests for navigation functionality and settings menu interactions. These changes improve the overall usability of the application by streamlining navigation and providing easy access to settings. * feat(daemon): enhance conversation session mode handling - Added a new `mode` flag to CLI commands for project and conversation creation, allowing users to specify 'design' or 'chat' modes. - Implemented `normalizeChatSessionModeFlag` function to validate and normalize session mode inputs. - Updated project routes to handle session mode during conversation creation and updates. - Enhanced web components to support session mode changes, including new props and handlers for managing session modes in conversations. - Adjusted UI elements to reflect the current session mode, improving user experience and interaction flexibility. This update provides a more robust framework for managing conversation modes, catering to diverse user needs and enhancing overall functionality. * feat(web): enhance HandoffButton and DesignBrowserPanel with improved functionality and styling - Updated HandoffButton to support framework-specific CLI prompts and improved local project path handling. - Enhanced DesignBrowserPanel to manage browser history with favicon support and improved address display. - Introduced new utility functions for formatting addresses and extracting hostnames. - Refactored CSS styles for better layout and responsiveness across components. - Added tests for new functionalities in HandoffButton and DesignBrowserPanel, ensuring robust behavior. These changes improve user experience by streamlining the handoff process and enhancing the design browsing capabilities within the application. * feat(web): enhance HandoffButton and ProjectView with improved instructions handling and UI updates - Updated HandoffButton to include a tabbed interface for switching between editor and CLI options, enhancing user experience. - Added support for opening the AMR website directly from the HandoffButton. - Refactored ProjectView to implement a modal for custom instructions, allowing users to edit and review instructions more intuitively. - Improved CSS styles for project instructions modal and handoff menu, ensuring better layout and responsiveness. - Added keyboard accessibility for closing the instructions modal with the Escape key. These changes streamline the handoff process and improve the usability of custom instructions within the application. * feat(daemon): add social share functionality and project folder management - Introduced new `share` command in CLI for building localized social-share targets for Open Design projects. - Implemented `printShareUsage` and `runShare` functions to handle share requests and display usage instructions. - Added API routes for social sharing, allowing users to create shareable links for projects. - Enhanced project routes with new endpoints for listing and creating project folders, improving project organization. - Updated relevant files and tests to support new functionalities, ensuring robust behavior. These changes enhance user experience by facilitating social sharing and better project management within the application. * feat(web): enhance DesignBrowserPanel and DesignFilesPanel with improved address formatting and drag-and-drop functionality - Refactored `formatAddressDisplay` to utilize a new `formatAddressDisplayParts` function, separating URL and title handling for better clarity. - Updated `DesignFilesPanel` to improve drag-and-drop interactions, including enhanced directory navigation and file management features. - Adjusted CSS styles for better visual consistency and responsiveness across components. - Added tests for new functionalities in `DesignBrowserPanel` and `DesignFilesPanel`, ensuring robust behavior. These changes improve user experience by streamlining address formatting and enhancing file management capabilities within the application. * feat(web): enhance DesignBrowserPanel and DesignFilesPanel with new reference categories and improved folder creation - Added new reference categories in DesignBrowserPanel, including 'Inspiration', 'Real Interfaces', 'Color', 'Typography', and more, each with curated design resources. - Improved folder creation logic in DesignFilesPanel to suggest names based on existing folders, enhancing user experience. - Updated CSS styles for better layout and responsiveness across components, particularly in control rows and search functionalities. - Added tests for new reference categories and folder creation features, ensuring robust functionality. These changes enrich the design resource catalog and streamline folder management, improving overall usability within the application. * feat(web): implement reference icon functionality and enhance social share features - Replaced favicon URL generation with a new `referenceIconUrl` function to provide reliable icons for curated design sites, improving visual consistency in the DesignBrowserPanel. - Updated the FileViewer component to enhance social share functionality, including clearer messaging for protected deployments and improved UI for sharing links. - Added CSS styles to support new visual states for social share components, ensuring better user experience. - Expanded tests for the new `referenceIconUrl` function and social share interactions, ensuring robust functionality. These changes enhance the design resource presentation and improve the sharing experience within the application. * feat(web): enhance DesignFilesPanel and FileViewer with improved folder management and social share functionality - Added optimistic folder path management in DesignFilesPanel to improve user experience during folder creation. - Updated social share logic in FileViewer to handle protected deployments more effectively, ensuring clearer messaging and UI updates. - Refactored CSS styles for better layout and responsiveness in both components, enhancing overall usability. - Expanded tests for new folder management features and social share interactions, ensuring robust functionality. These changes streamline folder management and enhance the social sharing experience within the application. * feat(daemon): enhance project tab management with new state handling - Updated database schema to include a new `state_json` column in the `tabs_state` table for improved project tab state management. - Implemented functions to normalize and parse project tab states, including handling browser workspace tabs. - Modified `listTabs` and `setTabs` functions to utilize the new state management features, allowing for better tracking of active tabs and saved states. - Refactored related types in the contracts and web applications to support the new tab state structure. These changes improve the functionality and user experience of managing project tabs within the application. * feat(web): enhance project tab management and browser tab functionality - Updated project routes and server logic to include `browserTabs` in the request body, improving tab state management. - Implemented validation for `browserTabs` to ensure it is an array, enhancing error handling. - Refactored `setTabs` function to accommodate the new structure for managing tabs, including browser-specific tabs. - Added tests for browser tab state persistence and management in the new `project-tabs-state.test.ts` file, ensuring robust functionality. These changes improve the user experience by providing better management and persistence of project and browser tabs within the application. * feat(web): enhance ProjectView with improved chat send management and UI updates - Added support for queue-only chat sends, allowing for better handling of messages during busy conversations. - Refactored chat send functions to improve state management and persistence of queued messages. - Updated CSS styles in the DesignFilesPanel for better layout and responsiveness, including search control enhancements. - Expanded tests for new chat send functionalities and search interactions, ensuring robust behavior. These changes improve the user experience by streamlining chat interactions and enhancing the overall design file management interface. * feat(web): improve project tab state management and caching - Enhanced the `loadTabs` function to utilize cached tab states, improving performance and user experience during data fetching. - Implemented `normalizeTabsState` and caching functions to manage tab states effectively, including validation and error handling. - Updated `writeCachedTabs` to ensure the latest state is stored in local storage, facilitating better persistence of tab information. - Modified `listTabs` in the daemon to include `updatedAt` in the state retrieval, allowing for more accurate tracking of tab updates. These changes streamline tab management and enhance the overall responsiveness of the application. * feat(web): integrate Lexical for enhanced text composition and mention handling - Added Lexical as a dependency to improve text composition capabilities within the chat interface. - Implemented mention functionality with the creation of `MentionNode` and related serialization/deserialization logic for inline mentions. - Enhanced `ChatComposer` and `ChatPane` components to support queue-only message sending and improved state management for queued messages. - Updated `DesignBrowserPanel` and `PreviewDrawOverlay` to incorporate new features for capturing and annotating browser snapshots. - Refactored various components to streamline interactions and improve user experience during chat and design tasks. These changes significantly enhance the text editing experience and provide better management of chat interactions, improving overall usability in the application. * feat(workspace): integrate terminal viewer and enhance chat functionality - Added a new terminal viewer component with customizable themes and improved styling for better user experience. - Integrated terminal functionality into the workspace, allowing users to interact with a terminal directly within the application. - Updated chat components to support active conversation states, enabling seamless message handling and interaction. - Refactored chat-related props and state management to enhance performance and maintainability. - Removed deprecated file tree explorer components to streamline the workspace interface. This update enhances the overall functionality of the workspace, providing users with a more integrated and responsive environment for both terminal and chat interactions. * feat(workspace): enhance chat and session mode functionality - Introduced a new `+` launcher in the workspace for easy access to project files and new tabs, including Side Chat and Terminal. - Added Side Chat functionality that allows users to create context-aware conversations based on existing chats. - Implemented a new terminal tab type for interactive terminal sessions using `node-pty`, enabling users to run shell commands directly within the workspace. - Enhanced chat functionality with a new session mode toggle, allowing users to switch between 'design' and 'chat' modes seamlessly. - Added a feature to copy chat responses as markdown to the clipboard, improving usability and sharing capabilities. - Updated various components and styles to support these new features, ensuring a cohesive user experience. This update significantly improves the workspace's interactivity and usability, providing users with more tools for collaboration and development. * feat(workspace): enhance session mode toggle and chat functionality - Improved the SessionModeToggle component to include localized guidance cards that provide context-aware descriptions for each mode. - Updated the UI to display a popover with mode descriptions when hovering over options, enhancing user understanding of the available modes. - Added a new copy button in the AssistantMessage footer to allow users to copy responses as raw Markdown, improving usability for external documentation. - Enhanced localization support by updating i18n keys and translations for various languages, ensuring consistent user experience across different locales. - Refactored styles for the session mode toggle and associated components to improve layout and responsiveness. This update significantly enhances the user experience by providing clearer guidance and improved functionality in chat interactions. * feat(styles): enhance session mode toggle styling for improved visibility - Added new CSS rules to ensure the session mode toggle popover and hover cards are displayed correctly with increased z-index and visibility. - Updated styles for various chat components to maintain consistent positioning and overflow behavior when session mode elements are present. - Improved overall layout responsiveness for chat interactions, enhancing user experience during mode transitions. This update refines the visual presentation of session mode toggles, ensuring they are more accessible and user-friendly. * feat(web): finish Lexical composer input with atomic mention pills * fix(web): make browser reference board scroll with a pinned toolbar The browser tab's default Reference Board (.db-start) is wrapped by PreviewDrawOverlay's position:absolute container, which is not a flex parent — so .db-start's flex:1 1 auto never bounded its height and the board grew to its content height instead of scrolling (and the sticky .db-reference-toolbar could not pin). Fill the overlay with height:100% like the .db-webview/.db-fallback siblings already do, restoring scroll and the sticky toolbar. * test(daemon): expect updatedAt in persisted tab state round-trip listTabs() returns the tabs_state row's updatedAt timestamp on the saved-state path, but these two toEqual assertions predated that field and failed strictly. Match the real shape with expect.any(Number). * feat(chat): enhance ChatComposer with session mode management and UI improvements - Introduced a new `sessionMode` prop to the ChatComposer, allowing users to switch between different session modes (e.g., 'design'). - Added a SessionModeToggle component for improved user interaction and visibility of session options. - Updated the ToolsTab type to reflect the removal of the pet option, streamlining the tools available in the composer. - Refactored styles to enhance the visibility and positioning of session mode elements, ensuring a better user experience during mode transitions. - Improved the handling of draft state and user interactions within the composer, enhancing overall functionality. These changes significantly improve the ChatComposer's usability and flexibility, providing users with clearer options and a more responsive interface. * feat(chat): implement caret floating layer for mention and slash popovers - Introduced a new `CaretFloatingLayer` component to manage the positioning of mention and slash command popovers relative to the caret. - Enhanced `ChatComposer` to utilize the caret rectangle for accurate popover placement, improving user experience during text input. - Updated `LexicalComposerInput` to pass caret position data to the trigger handling logic, allowing for dynamic popover adjustments. - Refactored styles for popovers to ensure consistent appearance and behavior, including improved animations and responsiveness. - Added accessibility features to mention and slash popovers, enhancing usability for keyboard navigation. These changes significantly improve the interaction model for mentions and commands within the chat interface, providing a more intuitive and responsive user experience. * feat(chat): update mention tab order and improve search functionality - Reordered the mention tab sections in the ChatComposer to prioritize 'Design files' over other categories, enhancing user experience during mentions. - Updated the search prompt to reflect the new tab order, ensuring clarity in search functionality. - Enhanced the mention selection logic to accommodate the new tab structure, allowing for a more intuitive navigation experience. - Added tests to verify the correct display and functionality of the updated mention tabs and search behavior. These changes significantly improve the usability of the mention feature within the chat interface, making it easier for users to find and select relevant items. * feat(chat): enhance context management in ChatComposer and HomeHero - Added functionality to manage MCP servers and connectors within the ChatComposer, allowing users to remove these contexts seamlessly. - Updated the HomeHero component to support the selection and removal of MCP servers and connectors, improving context handling in user interactions. - Enhanced the search prompt to include files, ensuring users can search across all relevant categories. - Refactored related components and styles for better integration and user experience. - Added tests to verify the correct functionality of the new context management features. These changes significantly improve the usability of context features in the chat interface, making it easier for users to manage their interactions effectively. * feat(chat): enhance message handling with session mode and plugin snapshot support - Added new columns to the database schema for `session_mode` and `applied_plugin_snapshot_json` to support enhanced message context. - Updated the `upsertMessage` and `listMessages` functions to handle the new fields, ensuring messages can store and retrieve session mode and plugin snapshot data. - Enhanced the `ChatComposer` to manage and send the applied plugin snapshot as part of the message context, improving user interaction with plugins. - Introduced a new `MessageSessionModeChip` component to visually represent the session mode in the chat interface. - Updated styles for better presentation of session mode and plugin context within messages, enhancing user experience. These changes significantly improve the context management capabilities in the chat interface, allowing for richer interactions and better tracking of session-specific data. * feat(chat): add annotation handling and UI improvements in ChatComposer and PreviewDrawOverlay - Implemented functionality to stage draw annotations into the composer input without sending, enhancing user interaction. - Added a new button in the PreviewDrawOverlay to allow users to append notes to the input, improving workflow flexibility. - Updated the ChatComposer tests to verify the correct staging of annotations and their integration into the input. - Enhanced internationalization support by adding new translation keys for annotation actions across multiple languages. These changes significantly improve the user experience by providing more intuitive annotation handling and better integration within the chat interface. * feat(capture): implement page capture functionality and enhance folder management dialogs - Added new capture functionality to allow users to take snapshots of the current page, improving user interaction with visual content. - Introduced in-app dialogs for folder creation and moving files, replacing the unsupported window.prompt in the Electron desktop host, enhancing usability across platforms. - Updated the DesignFilesPanel to support these new dialogs, ensuring a seamless experience for managing project files. - Enhanced internationalization support by adding new translation keys for folder management actions across multiple languages. These changes significantly improve the user experience by providing intuitive capture options and streamlined file management within the application. * feat(projects): implement folder deletion and enhance project file management - Added a new API endpoint to delete project folders, improving the file management capabilities within the application. - Introduced utility functions for ensuring project subdirectories and safely deleting folders, enhancing the robustness of folder operations. - Updated the DesignFilesPanel and FileWorkspace components to support folder deletion actions, providing users with a more intuitive interface for managing project files. - Enhanced internationalization support by adding new translation keys for folder management actions. These changes significantly improve the user experience by streamlining folder management and providing clearer options for users to organize their projects effectively. * feat(composer): enhance BoardComposerPopover with image attachment functionality - Added support for attaching images to comments, allowing users to upload and preview images directly within the composer. - Implemented new handlers for image input changes and clipboard pasting, improving the user experience for image uploads. - Updated the component's props to include image-related callbacks and state management for attached images. - Enhanced styles for image thumbnails and removal buttons, ensuring a cohesive design with the existing comment popover interface. These changes significantly improve the functionality of the BoardComposerPopover, providing users with a more interactive and visually rich commenting experience. * feat(file-viewer): enhance comment attachment functionality with image support - Updated the `onSendBoardCommentAttachments` prop to accept an additional `images` parameter, allowing for image attachments alongside comments. - Introduced state management for handling attached images, including functions to add and remove images, and to generate previews. - Implemented a modal for previewing attached images, improving user interaction when managing comment attachments. - Updated the `FileWorkspace` component to reflect changes in the props, ensuring consistency across components. These enhancements significantly improve the commenting experience by enabling users to attach and preview images directly within the file viewer. * feat(home-hero, project-view, styles): enhance functionality and user experience - Updated the HomeHero component to prevent unnecessary state changes during programmatic updates, improving user interaction with prompts. - Enhanced the ProjectView component to support image attachments alongside comments, allowing for a more versatile commenting experience. - Implemented a new image upload process that queues tasks efficiently, ensuring smooth handling of comment attachments. - Added CSS to reserve scrollbar space in design files, preventing layout shifts when scrollbars appear, thus enhancing visual stability. These changes collectively improve the user experience by streamlining interactions and ensuring consistent UI behavior across components. * feat(preview-comments, chat): enhance comment functionality with attachment support - Added support for `attachments_json` in the `preview_comments` table, allowing users to attach files to comments. - Updated relevant functions to handle attachments, including `upsertPreviewComment` and `listPreviewComments`, ensuring attachments are properly managed and displayed. - Enhanced the `CommentSidePanel` to render attached files, providing users with a visual representation of their attachments. - Improved the `BoardComposerPopover` and `ChatPane` components to support image attachments, including drag-and-drop functionality for reordering queued sends. These changes significantly enhance the commenting experience by enabling users to attach and manage files directly within the chat interface, improving overall usability and interaction. * refactor(comment-attachments): rename attachment normalization functions for clarity - Updated the `normalizeCommentAttachments` function to `normalizePreviewCommentAttachments` for better context in handling preview comment attachments. - Adjusted the `upsertPreviewComment` and `normalizePreviewComment` functions to utilize the renamed attachment normalization function. - Added tests to ensure that image attachments are correctly saved and retrieved, addressing a regression issue with attachment persistence. These changes enhance code clarity and maintainability while ensuring the functionality for handling comment attachments remains robust. * feat(comment-attachments): enhance comment submission with image and note validation - Updated the `upsertPreviewComment` function to require either a text note or at least one image attachment for comment submission, improving validation logic. - Modified the `BoardComposerPopover` to allow saving comments with only images, enhancing user experience by simplifying the commenting process. - Adjusted the `FileViewer` to support saving comments with image-only notes, ensuring consistency across components. - Improved styles in the chat and home hero components for better visual representation of attachments and comments. These changes collectively enhance the commenting functionality, providing users with more flexibility in how they submit comments while ensuring robust validation. * feat(project-view): implement auto-start for queued chat sends - Replaced the `startingQueuedChatSendIdRef` with a state variable `queuedAutoStartBlocked` to manage the auto-start behavior of queued chat sends. - Updated the `useEffect` to ensure that queued sends are processed one at a time after the active conversation completes, enhancing the chat experience. - Added a test to verify that queued sends auto-start correctly after the active run finishes, ensuring reliable functionality. These changes improve the handling of queued chat messages, providing a smoother user experience during conversations. * feat(project-view): refine auto-start logic for queued chat sends - Replaced the state variable `queuedAutoStartBlocked` with a reference `startingQueuedChatSendIdRef` to manage the auto-start behavior of queued chat sends more effectively. - Updated the `useEffect` to ensure that queued sends are processed sequentially, improving the handling of chat messages during active conversations. - Introduced a new state variable `queuedAutoStartTick` to track the auto-start process, enhancing the responsiveness of the chat interface. These changes improve the reliability and user experience of the chat functionality by ensuring queued messages are handled smoothly and efficiently. * feat(comment-attachments): improve attachment handling in preview comments - Updated the `upsertPreviewComment` function to merge existing and incoming attachments, ensuring that image attachments are preserved when updating comments without new files. - Introduced a new helper function, `mergePreviewCommentAttachments`, to handle the merging of attachments without duplicates, enhancing the robustness of attachment management. - Added tests to verify the correct merging of attachments and the preservation of existing attachments during comment updates, improving overall functionality and user experience. These changes enhance the commenting system by providing better management of image attachments, ensuring users can update comments seamlessly while retaining their attached images. * feat(BoardComposerPopover): enhance popover positioning and measurement - Updated the `popoverAnchorStyle` function to incorporate viewport scroll positions, ensuring the popover remains within visible bounds. - Introduced a new `PopoverSize` type to manage measured dimensions, improving the accuracy of popover placement. - Implemented a `useLayoutEffect` to dynamically measure the popover size and adjust its position accordingly, enhancing user experience during interactions. - Added tests to verify that the popover correctly adjusts its position based on target and viewport dimensions, ensuring it remains fully visible. These changes improve the usability of the BoardComposerPopover by ensuring it is properly positioned within the viewport, enhancing the overall commenting experience. * feat(comment-attachments): enhance image attachment handling in comments - Updated the `normalizeCommentAttachments` function to include image attachments in the comment payload, allowing comments to be submitted with only images. - Introduced a fallback message for comments that consist solely of image attachments, improving user guidance. - Enhanced the `renderCommentAttachmentHint` function to display image attachment details, ensuring users are informed about attached images. - Added tests to verify that image attachments are preserved in comment submissions and correctly rendered in hints, improving overall functionality and user experience. These changes enhance the commenting system by providing better support for image-only comments, ensuring users can effectively utilize image attachments in their interactions. * feat(comment-attachments): refine comment context handling and enhance drag-and-drop functionality - Updated the `normalizeCommentAttachments` function to conditionally omit comment text when the context is set to 'query', improving clarity in comment submissions. - Enhanced the `renderCommentAttachmentHint` function to only display comments when not in 'query' context, ensuring a cleaner output. - Implemented drag-and-drop functionality in the `CommentSidePanel` for reordering comments, improving user interaction and organization of comments. - Added tests to verify the correct handling of comment context and the functionality of the drag-and-drop feature, ensuring robust performance. These changes enhance the commenting system by providing clearer context management and improved usability through drag-and-drop capabilities. * feat(FileViewer, ProjectView): enhance comment attachment handling and status updates - Updated the `FileViewer` component to manage the state of sent comment IDs, ensuring that active previews are correctly updated after sending comments. - Refined the `ProjectView` component to filter out comment attachments from the board-batch source and update their status to 'applying' during processing, improving user feedback on attachment handling. - Introduced logic to handle the removal of sent comment IDs from the preview, enhancing the overall user experience when managing comments and attachments. These changes improve the functionality and responsiveness of the commenting system, providing clearer feedback and better management of comment attachments. * feat(conversation-forking): introduce message-based conversation forking - Added the ability to fork conversations from a specific message, enhancing the chat experience by allowing users to create new conversations that inherit context up to a chosen point. - Updated the CLI commands and help documentation to reflect the new `--fork-after` option, which specifies the message ID to stop copying from. - Enhanced the backend to handle the new forking logic, ensuring that only messages up to the specified ID are included in the new conversation. - Implemented tests to verify the forking functionality, ensuring robust performance and correct behavior when forking conversations. These changes improve the flexibility of conversation management, allowing users to create tailored discussions based on previous interactions. * feat(terminal-service): enhance session output management and memory handling - Introduced new parameters for managing session output, including `maxBufferBytes`, `exitTailBytes`, `flushIntervalMs`, and `flushThresholdBytes`, to optimize memory usage and performance. - Implemented a `trimBuffer` function to efficiently evict old events based on byte and count limits, improving memory management during active sessions. - Added logic to coalesce buffered PTY output into single `data` events, reducing the frequency of event emissions and enhancing performance during high-throughput scenarios. - Updated session event structure to include `byteLength`, allowing for better tracking of output size and memory usage. These changes improve the efficiency and responsiveness of terminal sessions, ensuring better resource management and user experience. * feat(workspace-context): enhance project attachment handling and workspace context management - Introduced `formatProjectAttachmentHint` function to render project attachments in a user-visible order, improving clarity for users referencing attachments. - Added `normalizeWorkspaceContextItems` function to standardize workspace context items, ensuring consistent handling of various context types. - Updated `mergeRunContextSelections` to include workspace items, enhancing the context management during chat interactions. - Enhanced `renderRunContextPrompt` to display active workspace context, providing users with better visibility of their current workspace state. - Implemented tests for new functions to ensure robust functionality and correct behavior in handling project attachments and workspace contexts. These changes improve the user experience by providing clearer context and better management of project attachments within the application. * feat(database, chat): enhance message and conversation data structure - Added `run_context_json` to the `messages` table schema to store contextual information for each message. - Updated migration logic to include the new `run_context_json` field and ensure backward compatibility. - Enhanced conversation retrieval to include `messageCount`, providing better insights into the number of messages per conversation. - Improved attachment handling in the `ChatComposer` component by introducing ordering logic for attachments, ensuring a consistent user experience. - Refactored `SessionModeToggle` to simplify state management and improve tooltip visibility. These changes enhance data management and user interaction within the chat application, providing clearer context and improved functionality. * feat(attachment-handling): improve attachment sorting and context management - Introduced `sortAttachmentsByUserOrder` function to ensure attachments are displayed in a user-defined order, enhancing clarity and usability. - Updated `historyWithApiAttachmentContext` to utilize the new sorting function, improving the context provided with message histories. - Refactored `buildAnthropicMessageContent` to apply sorting for image attachments, ensuring consistent handling across different message types. These changes enhance the user experience by providing better organization and visibility of attachments within the chat application. * feat(chat): enhance conversation listing and loading states - Improved the SQL query for listing conversations to include message counts, providing better insights into conversation activity. - Added loading states to the ChatPane component, enhancing user experience during data fetching. - Implemented a search feature in the conversation history, allowing users to filter conversations by title for easier navigation. - Updated styles for loading indicators and conversation list to improve visual feedback during loading states. These changes enhance the usability and responsiveness of the chat interface, providing users with clearer context and improved interaction capabilities. * feat(chat): optimize suggestion filtering and enhance design system integration - Refactored suggestion filtering in the ChatComposer component to utilize `useMemo`, improving performance by memoizing results based on dependencies. - Added new props in ChatPane for handling plugin and design system details, enhancing the integration of these features within the chat interface. - Updated the FileWorkspace component to manage tab states for design system and browser tabs, improving user navigation and context management. - Introduced modals for displaying plugin and design system details in the ProjectView, enhancing user experience by providing contextual information. These changes improve the efficiency of suggestion handling and enhance the overall user experience in managing plugins and design systems within the chat application. * fix(chat): adjust styling and layout for improved chat interface - Reduced the conversation row height in ChatPane for better alignment with design specifications. - Updated CSS styles across various components to enhance layout consistency, including adjustments to margins, padding, and flex properties. - Improved visual feedback and responsiveness in chat elements, ensuring a more cohesive user experience. These changes refine the chat interface, making it more visually appealing and user-friendly. * feat(comments): implement structural equality checks for comment snapshots - Added `commentSnapshotOverlayEqual` and `commentSnapshotEqual` functions to compare comment snapshots based on their structural properties, improving performance by avoiding unnecessary state updates in the `FileViewer` component. - Updated `HtmlViewer` to utilize these equality checks, optimizing the handling of live comment targets during pointer movements and hover events. - Enhanced the overall responsiveness of the comment system by preventing redundant re-renders when comment snapshots remain unchanged. These changes enhance the efficiency of comment handling and improve user experience in the commenting interface. * feat(chat): enhance comment attachment sorting and order management - Introduced `sortChatCommentAttachmentsByOrder` function to ensure comment attachments are displayed in a user-defined order, improving clarity and usability. - Updated the `currentCommentAttachments` function to utilize the new sorting logic, enhancing the organization of attachments. - Adjusted the order assignment for visual attachments to ensure consistent handling based on existing attachment orders. These changes improve the user experience by providing better organization and visibility of comment attachments within the chat application. * feat(chat): enhance queued send strip with overflow handling and styling improvements - Added overflow handling to the queued send strip, allowing for better visibility of additional queued items when the list exceeds the visible limit. - Updated CSS styles for the chat components, including adjustments to layout, padding, and font sizes to improve overall aesthetics and usability. - Refactored the structure of the queued send row to utilize a grid layout, enhancing alignment and responsiveness of the elements within the chat interface. These changes improve the user experience by providing clearer visibility of queued messages and a more polished interface. * feat(database): add index for created_at and enhance comment retrieval - Introduced a new index on `preview_comments` for `created_at` to optimize query performance when retrieving comments. - Updated the `listPreviewComments` function to order results by `created_at` and `rowid`, improving the organization of displayed comments. - Enhanced the test suite to verify the injection of the new URL preview selection bridge and its functionality in various scenarios. These changes improve the efficiency of comment retrieval and enhance the user experience in the commenting interface. * feat(tooltip): implement tooltip system for enhanced user guidance - Introduced a new `TooltipLayer` component to manage tooltip display across the application, improving user interaction by providing contextual information on hover and focus. - Updated various components to utilize the tooltip system, including buttons and icons, ensuring consistent tooltip behavior and styling. - Enhanced CSS styles for tooltips, improving visibility and responsiveness, while maintaining a cohesive design across the application. These changes enhance the user experience by providing clearer guidance and improving the overall usability of interactive elements. * feat(tooltip): enhance tooltip integration across components - Updated various components to include tooltip functionality, improving user guidance with contextual information on hover and focus. - Added `data-tooltip` and `data-tooltip-placement` attributes to buttons and interactive elements for consistent tooltip behavior. - Enhanced CSS styles to ensure tooltips are displayed correctly and responsively, maintaining a cohesive design across the application. These changes improve the overall user experience by providing clearer guidance and enhancing the usability of interactive elements. * feat(project-view): refactor comment handling and enhance tooltip positioning - Introduced `mergeSavedPreviewComment` function to streamline the management of preview comments, improving clarity and maintainability. - Updated `ProjectView` to utilize the new comment merging function, enhancing the efficiency of comment updates. - Refactored `TooltipLayer` to use x and y coordinates for positioning, improving tooltip display accuracy and responsiveness. - Enhanced CSS styles for tooltips, ensuring better visibility and layout consistency across the application. These changes improve the user experience by providing more efficient comment handling and refined tooltip interactions. * feat(design-files): enhance workspace hint and file handling in project context - Introduced `formatDesignFilesWorkspaceHint` function to provide a detailed overview of the current Design Files workspace, including folder and file listings. - Added limits for the number of folders and files displayed to improve clarity and prevent overwhelming users with excessive information. - Updated the `startServer` function to integrate the new workspace hint, ensuring that the context of existing project files and folders is communicated effectively. - Enhanced tests for the new workspace hint functionality to ensure accurate representation of project context. These changes improve user experience by providing clearer insights into the Design Files workspace and facilitating better project management. * feat(composer): atomic @mention keyboard navigation and deletion * feat(database): add attachments_json field to comments and enhance migration logic - Introduced `attachments_json` field in the database schema for comments to support attachment storage. - Updated migration functions to include the new field, ensuring existing data is properly migrated. - Refactored related SQL queries to accommodate the new field, improving data handling for comments. These changes enhance the comment functionality by allowing attachments to be stored and retrieved effectively. * chore(nix): refresh pnpm deps hash * feat(chat-composer): implement design toolbox for enhanced design actions - Added a new design toolbox feature to the ChatComposer, allowing users to access various design actions such as 'auto-match', 'motion', and 'visual-polish'. - Introduced a state management system for the design toolbox, including hooks for opening and closing the toolbox. - Enhanced the user interface with new components and styles for the design toolbox, improving accessibility and usability. - Implemented functionality to apply design actions directly from the toolbox, streamlining the design workflow. - Added tests to ensure the correct behavior of the design toolbox and its interactions within the ChatComposer. These changes significantly enhance the design capabilities within the ChatComposer, providing users with a more efficient and intuitive design experience. * feat(chat-composer): enhance design toolbox resource management - Expanded the design toolbox functionality in ChatComposer to include a comprehensive resource index, allowing for better organization and retrieval of skills, plugins, MCP servers, templates, connectors, and project files. - Introduced new types and interfaces to support the expanded resource management, improving type safety and clarity in the codebase. - Updated the design toolbox action descriptions and search functionality to reflect the new resource capabilities, enhancing user experience. - Added tests to validate the new resource indexing and search features, ensuring robust functionality. These enhancements significantly improve the design workflow by providing users with a more organized and efficient way to access various design resources. * feat(workspace): enhance workspace context management and UI integration - Introduced a new function `renderWorkspaceContextToolHints` to provide contextual hints based on the type of workspace items (browser, terminal, files, live artifacts). - Updated `ChatComposer`, `ChatPane`, and `FileWorkspace` components to support and display workspace context items, improving user interaction and accessibility. - Enhanced the `QuickSwitcher` and `TabLauncherMenu` components to include workspace context items in search results, allowing users to navigate between tabs and files more efficiently. - Added new translations and updated existing ones to reflect the inclusion of workspace tabs in the user interface. These enhancements significantly improve the usability and functionality of the workspace, providing users with better context and navigation options. * test(e2e): assert Lexical composer content with toHaveText, not toHaveValue The chat composer and home hero input are now Lexical contenteditable editors, not native form controls, so Playwright's toHaveValue (form-only) fails with "Not an input element". Switch all chat-composer-input and home-hero-input content assertions to toHaveText, and assert multi-line soft-break cases with separate toContainText checks since the editor's textContent collapses the newline (the newline reaching the sent payload is already covered by downstream message/payload assertions). * chore(nix): refresh pnpm deps hash * chore(nix): refresh pnpm deps hash * test(e2e): open settings through the entry settings menu The home settings entry is now a menu (EntrySettingsMenu): clicking the gear opens a popover whose "Settings" item opens the full execution-mode dialog. Update the three specs that assumed a single click opened the dialog directly to go through the menu trigger + open-details item. * test(web): drive HomeView context picker through the Lexical helper The home hero input is a Lexical contenteditable, so fireEvent.change / reading .value throws "element does not have a value setter". Switch the MCP+connector first-turn-context spec to setHomeHeroPrompt / homeHeroPromptText like the rest of the file already does. * Fix PR review blockers * Update Nix pnpm deps hash * Enhance project folder routes with error handling and tests - Added checks for project existence in GET, POST, and DELETE folder routes, returning a 404 error if the project is not found. - Updated tests to verify 404 responses for unknown project IDs in folder operations. - Improved folder metadata handling in project routes. - Refactored the TabLauncherMenu component for better UI structure and scrolling behavior. - Adjusted styles for the TabLauncherMenu to improve usability and visual consistency. * Update ProjectView component and enhance design toolbox localization - Modified the ProjectView component to include workspacePanelTrack in chat panel width adjustments. - Updated localization files to add new keys and translations for the design toolbox in English, Simplified Chinese, and Traditional Chinese. - Enhanced user experience by providing comprehensive tooltips and prompts for design toolbox actions. * Add browser use prompt launcher * Cover browser use prompt launcher * Add design toolbox badge translations * Refactor ChatComposer and DesignBrowserPanel components - Removed the design system picker from the ChatComposer component and integrated it into the StagedRunContexts for better context handling. - Updated the DesignBrowserPanel to include a new function for determining viewport icons and modified the browser use categories to reflect changes in action prompts and titles. - Enhanced the HandoffButton component with a new feature to copy the project path to the clipboard, improving user experience. - Added new styles for search input and empty states in design files, enhancing the UI consistency across components. * Enhance ChatPane and DesignBrowserPanel components with new features and styles - Added scroll handling features to the ChatPane component, including scrollable state management and improved user experience during chat interactions. - Updated the DesignBrowserPanel to include new localization keys and improved category titles for better clarity in the browser use prompt. - Enhanced styles for chat log scrolling behavior, providing a more intuitive interface for users. - Implemented keyboard shortcuts for tab navigation in the WorkspaceTabsBar, improving accessibility and user efficiency. * Fix web workspace test hang * Update styles and functionality for workspace tabs and chat components - Adjusted CSS for the MAC_WINDOW_CHROME to improve spacing and margins for better layout. - Enhanced the ChatComposer component to ensure proper tab order and mention handling, improving user experience. - Implemented keyboard shortcuts for navigating workspace tabs, allowing for more efficient tab management. - Updated localization strings for clarity in search prompts across multiple languages. - Improved the HandoffButton component's UI for better visibility and interaction when copying project paths. * Update visual settings flow * Refresh Nix pnpm deps hashes --------- Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> Co-authored-by: qiongyu1999 <2694684348@qq.com> Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
1b4084ced0 |
fix(desktop): route shell:open-path through explorer.exe on WSL (#1581) (#3298)
On WSL Electron, clicking "Continue in CLI" handed the validated project
directory to shell.openPath, which delegates to xdg-open on Linux.
xdg-open on common WSL installs routes inode/directory MIME through the
default browser via wslu, so the click landed on a Chrome tab showing a
file:// directory listing instead of a native file manager.
Detect WSL via os.release().includes("microsoft"), translate the resolved
POSIX path with `wslpath -w`, and hand the Windows path to explorer.exe so
the host's Explorer opens the folder. Fall back to shell.openPath for
non-WSL Linux, macOS, native Windows, and WSL setups where wslpath or
explorer.exe are missing.
Pull the routing decision into a pure helper (open-path.ts) so the WSL
branch is unit-testable with mocked deps, mirroring the dependency-
injection pattern createDesktopUpdater already uses. Path validation
(validateExistingDirectory, isOpenPathAllowedForProject) stays in front
of the new branch — no change to the allowlist gate.
|
||
|
|
82ea01836e | fix: polish Windows chrome and selected rows (#2169) | ||
|
|
b5b975769a |
fix Windows folder import from project modal (#2863)
Co-authored-by: Lanzhou3 <217479610+Lanzhou3@users.noreply.github.com> |
||
|
|
af66a929bc | Fix release updater smoke recovery (#2687) | ||
|
|
b4e94b0534 |
Harden packaged updater downloads and install handoff (#2677)
* Add managed download package for updater resumes * fix(download): clear stale pid locks * test(e2e): harden windows updater resume smoke * feat(updater): make update downloads silent in ui * fix(updater): keep install handoff prompt visible * fix(ci): build platform before download in postinstall |
||
|
|
ec96585a8f | fix: harden Windows packaged updater flow (#2595) | ||
|
|
415c2e9502 | fix: clear applied updater install state (#2592) | ||
|
|
1ef865dd31 |
fix: defer Windows packaged updater installer (#2575)
* 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 * fix: align packaged updater validation |
||
|
|
b2b94dbde7 |
feat(desktop): follow OS language in packaged builds (cherry-pick of #2544 into release/v0.8.0) (#2560)
* feat(desktop): follow OS language in packaged builds
Packaged Electron currently shows Open Design in en-US regardless of
the OS language setting, because the renderer's i18n picks its locale
from `navigator.language` and Chromium hard-codes that to en-US unless
the host process intervenes. Browser users and `tools-dev` users are
unaffected because their `navigator.language` already reflects the
OS / browser preference.
This change:
- Adds `applyOsLocaleSwitch(app)` in `@open-design/desktop/main`. It
reads `app.getPreferredSystemLanguages()[0]` and (when called before
Electron's `ready` event) points Chromium's `--lang` flag at it, so
the renderer's `navigator.language` follows the OS. Safe to call
more than once: `appendSwitch` is a no-op once `app.isReady()`.
- Calls the helper from both Electron entries: `apps/packaged` before
its own `whenReady`, and `runDesktopMain` for tools-dev parity.
- Forwards the resolved locale through
`BrowserWindow.webPreferences.additionalArguments` as
`--od-os-locale=<bcp-47>`, parsed by the preload and exposed at
`window.__od__.client.osLocale`. The host bridge type
(`OpenDesignHostClient.osLocale`) is extended accordingly.
- Updates `detectInitialLocale` in `apps/web/src/i18n/index.tsx` to
read that field as a new step between the existing localStorage and
navigator fallbacks. Browser/web continues to fall through to
`navigator.languages` unchanged.
The explicit `osLocale` channel exists in addition to `--lang` because
some `app.getPreferredSystemLanguages()` strings (e.g. `zh-Hant-TW`,
`pt-PT`) need to round-trip through `resolveSystemLocale` to land on
the right supported locale, which Chromium's `navigator.language`
cannot do on its own.
* fix(web): route OS locale read through getOpenDesignHost
The first cut of detectInitialLocale read `window.__od__.client.osLocale`
directly, which trips `tests/host-boundary.test.ts` — that guard test
keeps web source from referencing preload globals by name so the
boundary stays single-source. Switch to `getOpenDesignHost()` from
`@open-design/host`, and rewrite the i18n test to install the host via
`installMockOpenDesignHost` instead of poking the global directly.
* fix(tools-pack): unblock mac packaged build on pnpm workspaces
Two independent issues prevented `pnpm tools-pack mac build --to all`
from completing on a clean macOS workspace, both unrelated to the
desktop OS-locale change in this PR but bundled here because verifying
that change end-to-end required the packaged pipeline to actually
finish.
1. `apps/web/.next/standalone/node_modules/.pnpm/node_modules/<pkg>`
contained dangling symlinks left by Next's nft trace (e.g. a
`semver -> ../semver@5.7.2/node_modules/semver` link to a
`.pnpm/<pkg>@<ver>` directory pnpm never created). The downstream
`cp { dereference: true }` aborted the whole packaged pipeline
with ENOENT. Walk every artifact tree before copy and unlink
symlinks whose target doesn't resolve. Targets that *do* resolve
stay untouched.
2. Next 16's standalone build under pnpm workspaces does not hoist
peer-dep packages (react, react-dom, styled-jsx) into
`<standalone>/apps/web/node_modules`. The downstream
`web-standalone-after-pack.cjs` audit then does
`createRequire(server.js).resolve('react/package.json')`, whose
module walk falls out of the standalone tree and aborts the
electron-builder phase. Add a `hoistStandaloneNextPeerDeps` step
for the web standalone artifact only: it locates the
`<pkg>@<version>` (not peer-resolved sibling) directory under
`.pnpm` and symlinks it into `apps/web/node_modules/<pkg>`. The
subsequent `cp { dereference: true }` then writes the real
directory into the cache so the packaged tree stays self-contained.
Verified by `pnpm tools-pack mac build --to all` succeeding end-to-end
(zip + dmg + app), then `pnpm tools-pack mac install` and
`pnpm exec tools-pack mac inspect --expr` reading the desired
`__od__.client.osLocale` from the packaged renderer.
* feat(desktop): fold encodeURIComponent + manual locale source + pet window from #2554
Three defensive improvements lifted from @Eli-tangerine's parallel
implementation on #2554, kept consistent with the OS-locale chain
already on this branch:
- The argv value crossing main → preload is now wrapped with
encodeURIComponent / decodeURIComponent so a locale string with `;`,
`=`, or any other Chromium argv special char round-trips cleanly.
BCP-47 region tags don't carry those today, but the renderer parser
no longer has to assume it.
- `setLocale` now also writes `open-design:locale-source = "manual"`
to localStorage, and `detectInitialLocale` only treats the stored
locale as winning when that marker is present. An untagged value
(left over from a future auto-write path, or a stale install) no
longer pins the app to an old language once the host injects a
fresh OS locale. Today `setLocale` is the only writer so the marker
has no behaviour difference yet — this is a defensive net.
- `createDesktopPetWindow` now receives `osLocale` and forwards the
same `additionalArguments` as the main `BrowserWindow`, so the
pet renderer's `__od__.client.osLocale` is consistent with the main
window's instead of being silently undefined.
Co-authored idea credit: changes mirror the locale-piece of
@Eli-tangerine on #2554 — that PR is closing in favour of this one.
Tests: detect-initial-locale gets a new "untagged localStorage value
loses to host locale" case. desktop 62/62, host 13/13, web i18n +
host-boundary 15/15 stay green.
* feat(web): fold onboarding view styles from #2554
Pulls the 747-line addition to `apps/web/src/styles/home/entry-layout.css`
from @Eli-tangerine's #2554 — the visual layer for the global onboarding
flow (`/onboarding` view, Connect / About-you / Design-system steps).
The view itself was already plumbed through `EntryShell.tsx`; this adds
the styling that makes it shippable on v0.8.0.
#2554 is closing in favour of this branch, so the CSS lands here so the
onboarding work doesn't get dropped on the floor.
Co-authored idea credit: @Eli-tangerine — original styling on #2554.
* fix(tools-pack): make hoistStandaloneNextPeerDeps idempotent across builds
Addresses non-blocking review by @PerishCode on #2560: the previous
`if (await pathExists(linkPath)) continue;` guard uses `access()`,
which follows symlinks. A stale symlink from a previous build whose
`.pnpm/<pkg>@<version>` target moved (e.g. after a react/react-dom
version bump that invalidates the workspace-build cache key and forces
a re-run) reports as missing through `pathExists`, then `symlink()`
rejects with EEXIST and the unhandled rejection aborts the packaged
build.
Switch to `lstat` (which does not follow the link) so we can tell
"genuinely empty slot", "real directory left by Next" and "stale
symlink" apart, then unlink stale entries before re-creating. Also
move `stripBrokenSymlinks` ahead of `hoistStandaloneNextPeerDeps` in
`copyWorkspaceBuildArtifactsToCache` so any leftover dangling links
that survived a previous run are cleared before hoist tries to write.
|
||
|
|
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 |
||
|
|
899c9fe4d8 | Support nightly and preview package identity (#2437) | ||
|
|
31ca20f2c6 | Add packaged update apply observations (#2429) | ||
|
|
32696d640c |
fix(desktop): keep the macOS Dock icon when the desktop-pet window opens (#2413)
The desktop-pet companion window calls setVisibleOnAllWorkspaces so it floats across every Space. By default Electron's macOS implementation transforms the whole process type between UIElementApplication and ForegroundApplication while applying that call. On Electron 41 / macOS 26 that round-trip races during the launch burst — the pet window is created alongside the main window — and the process can stay stuck as an accessory app: no Dock icon, no menu bar, even though the windows render fine. Pass skipTransformProcessType: true so the pet window's all-Spaces behavior never touches the app's process type. The desktop pet is a cosmetic companion window; the main window keeps the app a regular Dock app. The pet still floats on every Space via its alwaysOnTop floating level. Fixes #2394 |
||
|
|
15d08d4158 | feat: add windows packaged auto update flow (#2362) | ||
|
|
ad37fd30cf | Add desktop updater UI flow (#2270) | ||
|
|
2c128e0e91 | refactor desktop host bridge (#2246) | ||
|
|
4424f08be0 |
[codex] Add packaged desktop auto-update (#1375)
* Add packaged desktop auto-update * Handle counted beta nightly update versions * Refresh desktop auto-update branch for main * Serialize desktop updater operations * Refresh auto-update branch for packaged paths |
||
|
|
e64f1d8497 |
fix(desktop): export PDF saves to a file instead of the OS print dialog (#1920)
* fix(desktop): export PDF saves to a file instead of the OS print dialog The `od:print-pdf` IPC handler called `webContents.print()`, which opens the printer-first macOS system print dialog. That handler is the destination of the renderer's `window.__odDesktop.printPdf()` bridge, so on desktop "Export PDF" felt like a print flow rather than a file export — from both the PreviewModal share menu (which always uses this path) and the FileViewer share menu (whose daemon-route fallback lands here too). Route the handler through a direct Save-as-PDF flow instead: a native Save dialog, then `webContents.printToPDF()` straight to the chosen file — the same shape `exportPdfFromHtml` already uses for the daemon-backed export path. The flow is extracted into `savePrintReadyDocumentAsPdf` behind a structural `PrintReadyPdfTarget` surface that has no `print()` method, so the regression cannot be reintroduced. Fixes #1774 Co-authored-by: multica-agent <github@multica.ai> * fix(desktop): preserve PDF page sizing in print bridge Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4d0ea247a7 |
fix(desktop): swallow setTypeOfService EINVAL crashes in dev main (#647) (#1298)
* fix(desktop): swallow harmless setTypeOfService EINVAL crashes in dev main The packaged Electron entry (apps/packaged/src/logging.ts) already filters the undici "setTypeOfService EINVAL" crash that issue #895 introduced for the prod build, but the dev / source-built desktop entry was missing the parallel guard. Result: switching settings tabs in a from-source desktop run could fire a fresh fetch, undici would try to set IP_TOS on the outbound socket, the kernel would refuse on certain macOS / VPN configurations, and the rejection bubbled to Electron's default handler as the "JavaScript error in the main process" dialog reported in issue #647. Add the same defensive filter to apps/desktop: - isHarmlessSocketOptionError matches only the canonical undici shape (syscall name AND EINVAL code). A contradicting code (EACCES, EPERM, etc) explicitly fails the match so real bugs don't get hidden. - The uncaughtException handler logs harmless cases at warn and returns silently. For anything else it removes itself from the listener list and re-throws via setImmediate, restoring Node's default crash path so Electron's native dialog renders exactly as it would without this filter. - unhandledRejection mirrors the same harmless / fall-through split. The filter is installed BEFORE app.whenReady so it is armed by the time the renderer fires its first fetch. The helper is duplicated rather than imported from apps/packaged because AGENTS.md forbids cross-app private-source imports. The file header calls out the parallel and notes that the two copies should stay in sync until the helper is promoted to a shared workspace package (follow-up); the contract is identical so a regression in one will surface in the other's test suite. Tests in apps/desktop/tests/main/uncaught-exception.test.ts mirror apps/packaged/tests/logging.test.ts: 8 cases pinning the matcher shape, 2 cases pinning the handler's harmless-log-warn vs fall-through-rethrow split. Validated: pnpm guard, pnpm --filter @open-design/desktop typecheck, pnpm --filter @open-design/desktop build, and pnpm --filter @open-design/desktop test (14 passed, 10 new). * fix(desktop,packaged): fail-fast on non-harmless unhandled rejections The previous unhandledRejection listeners logged non-harmless reasons and returned, which kept the main process alive after any rejected promise. A real bug, a failed IPC registration, or any unexpected async exception was reduced to a console line instead of surfacing through Node/Electron's default crash path the filter was meant to preserve. Both copies now route non-harmless rejections through a parallel factory (createDesktopUnhandledRejectionHandler / createFatalUnhandledRejectionHandler) that mirrors the uncaughtException policy: harmless setTypeOfService EINVAL shapes log at warn and return, anything else logs at error, removes the listener, and re-throws via setImmediate. Listener removal happens before the scheduled throw, so the rethrown reason lands in the uncaughtException path with no recursion. Tests cover the harmless branch, the detach + ordered rethrow, and non-Error / primitive rejection reasons (Promise.reject(42)) which must fall through. Desktop suite: 13/13, packaged suite: 16/16. Flagged on PR #1298 by Siri-Ray and the codex P2 review thread; the two file copies stay in lockstep per the AGENTS.md sync invariant. --------- Co-authored-by: Nagendhra <nagendhra405@gmail.com> |
||
|
|
a797e079b1 |
fix(desktop): exit fullscreen before hiding window on macOS close (#1249)
* fix(desktop): exit fullscreen before hiding window on macOS close (#1215) When a preview is in 演示 → 全屏 mode, the macOS close handler called window.hide() directly, leaving the OS fullscreen Space orphaned as a black screen — the window vanished but the Space stayed up. Extract hideWindowExitingFullscreen as the named invariant ("hide, but first leave fullscreen so the OS Space tears down with the window") and route the darwin close handler through it. The hide is deferred until 'leave-full-screen' fires so we don't race the OS Space teardown. Bootstraps Vitest on apps/desktop with a single test under tests/main/hide-window-exiting-fullscreen.test.ts that exercises the helper through a structural mock — the bug shape is pure logic, no real Electron window required. Spec was red against a hide-only helper and green after the leave-full-screen sequencing. * docs(agents): codify bug follow-up workflow Distill the spec-first / cheapest-layer / scope-discipline / invariant-shaped-fix / baseline-diff playbook used recently on #135 and #1215 into a top-level subsection of root AGENTS.md, framed as a default action shape with explicit room for case-by-case judgment rather than a hard rule. Includes a single pointer back to the worked example spec. * docs(agents): require staged human verification for visible bugs Add the human-verification gate as a sixth bullet in the Bug follow-up workflow. UI / platform-native / animation symptoms can pass green specs and still ship the visible regression — proven by #1215, where the desktop unit test green-lighted the helper logic but only a side-by-side buggy-vs-fix run on a real macOS Space proved the black-screen actually went away. Reinforces the production-API-only seed constraint while we're there: source-level backdoors prove a fake flow, not the real one, so they invalidate the verification. * fix(desktop): defer hide across the fullscreen-enter transition (#1215) mrcfps observed on PR #1249 that the close handler only catches windows already in fullscreen — Electron's enter-full-screen event is async on macOS, so isFullScreen() can still read false during the OS Space transition triggered by requestFullscreen(). A close in that window took the plain hide() path and stranded the same black Space the fix was meant to eliminate. Track in-flight fullscreen entry from webContents.enter-html-full-screen (set) and BrowserWindow.{enter,leave}-full-screen (clear), and surface it through WindowFullscreenSurface.isEnteringFullscreen. The helper now parks on enter-full-screen until the OS confirms the Space, then runs the existing exit-then-hide path. Adds a regression test ("waits out a fullscreen-enter transition before exiting and hiding") that goes red against the previous helper. |