mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
codex/design-system-module-only
128 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7d2db7014a |
feat(web): integrate project chat module and offline review demo
Connect the scoped chat implementation to the existing project controller, including history, message navigation, shared composer controls and the queued-message stack. Document the compatibility boundary and include the self-contained demo with client review evidence. |
||
|
|
f7c39b9d2a |
feat(web): show the page taking shape while a run builds it
The Design Files pane used to sit on an empty state until a run finished and the first artifact dropped in. Everything the run was doing happened somewhere the user could not see, and the wait carried no signal that anything was happening at all. It now watches the shallowest index.html the run has produced (or the most recently written HTML file) and renders it live beside a feed of the steps behind it, with a toggle between the step feed and the full preview. A new cross-runtime contract, preview-build-focus, lets the sandboxed preview frame answer "where in this page are you writing right now" — either by a literal string the agent just wrote or by naming one of the page's own top-level sections — so the host can walk a cursor down the page as it is written instead of jumping to wherever the last run of text happens to land. The frame has no allow-same-origin, so it posts to '*' and the host identifies it by event.source. latest-user-prompt goes away with the empty state that echoed it: the pane now names what the run is doing, which is the more useful of the two, and keeping both meant recomputing a prompt string on every streamed token for a line nobody read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ab0a1a4e61 |
feat(web): refresh the home and entry surfaces
Reworks the entry experience end to end — the Home hero and its composer, the
nav rail, the workspace tab bar, the recent-projects strip, the community
gallery and the message center — and adds the per-project run status the rail
and the strip now render.
Home / entry
- Hero headline cycles a noun out of `homeHero.titleWords` (RotatingTitleWord);
locales whose `homeHero.title` carries no `{word}` stay a plain sentence.
- The composer is now one component in two shapes: `page` (Home's full column)
and `dock` (the bar alone, used by the community view), sharing @mentions,
staged files, the working-directory row and submit.
- ComposerModePicker is gone; ComposerPlusMenu absorbs what it offered.
- Entry layout, nav rail (with RailRecentRow), workspace tabs and the recent
projects strip are restyled together against one set of measurements.
Run status
- New `projectRunStatus` state, `useProjectRunStatuses`, `run-progress` and
`latest-user-prompt` runtime helpers, and `ProjectRunStatusIcon`.
- `ChatRunsResponse` gains `awaitingInputProjectIds`: a run that asked a
question reads `succeeded` while the project is still blocked, so a caller
rendering per-project status needs the set to avoid showing such a project
as finished. Optional — absent means unknown, not none.
Community / message center / design files
- Community gallery gets its own empty state, and the message center gets
archive plus a reworked card.
- Design files panel gets a dedicated empty state component.
Daemon / desktop / sidecar
- Project create bounds its read-only preparation work (15s) so a stalled
local catalogue scan cannot strand the web on an optimistic route or leave a
half-initialized project behind.
- The startup splash runs the same pixel-scan wordmark as the Home hero,
ported to raw WebGL because the splash renders before the sidecar boots
(replaces the splash video).
- Sidecar proxy tracks the in-flight request/response so a keepalive replay
tears down the previous attempt instead of leaking it.
New visuals: SiriOrb, MarqueeLabel, SpaceBackground, the composer beam and the
chat-cards-next surface.
No placeholder data ships with this: the community card byline (author name and
view/remix counts) and the message center's temporary cover image are removed,
so the card footer shows the title only and the media slot renders only when a
message payload actually carries `imageUrl`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016cbrF36bYyw9qpnksvZTjd
|
||
|
|
34ad192de9 |
fix(web): recover aborted srcdoc preview navigation (#6882)
* fix(web): recover aborted srcdoc preview navigation * fix(web): close srcdoc recovery races * fix(web): version srcdoc base navigation |
||
|
|
7a37c1462d |
feat(export): add reliable standalone HTML export (#6855)
* feat(export): add standalone HTML export Bundle project-local HTML, CSS, JavaScript, images, fonts, workers, and iframe dependencies into one offline-safe file. Expose the same contract through the web download flow and od CLI, with structured failures and bounded output assembly. * fix(daemon): honor standalone source URL semantics Resolve document assets against local base href values, reject browser-invalid TypeScript and JSX sources with a structured 422, and count unquoted first-level resources before graph traversal. Validated with daemon tests, workspace typecheck, and guard. * fix(daemon): handle standalone data scripts and CSS URL tokens Preserve non-JavaScript script payloads as data blocks and scan CSS values without treating strings or comments as project dependencies. Validated with focused and full daemon tests, workspace typecheck, and guard. * fix(daemon): classify standalone export resources correctly Preserve non-HTML iframe MIME types, inline only resource-bearing link relations, and propagate unexpected Vite dist read failures instead of silently falling back. Validated with focused export tests, the full daemon suite, guard, and workspace typecheck. * fix(export): preserve version and stylesheet semantics Hide standalone HTML export for historical versions until dependency snapshots are available, avoiding an action that always fails. Keep disabled and alternate stylesheets as embedded link elements so standalone export preserves activation semantics. Validated with focused red/green coverage, the full web suite, pnpm guard, and pnpm typecheck. --------- Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> |
||
|
|
9a402b2e4c |
feat(campaign): extend the DeepSeek V4 campaign to Pro + Flash (#6861)
* feat(campaign): extend the DeepSeek V4 campaign to Pro + Flash The V4 Flash free week ends 8/13 20:00; a two-week window offering BOTH V4 Pro and V4 Flash opens at that same instant and runs to 8/27 20:00. This grows the campaign already in production rather than landing a second stack beside it. That implementation ran the free week for a week: its audience resolution (wallet balance is not a subscription), i18n wiring, localized countdown, frequency control and attribution are all already exercised, and it carries a product decision this change must not undo -- the URL review backdoors (campaign / audience / usage overrides) were deliberately removed, and reviewing the campaign is done by overriding `window.startAt`, never through query parameters. Almost all of it is the campaign module growing a second model: `isDeepSeekV4FlashCampaignModel` now matches either id, which is why the model switcher badges both without its rendering changing at all, and the modal's benefit line is an i18n key, so naming both models was copy. `「立即使用」` now switches a paid user to Pro, the headline benefit. Two things needed real edits. Model-benefit impressions fired once with a hardcoded Flash id; product compares the two models' reach separately, so they now fire once per campaign model actually on screen, each with its own `model_id`. And `campaign_id` / `element` move to the spec's `deepseek_v4_pro` naming, with `deepseek_v4_flash` kept declared so the finished campaign's warehouse rows stay valid. Copy is rewritten across all 19 locales -- dates, the two-week framing, both model names, and the peak-hours queue caveat product added. Dates follow each locale's existing format rather than one imposed format, so only the day numbers move. Frequency control is keyed on the campaign id, and the id is new, so a user who dismissed the free week is still owed this campaign's single showing. That was an invisible dependency on remembering to change the id; it now has a test. Module and i18n key names keep their `…V4Flash` spelling: renaming 28 keys across 19 locales mid-campaign is risk without user-visible benefit, and belongs in a cleanup pass once the campaign is off. * fix(web): read a rolling model window as a wait, not a failure vela's hosted gateway rejects a request that exhausted its rolling per-model window with `model_limit_exceeded`, phrased for API callers as "You have reached the 5-hour usage limit for X. Try again after <instant>. This request was not charged to Wallet Credits." Two things went wrong with that sentence on the way to a user. The daemon's `isHardQuotaText` matches its "usage limit" wording, so a window that resets on its own was classified as `hard_quota` — an exhausted quota, marked non-retryable. Nothing was charged and nothing needs topping up, so that both misnames the cause and counts a self- healing wait against reliability as a real failure. The web runtime then had nowhere to put it. The AMR branch of `resolveRunFailureUi` ends in a catch-all returning the generic "task failed" title with a null message key, which renders the raw upstream string — so a localized chat showed an English sentence under a wrong heading. Home is worse: its send path fails before a run exists, never reaches that mapping at all, and prints `err.message` verbatim. Classify the window separately, ahead of the hard-quota reading, and give both surfaces the same localized copy: high demand, when it reopens, and the reassurance that nothing was charged. The instant is rendered in the reader's locale — the gateway reports UTC. Both surfaces read the signal through one shared contracts module so they cannot drift, and degrade to copy that does not promise a time when no instant is readable. Other rate limits are untouched: a genuine session-limit exhaustion keeps its hard_quota reading, and every other business failure on Home still prints verbatim. * fix(analytics): keep a campaign entry for the whole campaign The attribution record expires after seven days, but this campaign runs fourteen. A visitor who clicks the banner on day 1 and subscribes on day 11 arrives at checkout with no entry left to attribute the payment to, so the campaign under-reports against the metric it is judged on -- 活动归因付费人数 / 金额, both defined as payments carrying its `campaign_id`. Extended only for entries that carry a `campaignId`. Raising the window globally would move the attribution口径 for every other entry point and the dashboards already built on it; the campaign is the thing that needs a longer memory, so it is the thing that gets one. Past fourteen days a campaign entry expires like any other -- the longer window is scoped to the campaign, not an open-ended exemption. * fix(campaign): carry the new campaign id through ingest and e2e fixtures Two places still recognised only the finished free week's id, both found in review. The daemon's AMR entry ingest allowlist is fail-closed: an unrecognised `campaignId` voids the WHOLE entry rather than just its campaign field. With only `deepseek_v4_flash` listed, every entry this campaign produces would have been dropped at ingest -- and its success metrics are defined as payments carrying its `campaign_id`, so it would have reported zero conversions while converting normally. Both ids are listed now, since entries minted during the free week can still arrive inside their attribution window. The Playwright suite pre-seeds a dismissal so the campaign modal never opens over a functional spec. That seeding was keyed on the old campaign id, so the modal would have covered Home in every UI spec. `home-hero- rail` also clears storage and re-seeded its own hardcoded copy of the key; it now reapplies the shared map instead, so a future campaign cannot be dismissed in one place and left interrupting specs in the other. * fix(campaign): land the workbench badge on the console, like the modal The badge opened a hardcoded `https://open-design.ai/zh/pricing/` while the modal's CTA opened the console's plan surface scoped to the caller's workspace. Both are in-product entry points for a signed-in user, so one funnel was being split across two destinations -- and only one of them lets the user actually subscribe. The hardcoded link also pinned `/zh/`, so every non-Chinese user landed on a Chinese page from a badge that renders in all 19 locales. Now shares the modal's destination, including the workspace scope, so the console can resolve the right upgrade surface for that workspace. Verified against a real browser: the badge opens `/amr/dashboard?workspaceId=…&billing=plan` carrying entry id, source, campaign id, conversion source and device id. |
||
|
|
91f34d66e9 | fix web powered preview behind reverse proxies (#5592) | ||
|
|
b55d443b44 |
fix(web): streamline Home submit preflight (#6756)
* fix(web): retry transient home balance preflight * refactor(web): streamline home submit preflight * fix(web): keep local submit independent from cloud identity * fix(web): preserve local and legacy submit lanes * fix(web): keep in-memory workspace witness valid * fix(web): retain workspace preflight compatibility * fix(web): require workspace witness for team plugin apply * fix(plugins): apply exact local plugin source * fix(web): invalidate legacy witness on identity change * refactor: keep Home submit local-first * test: align local-first creation coverage * fix: preserve exact local plugin context * fix: fence exact local plugin use * fix: disable inexact legacy plugin fallback * test: mock exact plugin apply in visuals * fix: reject unsupported exact project source * Revert "fix: reject unsupported exact project source" This reverts commit |
||
|
|
e44bd3f907 | fix(web): recover stalled iframe preview refreshes (#6714) | ||
|
|
1ca8620e9f |
feat(web): report preview iframe failures to PostHog (#6671)
* feat(web): report preview iframe failures * fix(web): preserve preview error stacks * fix(preview): scope and normalize observability bridge * fix(preview): ignore retained iframe diagnostics * fix(preview): consume early diagnostics once |
||
|
|
4b720a0045 |
fix(web): stop replaying stale succeeded Design runs on reload (#6578)
* fix(web): stop replaying stale succeeded Design runs on reload A Design-mode assistant message can be persisted as `succeeded` with its delivery metadata (`resultDeliveryState`/`producedFiles`/`traceObjectFiles`) absent — historical rows created before a final project-file refresh, or after an interrupted persistence path. `designDeliveryVerificationPending` has no age bound, so `shouldReplayTerminalRunMessage` returns true for such rows forever: every reload re-enters the reattach path, and if the SSE replay drops, `onError` reverts the message to `succeeded` without sealing `completedReattachRunsRef`, looping indefinitely. Bound the reconciliation window: a succeeded Design row whose `endedAt` is older than DESIGN_DELIVERY_RECONCILIATION_WINDOW_MS (5 min) is no longer auto-replayed — it renders as a terminal outcome instead. Freshly completed runs (or rows without a terminal timestamp) keep the existing one-shot reconciliation. * fix(web): bound Design replay reconciliation from startedAt/createdAt too Addresses #6578 review feedback: - Bound the #6505 reconciliation age from any persisted terminal timestamp (`endedAt` then `startedAt` then `createdAt`) so legacy rows persisted before `endedAt` existed are also flagged stale and stop auto-replaying on reload. - Widen the `designDeliveryReconciliationStale` input type to include `startedAt`/`createdAt` so the legacy-shape fixtures typecheck. - Update synthetic terminal timestamps in tests to realistic values so the real-clock age bound does not read them as years old; add a regression for the no-`endedAt` legacy shape. |
||
|
|
04c6af5b57 |
fix(web): count unique produced files in "Files from this turn" header (#6420)
* fix(web): count unique produced files in "Files from this turn" header
The disclosure header summed per-op tool_use counts, so a file written
or edited several times inflated the total (e.g. "Write 9" for 6 files,
"Edit 4" for 2 edited files) — mismatching both the titled semantics
("Files from this turn") and the unique-file rows below it (#5909).
Count each unique file once in the header, categorized by its primary
artifact op (edit > write), matching how each row's badge is chosen.
The op-level counter (countFileOps) is unchanged for other callers.
Red spec: flip the existing FileOpsSummary component assertion from the
op-level "Edit 4" to the file-level "Edit 2", plus a unit test for the
new countArtifactFileOps helper.
* test(web): cover the write-then-edit path for the artifact file count
The countArtifactFileOps case only exercised a read+edit file for the
`edit > write` rule. The #5909 repro path is create + edit the SAME file,
so switch the b.ts entry to write-then-edit and assert it still counts as
one edit (not one write + one edit), while the op-level counter sees the
additional write (roian6 / lefarcen review on #6420).
|
||
|
|
4de25d3f1b |
fix(web): 让长演讲者备注在 presenter 视图里滚动而非裁切 (#6271) (#6314)
问题:.presenter 视图里 .notes 这个 aside 作为 body 的 grid item 默认
min-height: auto,不收缩;body 的隐式 grid row 也 auto,跟着内容撑。
当备注文字到几百词时,.notes 把 body row 撑过 viewport,而 body
{ overflow: hidden } 直接切掉底部,.notes-body { overflow: auto }
永远不触发 —— 用户看不到下面的备注,也没有滚动条 (#6271)。
修复:
1. body 加 grid-template-rows: minmax(0, 1fr),把 implicit row 锁到
viewport 高度,row 不再随内容增长。
2. .notes 加 min-height: 0,让 grid item 在 bounded row 里可以收缩,
.notes-body { overflow: auto } 才能真正生效。这跟 .stage 已有的
min-height: 0 模式对称。
回归测试:在 speaker-notes.test.ts 加一个 describe,80 行 ~800 词的
备注塞进 buildSpeakerNotesPresenterHtml,断言:
- body 的 CSS 里包含 grid-template-rows: minmax(0, 1fr)
- .notes 的 CSS 里包含 min-height: 0
- .notes-body 仍然带 overflow: auto
- 完整长备注文本被原样写入 od-presenter-data
锁住这几个不变量,后续 refactor 不会再让长备注被静默裁切。
Co-authored-by: Ray Xi <2667192167@qq.com>
|
||
|
|
356c8c364f |
feat(workspace-team): team workspaces, shared resources, workspace-scoped billing, and the #5517 redesign (#6142)
* fix(web): scope plugin URL imports to workspace * test(web): cover billing interest remount lifecycle * feat(packaged): enable the workspace-team transport for test-profile builds The vela test backend now serves the workspace-team API, so a packaged build baked with the test AMR profile should drive team projects / collab / resource sharing there instead of leaving them dormant. Replace the single feature-test equality check with a profile -> vela web origin map so adding a backend is one entry, and keep every unlisted profile (prod above all) off. * test(daemon): cover billing catch-up after SSE reconnect * fix(collab): surface and start first materialization when opening a shared project QA P0: a brand-new member on a fresh install joins someone's workspace, opens a project from it, and sees no loading state and no files — content only appears on a SECOND open, much later. Two defects, both on the first `/collab/status` request that opening a shared project makes. 1. No pull is started. The daemon's self-materialization block was gated on `callerIsOwner`, so a non-owner member got the placeholder record registered and nothing else. The web's auto-pull is gated on `publishedVersion` advancing past its cursor, and a fresh daemon's first status response cannot carry a published head: `collab.publishedVersion()` reads an in-process map that has never been written, and the real hub head is fetched fire-and-forget into `headEnrichmentCache` for a LATER poll to consume. Materialization therefore arrived only whenever a proactive lane (hub push / reconnect catch-up / the 30s recovery floor) next fired — the "second open" the report describes. 2. No download state is shown. `useProjectCollab.downloadPending` — which picks DesignFilesPanel's "Syncing files from the team…" over its empty state and create-a-file CTAs — reads only `publishedVersion > cursor`, `contentTransferState`, and an in-flight pull. All three are blank on that first response, so the member was shown an empty project inviting them to start creating over content that was still on its way. The fix makes the placeholder stamp the load-bearing signal on both sides: - `materializePlaceholderOnOpen` (routes/collab-sync.ts) replaces the owner-only block: any viewer opening a shared project whose only local record is an unmaterialized placeholder starts its pull on that same request, through the same coalesced flow. The retracted-share heal in its catch stays owner-only — a member who hits the hub tombstone has merely lost access and must not unshare anyone's project. - `/collab/status` reports `awaitingFirstMaterialization`, a purely local, synchronous fact that needs no hub round-trip and is therefore available on the very first response. `od collab status` prints it too. - `localFilesAreNotTheContentYet` (useProjectCollab.ts) makes it always download-pending, bypassing the member-only `shouldAutoPull` gate: whether the local files are the content is a fact about the record, not about who may pull it, so the owner of an unmaterialized placeholder (the reinstall case, recvqzaDvUU6B3) stops seeing the same empty state too. Red evidence: all six new specs fail on origin/feat/workspace-team @ |
||
|
|
b99a9fdc3d |
feat(manual-edit): add direct manipulation and reliable history (#5890)
* feat(web): manual edit v2 direct manipulation (v2.2–v2.4) Selection overlay with action bar and drag/resize handles, floating text toolbar with range-format highlight (queryCommandState round trip), Manus color picker, image replace/crop/paste/drop, element copy/duplicate/delete, and the systematized in-place content pipeline: every element-scoped patch now reconciles the live iframe DOM from the saved source over od-edit-apply-dom (replace/insert-after/append-child/prepend-child/remove) with positional identity restamping, so commits, undo and redo never reload the canvas, never flash, and never move the scroll position. Frozen-source reloads remain only as a scroll-capturing fallback. Includes the Chromium Home/End double-booking fix, the dblclick word-selection guard, and light targets-broadcast dedupe. * test(web): manual edit v2 unit and e2e coverage Bridge (in-place apply ops, positional restamp, selection format state, Home/End consumption, dblclick selection guard), source-patches (insert readback, restore descriptors, sanitization), gestures/shortcuts/color units, and host-level FileViewer suites proving content commits mutate the canvas without a srcDoc reload, hand selection to inserted elements, and record labeled manual versions for undo/redo. Also realigns the manual-edit-history and srcdoc-reload-races helpers with the v2.1 opt-in inspector two-step flow. * docs(specs): manual edit v2 architecture spec Six-layer map (identity/chrome/style/content/history/external-sync), core invariants (DOM equals saved source without reloads; positional identity restamps on structural change; every save is a labeled manual version), patch-kind application matrix, performance contract, and the v2.1–v2.4 revision log including the real-browser verification round. * fix(tools-dev): fail fast when a sidecar process dies during startup wait waitForDaemonRuntime/waitForWebRuntime now accept a liveness probe for the spawned pid, so a crashed daemon/web process surfaces immediately instead of burning the full startup timeout. * fix(web): edit brand-kit pages in place — no reload flash, no scroll jump Brand-kit targets carry runtime-annotated ids with no markup of their own in the saved source (edits persist into the payload / runtime overrides), so the in-place readback came up empty and every commit fell back to a srcDoc reload — the flash-and-jump-to-top the user hit when editing text after an image. New od-edit-apply-dom op 'apply-content' mirrors the patch fields (text/href/src/alt/html/attributes, on*/data-od-* filtered) onto the live element instead, and set-outer-html reuses replace with the patch html. The reload fallback's scroll restore also gains a clamped-only retry ladder (600–4000ms) so async-rendering pages (brand kit renders after load) no longer land at the top when a reload is genuinely needed. * refactor(FileViewer): unify manual edit history management and enhance undo/redo functionality This update introduces a consistent approach to managing manual edit history and undo/redo stacks by utilizing refs to mirror state. The new `updateManualEditHistoryStack` and `updateManualEditUndoneStack` functions ensure that the state remains in sync with the refs, preventing stale data issues during undo/redo operations. Additionally, the handling of in-session undo (Cmd+Z) has been improved to escalate to the host's global history when no local changes remain, ensuring a seamless user experience. Tests have been added to verify the correct behavior of these features. * feat(manual-edit): enhance manual edit functionality with space scaling and improved gesture handling This update introduces a new `ManualEditSpaceScale` interface to manage the scaling between viewport pixels and CSS pixels, ensuring accurate positioning during manual edits. The `manualEditMovePreviewTransform` and `manualEditResizeStyles` functions have been updated to utilize this scaling, improving the precision of drag and resize gestures. Additionally, the `ManualEditSelectionOverlay` component now maintains pointer capture during gestures to prevent interaction issues with nested iframes. Tests have been added to verify the correct behavior of these enhancements, ensuring a smoother user experience in manual editing scenarios. * feat(FileViewer): add localized toasts for image upload process in manual edit This update enhances the manual editing experience by introducing localized toast notifications for the image upload process. New messages for uploading, processing, and successful addition of images have been added to the i18n files. The `insertManualEditImage` function now manages these notifications, providing users with real-time feedback during image uploads. Additionally, tests have been implemented to ensure the correct display of these toasts across different locales. * fix(manual-edit): drag whole image to move + lock selection frame to element box (#5888) Two direct-manipulation fixes reported from real prototype/landing/deck edits: 1. Move by dragging the image body, not only the top pill. Adds a full-interior moveBody surface inside the selection frame that starts the same undoable move gesture; scoped to image targets so text/link bodies keep click-to-edit. Rendered under the resize handles so edges still resize. 2. Keep the selection frame locked to the element during resize. The frame previously drew the gesture-intent x/width and only adopted the iframe's measured y/height, so an element that re-centers, caps at max-width, or reflows on width change left the frame drifting horizontally off the picture. adoptMeasuredExtent now takes the full measured rect and displayRect follows it, so the chrome always wraps the element. Adds ManualEditSelectionOverlay tests for body-drag move and measured-box frame sync; records the change as spec v2.5. Co-authored-by: Claude <noreply@anthropic.com> * test(web): stabilize manual edit browser validation * fix(manual-edit): address structural edit review blockers * fix(manual-edit): preserve runtime formatting and restore order * fix(manual-edit): flush toolbar styles before navigation * fix(manual-edit): preserve runtime target identity * fix(manual-edit): serialize saves and image handoff * fix(manual-edit): clear stale runtime clipboard * fix(manual-edit): scrub rich text identities * fix(manual-edit): save inspector text after formatting * fix(manual-edit): action-bar boundary avoidance, select-without-reflow, image paste shortcut (#5892) Three edit-mode canvas fixes from user feedback: 1. Action bar no longer covers the element frame/handles. A small or edge-hugging element previously had its move pill and resize handles hidden under the floating action bar (clamped to the top), so it could not be moved or resized. New pure helper manualEditActionBarTop places the bar above the frame, flips it below when the element hugs the top edge, and keeps it inside the canvas; horizontal clamping already handles the side edges. Adds a data-placement attribute. 2. Selecting a text element no longer makes it jump/grow. The bridge fused 'select' and 'enter inline edit' into one click, and making a deck text box contenteditable releases its line-clamp/height cap so it visibly reflows larger the instant it is selected. The first click now only selects (no contenteditable, no reflow); editing begins on a second click of the already-selected element or a double-click — matching how images/containers already select. 3. A selected image/container can be pasted with Cmd/Ctrl+V. The native paste event only fires while an editable element holds the caret, which never happens for non-text selections, so the shortcut did nothing for images. The bridge keydown handler now forwards od-edit-paste-request for a selected non-editable element (paired with the existing Cmd/Ctrl+C copy). Updates the 14 inline-edit bridge tests to enter edit via double-click and adds coverage for all three fixes; recorded as spec v2.6. Co-authored-by: Claude <noreply@anthropic.com> * fix(manual-edit): image corner scaling, nowrap unlock on resize, restore image paste Three edit-mode canvas fixes from user feedback: 1. Images get four corner anchors that scale aspect-locked from any corner, anchored at the opposite corner (new resize-nw/ne/sw/se gesture kinds). The dominant pointer axis drives the scale, the floor binds the larger dimension, and corner gestures skip snapping so the ratio never breaks. Corner resizes persist width AND height (space-scale corrected) plus the offsets that keep the anchored corner fixed. 2. Resizing an element with white-space: nowrap (or pre) now unlocks wrapping (normal / pre-wrap) alongside the width — a narrowed heading previously stayed on one line and overflowed its own new box. ManualEditStyles and the bridge style props gain whiteSpace. 3. Image paste works again. The v2.6 Cmd/Ctrl+V keydown branch called preventDefault, which suppresses the native paste event — the only place clipboard image bytes are readable — so pasting an image did nothing. The shortcut now arms a 150ms one-shot element-paste fallback instead; any real paste event disarms it and routes image vs element itself, so the two channels never double-fire. Adds gesture/overlay/bridge coverage for all three; recorded as spec v2.7. * fix(manual-edit): replay runtime history in place * fix(brands): reject scraped CSS junk font families before token emission An extraction can capture CSS source text instead of a real font family (e.g. Tailwind v4's `--theme(--default-font-family` from aliyun.com). Interpolated raw into the kit's `--brand-font-family`, the unbalanced parenthesis swallows every later :root declaration, so the component kit renders unstyled (UA serif, collapsed buttons) while earlier color tokens still work. isRenderableFontFamily() now guards both emission seams: the seed's fontStack (kit/variables/artifacts token block) and brandFontAssets' display-family stack (h1-h3 face). Junk families drop out and the renderable fallbacks + system stack take over. * fix(manual-edit): replay runtime-only style history from the persisted override rule Runtime-only brand-kit targets persist set-style patches in the style[data-od-manual-edit-runtime-overrides] rule, not as element inline styles. The in-place history replay read inline styles only, so redo of a runtime style change replayed empty values while reporting success — the file held the redone style while the canvas stayed on the undone one. readManualEditSavedStyles() now reads a target's persisted styles wherever they live (inline attribute for source-backed nodes, the runtime override rule otherwise), and the set-style history branch replays through it. The same inline-only read also made reconcileManualEditStyleSave() treat every runtime style save as "target vanished" — erroring, dropping the selection, and reloading the canvas — so it now consults hasManualEditRuntimeStyleOverride() before taking that path. Regressions: source-patches unit coverage for the saved-styles read and the override predicate, plus a FileViewer spec that styles a runtime target, undoes and redoes it without any srcdoc change, and asserts the redo preview carries the persisted value (red on the previous head). * fix(manual-edit): merge partial runtime style patches into the persisted rule Successive debounced toolbar autosaves send only the touched property, but setRuntimeStyleOverride() rebuilt the target's override rule solely from the incoming patch, so a runtime-only brand-kit target lost its earlier saved declarations (fontSize save, then color save -> font-size silently dropped). Persistence now merges the incoming patch into the existing rule declarations, treating empty incoming values as deletions and removing the rule once its last declaration is deleted. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d8b6b797fe |
Simplify chat execution disclosures (#5667)
* Simplify chat execution disclosures * test: cover collapsed file disclosures * refactor(web): consolidate chat task activity * fix(web): show small file outputs inline * fix(web): separate file outputs from reads * fix(web): collapse completed todo cards * refactor(web): compact completed todo lists * fix(web): make completed todos expandable * fix(web): keep four output files visible * refactor(web): clarify chat activity timeline * fix(web): make file output disclosure interactive * feat(web): emphasize chat run states * style(web): simplify chat run status colors * style(web): remove chat run status icons * feat(web): stream one chat activity at a time * style(web): align chat completion row * fix(web): simplify priority chat states * feat(web): simplify secondary chat details * style(web): simplify secondary chat hierarchy * fix(web): keep execution state out of artifacts * fix(web): use icons for artifact operations * style(web): frame todo disclosure header * fix(web): clarify artifact and tool semantics * fix(web): clarify chat disclosure hierarchy * fix(web): keep execution history neutral * fix(web): simplify user action cards * fix(web): align failure card actions * fix(web): refine chat action hierarchy * fix(web): address chat disclosure review feedback * fix(web): restore chat recovery actions * fix(web): make collapsed action details inert --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> |
||
|
|
c9e622d0a9 |
fix(daemon): ship baseline OpenCode for Windows and stop retrying AVX2 illegal-instruction crashes (#5733)
* Classify AVX2 illegal-instruction agent crashes as cpu_unsupported and stop retrying them The Windows opencode.exe bundled with the AMR runtime is a Bun build that requires AVX2. On CPUs without it (Intel Atom/Celeron/Pentium N-series through 2021) the binary dies with STATUS_ILLEGAL_INSTRUCTION before readiness; vela surfaces an ACP fatal, the daemon classified it as a retryable fatal_rpc_error, and the automatic retry deterministically failed again. 88 of 105 pre-readiness Windows crash traces in Langfuse are this single failure. The crash text (Bun 'Illegal instruction' banner with a no_avx2 CPU feature line, or the raw 0xC000001D / decimal 3221225501 exit status) now classifies as process_exit/cpu_unsupported ahead of the fatal_rpc_error close-reason promotion, is never auto-retried, and renders a dedicated error card telling the user to update Open Design instead of a dead Retry button. * Bump @powerformer/vela-cli to 0.0.23 so packaged Windows builds bundle the baseline (non-AVX2) OpenCode The 0.0.23 win32-x64 platform package compiles the OpenCode fork with --target=windows-x64-baseline, so the bundled opencode.exe no longer requires AVX2 and starts on Intel Atom/Celeron/Pentium N-series CPUs. Verified in vela CI: the build log shows the baseline target and the new post-publish smoke job boots the published bundle's opencode.exe on a real Windows runner (powerformer/vela#838). * Refresh Nix pnpm deps hashes for the vela-cli 0.0.23 lockfile change * Add the abort-after-panic (exit status 3) production shape to cpu_unsupported specs A user on an AVX-but-no-AVX2 CPU (Sandy/Ivy Bridge era) hit the same illegal-instruction crash, but Bun panicked during its own panic handler and abort()ed, so vela reported exit status 3 instead of STATUS_ILLEGAL_INSTRUCTION. The stderr banner (no_avx2 / Illegal instruction) is the only signal; encode the real Langfuse trace shape so the classifier keeps catching it. * Bump @powerformer/vela-cli to 0.0.25 so packaged Windows builds bundle the baseline (non-AVX2) OpenCode 0.0.25 is the first stable cut after powerformer/vela#838 merged: its win32-x64 package compiles the OpenCode fork with --target=windows-x64-baseline (verified in the release CI log), the post-publish smoke boots the published bundle on a real Windows runner, and the lockfile regenerator now pins the baseline asset so a routine version bump cannot silently flip back. 0.0.24 predated that merge and still shipped the AVX2 build. * Tighten cpu_unsupported to precise signals only (no bare 'Illegal instruction') Review is right that a bare illegal-instruction line is too broad: an unrelated SIGILL on an AVX2-capable machine (runtime bug, corrupted jump) would be mislabeled 'Processor not supported' and lose its retry. Bun's crash banner always prints the CPU-feature line, so machines actually missing AVX2 are identified by no_avx2; the bare Windows STATUS_ILLEGAL_INSTRUCTION renderings (0xC000001D / 3221225501) stay because those runs carry no banner at all. Generic illegal-instruction banners without no_avx2 return to process_crashed / fatal_rpc_error, locked by a new negative spec. * Refresh Nix pnpm deps hashes for the vela-cli 0.0.25 lockfile change * Gate raw STATUS_ILLEGAL_INSTRUCTION on the bundled-opencode startup context |
||
|
|
ace06eac10 | fix(web): respect preview viewport in image exports (#5828) | ||
|
|
85ec1b6243 | fix(web): sanitize deck thumbnail markup with DOMPurify (#5847) | ||
|
|
c8bad6bcb4 |
fix(web): keep report-only Design runs off the ARTIFACT_NOT_FOUND path (#5724)
A successful Design turn with zero produced files is only a missing deliverable when the run actually attempted to mutate project files. Image-analysis and report-only audit turns answer in prose without ever calling a write tool; classify them as report_only and let them finish as normal text results instead of appending ARTIFACT_NOT_FOUND. |
||
|
|
05cb03c8ab | fix(web): sandbox the speaker-notes presenter deck iframes (#5571) | ||
|
|
167db9de20 |
feat(web): add preview delivery status feedback (#5557)
* feat(web): add preview delivery status feedback
* fix(web): keep delivery retry in chat
* feat(web): stage preview delivery progress
* refine(web): clarify preview delivery stages
* fix(web): preserve preview delivery feedback
* fix(web): address preview delivery review
* refine(web): keep preview delivery status passive
* Revert "refine(web): keep preview delivery status passive"
This reverts commit
|
||
|
|
b5d9a12f48 |
fix(web): break redirect-loop scripts that freeze the HTML preview (#5469)
* fix(web): break redirect-loop scripts that freeze the HTML preview A generated or hand-edited artifact carrying a self-redirecting directive — most reliably a `<meta http-equiv="refresh">` that reloads the same document, or a cycle of meta refreshes (A -> B -> A -> ...) — reloads the preview iframe forever and pegs the main thread until the whole design workspace freezes. There was no loop detection or hop limit, so the preview stayed unusable. Add an in-iframe circuit breaker (`injectPreviewRedirectGuard`, always injected by `buildSrcdoc`) that counts meta-refresh hops in `window.name` — the one store that survives an iframe navigating itself — resets the count once a full window elapses with no further refresh (timeout safeguard so a slow legitimate auto-refresh never accumulates), and once the hop budget is exceeded, or immediately for a near-instant self-refresh, strips the offending `<meta>`, stops the frame, and posts `od:redirect-loop-blocked` to the host. The host parks the srcDoc iframe on a static "loop detected" placeholder, the reliable stop the browser's unforgeable `window.location` otherwise denies. Route any self-redirecting source onto the srcDoc path (`htmlNeedsRedirectGuard`) so the guard is always present instead of the URL-load path serving the document raw and unguarded. Covered by a runtime spec that runs the real injected guard through a VM modelling a sandboxed iframe reloading itself (hop budget, window reset, immediate self-refresh kill, chain-break, host message) plus render-mode detection specs. Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/94709b20-f403-4ed7-a960-1e26572fa394 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) * fix(web): cover srcdoc redirect guard gaps * fix(web): avoid redirect guard false positives |
||
|
|
1278816a8f |
fix(web): route cross-project chat file links in-app instead of a home-page window (#5611)
* fix(web): route cross-project chat file links in-app instead of a home-page window
Chat file links pointing at @-referenced projects arrive as absolute
managed-projects disk paths or app file routes for another project.
asInProjectFilePath refused both, so the click fell through to the
default target="_blank" behavior and Electron's window-open handler
produced a detached child window whose unroutable path rendered the
home screen instead of the file.
resolveChatFileLink now classifies those hrefs and navigates to the
owning project's file route in the same window; any remaining
schemeless path-like href is swallowed instead of falling back to the
useless home window. Thinking-block markdown gets the same handler.
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* worker: 当有文件链接在chatpane中出现时,点击文件链接唤起的是home页的预览
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* fix(web): classify owning-project chat links before the basename fallback and keep the handler on opener-less surfaces
Review round on #5611:
- resolveChatFileLink now classifies hrefs that name their owning project
(app file routes, managed-projects disk paths) BEFORE delegating to
asInProjectFilePath, so a cross-project /…/projects/other/index.html can
no longer be captured by a same-named file in the current project and
silently open the wrong workspace tab. Legacy name-keyed directories
(projects/<name>/ from 0.10.x previews) consequently navigate to that
name instead of basename-matching — an in-window bounce at worst.
- chatFileLinkClickHandler is always installed; only the workspace-file
open action is gated on onRequestOpenFile. Surfaces that mount the chat
without a workspace opener (design-system chat in DesignSystemFlow)
now still navigate cross-project targets and swallow unresolvable
path-like hrefs instead of falling through to the detached home window.
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* chore: drop the looper task attachment accidentally committed to the branch
.looper-attachments/ is runner-local reference material and must stay out
of git; a stray worker commit (
|
||
|
|
ad584ff901 |
fix(web): keep deck page counters in sync during keyboard paging (#5594)
* fix(web): keep deck page counters in sync during keyboard paging Host-driven deck navigation (keyboard shortcuts and prev/next buttons) used to mutate slide classes/transforms directly in the srcdoc bridge, bypassing the artifact's own navigation script. Decks that render their own page counter from an internal index never saw the move, so the bottom page number stayed frozen on the first slide while the canvas advanced. The bridge now prefers dispatching the navigation key to the artifact's own keyboard handler and only falls back to direct DOM mutation when no handler moves the deck. The mirror gap: iframe-focused keyboard navigation on transform-track decks mutates only the shared track element, which the bridge's MutationObserver (slide attributes only) never saw, leaving the HOST counter stale. The observer now watches the track's style too. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) * worker: 使用键盘快捷键翻页的时候,下面的页码不会变动 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) * fix(web): wait out deferred deck key handlers before the bridge's direct-DOM fallback Review follow-up for #5594: the artifact-key probe treated only a synchronous index change as proof the artifact handled the key. Decks that defer their keydown state update (requestAnimationFrame or setTimeout) fell through to direct DOM mutation immediately AND received the key twice — the window dispatch and the document re-dispatch both reach a deferred window listener — so one host "next" advanced two slides and desynced the artifact's internal index. The probe is now asynchronous: dispatch to one target, then wait one bounded mutation window (MutationObserver on class/style/hidden plus a 48ms cap) for the active index or track transform to change before trying the next dispatch target or the direct-DOM fallback. Synchronous handlers still resolve on a zero-latency fast path, and a responsive dispatch target is cached so later keypresses skip re-probing. Decks with no keyboard handling must not pay the probe wait: the key path is gated on the artifact actually shipping keyboard navigation, seeded from a source scan of the artifact html before any od bridge is injected plus registration-time tracking through the existing addEventListener patch — the same two-pronged pattern the od:slide message-listener flag already uses. This preserves the pinned invariant that the bridge-owned fallback stays synchronous for handler-less decks. Regression fixtures: a deferred window-listening deck (red on the synchronous probe with a visible double advance to slide 3) and seed-scan assertions proving od-injected bridge scripts do not contaminate the keyboard-navigation flag. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) * fix(web): catch external-script deck keyboard runtimes with a head-start registry hook Review follow-up for #5594: detectArtifactKeyboardNavigation() only sees inline HTML bytes, so decks that load their keyboard runtime via <script src> (design-templates/html-ppt wires ../assets/runtime.js, which registers an ArrowRight/ArrowLeft document keydown handler) were classified handler-less. Their listeners also register before the deck bridge executes at the end of <body>, so the bridge's own addEventListener patches installed too late — the flag stayed false, host-driven od:slide fell back to direct DOM mutation, and artifact-owned counters / internal indices went stale again. A tiny registry hook is now injected at the start of <head> — before any artifact script — wrapping window/document addEventListener and flagging keydown listeners that pass the shared navigation-key screen into window.__odArtifactKeydownNavigation. The deck bridge reads that flag lazily alongside its inline-source seed and its own late patches, so registrations are observed no matter when they happen relative to the bridge. The bridge marks its own top-of-file keydown listeners via __odDeckBridgeOwnListenerInstall so the framework branch's key-token source does not classify every framework deck as artifact-navigable. The navigation-key screen is now a single shared snippet between the hook and the bridge. Regression: a fixture that registers its keydown handler before the bridge evaluates with no keydown token in the html bytes (the script-src shape) — red on the previous commit (counter stays 1 while the canvas moves), green with the hook. Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
9d055a0fa6 |
feat(amr): add artifact-based upgrade guidance for Free users (#5591)
* feat(amr): add artifact-based upgrade guidance * fix(amr): fail open when billing plan is unavailable --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
53eff935e1 |
feat(amr): enable Parallel Search MCP (#5552)
Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
6426847fba |
fix(web): separate run success from result delivery (#5525)
* fix(web): distinguish run success from result delivery Persist delivery outcomes separately so successful design runs cannot report completion before a usable artifact is available. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): recover missing design delivery after reload * chore(ci): rerun pull request checks * fix(web): avoid reattaching local delivery finalization * fix(web): retry delivery failures * chore(ci): rerun pull request checks * chore(ci): retrigger managed checks --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
bc5b6f0588 |
fix(daemon): flag runs that end with unfinished work so status never reads "Completed" early (#5470)
A design-generation run that ended its turn with TodoWrite tasks still pending / in_progress / stopped — or was truncated by max_tokens — was stamped `succeeded` and read "Completed" on the Pet task center and the project pill, while the chat footer simultaneously read "Stopped with unfinished work". Users trusted "Completed", assumed the work was done, and re-requested, creating duplicate generations and eroding trust in the run-state UI (#1247 / #1060). Root cause: the daemon equates "the turn/process ended" with "succeeded" and never consults the agent's own declared work; multiple web surfaces then map `succeeded` -> "Completed" without any completeness signal, even though the chat footer already distinguishes unfinished work. Fix — one canonical signal, consumed everywhere so the surfaces cannot drift: - packages/contracts/api/run-completeness.ts defines the canonical "unfinished declared work" predicate (status !== 'completed', so pending / in_progress / stopped all count), matching the web footer's unfinishedTodosFromEvents exactly. - Daemon folds the last TodoWrite snapshot + a max_tokens truncation off the agent event stream (every runtime, via the single emitAgentEvent choke point) and derives run.endedWithUnfinishedWork once at finish(). - The project-status projection derives `incomplete` from the already persisted events_json — no schema migration — so it survives reload/reattach instead of flashing "Completed" once the in-memory run ages out. - Contracts carry endedWithUnfinishedWork on ChatRunStatusResponse and the SSE end payload (backward compatible: absent = finished), plus a new `incomplete` ProjectDisplayStatus. - Web surfaces (Pet task center, project pill, DesignsTab) render `incomplete` as amber needs-attention, never the success color; the footer now sources the same shared predicate. Truncation uses the display-only option (spec Option A): the run stays `succeeded` but is flagged incomplete, avoiding the code===0 / stdin-close hazards of reclassifying a truncated turn to failed. Regression coverage, red on main / green here: an HTTP-boundary e2e (e2e/tests/dialog/task-completeness.test.ts) drives the interactive chat path for unfinished / stopped / truncated / finished runs; daemon + contracts + web unit tests pin the projection and the canonical predicate. Ref: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/011f6f51-7a25-4043-ae94-182e0b8b0931 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
9d030e6fdf |
feat(chat): map long-tail failure_detail values to specific run-error cards (#5329)
* feat(chat): surface daemon failure_detail to sharpen run-error guidance The daemon already classifies each run failure into failure_category / failure_detail (run-failure-classification.ts) for retry policy and telemetry, but kept it daemon-internal — so the chat could only key error guidance off the coarse errorCode. That collapses distinct causes that share one code: a hard quota and a transient 429 both arrive as RATE_LIMITED, and a text-detected missing CLI leaks in as the opaque AGENT_EXECUTION_FAILED. Propagate the classification to the client (contract DTO + SSE end frame, mirroring the proven `resumable` path) and let resolveRunFailureUi take `detail` as a first-class input that can override a too-coarse code: - hard_quota / workspace_credits_exhausted -> a distinct "Quota exhausted" card that drops the futile Retry (primaryAction 'none') and steers to the hosted-AMR switch card instead. - cli_not_installed (text-detected) -> the same install-the-CLI card the AGENT_UNAVAILABLE code path already renders. A transient 429 without a hard-quota detail still offers Retry, and the Antigravity terminal switch-model flow still wins over the generic detail override. Stacks on #5308 (the code->guidance mapping this detail layer refines). * chore(i18n): rebrand user-facing "AMR" to "Open Design Cloud" The hosted model service is now Open Design Cloud, but the run-error cards this PR sharpens (switch / auth / balance), plus settings account + wallet labels, avatar console, onboarding gate, and the handoff website link, still showed the old "AMR" (or bare "Open Design") name. Normalize every user-visible brand mention to "Open Design Cloud" across all 19 locales. Value-only: no keys, agent ids, URLs, or raw daemon error strings touched. * fix(chat): persist daemon failure classification onto assistant message The daemon emits the terminal `error` SSE frame from the child-close handler before a run is finalized, so `failureCategory` / `failureDetail` (computed at finalize) weren't known when that frame was first persisted to the assistant message — the stored `status:error` event kept only `{ detail, code }`. A failure persisted without the live web error handler saving the message, or a conversation reloaded before that save landed, therefore fell back to the coarse errorCode UI and lost the specific fix guidance this PR adds. Stamp the finalize-time classification onto the persisted assistant message: enrich the last `status:error` event in place (append one only if a failed run somehow never persisted an error frame). Covered by a daemon regression that reads the stored assistant message via the conversation-messages API after a failed hard-quota run. * fix(chat): keep daemon failure classification on the live web reload/recovery paths Two live-propagation gaps let the sharpened run-error card degrade back to the coarse/raw UI even though the daemon now sends the classification: - appendErrorStatusEvent() no-op'd whenever the message already had a trailing status:error with the same detail. In the reload/reattach race (reload reads the daemon-persisted error frame first, then the run finishes and onError arrives with the finalize-time classification) that dropped the new code / failureCategory / failureDetail. Merge those into the existing event in place instead; no-op only when nothing is added. - The error-frame-then-status recovery probe in providers/daemon.ts broke out on a terminal failed status without copying failureCategory / failureDetail (unlike the post-loop status fallback), so markErrorRunFailure stamped null and the failureDetail mapping fell back to the generic card on the reconnect path. Mirror the two assignments before the break. Covered by chat-events (in-place merge) and daemon-sse-reconnect (error frame + terminal status fetch, no end frame) regressions. * feat(chat): map long-tail failure_detail values to specific run-error cards #5321 pipes the daemon's failure_detail to the chat but only mapped the high/medium-frequency causes (hard_quota, workspace_credits, cli_not_installed). This wires the recoverable long tail the daemon already classifies — timeout, inactivity_timeout, empty_output, session_resume_expired, git_bash_missing — each to a named failure type + one-line fix with a plain Retry. These are engine-neutral outcomes, so they resolve in a new AGENT_AGNOSTIC_DETAIL_FAILURE_UI layer BEFORE the AMR/Antigravity agent branches (which would otherwise return a generic retry for AMR). Adds the title/message i18n keys across all 19 locales and unit coverage for the new mappings. --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> |
||
|
|
bc6a246c65 |
feat(slides): enhance deck project experience (#5083)
* feat(deck): enhance slide deck functionality with speaker notes and navigation improvements - Added a switch for including speaker notes in slide decks, defaulting to true. - Updated keyboard navigation to include 'R' for resetting to the first slide. - Implemented click navigation for half-slide transitions. - Enhanced the deck framework to support hidden programmatic prev/next controls. - Improved handling of file inputs in question forms to support file submissions. This update aims to improve user experience in creating and presenting slide decks, ensuring better accessibility and functionality. * feat(artifacts): enforce semantic naming for user-facing deliverables - Updated guidelines to require semantic filenames derived from user briefs for new artifacts, moving away from defaulting to `index.html`. - Enhanced documentation to clarify naming conventions for HTML files, emphasizing the use of descriptive names like `investor-pitch-deck.html`. - Adjusted prompts and hints across the system to reflect these changes, ensuring consistency in user-facing outputs. - Improved test coverage to validate the new naming conventions and ensure proper implementation in various contexts. This update aims to enhance clarity and usability in project file management, promoting better organization and user experience. * feat(file-viewer): enhance speaker notes display and improve file workspace functionality - Updated the speaker notes section to include a styled placeholder for empty notes, improving user experience. - Changed the icon in the file workspace from "slides" to "panel-left" for better clarity. - Introduced new sub-category filtering options in the file workspace, enhancing organization and navigation. - Improved styling for the page creator search input and added visual feedback for loading states. These changes aim to enhance usability and visual consistency across the file viewer and workspace components. * test(web): align workspace tests with pages switcher * test(e2e): open design files through pages menu * fix(deck): keep source mode reachable * fix(workspace): restore uploaded file tabs * feat(deck-thumbnail): implement virtualized thumbnail rail for improved performance - Introduced a new `DeckThumbnailRail` component to optimize the rendering of deck thumbnails, reducing memory usage and improving load times. - Enhanced the `FileViewer` to integrate the new thumbnail rail, allowing for lazy loading of thumbnails based on viewport visibility. - Added tests for the `DeckThumbnailRail` to ensure correct functionality and caching behavior. - Updated styles for better visual consistency and performance during thumbnail rendering. These changes aim to enhance user experience by providing a smoother and more efficient way to navigate through deck slides. * feat(deck): enhance deck rendering with thumbnail support and keyboard shortcuts - Integrated `DeckSlideThumbnail` for rendering individual slides in a shadow root, improving performance and reducing main-thread load. - Updated `DeckThumbnailRail` to utilize the new thumbnail component, allowing for efficient slide navigation. - Implemented keyboard shortcuts for deck navigation, enhancing user experience during presentations. - Added tests for `buildDeckRenderInput` to ensure proper functionality of the deck stage fallback. - Improved styling for loading states in the viewer, providing a smoother visual experience. These changes aim to optimize the deck viewing experience by reducing resource consumption and enhancing interactivity. * feat(deck): improve thumbnail loading and speaker notes editing experience - Refactored `DeckSlideThumbnail` to enhance loading states with a new shimmer effect, improving visual feedback during thumbnail rendering. - Updated `DeckThumbnailRail` to manage loading states for thumbnails, ensuring a smoother user experience. - Simplified the speaker notes editing interface by removing the toggle switch, allowing direct editing from the notes body. - Enhanced accessibility by adding roles and focus states to speaker notes elements. - Added tests to verify the new loading states and speaker notes functionality. These changes aim to optimize the user experience in deck navigation and speaker notes management, providing a more intuitive and responsive interface. * feat(file-viewer): enhance version export functionality and speaker notes interaction - Introduced new export options for file versions, including PDF and image formats, improving the ability to download and share content. - Implemented a streamlined process for exporting version content, with progress indicators and error handling for a better user experience. - Enhanced speaker notes editing by refining event handling to prevent unintended focus shifts, ensuring a smoother editing experience. - Updated styles for improved visual consistency in the file version download interface. - Added tests to verify the functionality of version exports and speaker notes interactions. These changes aim to optimize the file viewer's usability and enhance the overall experience when managing and exporting project files. * fix(web): polish export menus and creator filters * fix(file-viewer): fall back to preview exports * test(file-viewer): cover version export fallbacks * test(file-viewer): assert loading skeleton by role * fix(file-viewer): restore html source mode affordance * fix(file-viewer): capture full version fallback snapshots * docs: trim slides handoff eof * test(exports): cover full preview snapshot requests * test(file-viewer): account for snapshot options arg * refactor(file-viewer, file-workspace): streamline export functions and enhance page creator dialog visibility * test(file-workspace): enhance slide template creation and mock plugin responses * feat(export): add support for versioned exports and enhance HTML export functionality * feat(export): implement versioned export support across multiple formats and enhance related tests * feat(file-viewer): enhance manual edit functionality with commit sequence tracking and add test for inline text session saving * style(speaker-notes): update filmstrip and thumb-frame styles for improved responsiveness and add corresponding tests * feat(file-viewer): enhance comment composer functionality with portal metrics and improve draw toolbar layout for mobile views * feat(analytics): track slide deck viewing experience Add analytics for the new deck/slides experience introduced in this PR: the deck viewer surface, in-deck slide navigation, thumbnail rail, and speaker-notes authoring. Presentation entry stays on the existing present_popover click to avoid double-counting. - contracts: DeckViewerSurfaceViewProps (surface_view), DeckViewerClickProps (ui_click), SpeakerNotesSaveResultProps (new speaker_notes_save_result event), wired into the event unions. - web: track* wrappers + fire helpers in FileViewer — deck_viewer surface_view on deck mount, slide_prev/next/reset, thumbnail_select, thumbnail_rail_toggle (expand/collapse), speaker_notes_edit, and speaker_notes_save_result (preview + presenter surfaces). - tests: deck analytics suite asserting the emitted (event, props) pairs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(file-viewer, file-workspace): enhance export toast functionality and improve localization for page creation * feat(home-view, plugins-home-section, agent-skill): update default facet preference, enhance category selection handling, and improve localization support - Set `preferDefaultFacet` to true in HomeView component for better user experience. - Introduced `handlePickCategory` function in PluginsHomeSection to manage category selection based on card layout visibility. - Updated PRIMARY_CATEGORIES to reflect the new order and added localization for titles and descriptions in adaptAgentSkill. - Enhanced tests to verify the correct handling of display copy and tags from SKILL frontmatter. * feat(plugins-home, file-workspace): introduce commercial category labels and enhance deck handling - Added a new module for resolving and localizing commercial category labels for plugins. - Updated the FileWorkspace and PluginCard components to display the commercial category label on deck cards. - Enhanced the PageCreatorPresetFrame to adjust frame classes based on deck presets. - Refactored subcategory handling in facets to align with the new commercial category taxonomy. - Improved localization support for new category labels across multiple languages. * feat(deck-preview): implement deck preview scaling and enhance home hero layout - Introduced `useDeckPreviewScale` hook to manage scaling of deck previews within a fixed 1280×720 logical viewport, ensuring proportional rendering across various templates. - Updated `FileWorkspace`, `HomeHero`, and `PluginCard` components to utilize the new scaling functionality for improved visual consistency. - Enhanced the layout of the home hero section to accommodate new subtype chips and descriptions, including a "More" dropdown for overflow handling. - Improved localization support for new UI elements across multiple languages. * fix(PreviewModal): clarify sidebar prop documentation and update test description - Updated the documentation for the `PreviewSidebar` interface to specify that the sidebar's stage-edge handles are not shown when the sidebar prop is absent. - Modified the test suite description for `PreviewModal` to be more concise and relevant. * test(e2e): select prototype visual card --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cd07d359df |
feat(chat): surface daemon failure_detail to sharpen run-error guidance (#5321)
* feat(chat): surface daemon failure_detail to sharpen run-error guidance The daemon already classifies each run failure into failure_category / failure_detail (run-failure-classification.ts) for retry policy and telemetry, but kept it daemon-internal — so the chat could only key error guidance off the coarse errorCode. That collapses distinct causes that share one code: a hard quota and a transient 429 both arrive as RATE_LIMITED, and a text-detected missing CLI leaks in as the opaque AGENT_EXECUTION_FAILED. Propagate the classification to the client (contract DTO + SSE end frame, mirroring the proven `resumable` path) and let resolveRunFailureUi take `detail` as a first-class input that can override a too-coarse code: - hard_quota / workspace_credits_exhausted -> a distinct "Quota exhausted" card that drops the futile Retry (primaryAction 'none') and steers to the hosted-AMR switch card instead. - cli_not_installed (text-detected) -> the same install-the-CLI card the AGENT_UNAVAILABLE code path already renders. A transient 429 without a hard-quota detail still offers Retry, and the Antigravity terminal switch-model flow still wins over the generic detail override. Stacks on #5308 (the code->guidance mapping this detail layer refines). * chore(i18n): rebrand user-facing "AMR" to "Open Design Cloud" The hosted model service is now Open Design Cloud, but the run-error cards this PR sharpens (switch / auth / balance), plus settings account + wallet labels, avatar console, onboarding gate, and the handoff website link, still showed the old "AMR" (or bare "Open Design") name. Normalize every user-visible brand mention to "Open Design Cloud" across all 19 locales. Value-only: no keys, agent ids, URLs, or raw daemon error strings touched. * fix(chat): persist daemon failure classification onto assistant message The daemon emits the terminal `error` SSE frame from the child-close handler before a run is finalized, so `failureCategory` / `failureDetail` (computed at finalize) weren't known when that frame was first persisted to the assistant message — the stored `status:error` event kept only `{ detail, code }`. A failure persisted without the live web error handler saving the message, or a conversation reloaded before that save landed, therefore fell back to the coarse errorCode UI and lost the specific fix guidance this PR adds. Stamp the finalize-time classification onto the persisted assistant message: enrich the last `status:error` event in place (append one only if a failed run somehow never persisted an error frame). Covered by a daemon regression that reads the stored assistant message via the conversation-messages API after a failed hard-quota run. * fix(chat): keep daemon failure classification on the live web reload/recovery paths Two live-propagation gaps let the sharpened run-error card degrade back to the coarse/raw UI even though the daemon now sends the classification: - appendErrorStatusEvent() no-op'd whenever the message already had a trailing status:error with the same detail. In the reload/reattach race (reload reads the daemon-persisted error frame first, then the run finishes and onError arrives with the finalize-time classification) that dropped the new code / failureCategory / failureDetail. Merge those into the existing event in place instead; no-op only when nothing is added. - The error-frame-then-status recovery probe in providers/daemon.ts broke out on a terminal failed status without copying failureCategory / failureDetail (unlike the post-loop status fallback), so markErrorRunFailure stamped null and the failureDetail mapping fell back to the generic card on the reconnect path. Mirror the two assignments before the break. Covered by chat-events (in-place merge) and daemon-sse-reconnect (error frame + terminal status fetch, no end frame) regressions. * fix(web): update AMR tier-upgrade test for 3-arg resolveRunFailureUi The main merge kept a two-argument call site that passed agentId as detail after the signature gained an explicit detail parameter. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: re-trigger validate workspace on approved head Co-authored-by: Cursor <cursoragent@cursor.com> * test: align AMR rebrand assertions with Open Design Cloud copy Update unit and e2e selectors that still expected pre-rebrand strings for account status, handoff website link, and switch-and-retry CTA. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
81b20dc6a2 |
fix(web): avoid missing design-system preview assets (#5381)
Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
28d8158994 |
fix(web): lower AMR low-balance warning threshold (#5349)
* fix(web): lower AMR low-balance warning threshold * fix(web): tailor low-balance CTA for free AMR users * fix(web): tighten AMR low-balance dialog * fix(web): add close buttons to AMR balance dialogs * fix(web): simplify model quality tags * fix(web): adjust model cost labels * chore: trigger PR CI --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
b3356807b6 |
fix(amr): improve unavailable model gating (#5335)
* fix(amr): honor full model list metadata * fix(amr): classify tier entitlement failures * fix(amr): refine unavailable model upgrade affordance * fix(web): restore stale AMR model fallback Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(web): surface AMR tier upgrade failures Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): mirror chat upgrade attribution Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): skip disabled default models Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): honor catalog default model * chore(pack): bump vela cli to 0.0.20 * fix(amr): align locked catalog fallback state * chore(nix): refresh pnpm deps hash * fix(amr): refresh entitlement-aware model state * fix(amr): refresh models after upgrade return * fix(amr): preserve default model analytics * fix(amr): tighten model gating follow-ups Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): repair default and refresh follow-ups Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): resolve explicit default before spawn Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): concretize default model in smoke paths Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): repair default model gating Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): share connection-test model cache Generated-By: looper 0.10.1 (runner=fixer, agent=codex) --------- Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
461e24d20c |
feat(web): surface actionable run-failure guidance for common errors (#5308)
The daemon already classifies run failures into failure_category / user_action (run-failure-classification.ts), but the chat error card only mapped ~7 codes to clear copy — everything else fell back to the generic "Task failed" title plus the raw upstream string, so users hitting common failures could not tell the cause or the fix (#895). Extend resolveRunFailureUi to name the failure type and give a one-line fix for eight more high-frequency, clear-root-cause codes, with the raw error preserved in the card's collapsible source area: - AGENT_UNAVAILABLE (CLI not installed) - AGENT_PROMPT_TOO_LARGE (shorten input / new conversation) - AMR_MODEL_UNAVAILABLE (switch model) - TOOL_LOOP_DETECTED (check target, then retry) - ROLE_MARKER_HALLUCINATION (retry recovers) - AGENT_RUNTIME_DEF_INVALID (update / contact support) - RATE_LIMITED (non-antigravity: wait/switch, keeps AMR promotion) - UPSTREAM_UNAVAILABLE (transient, keeps AMR promotion) The six agent-agnostic codes resolve before the amr/antigravity branches so a hosted run also gets the specific guidance. Adds 7 title keys + 8 message keys across all 18 locales and updates amr-guidance unit tests. Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
956b6607f5 |
feat(preview): add powered WebGL previews and examples (#5266)
* feat(powered-preview): implement cross-origin isolation for WebGL and Worker artifacts - Introduced a new powered preview feature that allows rendering HTML artifacts in a cross-origin-isolated iframe, enabling the use of Web Workers, WASM, and SharedArrayBuffer. - Added API endpoint `/api/preview/isolation` to report the daemon's directly-reachable HTTP origin for rendering. - Implemented logic in the file viewer to determine if an artifact requires powered preview based on its content. - Updated the file serving routes to support powered preview, ensuring proper headers are set for cross-origin isolation. - Enhanced the user interface to include new scenarios for WebGL and Worker visualizations in the home hero section, with corresponding translations and descriptions. This feature addresses the limitations of the opaque-origin preview sandbox, allowing for more complex GPU and compute applications to function correctly. * feat(preview): add WebGL templates and lazy HTML previews * test(web): stabilize project back routing assertion * fix(preview): address powered preview review blockers * fix(preview): isolate powered preview origin |
||
|
|
8ca68bbfe6 |
feat(web): pre-run Open Design Cloud balance gate with subscription dialog (#5103)
* feat(web): Open Design Cloud pre-run balance gate — two-tier dialogs with auto-resume Gate every AMR chat send before any run spawns. HARD tier (definitive empty wallet <= $0, or signed out) blocks with a subscription-invitation dialog: distinct copy and CTA per cause (console wallet page vs in-app sign-in), benefits card, and auto-resume — a completed sign-in or a recharge observed by a bounded wallet watch resumes the parked task on its own. SOFT tier (<= $4) shows a dismissible reminder with top-up / start-anyway / persisted don't-ask-again (never silences the hard tier). Blocked project sends park in the conversation queue with their full payload and pause auto-drain until a send passes the gate; home submits hold the create open (continuation promise) so drafts and staged context are never lost, and the home decision carries into the project's first auto-send to avoid double-prompting. Fail-open contract: unknown or unparseable wallet answers never block; hard blocks are confirmed with refresh=1 before firing. New amr_entry sources cover both surfaces and tiers; nine chat.amrBalanceGate.* / chat.amrLowBalance.* i18n key groups ship in all 19 locales. * fix(web): a stale wallet-refresh echo must not confirm a hard block A failed refresh returns the previous cached snapshot with stale=true and an error; treating it as a fresh definitive $0 (or signed-out) answer violated the gate's fail-open contract and could strand a user who just topped up while the wallet endpoint hiccuped. (Review feedback.) * chore: retrigger ci * test(e2e): align AMR UI checks with balance gate * fix(web): preserve AMR gated sends across conversation switches --------- Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> Co-authored-by: Amy <1184569493@qq.com> Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
1b6e1e45ad | fix(web): keep bare chat URLs readable (#4919) | ||
|
|
b3e338ddb2 |
test(web): cover zip encoder, design-system audit, chat-events, mentions, utils (#2451)
* test(web): cover stored-mode ZIP encoder CRC and central directory * test(web): cover design-system audit repair-actions and chat-events dedup * test(web): cover @mention parsing and agent label normalization * test(web): cover error flattener, prompt-template categories, design-system sentinel * fix(web): set ZIP UTF-8 filename flag (bit 11) for non-ASCII names buildZip wrote UTF-8 filename bytes (TextEncoder) but left the general-purpose bit flag at 0 in both the local and central headers, so readers fall back to CP437 and mis-decode non-ASCII names like 日本.md. Set general-purpose bit 11 (0x0800, EFS) in both headers when the filename contains a non-ASCII byte; pure-ASCII archives are unchanged (ASCII == CP437 == UTF-8), matching how Python/Go zip encoders behave. Strengthen the UTF-8 test to assert the flag + filename in BOTH the local and central-directory headers (parsed via EOCD), and add a case asserting pure-ASCII names do not set the flag. Addresses review feedback on #2451. |
||
|
|
6be4e56544 |
feat(workspace): add plan mode and Excalidraw sketch flows (#4862)
* feat(daemon): add 'plan' session mode and update related functionality - Introduced a new session mode 'plan' alongside existing 'design' and 'chat' modes, allowing for editable document creation. - Updated various functions and interfaces to accommodate the new session mode, including normalization and usage in commands. - Enhanced markdown rendering capabilities by integrating micromark and its GFM extension for improved markdown processing. - Added new input types for question forms, expanding the range of user interactions. - Updated UI components to reflect changes in session modes and ensure proper handling of next steps based on the current mode. * feat(chat): integrate SessionModeToggle into ChatComposer and HomeHero - Added SessionModeToggle component to both ChatComposer and HomeHero for improved session management. - Updated HomeHero styles to accommodate the new mode switcher layout. - Adjusted countdown timer in QuestionsPanel to extend the skip countdown from 120 seconds to 10 minutes, enhancing user experience. - Added tests to ensure the countdown functionality works as expected. * feat(FileViewer): implement synchronized scrolling for Markdown editor and preview - Added functionality to synchronize scrolling between the Markdown editor and preview panes. - Introduced new types and utility functions for managing scroll behavior. - Enhanced the MarkdownViewer component to handle scroll events and maintain scroll position across different modes. - Updated the component's state management to accommodate the new scrolling features. * feat(excalidraw): integrate Excalidraw into the project - Added @excalidraw/excalidraw as a dependency in package.json. - Updated vitest configuration to include an alias for Excalidraw. - Imported Excalidraw CSS in the layout component for styling. - Modified AssistantMessage component to handle optional projectId. - Enhanced FileOpsSummary to display delete operations. - Implemented new Excalidraw scene management in SketchEditor and FileWorkspace components. - Updated MarkdownViewer to support file mentions and improved file handling. - Refactored various components to accommodate Excalidraw integration and ensure compatibility with existing features. * feat(files): improve sketch and file handoff flows * test: align post-merge expectations * chore(nix): refresh pnpm deps hash * fix(workspace): stabilize sketch persistence and ci checks * fix(workspace): address review blockers in editable files * fix(workspace): persist cleared sketch scenes * test(workspace): type sketch editor mock scene * fix(workspace): serialize sketch autosaves * fix(workspace): keep sketch save revisions current * test(e2e): stabilize project workspace smoke flows * fix(analytics): preserve plan mode for BYOK runs * test(e2e): stabilize new project rail interactions * fix(files): stop bash delete parsing at shell operators * test(e2e): stabilize ui cold-start suites * fix(viewer): preserve absolute markdown image sources * feat(workspace): preload sketches and enhance markdown save options - Added functionality to preload persisted sketches before opening the tab. - Introduced new MarkdownSaveOptions type to manage save behavior. - Updated saveMarkdownText to handle options for refreshing files and showing saving state. - Enhanced FileViewer to maintain focus and selection during metadata refresh. - Implemented a Toast component for user feedback on save and export actions. * fix(web): stabilize markdown and sketch editor polish * fix(web): finish sketch editor merge resolution * chore(nix): refresh pnpm deps hash * fix(plan): bypass discovery and stabilize markdown sync * fix(web): simplify scene retrieval in SketchEditor component * feat(web): enhance markdown viewer with auto-save functionality - Implemented passive auto-save status in the MarkdownViewer component, replacing the manual Save button with an auto-save indicator. - Introduced new hooks and state management for tracking auto-save events and displaying the last saved time. - Added support for synchronized scrolling between the markdown editor and preview. - Created a new markdown-scroll-sync module to handle scroll synchronization logic. - Updated localization files to include new strings for auto-save messages. - Added a SketchEnginePrewarm component to optimize Excalidraw loading times. * feat(web): enhance session mode toggle with cost indicators - Added cost tiers for each session mode in the SessionModeToggle component, providing users with a visual representation of usage costs. - Introduced a new ModeCostTag component to display cost information alongside session mode labels. - Updated localization files to include new keys for cost labels and notes. - Enhanced styling for cost indicators to improve user experience and clarity. - Refactored EntryShell to open the new project modal instead of creating a blank project directly from the rail. - Implemented a utility function in markdown-scroll-sync to check for vertical progression in block offsets. * feat(web): add max height adjustment for session mode description card - Introduced maxHeight prop to the ModeDescriptionCard component to control the height of the description card based on available space. - Implemented useLayoutEffect in SessionModeToggle to dynamically calculate and set the maximum height of the description card, ensuring it does not overlap with the project tab bar. - Updated tests to verify that session modes display their expected usage/cost correctly in the UI. - Enhanced localization files to include new cost-related strings for various languages. * feat(web): implement goBack function for improved navigation and update auto-open logic - Added a new `goBack` function to handle in-app navigation, allowing users to return to the previous route instead of a hardcoded destination. - Updated the `navigate` function to maintain history state for better back navigation. - Refactored auto-open logic to prioritize produced artifacts, allowing markdown files to be opened alongside HTML files. - Updated tests to cover new navigation behavior and artifact selection logic. - Enhanced localization files to include new descriptions for workspace actions. * refactor(web): remove create design system functionality and update design files panel actions - Removed the `onCreateDesignSystem` prop and associated button from the DesignFilesPanel component. - Updated the empty state actions to include a button for creating a new document via the `onPaste` function. - Adjusted tests to reflect the removal of the design system creation action and ensure the new document button is functional. - Enhanced the MarkdownViewer component by adding a placeholder for the text area and removing the header bar for a cleaner interface. - Updated localization files to include a new placeholder string for the markdown editor. * fix(web): restore markdown placeholder translations * refactor(web): streamline MarkdownViewer and enhance localization - Removed unnecessary state management and reload functionality from the MarkdownViewer component for improved performance. - Added a placeholder text for the markdown editor in multiple localization files to enhance user guidance. - Updated styles for the save state indicator in the viewer to improve visual clarity and alignment. - Adjusted tests to reflect changes in the MarkdownViewer and ensure proper functionality. * fix(web): adjust SketchEditor button size and remove shortcut hints - Reduced the icon size in the SketchEditor component from 13 to 12 for better alignment. - Updated the removeSketchMermaidShortcutHints function to also remove the submit shortcut hints from the dialog, enhancing the user interface by decluttering unnecessary elements. - Adjusted tests to verify the absence of shortcut hints in the modal after updates. * fix(web): address plan mode follow-up polish * fix(web): align ci expectations after merge * test(e2e): stabilize project workspace helpers * test(e2e): scale settings visual timeout * fix(prompts): lock ElevenLabs voice picker choices * test(e2e): pin project workspace P0 worker * test(e2e): use rail new project entry * test(e2e): relax app restoration startup waits * fix(web): limit markdown pipe escaping to tables --------- Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> Co-authored-by: Amy <1184569493@qq.com> |
||
|
|
365553fc5d |
[codex] Fix deck keyboard double-step navigation (#4897)
* fix deck keyboard double navigation * fix deck slide handler detection Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Defer deck bridge slide fallback Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Fix self-handling deck navigation fixture Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Keep deck bridge fallback synchronous Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Preserve synchronous deck bridge fallback Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix deck bridge constant slide handlers * fix deck bridge stopped-handler state sync * Fix focused deck keyboard double navigation --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
59bca72f7e |
feat(export): programmatic screenshot-based PPTX/PDF export (#4604)
* feat(export): programmatic screenshot-based PPTX/PDF export
Replace the prompt-driven "ask the agent to run python-pptx" PPTX export
with a deterministic, programmatic pipeline. The daemon renders each deck
slide to a pixel-perfect PNG via the desktop's bundled Electron Chromium
(reused over sidecar IPC — no second headless engine, so the packaged app
does not grow) and assembles a one-image-per-slide .pptx (PptxGenJS) or
raster .pdf (pdf-lib).
- sidecar-proto: RENDER_SLIDES message + DesktopRenderSlides{Input,Result}
- daemon: deck-export.ts assembler (decode + pptx + pdf), POST
/export/pptx and /export/pdf-image routes, desktopSlideRenderer wiring
- desktop: deck-capture.ts renders the deck off-screen and captures one
PNG per `.deck > .slide` (skips presenter-mode .mini-slide clones)
- web: exportProjectAsPptx() fetch+blob download; ProjectView swaps the
prompt path for it
- cli: `od export pptx|pdf` dual-track closure
- remove the now-dead build-pptx-export-prompt lib + test
Tests: deck-export assembler unit tests + exportProjectAsPptx web tests.
Screenshot mode ships first; editable-rebuild modes are follow-up.
* chore(nix): refresh pnpm deps hash
* fix(export): deck PDF blank pages on opacity decks + image export capturing the modal
Two pre-existing deck-export defects surfaced while validating the new export:
- Vector PDF (Print-ready) left blank pages for decks that show one slide at a
time via opacity: DECK_PRINT_CSS forced each .slide onto its own page but
never reset opacity/visibility, and CSS transitions animated opacity from 0,
so inactive slides printed blank. Force opacity/visibility/animation/
transition in print media (both the web and desktop DECK_PRINT_CSS).
- "Export as image" captured Open Design's own format-chooser modal: the host
compositor snapshot ran after the modal opened, so the overlay leaked into
the PNG. Capture the clean preview before showing the modal; the cached
snapshot is reused so opening the modal never re-captures over the overlay.
* feat(export): dual-mode renderer + reuse screenshot for image/PDF export
Unify all screenshot exports on one off-screen renderer and make them
viewport-independent:
- Renderer (deck-capture.ts) now has two modes: deck → one 1920x1080 PNG per
slide (optionally just the slide at `index`); page (no `.slide` sections,
e.g. a website) → a single full-document PNG at natural size. Adds `index`
to the render input and `mode` to the result.
- Image export now renders through the daemon off-screen renderer (deck → the
current slide at fixed size; website → the whole page as one long image),
so the exported size no longer depends on the preview pane and can never
capture Open Design's own UI. Falls back to the host/iframe snapshot on web.
- "Export as PDF" (UI) now produces a pixel-perfect screenshot PDF that matches
the preview (same renderer as PPTX/image), falling back to the vector
print path on web or on failure.
- New POST /export/image route; PPTX on a non-deck returns a clear 422.
* feat(export): smart full-page image capture + PDF back to print-view default
Full-page image export now auto-selects the capture technique (users only see
"full page"):
- captureBeyondViewport (one clean off-screen pass, no fixed-element
duplication) when the output fits the machine's real GPU texture limit —
queried at runtime via WebGL MAX_TEXTURE_SIZE, not hard-coded — and
below-the-fold content actually rendered.
- scroll-segment stitch otherwise (too tall, or blank-below-fold scroll-driven
pages like parallax landings): scrolls a viewport at a time, captures each
frame, and stitches by real scroll offset into one long PNG. RAM-bound (a
plain buffer, not a GPU texture), capped by a memory budget; encoded with a
tiny dependency-free PNG encoder (node:zlib) so it bundles cleanly into the
ESM packaged main and has no Skia dimension cap.
- Output scale derives from the window DPR / actual captured chunk, fixing a
double-scale bug (DPR x clip.scale) that produced 4x-sized images.
PDF "Export as PDF" reverts to the print-ready vector path (instant, selectable
text) as the default; the pixel-perfect screenshot PDF stays available via
`od export pdf`.
* fix(export): PDF defaults to the CJK-safe screenshot path, not vector printToPDF
Chromium's vector printToPDF embeds no fonts in the packaged runtime and drops
CJK glyphs entirely — a Chinese page exported to "PDF" lost all its Chinese text
(only Latin survived). The off-screen screenshot renderer (already used for
image/PPTX) rasterizes the real browser render, so CJK is always correct.
"Export as PDF" now produces a pixel-perfect screenshot PDF that matches the
preview (one page per deck slide, or the whole page for a website), falling back
to the vector/browser print path only on web or on failure. Verified: a Chinese
site that lost its text under vector printToPDF renders fully under the raster
path.
* perf(export): stitch scroll-segments with Electron's native PNG encoder
The scroll-segment path was slow (~100s for a long parallax page) because of a
hand-written PNG encoder: a per-pixel JS BGRA→RGBA loop over tens of millions of
pixels plus zlib.deflate of a ~110MB high-entropy buffer.
Replace both with Electron's native image pipeline: stitch chunks as BGRA with
one Buffer.copy per chunk (capturePage already returns BGRA, which is what
createFromBitmap wants — no channel swap, no per-pixel JS) and encode once via
nativeImage.createFromBitmap(...).toPNG(). createFromBitmap is a CPU bitmap, not
a GPU texture, so it is not bound by the texture limit. Removes the hand-written
PNG encoder (crc32 / chunk framing / node:zlib).
Measured on a long parallax page: image 44s→17s, PDF 101s→19s (~5x), and the
native encoder also compresses better (55MB→26MB PNG).
* fix(export): render the image only on Save, after the format is chosen
Image export was rendering eagerly when the modal opened (a holdover from when
the capture had to run before the modal to avoid catching the overlay). Now the
desktop path renders off-screen and can't see the modal, so capture moves to the
Save click: open modal → pick format → Save → render + encode + download.
The Save button shows the "Saving…" state during the render; for the web-only
host-compositor fallback the modal is hidden during the brief capture so it
can't leak into the image. The snapshot is cached, so switching format after a
render re-encodes without re-capturing.
* perf(export): raster PDF embeds full pages as JPEG (52MB -> a few MB)
The screenshot PDF embedded full-page captures as PNG, so a photo-heavy long
page produced a ~52MB PDF. Full pages now render as JPEG (quality 82) — visually
near-identical for web screenshots but ~10x smaller. Deck slides stay PNG (crisp
text/graphics); image export still uses a lossless PNG source the client
re-encodes to the user's chosen format.
Threads a `pageImageFormat` hint through the render input; the desktop renderer
encodes page mode as JPEG (CDP captureScreenshot format:jpeg / nativeImage
toJPEG) and the daemon assembler embeds with embedJpg vs embedPng per image.
* feat(export): pre-pass for reveal-on-scroll + deck image as one long image
Two improvements found while testing real landing pages:
1. Pre-pass before full-page capture: freeze animations/transitions and scroll
the whole page once (then back to top) so reveal-on-scroll content
(IntersectionObserver / AOS / lazy images) is triggered and holds. This is
the standard full-page-screenshot technique. Result: pages that previously
came back blank-below-the-fold (and fell to the duplicate-prone scroll-
stitch) now succeed with a clean single-pass captureBeyondViewport.
Verified: a reveal-on-scroll landing page renders fully in one shot (no black,
no duplicates); only pure JS-scrollY parallax (which re-hides at scrollY=0)
still falls back to scroll-stitch.
2. Image export of a deck now stitches every slide top-to-bottom into one tall
image (the "whole deck as one picture") via the native BGRA stitch, capped so
a long deck can't exceed the bitmap limit. Ordinary pages remain a single
full-page capture; a specific slide index is still honored if given.
* fix(export): show the loading toast at the start (not after) + track real completion
The "Exporting" toast was set inside the promise's .then(), so for a multi-second
screenshot export it only appeared AFTER the export finished — looking frozen the
whole time. Now fireShareExport shows a loading toast immediately, clears it on
success, and shows an error toast on failure. The loading toast TTL is raised to
60s so it survives a long export, and PPTX threads its real promise (was fire-
and-forget) so the toast reflects actual completion. Adds fileViewer.exportFailed
across all locales.
* fix(export): detect scroll-driven pages by comparison, not by color
The blank-below-fold heuristic (flat-color fraction) was unreliable: a dark-
themed page that renders fine (Mindloop, 89% near-black below the fold) looked
"blanker" than a scroll-driven page that genuinely fails (Luxury, 78%), so the
black middle slipped through and exported with a black band.
Replace it with a color-independent comparison: render the document's MIDDLE
band two ways — scrolled into view (real content) vs captureBeyondViewport at
scroll 0 (what the one-shot produces). If they differ significantly, the page is
scroll-driven and we use scroll-segment stitch; otherwise the clean one-shot.
Verified: Luxury now exports full content (stitch), Mindloop stays a clean
one-shot, dark designs are no longer false-flagged.
* fix(export): recognize nested-`.slide` decks (export all slides + show PPTX)
A deck whose slides are `.slide` nested under `.deck-viewport`/`.deck-stage`
(not direct children of `.deck`/`body`) was missed: PDF/image exported only the
first slide and the PPTX option was hidden, even though the slide pager showed
"1 / 9".
- Renderer: find slides via `.slide` anywhere, filtering out presenter-mode
clones (`.mini-slide`/`.overview`/`.thumb`) in-page, instead of the rigid
`.deck > .slide` selector. showSlide now also sets `visibility:visible` and
toggles the common active-slide classes (active/visible/is-active/current) so
decks that hide via `visibility:hidden` and gate reveals on `.visible` render
every slide; animations are frozen so reveals reach final state instantly.
- UI: PPTX export shows whenever the artifact is deck-like (incl. the
content-detected `.slide` decks that drive the pager), matching the pager.
Verified on a nested-`.slide` deck: PPTX = 9 slides, image = 9 stitched, all
with content (previously only slide 1).
* perf(export): cache /raw/ assets + halve per-slide round trips
Two performance wins for screenshot export (covers + live preview benefit too):
1. /raw/ now emits ETag + Last-Modified + Cache-Control: no-cache, and answers
conditional GETs with 304. Covers, live preview, and the screenshot export
window all load project HTML + its fonts/CSS/images through /raw/, and in the
packaged app the hidden export window shares the same Chromium session/cache
as the web UI — so a second load reuses already-downloaded bytes instead of
re-fetching every asset. The validators are derived from file size+mtime, so
any agent rewrite changes them and busts the cache immediately (no-cache keeps
it always-revalidate, never silently stale). Previously /raw/ sent no cache
headers at all, so nothing was reusable.
2. Deck slide capture merges the slide-show DOM toggle and its two-frame settle
into a single executeJavaScript round trip (showSlide returns the settle
Promise) instead of two separate main<->renderer hops. Output is identical;
the loop is measurably faster and the saving scales with slide count.
Tests: apps/daemon/tests/project-raw-cache.test.ts covers the validators, 304 on
If-None-Match / If-Modified-Since, cache-bust after rewrite, and the streamed
media path. The merge is correctness-preserving by construction (same DOM ops,
same two-frame settle).
* perf(export): hand rendered images to the daemon as files, not base64 IPC
The desktop renderer used to return every rendered slide/page as a base64 data
URL inside the JSON IPC reply. For large images (photo-heavy decks) that means a
1.33x base64 blow-up plus JSON.stringify/parse of a multi-MB string plus a
multi-MB socket transfer across the desktop->daemon sidecar bridge, with a
matching RAM spike. The pixels only exist in the desktop process (it owns the
Chromium that captures them), so they must cross to the daemon — but they should
cross as a file on the shared filesystem, not as base64 in a JSON message.
Now the daemon picks a unique scratch dir under its data root
(<RUNTIME_DATA_DIR>/export-render/<id>), passes it as `outputDir` in the
RENDER_SLIDES request, the desktop writes the images there and returns their
paths in `slideFiles`, and the daemon reads them back and deletes the dir in a
finally. desktop only ever writes to the absolute path the daemon handed it, so
this works identically in dev and packaged (desktop never infers the data root).
A unique per-request id means concurrent exports never collide. Base64 data URLs
remain a fallback for older desktop builds that don't honor outputDir.
- sidecar-proto: DesktopRenderSlidesInput.outputDir + DesktopRenderSlidesResult.slideFiles
- deck-capture: emitImages() writes files when outputDir is set (all 3 paths:
deck per-slide, deck stitch, full-page incl. scroll-segment)
- deck-export: readSlideFiles() reads the handoff files (companion to decodeSlideDataUrls)
- import-export-routes: create/own/clean the scratch dir; prefer slideFiles
Tests: readSlideFiles unit tests; a route-level test that asserts the renderer
is handed an outputDir under the data root, the image returns, the scratch dir is
deleted after the response, and concurrent exports each get a unique dir.
* chore(export): one-line per-phase timing logs for screenshot export
A slow export now leaves a diagnosable trail instead of guesswork:
- desktop `[od-export] render`: load / assets(fonts+images) / prepare / render
phase breakdown + total, plus mode and whether the handoff used files.
- daemon `[od-export] assemble`: renderer(IPC) / read(file handoff vs base64) /
assemble(pptx/pdf build) + total + byte size.
These immediately surfaced that a slow image export was dominated by the
artifact's own in-browser compile (Babel/Tailwind CDN) and uncacheable external
media — not the export pipeline (file read was ~2ms). One info line per export.
* fix(export): normalize PDF page size to points; honor --title in CLI output name
Addresses review feedback on PR #4604:
- buildScreenshotPdf sized each PDF page by the captured image's pixel
dimensions, so the nominal page size scaled with the capture's device pixel
ratio (a 2x retina capture produced a page twice as large as 1x). Normalize
each page to a fixed longest-side in points (960pt; a 16:9 slide => 960x540pt,
matching PowerPoint) with the image's aspect ratio. The image still embeds at
full pixel resolution, only the page's points change.
- `od export pptx --title "X"` forwarded the title to the server but always saved
the local file under the source HTML's basename. Name the output after the
slugified title when --output is not given.
Tests: PDF page-size normalization assertion (loads the PDF, checks 960pt not
the 1px capture size); sidecar-proto render-slides IPC validation (outputDir,
enum, boolean, unknown-key rejection, minimal round-trip).
* test(export): cover the server Content-Disposition filename branch
The exportProjectAsPptx happy-path test only exercised the no-header local
fallback name; production always returns a Content-Disposition. Add a test that
pins the branch the desktop download actually uses (server filename wins).
* feat(export): support arbitrary-aspect decks (not just 16:9)
Screenshot deck export no longer assumes every deck is 16:9. The renderer
measures the deck's authored slide box (the rendered rect of the first slide
with layout, so fit-to-viewport decks report the stage they actually paint),
sizes the capture window + pinned stage to it, and clips capturePage to it. The
measured pixel dimensions flow to the PPTX assembler, which derives the slide
layout from the real aspect ratio (13.333" wide, height = width/aspect) instead
of hardcoding LAYOUT_16x9 — so 4:3, square, and portrait decks export
correctly-proportioned slides and PDFs instead of being letterboxed or clipped.
Falls back to 1920x1080 / 16:9 when the slide box can't be measured or is out of
a sane range, so existing 16:9 decks are unchanged.
Verified: demo-deck measures 1920x1080 (16:9, unchanged); a 1024x768 deck
measures 4:3. Tests: PPTX layout follows 16:9 / 4:3 / 9:16 aspect (asserted via
the slide cx/cy in presentation.xml).
* fix(export): capture off-screen carousel slides (translated-strip decks)
showSlide only toggled the active class/opacity, so decks that paginate by
translating a flex-strip container (e.g. html-ppt-zhangzara-grove) left slide 2+
off-screen and capturePage kept grabbing the first viewport region — exporting
the wrong slide or a blank page.
showSlide now reports where the active slide actually landed; if it is off the
top-left capture stage, showDeckSlide restacks just that slide into the viewport
(clears ancestor transforms + pins it fixed at 0,0) and re-settles before
capture. This branch only runs when the slide is genuinely off-stage, so
transform-scaled fit-to-viewport decks (active slide already at 0,0, and which
DO rely on an ancestor scale) are never touched.
Verified: a 3-slide flex-strip carousel — slide 0 stays at 0,0 (untouched),
slides 1/2 detected off-stage (x=1920) and restacked to 0,0 before capture.
* fix(export): gate PPTX on a host runtime; unify + center the image toast
- PPTX export has no web-only fallback (it needs the daemon's Electron-Chromium
screenshot renderer), so a web-only deployment showed a PPTX button that always
failed with 501. Gate `showPptxExport`/`canPptx` on `isOpenDesignHostAvailable()`
so the action only appears where it can succeed. Image/PDF keep their web
fallbacks and stay shown.
- Image export showed an in-modal spinner and a separate, non-portaled "saved"
toast that rendered off-center (its `position:fixed` resolved against the
preview pane's transform). Route image export progress through the same
portaled, viewport-centered `exportToast` used by PPTX/PDF: close the modal on
Save, show a loading toast, then success/error — one consistent, centered toast
style. Removes the now-dead imageExportBusy/imageExportCapturing/savedToast.
* fix(export): screenshot the current deck slide; never drop slides when stitching
Two more review findings on the screenshot export path:
- captureExportImageSnapshot() routed deck snapshots through the daemon without a
slide index, so /export/image fell into the stitch-whole-deck branch even for
"Copy screenshot" and "Export as image" — which both promise "the current
preview". Pass the active slide index for decks so both capture the current
slide. Stitching the whole deck into one long image is reserved for an explicit
action (a follow-up modal toggle).
- stitchDeckSlides() capped the output at DECK_STITCH_MAX_H by stopping the loop
and still returning ok:true, silently dropping trailing slides (~13+ on a 2x
capture) — partial-success data loss. It now captures slide 0 to learn the
native size, picks one uniform downscale so all `count` slides fit under the
cap, and stitches every slide (long decks just get a smaller per-slide size).
* fix(export): drop dead `scale` param; keep deck PDF slides PNG (not JPEG)
Two more review findings:
- The render-slides contract accepted a `scale` field (validated in sidecar-proto,
forwarded by handleScreenshotExport) that the desktop renderer never read — a
broken protocol surface on the feature's first release. Remove it from the
proto, the daemon route, and BuildDeckRenderInputOptions; the capture resolution
comes from the measured stage size and host DPR. (No scale multiplier is needed
today; if one is added later it must actually be applied in the renderer.)
- The deck branch derived its image encoding from `pageImageFormat`, so the
screenshot-PDF path (which sets pageImageFormat='jpeg') made deck slides lossy
JPEG — contradicting the contract ("deck slides stay PNG; JPEG is a full-page
page-mode optimization") and adding compression artifacts to text-heavy slides.
The deck branch now always encodes PNG; only `page` mode honors JPEG.
* fix(export): no silent truncation for tall pages; deterministic deck slide index
- The full-page scroll-stitch path clamped the document height to the RAM budget
and returned ok:true, silently dropping everything below the cap on very tall
pages. It now refuses with a clear "page is too tall — export as PDF instead"
error instead of returning a truncated image as success; pages within budget
still stitch their full height. (Decks downscale to fit since they are discrete
slides; a continuous page is failed rather than seam-spliced at reduced scale.)
- Deck screenshots now always send a concrete slide index
(slideState?.active ?? cached ?? 0) so a fresh open — or a deck detected only
from `.slide` markup that never emits od:slide-state — captures the current
slide instead of falling into the stitch-whole-deck branch.
* fix(export): explicit page-vs-deck signal; surface semantic export failures
Two review findings:
- Treating any `.slide` element as proof of a deck was too broad for the generic
/export/image and /export/pdf-image routes — an ordinary page with carousel or
testimonial `.slide` markup would skip full-page capture and stitch those
elements as slides. The caller now passes an explicit `deck` flag (the web
knows `effectiveDeck`; PPTX is deck-only): `deck:false` forces full-page
capture, `deck:true` forces slide capture, and the `.slide`-count heuristic
remains only as the no-signal fallback (e.g. the CLI).
- `exportProjectImageDataUrl()` returned null for every non-OK response, so a
semantic failure (e.g. the daemon's new "page is too tall — export as PDF")
was treated as "renderer unavailable" and silently downgraded to a partial
visible-viewport screenshot. It now returns a discriminated result; the caller
only falls back to a web capture when the off-screen renderer is genuinely
unavailable (501/no-host/network) and surfaces the real error otherwise (Copy
screenshot + Export as image both show the message).
Plumbs `deck` through sidecar-proto, the daemon route/options, exports.ts
(image + pptx + screenshot-pdf), FileViewer, and ProjectView. Proto test covers
deck round-trip + rejection.
* fix(export): harden the file handoff (path confinement) + narrow unavailable
Three security/contract findings on the render-slides file handoff:
- sidecar-proto now rejects a non-absolute `outputDir` (was: any non-empty
string), so a malformed render-slides request can't make desktop main mkdir +
write outside the daemon scratch area. Negative proto test added.
- The daemon canonicalizes every returned `slideFiles` path and requires it to
stay under the canonical `renderOutputDir` before reading — a buggy/malicious
renderer response can no longer make /export/{pptx,pdf-image,image} read and
stream back arbitrary files (path traversal / symlink escape). Returns 502 on
an out-of-scope path; handoff test proves an out-of-tree path is refused and
its bytes never reach the response.
- exportProjectImageDataUrl wrapped the whole flow in one try/catch, so a 200
with a corrupt/unreadable payload was reported as `unavailable` and silently
downgraded to the viewport screenshot. The `unavailable` path is now narrowed
to transport-level failures (the fetch itself); a bad 200 payload returns a
semantic `error` so the real failure surfaces.
* fix(export): CLI page/deck flag; reject out-of-range slide index
Two review follow-ups:
- `od export pdf|pptx` now accepts `--deck` / `--page` and forwards the signal in
the request body, so the CLI hits the route with the same page-vs-deck
semantics the UI uses (which sends effectiveDeck). Previously the CLI fell back
to the daemon's `.slide` heuristic, so an ordinary HTML file with carousel
markup could export as a deck from the CLI but a full page from the UI. (PPTX
stays deck-only server-side; the flag matters for PDF.) `--deck` and `--page`
are mutually exclusive; omitting both keeps the heuristic fallback.
- renderDeckSlides rejected nothing for an out-of-range `index`: it fell back to
range(count) and the daemon returned slide 0 with 200 for image export, so
asking for slide 99 of a 3-slide deck silently returned slide 0. It now fails
with a clear "slide index N is out of range" error.
* fix(export): If-None-Match precedence; renderer IPC outage -> 502 not 400
- rawRequestIsFresh fell through to If-Modified-Since even when the request sent
a non-matching If-None-Match, so a same-second rewrite (ETag changes, but
Last-Modified is identical at second granularity) could 304 changed bytes when
both headers were sent. If-None-Match is now authoritative when present
(RFC 9110 §13.1.3) — freshness is the ETag match alone. Regression test sends a
stale ETag + the current If-Modified-Since and expects 200.
- A rejection from desktopSlideRenderer (a 600s requestJsonIpc) — missing desktop
process, broken socket, timeout — landed in the outer catch and became
400 BAD_REQUEST, making renderer outages look like caller errors to retries /
monitoring. The IPC call is now wrapped and translated to 502
UPSTREAM_UNAVAILABLE, matching the !rendered.ok branch; the outer 400 stays for
real request-validation / assembly errors.
* fix(export): full-page stitch corrupts on fractional DPR (125%/150% scaling)
scrollSegmentStitch rounded the device pixel ratio to an integer
(`Math.round(size.width / PAGE_W)`), so on non-retina display scaling (1.25x,
1.5x) the output width and every row offset were wrong — the stitched full-page
screenshot (/export/image and the raster PDF page path) came back cropped
horizontally or with vertical gaps/overlap even though the page rendered fine.
Derive width/height/placement from the REAL captured device width and its true
(possibly fractional) ratio instead. Extracted scrollStitchGeometry /
scrollStitchRowOffset as pure helpers with a non-integer-DPR regression test
(1x / 1.25x / 1.5x / 2x).
* fix(export): broaden deck slide selector; content ETag for transformed HTML
- The renderer only recognized `.slide`, but shipped decks use other slide
contracts the print/export path already supports (e.g.
html-ppt-zhangzara-creative-mode uses `<section data-screen-label=...>`), so an
explicit deck export of those silently downgraded to a single full-page
capture. Broaden SLIDE_SELECTOR to the pdf-export family
(`.slide, [data-screen-label], .deck-slide, .ppt-slide`), and when
`deck === true` finds no slide surfaces, fail fast with a clear error instead
of capturing a page.
- /raw/ revalidation used the source file's mtime ETag even when the response is
substituted by a transform (Vite dev-entry -> dist/index.html, or preview
bridge injection). A change to dist/index.html with an unchanged source entry
could then return a stale 304. Compute a content ETag from the actual sent
bytes for transformed HTML; assets/fonts/images/streamed media keep the fast
mtime ETag + early 304. Regression: rewriting only dist/index.html returns 200.
* fix(export): gate PPTX on explicit deck; page-mode DOM intact; stitch RAM budget
Four review findings:
- PPTX action was gated on the `.slide` regex (`effectiveDeck`/`looksLikeDeck`),
so ordinary pages with carousel/testimonial `.slide` markup surfaced PPTX and
were forced through the deck renderer (hardcoded `deck: true`). Gate
show/canPptx on the EXPLICIT deck signal (`isDeckArtifact`: deck renderer / kind
/ presentation) instead; real decks keep PPTX, pages don't, and `deck: true`
is now always correct. Image/PDF stay on the broader signal (they handle pages).
- renderDeckSlides ran prepareDeck (hide chrome + freeze animations) BEFORE
deciding page vs deck, so page-mode exports rendered on a mutated DOM (content
using generic `.notes`/`.overview` classes vanished). Split the non-mutating
slide count from the deck-only DOM prep; page mode now captures the original
document.
- stitchDeckSlides capped only output height, so a wide/high-DPR deck could still
allocate >1 GiB (8192px stage @2x => W~16384 * 30000 * 4). Add a RAM byte budget
(320MB, like the page stitcher): downscale by min(heightScale, byteScale).
- sidecar-proto render-slides test now covers the `index` field (success + reject
negative / fractional / non-number).
* fix(export): image/PDF deck flag from explicit signal, not .slide heuristic
The image and screenshot-PDF exports still passed `deck: effectiveDeck` (the
`.slide` regex), so an ordinary HTML page with carousel/testimonial `.slide`
markup exported only the current card instead of the full page. Drive both off
the explicit `isDeckArtifact` signal (same as PPTX): a real deck → per-slide, a
page → full-page capture. Extracted `shouldCaptureAsDeck()` as a pure helper with
a regression test (page + slides + deck:false => page, not per-slide).
* fix(export): screenshot PDF download must prompt Save As (.pdf in allowlist)
The default Export PDF flow now streams a .pdf download via
exportProjectScreenshotPdf, but the will-download Save As hook only intercepted
.pptx + image extensions — so PDF silently wrote to the OS Downloads folder
while PPTX/images prompted. Add .pdf to SAVE_AS_EXTENSIONS with a PDF filter,
and extract saveAsDialogOptionsForFilename() as a pure helper with a runtime test
(PDF/PPTX/image prompt; uppercase matched; other extensions pass through).
* fix(export): single-shot guard for image export (no double-click duplicates)
The toast-based image export closes the modal and starts the save without an
in-flight guard (the old in-modal busy/disabled states were removed), so a fast
double-click / Enter-repeat on Save could enqueue two concurrent exports
(duplicate captures, downloads, and fireImageExportResult bookkeeping) before the
modal-close re-render removed the button. Add an imageExportInFlightRef guard
that returns early on re-entry and resets in finally — mirrors the existing
screenshotInFlightRef pattern.
* fix(export): If-Range guard on /raw/ stream; block image-modal reopen mid-export
Two non-blocking correctness issues:
- /raw/ honored Range unconditionally even with the new ETag/Last-Modified, so a
client resuming a cached font/media download after the file changed could
splice stale + fresh bytes. Gate Range on If-Range (RFC 9110 §13.1.5): serve
206 only when the If-Range validator (ETag or date) still matches the current
file, else fall back to a full 200. Regression test: stale If-Range + Range
returns 200 with the new full length.
- The image-export single-shot guard covered handleImageExportSave, but reopening
the modal mid-export reset the shared request/result refs, mis-attributing or
dropping the in-flight export's analytics result. openImageExportModal now
no-ops while an export is in flight.
* fix(export): drive image/PDF deck decision off the viewer signal (effectiveDeck)
The desktop screenshot image/PDF paths were gated on isDeckArtifact while the
vector-PDF fallback (and the viewer's own prev/next/Present) use effectiveDeck.
That diverged: a metadata-free `.slide` deck rendered as a deck in preview but
exported as a single full page on a desktop host, yet as a deck via the browser
fallback — same artifact, different output depending on host.
Drive image + screenshot-PDF off effectiveDeck (the viewer's deck decision), so
export matches what the user sees and is host-independent. PPTX keeps the
narrower isDeckArtifact: it is deck-only with no vector fallback, so it can't
diverge, and it must not offer slide export for incidental carousel markup.
Removes the now-dead isDeckForExport binding.
* test(web): update image-export specs for capture-on-Save modal flow
The image-export modal was redesigned in this PR from eager-capture-on-open
(preview + live format re-render + in-modal alert + disabled-until-ready Save)
to capture-on-Save unified with the PPTX/PDF portaled-toast flow: the dialog
just picks a format, and Save closes it and runs the single capture behind the
export toast. The 9 specs in file-viewer-image-export.test.tsx still drove the
old eager flow and failed in CI (Web workspace tests). Updated each to click
Save before asserting capture, pick the format before Save, assert the portaled
toast (role=alert error text unchanged) instead of the removed in-modal alert,
and replaced the obsolete "preparing label" spec with one proving no eager
capture happens on open or on format change.
* fix(cli): od export honors the server Content-Disposition filename
The web download helper prefers the daemon's Content-Disposition filename and
only falls back to a locally derived name. `od export` ignored it and always
synthesized the name from --title/basename, so the two surfaces could write
different filenames for the same export. Parse the header (RFC 5987 filename*
and plain filename, reduced to a hardened basename so an odd header can't steer
the write outside the cwd) and prefer it when --output is not given, keeping the
title-slug/basename fallback. Mirrors apps/web/src/runtime/exports.ts.
* fix(export): detect runtime-managed decks; image=whole deck; de-dup long pages
QA found three blocking export-fidelity issues on this PR:
1. Horizontal decks export only slide 1 (image: all such templates; PDF:
some). Runtime-managed decks (`<deck-stage>` web component with slotted
`<section data-screen-label>` children toggled via `data-deck-active`)
carry no literal `class="slide"`, so the viewer's `looksLikeDeck` regex
misses them and the UI sent an authoritative `deck:false`. The host then
force-captured page mode (`mode:'page', slides:1`) — a full-page shot of
whatever slide was visible. PDF same path: `deck:false` skips the host
DECK_PRINT_CSS, so decks without their own `@media print` print one page.
Fix: a broader EXPORT-only signal `sourceLooksLikeExportableDeck` /
`deckExportSignal` mirroring the host's slide-surface family
(`.slide`/`[data-screen-label]`/`.deck-slide`/`.ppt-slide`) plus
`<deck-stage>`. Kept OUT of `effectiveDeck` so the host's deck-stage-
incompatible prev/next nav is not surfaced as a dead "— / —" control.
2. "Export as image" of a deck returned the current slide only. It now
stitches every slide into one long image (matching the slide count the
viewer reports); Copy screenshot / Mark-Draw capture keep the current
slide via `captureExportImageSnapshot({ wholeDeck })`.
3. Long-page image/PDF export duplicated a fixed/sticky hero down the
output: the scroll-segment stitch captures the viewport per offset, so a
pinned element was copied into every segment. `preparePageForCapture` now
neutralizes `position:fixed`->absolute and `sticky`->static before
measuring/capturing, so each renders once (captureBeyondViewport already
de-dupes; applied uniformly for consistency).
Red specs: exports.test.ts (deck detection), neutralize-positioning.test.ts
(fixed/sticky normalization).
* chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714)
* fix(sidecar): decode IPC frames with StringDecoder (multibyte UTF-8 corruption)
Exported CJK artifacts intermittently showed `???` / `◆?` (U+FFFD) in place of a
character — e.g. "拥挤" rendered as "拥���", "交付边界" as "交付���界". The bad
character varied between exports, the source bytes on disk were correct, and the
daemon /raw/ serve was byte-identical, so it was not a font or storage problem.
Root cause is in the generic JSON-IPC transport. Both the server and client
socket readers did `buffer += chunk.toString()` into a STRING. A render request
carries the full artifact HTML over the desktop IPC; when the payload spans
multiple `data` events, a multibyte UTF-8 character (CJK = 3 bytes) straddling a
chunk boundary is decoded per-chunk, turning each partial half into U+FFFD. Small
payloads never hit a boundary (hence "works in my repro, breaks on the real
file"); large real artifacts do, at whichever character lands on the split.
Fix: feed each chunk through a per-connection `StringDecoder("utf8")`, which
holds an incomplete trailing byte sequence until the next chunk completes it.
Verified end-to-end against the QA "Blog Post" artifact in a packaged client:
"拥���" → "拥挤" after the fix. Red spec: a ~1.3 MB CJK payload round-tripped
through createJsonIpcServer/requestJsonIpc (forces multi-chunk delivery) is now
byte-exact; it fails on the pre-fix reader.
* fix(export): vector deck PDF rendered only the first slide
A deck exported via the vector PDF fallback (POST /export/pdf →
exportPdfFromHtml) collapsed to a single page: only the runtime-active slide
appeared. Decks gate visibility with `.slide:not(.active){display:none!important}`
(specificity 0,2,0); the host DECK_PRINT_CSS `.slide{}` rule (0,1,0) cannot win
that cascade, so every non-active slide stayed `display:none` in print.
Fix: before printToPDF, mark every slide surface active (the same class set the
screenshot path toggles in deck-capture's showSlide), so the deck's own
`.slide.active` styling applies to all slides and DECK_PRINT_CSS paginates them
one per page. Shadow-DOM `<deck-stage>` decks are unaffected (their own
`@media print` already lays out every slide).
Verified with an offscreen printToPDF of a 12-slide `.slide`-class deck: 1 page
-> 12 pages, each a distinct centered slide.
* fix(export): screenshot PDF fails fast instead of masking errors as vector PDF
Per review: the raster-PDF path fell back to the vector `exportProjectAsPdf` for
EVERY non-ok screenshot result, so a semantic failure (bad deck routing, a 422,
a renderer-side 502, "page too tall", unreadable output) silently handed the user
a different (vector) PDF — the exact fidelity/CJK-glyph class of bug the
screenshot path exists to avoid.
exportProjectAsPptx now returns the same tri-state as exportProjectImageDataUrl:
`{ok:true}` / `{ok:false,unavailable:true}` (501 or transport — caller may fall
back) / `{ok:false,error}` (semantic — must surface). The PDF action only falls
through to the vector path on `unavailable`; a semantic error throws and is shown
in the export toast (onErr now prefers the export's own user-facing message).
* chore(nix): refresh pnpm deps hash
* fix(export): guard deck capture against stale-frame duplicate pages
QA saw a deck export with duplicate pages (e.g. two identical 目录 pages, a
slide silently missing). Root cause is a compositor race: after showing slide i,
`capturePage()` can return the PREVIOUS slide's frame when the new slide hasn't
painted yet (more likely on slower / loaded machines and slides with heavier
reveal content), so the loop emits an exact duplicate of the prior page. The
source has 12 distinct slides and live navigation is fine — the race is purely in
the offscreen capture loop.
Fix: after each capture, compare a cheap sampled checksum to the previous
slide's; if byte-identical (which can't happen for distinct slides), wait for
more frames and re-capture (bounded, 4 attempts × ~60ms). Two genuinely-identical
adjacent slides exhaust the retries and emit once. Applied to both the per-slide
(PDF/PPTX) and stitch (whole-deck image) loops.
Test: imageSignature distinguishes captures by content and length. (The race
itself is timing-dependent and not reproducible on a fast/idle machine — both
file:// and packaged-http exports of the reported deck render 12 unique pages
here — so the guard hardens the failure mode rather than relying on local repro.)
* fix(export): paginate tall pages for raster PDF instead of refusing
Per review: the single-image RAM/texture guard in capturePage refused any page
taller than the budget with "page is too tall — export as PDF instead". That is
right for /export/image, but /export/pdf-image routes ordinary-page PDFs through
the same branch — and since the screenshot-PDF path now fails fast (no silent
vector fallback), a long landing page exported as PDF hit a self-contradictory
hard error and regressed tall-website PDF export.
Fix: the PDF path (`jpeg`) now paginates a too-tall page into a multi-page raster
PDF — captureBeyondViewport per texture/RAM-bounded chunk, one image per chunk,
which the daemon assembles into one PDF page each. /export/image (png) keeps its
refusal (it has nowhere to paginate to). tallPageChunkHeights extracted + tested.
Verified offscreen: a ~20400px page → PDF path returns 3 paginated pages
(ok/page), image path still refuses.
* fix(export): capture deck slides via CDP (structural fix for duplicate pages)
Replaces the pixel-compare/retry guard (88d21c7) with a structural fix, per
review feedback: comparing each capture to the previous slide is the wrong
abstraction (it can't tell a stale frame from two genuinely-identical adjacent
slides, and wastes retries on the latter).
Root cause: the deck path used `webContents.capturePage()`, which grabs the last
COMPOSITED frame and can return the previous slide's frame when the just-shown
slide hasn't composited yet — emitting an exact-duplicate page. The page path
never had this because it uses CDP `Page.captureScreenshot`, which renders the
CURRENT DOM to a fresh frame.
Fix: deck capture now uses CDP `Page.captureScreenshot` too (attach the debugger
once around the deck loop; fall back to capturePage if it can't attach). The
captured pixels always reflect the slide just shown — no compare, no retry, no
identical-slide edge case. Animations/transitions are already frozen
(prepareDeckStage), so each slide is captured at its final state, never a
mid page-turn frame. Removed imageSignature + the retry loop.
Verified: 12-slide deck still stitches to 12 distinct slides at the correct dims.
* fix(export): current-slide capture of runtime decks uses the visible slide
Per review: deckExportSignal makes runtime-managed decks (<deck-stage> /
data-screen-label) exportable, but the current-slide path (Copy screenshot /
annotation capture) still resolved the slide index as `slideState?.active ?? 0`.
Those decks are deliberately kept out of effectiveDeck, so the viewer never
receives their active-slide bridge and slideState is null — meaning Copy
screenshot always off-screen-rendered slide 0 instead of the slide on screen,
inconsistent with the PPTX/PDF fix on the same templates.
Fix: planDeckImageCapture() decides per capture — whole-deck (Export as image),
ordinary pages, and tracked .slide decks render off-screen (with the active index
when tracked); an untracked deck's current-slide capture skips the off-screen
path and falls through to the visible host snapshot (which IS the current slide).
Tests: planDeckImageCapture unit cases (exports.test.ts) + a FileViewer
regression — Copy screenshot of a data-screen-label deck with no tracked slide
uses the host snapshot and does NOT off-screen-render slide 0.
* fix(export): don't mask post-response failures / debugger-less tall PDF as fallback
Two review edge cases:
- exportProjectAsPptx wrapped resp.blob() + triggerDownload() in the same
try/catch that maps to `{unavailable:true}`, so a corrupt body or a
client-side download failure (after a 200) was reported as "renderer
unavailable" — letting the PDF caller silently downgrade to the vector path.
Only the fetch (transport) and 501 now map to `unavailable`; post-response
failures return `{error}` so they surface. Unit test added.
- capturePage's no-debugger fallback still returned "page is too tall — export
as PDF instead" for the PDF path (jpeg). Pagination needs CDP, and we only
reach this branch when the debugger can't attach, so it now surfaces a
distinct retryable error instead of telling the user to switch to the format
they already chose. (The debugger attaches in normal packaged exports; this is
a rare transient.)
* fix(export): distinguish CDP attach failure from later CDP command failure
Per review: when the debugger attached but a later CDP command threw (a real
Chromium/GPU/clip error), the broad catch swallowed it and the too-tall PDF
refusal reported "renderer is busy, please retry" — hiding the actionable error
and sending users into a pointless retry loop. The retryable busy message is
only correct when the attach itself failed.
Track the caught CDP error (cdpError) separately: the too-tall PDF branch now
surfaces the real CDP error message when the debugger was available but a command
failed, and reserves the retryable "busy" message for true attach contention.
* fix(export): reject `od export pptx --page`; test the tall-PDF error split
Two review items:
- CLI: `od export pptx --page` advertised a page mode that can never work (the
daemon forces deck mode for /export/pptx). Reject `--page` for pptx with a
clear contract error pointing at `od export pdf --page` instead of silently
ignoring it.
- Lock down the cdpError split with a regression: extract tooTallPdfErrorMessage
and unit-test both branches — attach failure → retryable "busy" message;
attached-but-CDP-command-failed → the real Chromium/GPU error surfaces (and
neither tells the user to "export as PDF", which they already chose).
* fix(export): keep current-view captures viewport-based; reject weak If-Range
Two review items:
- planDeckImageCapture sent ordinary-page Copy screenshot / captureViewport
annotation through the off-screen renderer (useOffscreen:true, no index), which
renders the WHOLE document instead of the visible region — a regression for
screenshot/annotation viewport semantics. Now: Export-as-image (wholeDeck) and
tracked-deck current-slide still render off-screen; an ordinary page's
current-view capture (and an untracked deck's) falls back to the visible host
snapshot. Tests updated.
- ifRangeAllowsPartial accepted weak entity-tags for a 206, but RFC 9110 §13.1.5
requires a strong validator and our /raw/ ETag is weak (W/"size-mtime"). A
same-size rewrite / mtime collision could splice stale + fresh bytes under a
matching weak tag. Now any entity-tag If-Range falls back to full 200; only the
date form authorizes a range. project-raw-cache.test.ts pins it (weak-ETag
If-Range → 200, fresh date → 206, stale date → 200).
* fix(export): resolve imported-folder project files via metadata.baseDir
Per review: the new screenshot export routes (and the vector /export/pdf) read
the source with readProjectFile() and no metadata, so it fell back to
<OD_DATA_DIR>/projects/:id and returned FILE_NOT_FOUND for imported-folder
projects (whose workspace lives at metadata.baseDir) even though the file renders
in the UI.
Thread project metadata through: BuildDeckRenderInputOptions and
BuildDesktopPdfExportInputOptions gain a `metadata` field passed to
readProjectFile; handleScreenshotExport and the /export/pdf route load it via
getProject(db, id)?.metadata. HTTP regression added: an imported-folder project
(created through /api/import/folder) hitting /export/image now returns 200 with
the rendered image instead of 404.
* chore(nix): refresh pnpm deps hash
* Show PPTX export for detected decks
* Fix deck export detection for page captures
* Route CLI image export through screenshot renderer
* Route legacy image export through screenshot renderer
* fix(export): per-viewport PDF pagination + parallax-faithful image capture
A long non-deck page exported to PDF came out as one giant page, and the same
page exported as an image dropped its scroll-pinned text. Both stemmed from the
page-capture path: PDF assembled one PDF page from a single tall capture, and
the image path flattened fixed/sticky positioning (fixed->absolute,
sticky->static), which deleted parallax headline/foreground text.
- PDF: add a `paginate` render-slides input. A non-deck page now captures one
image PER VIEWPORT, top to bottom, and the daemon assembles a multi-page PDF
(one screen per page). Decks still paginate per slide; page-mode only.
- Image: capture each viewport live at its real scroll offset and stitch into
one tall image, keeping fixed/sticky CSS as authored -- the SAME capture logic
as the PDF path, differing only in assembly. Drop the captureBeyondViewport
one-shot and its isScrollBound heuristic (it rendered the whole document at
scroll 0 and got parallax/reveal-on-scroll content wrong), and drop the
fixed-neutralization step (it dropped pinned text).
Adds paginateViewportBand unit coverage and a paginate IPC round-trip/rejection
case; removes the now-unused neutralizeFixedAndStickyPositioning helper and test.
* fix(export): capture deck-stage at authored size; share pptx in contracts
Addresses two review findings on the screenshot export surface.
- deck-stage fidelity (blocking): the <deck-stage> runtime fits its canvas to
the viewport with `transform: scale(...)` by default and documents that PPTX
export must set the `noscale` attribute so the DOM is captured at the authored
slide size. The renderer never set it, so a deck whose authored canvas differs
from the 1920x1080 capture viewport was measured + captured at the preview-
scaled size. prepareDeckStage now sets `noscale` on every <deck-stage> (a
no-op for plain `.slide` decks).
- contract boundary: `pptx` was a first-class CLI/daemon export format but the
shared `EXPORT_FORMATS` in `@open-design/contracts` still declared only
`['pdf', 'image']`, so the capability was typed through an ad hoc local union.
Add `pptx` to the shared contract, import it in the CLI instead of a local
duplicate, and route `pptx` through the generic `/export` route (to the
screenshot renderer) alongside `image`.
* fix(export): route CLI --format pdf through the raster screenshot PDF path
`od export --format pdf` still posted to the generic `/export` route, whose
desktopArtifactExporter renders vector PDF via printToPDF() and drops CJK glyphs
in the packaged runtime. The web UI was deliberately switched to the raster
`/export/pdf-image` path for that reason, so the CLI diverged from the UI on the
exact decks/pages this feature targets.
Route all three CLI formats through the screenshot renderer (pdf →
/export/pdf-image, matching the UI). Extract the format→route mapping into a
pure `exportRoutePath` helper so it is unit-testable without executing the CLI
entrypoint, and assert no format falls through to the vector `/export` route.
* fix(export): route generic POST /export pdf through the raster screenshot path
The shared ExportRequest contract advertises `pdf` as part of the screenshot-
rendered export surface, but the generic `/export` route still sent `format:
'pdf'` to desktopArtifactExporter's vector printToPDF() path, which drops CJK
glyphs in the packaged runtime. So a contract caller hitting POST /export got the
lower-fidelity PDF while the dedicated /export/pdf-image route, the UI, and the
CLI all use the raster screenshot PDF — the API surface was internally
inconsistent.
Route every /export format (pdf included) through handleScreenshotExport so the
generic endpoint matches the dedicated routes and the contract; drop the now
unused desktopArtifactExporter / buildDesktopArtifactExportInput wiring from the
route. Add an HTTP-level regression asserting POST /export with format:'pdf'
runs the screenshot renderer and streams back a real (%PDF) raster PDF.
* Restore editable PPTX export
* Clarify authored slide measurement
* Enable PPTX export from browser
* Stabilize large editable PPTX text
* Use workspace root for PPTX export resource
* Let CLI exports auto-detect decks
* Avoid tracking generated PPTX bundle
* Fix generic export deck routing
* Fix deck export routing regressions
* Add CLI page-mode export flag
* Preserve authored deck capture DOM
* Load PPTX vendor bundle from gzip resource
* Harden export CLI and PPTX bundle loading
* Preserve editable PPTX slide background images
* Preserve export render sizing contract
* Classify screenshot export request errors
* Preserve freeform slide deck exports
* Preserve UTF-8 export filenames
* Align export routing and CLI JSON contract
* Preserve export compatibility paths
* Keep PDF export on screenshot renderer
* chore(nix): refresh pnpm deps hash
---------
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
|
||
|
|
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 ( |
||
|
|
86824a30d8 |
feat(web): unify task-failure error card across all failure states (#4656)
* feat(web): unify task-failure error card across all failure states
Collapse the three divergent failure UIs (gray .msg.error card, red
.status-pill.is-error pill, accent .amr-card) into one neutral run-error
card with a consistent four-part structure: ① error-type title, ② detail
copy, ③ 1–2 fix CTAs anchored bottom-right, ④ collapsible error-source
(code-line styled, one line collapsed, expand/copy on the source row).
- amr-guidance: add titleKey mapping (code → title i18n); split "sign-in
required" into AMR (inline AmrLoginPill, single CTA, no promotion) vs
non-AMR ({agent} copy, retry + use-official-agent) per the PRD.
- ChatPane: rebuild the error card JSX into the unified shell, preserving
every recovery handler (authorize/recharge/resume/retry/BYOK); interpolate
{agent} via t(key, {agent}) + agentDisplayName.
- AmrLoginPill: drop the redundant device-code chip (the activation URL
already carries the code); reword the activation hint.
- i18n: add run-error title/source/sign-in keys across all 19 locales.
- tests: update amr-guidance + AmrLoginPill specs for the new behavior.
* test(e2e): align run error assertions with unified card
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: Mason <jinmeihong0201@gmail.com>
|
||
|
|
20c61f7732 |
fix(web): gate PDF print-ready handshake on a usable content size (#4458) (#4545)
* fix(web): gate PDF print-ready handshake on a usable content size (#4458) One blank-PDF path in #4458: the in-iframe print-ready handshake reported the artifact's content size after a fixed two animation frames, which can fire before a heavier artifact has finished layout (size still 0). With no usable __odPrintSize, the desktop bridge's inferPageSize falls back to measuring the wrapper viewport — which, per its own docs, blanks artifacts whose visible content sits below the fold. - Add reportPrintSizeWhenStable(): poll animation frames until the measured size is usable (positive, finite), then report it; bounded so a genuinely empty artifact reports best-effort instead of hanging the handshake. - Add isUsablePrintSize() and use it in the parent readiness cache's size validation. Both are injected via toString() and unit-tested directly (real-logic behavior assertions, not just string-presence). Scoped to this one root-cause path. A separate blank-page mode where the reported size is valid but printToPDF captures before the iframe composites needs a real-Electron repro to confirm and is left for follow-up. * test(web): add eval-based runtime behavior regression for the print-ready cache (#4458) #4458 notes the existing coverage only proves the print-ready scripts are injected, never that they behave. Extract the real parent-cache <script> from a live export, run it against a minimal fake window, and drive it with postMessage events to assert the runtime size gate: a usable size is cached for inferPageSize(), while a zero/non-finite size or a wrong-nonce message is rejected so it cannot blank or poison the PDF page. |
||
|
|
d19c53d274 |
Agent-driven brand extraction + two-loop memory overhaul (#4260)
* 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(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> * 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). * 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 * fix(brands): non-blocking extraction start + lifecycle-accurate status (#4470) Acceptance fixes for the Brand Kit surface (PR #4260, even-ferry), addressing five reported issues: - Perf: `startBrandExtraction` no longer blocks the POST /api/brands response on the synchronous programmatic harvest+finalize. It now waits only a short budget (~1.2s) so fast origins still land on a ready, applyable design system (the instant "aha"), while slow/blocked origins return immediately on the "Extracting…" skeleton and finalize in the background. Measured: example.com POST dropped from a ~19s block to ~1.2s, then finalized to `ready` in the background. - Brand Kit stays stuck on "Extracting…" after a manual stop: the backing run's canceled/failed status now reconciles the brand to `failed` with a reason (this extends the existing reconcile; adds a regression spec). - Brand Kit ignores a backing run that is awaiting user input (anti-bot wall / question form): new reversible `needs_input` brand status, derived on read (never persisted) so it flips back once the user answers. Surfaced as a "Needs input" badge + hint in the card and a distinct status dot in the list; the list keeps polling while paused. - Home → Recent projects showed brand-extraction projects as "Not started": /api/projects now falls back to the brand lifecycle for brand projects whose run-derived status is `not_started` (extracting→running, needs_input→awaiting_input, ready→succeeded, failed→failed), so a live extraction is never shown as not started. - "Use in new chat" produced no visible result: it now carries a confirmation notice through the Home intent channel so Home shows a "Using <brand>" toast, making the navigate+apply verifiable (the composer already preselects the brand's design system). Tests: daemon brand-routes (canceled→failed, needs_input transient) and brand-extraction-engine (fast return + background finalize) specs; web BrandPreviewCard specs (needs_input badge/hint, Use-in-new-chat notice). #4 validated end-to-end against a real daemon. guard + typecheck green. * [codex] Fix brand extraction incomplete states (#4486) * fix brand extraction incomplete states * fix web memory tests for advanced modal Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix onboarding P0 brand-step navigation Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix daemon brand extraction test timeout Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix brand extraction fallback prompt state Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> * [codex] Fix brand extraction terminal status reconciliation (#4532) * Fix brand extraction terminal status reconciliation * Keep prefetched brand pages previewable * fix(brands): preserve ready extraction status Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(brands): persist terminal extraction signal Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(brands): clear stale terminal retry markers Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> * Fix brand kit modal intent handling (#4528) Co-authored-by: Siri-Ray <2667192167@qq.com> * fix(web): let design system contents scroll in narrow review layout (#4526) Co-authored-by: Siri-Ray <2667192167@qq.com> * [codex] Fix Brand Kit preview lifecycle guards (#4527) * Fix brand kit preview lifecycle guards * fix(brands): keep detail selected on delete failure Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(brands): handle delete HTTP failures Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> * ci: retrigger pull_request checks * test(e2e): stabilize entry settings shard --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: PerishCode <perishcode@gmail.com> Co-authored-by: lefarcen <935902669@qq.com> Co-authored-by: PerishFire <39043006+PerishCode@users.noreply.github.com> Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
7cdd399442 |
fix(web,daemon): sanitize preview document title so printed PDF filenames are Teams-safe (#4278)
* fix(web): sanitize preview document title so printed PDF filenames are Teams-safe (#3918)
* fix(web,daemon): harden invoice title sanitization for URL-load path (#3918)
Four defects in the prior commit (
|
||
|
|
08668426c7 |
fix(web): wait for print-ready content before browser PDF export (#4329)
* fix(web): wait for print-ready content before browser PDF export (#3872) Browser fallback PDF export now reuses the injected print-ready cache so the popup waits for fonts, images, CSS image URLs, and stable layout before calling window.print().\n\nAdd coverage for both sandboxed and trusted browser export wrappers so the fallback path cannot regress to the old fixed-delay behavior.\n\nGenerated-By: looper 0.9.9 (runner=worker, agent=codex) * fix(web): restore fast PDF fallback when print handshake is blocked Generated-By: looper 0.9.9 (runner=fixer, agent=codex) * fix(web): force lazy print images eager before PDF wait Generated-By: looper 0.9.9 (runner=fixer, agent=codex) |
||
|
|
eb749841b4 |
fix(home): surface community slide decks on Home with letterbox-free 16:9 previews (#4350)
* fix(plugins): surface slides decks on Home instead of marketplace sources The 23 community slides/deck plugins from #4127 were registered as a restricted community marketplace source, so they showed up as installable "available sources" cards (受限 + Install) instead of on Home. Relocate them to plugins/_official/examples so they ship bundled — appearing in the Home Community gallery and the deck example-prompt rail, exactly like the existing zhangzara / swiss / guizang decks — and drop their community-registry entries. - Move 23 plugins/community/<slug> → plugins/_official/examples/<slug> (upstream LICENSE / author / homepage / "Based on" attribution preserved) - Rename manifest name community-<slug> → example-<slug> (bundled convention) - Remove the 23 entries from plugins/registry/community/open-design-marketplace.json (the 3 pre-existing community entries stay) - HomeHero: uncap the deck example-prompt rail (other chips keep the curated 18) so every bundled slide template is reachable straight from "All" - curatedPriority: pin the 23 slides to the front of both the deck example rail and the Community Slides shelf Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(home): drop provenance tail from deck seeds & fix deck preview letterbox Two polish fixes for the slide/deck plugins now surfaced on Home: 1. Example-prompt seed: strip a trailing source-attribution sentence ("…。移植自 foo/bar 的 baz 模板。" / "Based on …") from the composer seed so the prompt doesn't end on provenance boilerplate. Conservative — only fires when the final sentence opens with an attribution marker and real text precedes it; attribution woven mid-description (followed by a use-case sentence) and the gallery/detail descriptions are left intact. Latin '.' only ends a sentence at whitespace/EOL so "STYLE_PRESETS.md" / "github.com/…" don't mis-split. 2. Deck gallery preview: decks ship a fixed 16:9 stage that scales to its viewport, so the tall scroll-preview frame letterboxed it and showed a dark --stage-bg band above/below the slide. Tag deck cards with data-od-mode and give them a 16:9 frame the iframe fills natively (no transform, no pan), so the stage sits edge-to-edge with no band. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(home): deck preview frame to 16:9 so no white strip below the slide The deck preview override targeted the inner html-frame while the gallery frame kept its fixed 300px height, leaving a white strip under the 16:9 slide. Make the gallery frame itself 16:9 for deck cards so the stage fills it exactly — title bar + slide, nothing left over. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(decks): hide the page-counter chrome on all 23 community slide decks The five upstream deck families each render a different page-counter ("1 / 10") — a center pill (.deck-controls), an id="counter" badge, a fixed .deck-counter, or a .count span inside a Shadow-DOM rail — so the home gallery thumbnails showed it inconsistently. Per maintainer request, drop it everywhere rather than unify: inject a display:none rule for the light-DOM counters and inline- hide the Shadow-DOM .count span. Keyboard/touch navigation is untouched; only the visual counter is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(home): fix deck letterbox band in the example-prompt rail tiles too The earlier 16:9 deck-preview fix only covered the Community gallery cards; the hero example-prompt rail tiles render the deck in a 4:3 (1440×1080) preview and still showed a dark --stage-bg band on top. Tag deck preset tiles with data-od-mode and give them a 16:9 preview the iframe fills natively, matching the gallery treatment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(home): remove the ↗ open-external button from community gallery cards The ↗ only rendered when a tile's preview was an HTML source, so it appeared on some cards (most decks, a few prototypes) and not others (images, srcless tiles) — inconsistent chrome across the shelf. Drop it and the whole onOpenExternal pass-through (PluginCard → PluginsHomeSection → HomeView) plus the now-dead .plugins-home__gallery-open styles. * fix(home): make the community search clear (×) button visible The clear button uses the shared <Button> component, whose CSS-module class set padding 6/12 and crushed the 12px × glyph to zero width inside the 26px button (a blank grey square). The .plugins-home__search-clear reset only out-specified the global `button {}` element rule, not the component class — scope it under .plugins-home__search so the padding:0 / 20x20 sizing actually wins. * fix(home): align deck example-prompt tile title row with other tiles Deck preset tiles used a 16:9 `auto` preview row, so their card was shorter than the fixed-150px sibling tiles; the flex rail stretched them back up and the slack leaked into the title row, making deck titles ~6px taller. Keep deck tiles on the standard 150px preview cell and center the native 16:9 stage inside it (neutral margin, no dark band), so card and title-row heights match prototype/HyperFrames/image tiles. * fix(deck-bridge): mirror the visible class so reveal animations fire on navigation Reveal-animation decks (the frontend-slides family) gate staggered entrances on a separate `.slide.visible` class that the deck's own show() adds alongside `.active`. The host bridge's setActive() flipped only the active class, so bridge-driven page turns showed slide chrome but left every .reveal child at opacity:0 — the body rendered blank. Mirror `.visible` in lock-step with the active slide (only for decks that use it, a no-op elsewhere). Adds a red→green regression test. * fix(home): unpin the bare Frontend Slides template from the slides shelf example-frontend-slides is the family-root template with a generic cover; pinned first it read as filler leading the Slides shelf and deck chip. Drop it from PINNED_SLIDE_PLUGIN_IDS so it falls to the uncurated tail while the styled variants (Creative Voltage, Electric Studio, …) keep the lead. --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |