mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
fc85111f12b65ae8348d5f9952007e93f3c1931c
335
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
27e89e83ee |
feat(files): add HTML file version history (#4872)
* feat(files): add HTML file version history * fix(files): address version history review blockers * fix(files): harden HTML version history * fix(files): preserve deck version previews * fix(files): serialize version history renames * fix(files): reserve version history storage paths * fix(files): rotate reused version history paths * fix(files): surface HTML version capture failures * fix(web): preserve upload display names * fix(cli): reject invalid version source * fix(web): label HTML view mode tabs * fix(files): serialize HTML version captures |
||
|
|
31d98634c3 |
feat(web): add composer context sources (#4869)
* feat(web): add composer context sources * fix(web): require resolved context paths * fix(web): keep local context removal authoritative * fix(web): preserve project reference fallbacks * fix(web): clear linked dirs with context chips * fix(web): preserve context dirs when changing workdir * fix(web): promote matching context workdir * fix(web): preserve shared context dir ownership * fix(web): retain home context dirs in composer * fix(web): preserve promoted context dirs on clear * fix(web): keep linked context chips after send * fix(web): hydrate home workspace context chips * fix(web): stabilize workspace context submissions * fix(web): drop stale home workspace context dirs * fix(web): fail missing workspace context dirs * fix(web): keep home workspace context visible * feat(web): implement project directory materialization for references - Added `ensureReferencedProjectDir` function to create a managed project's folder if it doesn't exist, ensuring that references to new projects resolve correctly. - Updated `getProjectDetail` to include an option for directory materialization, preventing errors when referencing uninitialized projects. - Enhanced `ProjectReferenceModal` to filter out external projects with missing directories, improving user experience. - Introduced tests for the new functionality to ensure proper behavior when referencing projects. This change stabilizes project referencing and enhances the overall workflow for users dealing with managed projects. * fix reference context fail-fast paths |
||
|
|
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> |
||
|
|
d379650dc1 |
add Instagram, LinkedIn, and Xiaohongshu to the settings social links (#4977)
The settings popover already linked X, Threads, and YouTube; add the three remaining official Open Design accounts (Instagram, LinkedIn, Xiaohongshu) as follow rows, and add matching rotating "useful info" tips in the design files panel so each account is surfaced there too. New i18n keys are fully localized across all 19 locales, and the settings_popover analytics `element` union gains follow_instagram / follow_linkedin / follow_xiaohongshu. Co-authored-by: Tuolalala <xinglige0205@gmail.com> |
||
|
|
56c410e9ef |
[codex] strengthen daemon diagnostics coverage (#4948)
* test: expand automation coverage gaps * test: cover additional automation gaps * test: strengthen daemon diagnostics coverage * fix(e2e): align media provider project modal helper |
||
|
|
ca89845d03 |
feat(brands): surface the draft design system live and browse images in a lightbox (#4777)
* feat(brand-extraction): enhance programmatic extraction with reconciliation and UI improvements - Introduced `reconcileProgrammaticExtractionTranscript` to manage the state of brand extraction transcripts, ensuring accurate status updates during extraction processes. - Updated the `cancel-extraction` route to handle asynchronous operations, allowing for better user feedback and immediate reconciliation of extraction states. - Enhanced the `startBrandExtraction` function to support draft design system creation, ensuring that users can see their systems immediately, even if extraction fails or is stopped. - Improved error handling and logging for reconciliation processes, providing clearer insights into failures during brand extraction. - Added tests to validate the new reconciliation logic and ensure that the extraction process behaves as expected under various scenarios. These changes improve the overall user experience by providing more robust handling of brand extraction states and enhancing the visibility of the extraction process. * feat(brand-extraction): enhance brand preview rendering and UI feedback - Added functionality to render brand previews into projects when a draft is stopped, improving user experience by providing immediate visual feedback. - Updated the `startBrandExtraction` function to include metadata for design system IDs and statuses, ensuring accurate tracking of brand extraction states. - Enhanced the `renderBrandPreviewIntoProject` function to support new status types, including 'draft', allowing for better representation of brand states. - Introduced new localization strings for draft-related messages, improving clarity for users during the brand extraction process. - Added tests to validate the new preview rendering and status handling, ensuring robustness in the brand extraction workflow. These changes improve the overall user experience by providing clearer feedback and enhancing the visibility of brand extraction processes. * feat(design-kit): browse design-system images in a lightbox carousel The image-preview lightbox showed a single static image with no way to move between samples. Replace the single-image state with an index into the visible, non-broken samples so the modal becomes a carousel: prev/next chevrons, an "N / total" counter, ArrowLeft/ArrowRight keys, and wrap-around. Broken tiles are filtered once through a shared lightboxItems memo so the gallery and lightbox indices stay aligned with the delete handler. * feat(brands): reveal the draft design system the moment extraction starts A brand extraction pre-creates a draft `user:` design system, but the project view only rendered it once the registry summary had refreshed, leaving the Design System tab empty for the whole extraction. Build a fallback summary from project metadata so the draft renders immediately, auto-open the Design System tab when extraction begins or is cancelled, and hydrate the freshly created project with its brand metadata. Draft pre-create now records the source URL in provenance and fails loudly instead of swallowing the error. Because ProjectView imports DESIGN_SYSTEM_TAB at module scope, every FileWorkspace test mock must export the constant. * fix(design-system-picker): restore fullscreen preview styles dropped in routines.css reorg The brand-extraction routines.css reorganization accidentally removed the `.project-ds-picker-fullscreen` rule block, its `project-ds-picker-fullscreen-in` keyframes, and its responsive overrides. The modal-window drag guard and two style specs still depend on those rules (the fullscreen design-system preview must stay above modal chrome), so they went red. Restore the block verbatim to match main. * feat(brand-extraction): add continue extraction functionality for incomplete brands - Introduced a new API endpoint to restart the deterministic programmatic extraction for existing brands without creating duplicates. - Implemented a tracking mechanism for ongoing extractions to manage their state effectively. - Enhanced the UI components to support the new continue extraction action, including loading states and callbacks. - Updated relevant tests to ensure the new functionality works as expected and integrates smoothly with existing workflows. These changes improve the user experience by allowing users to resume brand extractions seamlessly, enhancing the overall efficiency of the brand management process. * feat(skills): integrate skill management into the application - Added a new SkillDetailsModal component to display detailed information about skills. - Enhanced the ChatComposer and HomeHero components to support skill selection and details viewing. - Updated the ComposerPlusMenu to include a skills submenu, allowing users to search and pick skills. - Implemented functionality to handle skill details in various components, improving user interaction with skills. - Refactored existing components to accommodate skill-related features, ensuring a cohesive user experience. These changes enhance the application's capabilities by providing users with better access to skill information and management, streamlining workflows involving skills. * feat(brand-extraction): improve brand assistant message handling and UI feedback - Enhanced the AssistantMessage component to better manage events and display content, ensuring that messages are rendered correctly based on their content and events. - Updated the ChatPane component to hide empty brand assistant messages and improve the visibility of relevant content, enhancing user experience during brand extraction processes. - Introduced new utility functions to determine the visibility of brand assistant events and content, streamlining the logic for displaying messages. - Refactored the ProjectView component to handle brand browser assist confirmations more effectively, ensuring that the UI responds appropriately to user interactions. - Added tests to validate the new message handling logic and ensure that brand extraction messages are displayed correctly. These changes enhance the overall user experience by providing clearer feedback and improving the management of brand assistant messages during extraction workflows. * fix(brands): address browser assist review blockers * fix(web): restore new project modal entrypoints * fix(web): switch to replacement brand retry conversations * fix(web): restore skill-change preview invalidation * fix(brand): harden retry completion flow * fix(brand): close extraction lifecycle races * test(e2e): wait for privacy banner dismissal * fix(brand): guard retry startup races * fix(brand): isolate extraction retry attempts * fix(design-systems): clean up partial draft creation * fix(brands): let stop supersede stalled transcript * fix(brands): fail thin browser html retries * fix(brands): preserve stopped html retries * fix(brands): reject off-path browser retries * fix(brands): expose deterministic retry in cli * fix(brands): serialize cancel with finalization * fix(brands): bound cancel settlement wait * fix(brands): bound retry takeover waits * refactor(next-step-actions): replace titles with localized keys and update related prompts * fix(brands): retry after browser read failures * fix(brands): accept canonical browser redirects * fix(brands): reset retry assist stall timer * test(web): align picker compact preview assertions * fix(brands): harden design kit cleanup paths * test(e2e): stabilize AMR runtime recovery checks * feat(brands): enhance brand extraction from post-wall pages - Introduced `browserHarvestIsUnusable` function to determine if a harvested page lacks usable brand signals, allowing for more permissive extraction from content-rich pages that may embed anti-bot widgets. - Updated `extractBrandFromHtml` to utilize the new function, ensuring that valid post-wall pages are processed correctly. - Enhanced `isChallengePage` to differentiate between genuine content-rich pages and bot protection interstitials, improving the accuracy of brand extraction. - Added tests to validate the new behavior, ensuring that pages with minimal color palettes or embedded widgets are handled appropriately. * feat(brands): improve handling of recoverable extraction states - Refactored brand extraction logic to maintain a `needs_input` status for recoverable scenarios, such as blocked pages or mid-load content, instead of marking them as `failed`. - Updated related functions and routes to reflect this change, ensuring that the user interface presents a calm, retryable state rather than a terminal failure. - Enhanced tests to validate the new behavior, confirming that the system correctly identifies and manages recoverable extraction attempts. * feat(projects): add design-system project creation from existing projects - Implemented a new command `od project create-design-system` to duplicate a regular project as a design-system workspace, including a prompt for design-system generation. - Enhanced the API to support the creation of design-system projects, ensuring proper metadata and file copying from the source project. - Added a detailed prompt structure for the design-system generation process, outlining requirements and expected outputs. - Introduced tests to validate the new functionality, ensuring that design-system projects are created correctly with the appropriate context and files. - Updated the UI components to integrate the new design-system creation functionality, allowing users to initiate the process seamlessly. * feat(projects): add project duplication workflow * fix(brand): keep browser assist recovery retryable * fix(plugins): roll back failed template duplicates * feat(brand): enhance browser assist functionality and improve user guidance - Introduced a new `brandBrowserAssistOdCard` function to generate browser assist cards for better user interaction. - Updated the content structure in `reconcileProgrammaticExtractionTranscript` to include browser assist cards when applicable. - Enhanced user guidance in error messages for stalled and stopped extraction processes, providing clearer instructions for recovery actions. - Added tests to ensure the new browser assist card functionality is correctly integrated and displayed in the chat interface. - Improved localization for various user prompts and messages related to browser assist actions. * feat(design-browser): enhance design file handling and user notifications - Added support for opening design files directly from the status message in the DesignBrowserPanel. - Introduced a new `actionTarget` property to differentiate between design files and regular files in status messages. - Implemented a `stopLoading` function to halt page loading when necessary, improving user experience during snapshot captures. - Updated localization files to include new status messages and project type labels. - Enhanced toast notifications for browser snapshots with improved styling and functionality. - Added tests to ensure new features work as expected and do not interfere with existing functionality. * feat(app): implement design system project creation with preset prompts - Updated project creation logic to route through the default design router, ensuring user interaction prompts for design system selection. - Introduced a new localization key for the design system creation prompt, enhancing user guidance. - Adjusted plugin ID to avoid assumptions about prototypes, improving the flexibility of project creation. - Updated tests to reflect changes in project creation behavior and ensure correct routing and prompt handling. * feat(chat): update icon sizes and implement draft persistence in HomeView - Increased icon sizes in ChatComposer from 13px to 16px for better visibility. - Added localStorage persistence for the Home composer prompt and design system selection to retain user input across tab switches. - Implemented functions to read and write drafts to localStorage, ensuring a seamless user experience. - Cleared drafts upon run creation to prevent reappearance of previous inputs. - Enhanced Toast component to handle auto-dismissal correctly, even with changing parent components. * fix(pr): address review blockers * fix(web): preserve authoring prompt confirmation * test(daemon): cover project CLI copy commands * fix(web): surface skill detail load failures |
||
|
|
e75cb416fc |
[codex] Add BYOK provider presets (#4902)
* Add BYOK provider presets * Fix BYOK preset picker accessibility names Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Hide unsupported Bedrock BYOK preset Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Restore BYOK preset test coverage Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Restore BYOK protocol fallback coverage Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Hide Bedrock from BYOK protocol tabs Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Downgrade unsupported Bedrock chat configs Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Clear Bedrock secrets during config downgrade Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Scrub nested Bedrock config on downgrade Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Persist Bedrock config downgrade migration Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Disable MiMo model discovery fetch Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Tighten Bedrock runtime URL detection Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Scope BYOK key drafts to provider presets Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Preserve BYOK provider form drafts Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Filter BYOK media models from chat discovery Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Fix BYOK provider draft persistence Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * Fix BYOK account model load status * Fix BYOK provider status coverage --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.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> |
||
|
|
3652fb7237 |
[codex] restore AMR wallet balance follow-ups (#4771)
* feat: show AMR wallet balance * fix: preserve AMR wallet reauth state * chore: retrigger PR validation * fix: bound AMR wallet balance fetch * chore: retrigger wallet validation * chore: bump bundled Vela CLI to 0.0.18 * chore: retrigger Vela CLI validation * chore(nix): refresh pnpm deps hash * feat(web): reposition AMR as Open Design's cloud subscription (#4684) * feat(web): reposition AMR as Open Design's cloud subscription Fold the standalone "AMR" product into Open Design's own cloud service: unify all user-facing copy, surface the signed-in account's plan + wallet balance, and add upgrade CTAs. - Copy: user-facing "AMR" / "Open Design AMR" -> "Open Design" across 19 locales, agent labels, and daemon error strings (balance/recharge wording preserved). - Account: daemon reads plan + balance via `vela billing summary` (CLI route, control-key) and injects them into the vela status; web shows plan + balance on the settings card, the model-switcher account row, and the runtime-switcher avatar row (balance field aligned with the console wallet's headline balanceUsd). - Upgrade: filled "Upgrade" CTA next to the plan on all three surfaces when the tier is below max; opens the console wallet at ?view=plans. - Runtime-switcher Open Design row restructured to carry account info inline, with a selected-state highlight. - Analytics: new amr_entry elements settings_amr_upgrade / inline_amr_upgrade / avatar_amr_upgrade / avatar_amr_agent_card. - specs/: design doc for the Open Design account/avatar direction. * fix(daemon): surface live account for env-backed auth + isolate cache per credential Address @nettee review on #4684: - applyVelaLiveAccount() now populates plan/balance even when the login status has user:null (VELA_RUNTIME_KEY / VELA_LINK_URL env auth), so env-backed Open Design sessions no longer drop the fetched billing data. - Key the live-account cache by the full credential revision (auth source + profile + userId + config mtime), not just profile, and clear it on logout — a logout / account switch can no longer surface the previous account's plan or wallet balance before the background refresh runs. - Regression tests for both in tests/integrations/vela.test.ts. Sync tests to the AMR -> Open Design copy: - web: tests/providers/sse.test.ts error-string assertions. - e2e: onboarding / logout / run-failure / login-pill specs + visual fixture. * fix(i18n): drop duplicated "Open Design" in switchBody fallback copy The AMR -> Open Design rename sweep collided with the existing brand mention in chat.amrCard.switchBody, rendering "Open Design official Open Design model service" across 13 locales (zh-TW, ja, ko, ar, es-ES, hu, id, fa, pl, tr, ru, pt-BR, uk). Remove the redundant second mention so the retry/switch card reads cleanly. Flagged by @nettee on #4684. Also start aligning InlineModelSwitcher unit tests to the new copy (agent display name AMR -> Open Design, compact error string). * fix(amr): resolve live billing before first /status; hide Upgrade until plan known Address @nettee review on #4684 (two correctness issues in the new plan/balance path): - Cold cache: the signed-in /status handler now BLOCKS the first response on fetchVelaBillingSummary() when the cache is empty, instead of returning config-only and refreshing in the background. The new account surfaces read /status once and never re-poll, so the old flow left plan/balance hidden until the user refocused. Warm cache keeps the non-blocking fast path. fake-vela gains a `billing summary` handler and vela.routes.test.ts covers the cold-cache-first-open case. - Upgrade CTA: canUpgradeVelaPlan() now treats an unknown/empty plan as NOT upgradeable, so a signed-in session whose live summary hasn't resolved no longer flashes Upgrade at top-tier users. Unit-tested. * style(amr): bold the plan/balance text in the switcher + avatar account rows The settings card already renders both bold (the plan value reuses .agent-card-amr-balance-value); align the inline switcher account row and the runtime-switcher avatar row so plan + balance read with the same emphasis everywhere. * fix(amr): single-flight cold billing fetch + surface plan/balance on its own field Address @nettee follow-up review on #4684: - Race: the cold-cache decision keyed off the refresh throttle, so a second /status arriving during the first billing fetch saw refreshAccount=false, fell through, and returned config-only — which the read-once surfaces can't recover from. Now `peekVelaLiveAccount(key) === null` is the cold signal and the billing fetch is single-flighted per credential revision: concurrent cold callers await the same in-flight promise. setVelaLiveAccount stamps the fetch time so the warm path doesn't immediately re-refresh. - Identity: applyVelaLiveAccount no longer fabricates a `{ id: '', email: '' }` user for env-backed sessions. The live billing projection now rides on a new VelaLoginStatus.account field, so `user` stays null (preserving the analytics `user.id` null signal) and plan/balance are surfaced uniformly. SettingsDialog / InlineModelSwitcher / AvatarMenu read status.account.{plan,balanceUsd}. Regression coverage updated in vela.test.ts (applyVelaLiveAccount) and vela.routes.test.ts (cold-cache first open now asserts body.account). * style(amr): match the switcher plan text color to the balance The inline switcher account row showed the balance in --text-strong but the plan in --text-muted, so "$247.51 Plus" read with two different weights of grey. Use --text-strong for the plan too so balance + plan sit at the same emphasis. * fix(amr): treat tier-less billing summary as free; register new entry sources Address @nettee review on #4684: - Free vs unknown: fetchVelaBillingSummary() returned plan:undefined when `membershipTier` was absent, but that field is omitted for free accounts. The new surfaces read status.account.plan for both display and canUpgradeVelaPlan, so a successful free-account read rendered as "unknown" — plan hidden and the Upgrade CTA suppressed for the exact users who should see it. A successful summary without a tier now normalizes to the explicit 'free' sentinel; "unknown" stays signalled by a null account (failed fetch). Regression in vela.routes.test.ts (balance, no tier → account.plan === 'free'). - Analytics: the four entry sources this PR adds (settings_amr_upgrade, inline_amr_upgrade, avatar_amr_upgrade, avatar_amr_agent_card) were in the source→page map but not in the AMR_ENTRY_SOURCES validator set, so parseAmrEntryAnalyticsPayload rejected them and /analytics-entry 400ed for the new buttons. Added them to the set; parser regression covers all four. * fix(amr): invalidate the env-backed live-account cache on config/account change Address @nettee 11:19 review (the inline comment did not post, but the flagged env-backed live-account cache path has a real leak): readVelaCredentialRevision forced configMtimeMs to null for env-backed auth, so the live-account cache key became `env|profile|1||` with nothing to distinguish accounts. Because the live billing summary is fetched with the config profile's controlKey, a config rewrite (account switch) left an env-backed (VELA_RUNTIME_KEY) session serving the previous account's plan/balance. Keep configMtimeMs for env auth too so a config/account change yields a fresh key. Regression added in vela.test.ts. * fix(amr): fingerprint the configured AMR env in the live-account cache key Address @nettee 11:35 review on #4684: configMtimeMs alone wasn't enough. The AMR env credentials (VELA_RUNTIME_KEY / VELA_LINK_URL) can come from agentCliEnv.amr in app-config, and env-backed sessions report user:null — so switching the Settings-backed credential from account A to B on the same profile, without touching ~/.amr/config.json, left the revision at `env|profile|1||<same-mtime>` and let B reuse A's cached plan/balance. readVelaCredentialRevision now adds a non-secret credentialFingerprint (sha256 of the runtime key + link url, truncated) and velaLiveAccountCacheKey includes it. Regressions: a unit test (env key differs by fingerprint) and a route test that rewrites agentCliEnv.amr's VELA_RUNTIME_KEY without touching the config and asserts the second account does not inherit the first's cached billing. * feat(amr): label + stack balance/plan in the switcher account row; fix its tests UI: the inline switcher Open Design account row rendered "$247.51 Plus" with no labels, so users couldn't tell what the two values were. Balance and plan now stack on two labelled lines ("可用余额 / Available balance" and "当前套餐 / Current plan"), with the Upgrade CTA on the plan line. The settings.amrBalance / settings.amrPlan labels are made descriptive across all 19 locales (the settings card reuses them), so the meaning is explicit everywhere. Tests: rewrite InlineModelSwitcher.test.tsx for the account-row structure — the agent radio now carries status only in its aria-label (icon-only visible), and the sign-in / signing-in / cancel affordances live on the inline-model-switcher-account-action button. Redundant within(amrButton) getByText assertions are dropped (the radio-name query already asserts status); pending/error assertions target the account row. 22/22 green. * test(amr): align AmrLoginPill tests to the Open Design copy Account-status group is labelled 'Open Design account status', the console link is 'Console', and the compact sign-in error reads 'Sign-in failed.' — update the queries to match. 29/29 green. * feat(amr): redesign the account row — tier badge + balance subtitle; short labels Iterated on the Open Design account row in both the inline model switcher and the runtime-switcher (avatar) list so the plan/balance no longer collide: - Plan tier renders as a small neutral pill right after the "Open Design" name (identity + tier read as one group, à la Claude's "name · Max"). - Balance moves to a second line below the name, labelled "余额 $247.51" (small grey label + bold value) so users know what the number is. - Upgrade stays as the trailing action, vertically centred against the two lines. Account logo bumped to 24px to balance the taller row. - Revert the amrBalance / amrPlan labels back to the short "余额 / 套餐" ("Balance" / "Plan") across all 19 locales. * test(amr): align remaining web specs to the Open Design copy + account row Update the rest of the web component specs that still asserted the pre-rename wording / old account-row shape (the `Web workspace tests` red CI @nettee flagged): - EntryShell.onboarding: "Sign in to Open Design Cloud" -> "Sign in to Open Design"; "AMR sign-in failed." -> "Sign-in failed.". - SettingsDialog.execution: AMR card queried as /^Open Design\b/ (was /^Open Design AMR\b/). - HandoffButton: agent label "Open Design"; website link "打开 Open Design 官网". - AvatarMenu: drop the two obsolete avatar console-shortcut specs (the avatar console link was removed when the row was consolidated; the upgrade entry remains covered by the daemon parser + contract). Full web suite green: 3403 passed. * test(amr): cover the avatar-row upgrade CTA (plan/balance + stamped link) Replaces the two removed avatar console-shortcut specs with focused coverage for the new runtime-switcher upgrade path (@nettee): mocks a signed-in /api/integrations/vela/status, opens the menu on the non-prod `test` profile, asserts the rendered plan badge + balance, and verifies the clicked upgrade link carries view=plans and od_entry_source=avatar_amr_upgrade attribution. * test(e2e): update avatar visual spec to the redesigned account row The Playwright visual (settings-workspace) job was red on a functional assertion, not a pixel diff: the spec queried the removed `.avatar-amr-account-link` console entry and 'AMR account' / 'Balance & recharge' copy. Rewrite it to override the signed-out status with a signed-in plan/balance, then assert the Open Design row renders the Plus badge + $247.51 and the upgrade link points at ?view=plans. * chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714) * test: capture Open Design account balance visuals * chore: retrigger workspace validation --------- Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> Co-authored-by: a1chzt <chizblank@gmail.com> * [codex] keep AMR wallet balance stable during refresh (#4730) * fix: keep AMR wallet balance visible during refresh * fix: remove AMR wallet refresh hover tooltip * chore: retrigger validation * fix(ci): align AMR wallet branch validation * feat(web): Open Design 云账户入口 UI 优化(名牌/置顶/新 logo) (#4787) * feat(web): polish Open Design cloud account surfaces (plan badge, pin-to-top, new logo) - 模型切换器(InlineModelSwitcher): Open Design 账号卡片上位为可点选入口并置顶, 移除重复的代理行 - 头像菜单(AvatarMenu): Open Design 置顶代码代理列表; 执行模式选中态改为勾选标记(不再与代理选中态争色块) - 统一套餐名牌: 新增共享 PlanBadge 组件(全局 CSS, 跟随主题色), 替换设置页/切换器/头像菜单三处各自实现 - 设置页 CLI 卡片: 删除无用的钱包刷新按钮与"更新于…缓存"行; 余额统一为两位小数 - 全局替换 AMR logo 资产(amr.svg) * test(web): align AgentIcon AMR assertion with new logo fill --------- Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> * fix(web): preserve AMR wallet balance in switcher * fix(amr): avoid stale wallet status on refresh failures * fix(web): align avatar AMR balance row * fix(web): avoid stale AMR wallet snapshots * fix(web): keep avatar AMR console link * fix(amr): support wallet fallback for older vela cli --------- Co-authored-by: michaeltaxman911-hue <michaeltaxman911@gmail.com> Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
54de349c47 |
fix: replace expired Discord invite (#4849)
Co-authored-by: koki yanlai xu <koki@kokideMacBook-Air.local> |
||
|
|
6755e938a4 |
test(platform,sidecar-proto): cover proc-tree, stamp readers, ipc-path validation (#2406)
* test(sidecar-proto): cover normalizeIpcPath validation and Windows pipe detection * test(platform): cover readFlagValue and stamp readers * test(platform): cover collectProcessTreePids BFS traversal |
||
|
|
0fecb15baa |
chore(release): bump main to 0.12.1 ahead of stable 0.12.0 (#4900)
Stable shipped 0.12.0 (tag open-design-v0.12.0, 2026-06-26) but main's packaged base version stayed at 0.11.1. release-beta's metadata guard requires the packaged base version to be strictly greater than the latest stable, so the daily beta build (notify-daily-feishu, 01:00 UTC) has failed every morning since 2026-06-27 with: packaged base version 0.11.1 must be strictly greater than latest stable 0.12.0 Bump all 18 workspace package.json versions in lockstep to 0.12.1 so main leads stable again. Verified the real prepare-beta guard now resolves 0.12.1-beta.1 (EXIT 0) against the live stable/beta feeds. |
||
|
|
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>
|
||
|
|
f110687d1a |
fix: document data-od-id contract in HTML prompt (#4305)
* Document data-od-id prompt contract * Address data-od-id uniqueness review * Mirror data-od-id prompt contract to contracts package --------- Co-authored-by: fuyizheng3120 <182291973+fuyizheng3120@users.noreply.github.com> |
||
|
|
ca9c2d10b5 |
test(contracts,agui-adapter): cover error helpers, schema parse, ctx.now fallback (#2393)
* test(contracts): cover error helpers and schema parse round-trips * test(agui-adapter): cover ctx.now fallback and optional field passthrough |
||
|
|
fc4e51871c |
[codex] Keep prefetched brand pages previewable (#4530)
* Keep prefetched brand pages previewable * Scope brand reconcile to extraction conversation Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Stabilize BrandsTab polling test Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Stabilize conversation run status ordering Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Preserve stale ready brand terminal failures Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Scope brand reconciliation to extraction run 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> |
||
|
|
12ee8d05eb |
[codex] Track trace touched files for reopen flows (#4432)
* Track touched files for trace object uploads * Fix trace touched-file cleanup and aliases Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * Scope trace touched files per run Generated-By: looper 0.9.9 (runner=fixer, agent=codex) * Fix reattach trace telemetry finalization Generated-By: looper 0.9.9 (runner=fixer, agent=codex) * Fix full replay trace object files Use replayed reattach events when reconstructing touched trace object files during full replay, and cover the finalized save path with a focused regression. Generated-By: looper 0.9.9 (runner=fixer, agent=codex) * Scope pending write tracking per run Generated-By: looper 0.9.9 (runner=fixer, agent=codex) * Guard trace touched-file path matching Generated-By: looper 0.9.10 (runner=fixer, agent=codex) * Retrigger workspace validation 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> |
||
|
|
8fa6c5e198 |
[codex] Add run lifecycle tracing (#4431)
* Add run lifecycle tracing fields * Fix lifecycle artifact and stdin timing * chore: retrigger workspace validation Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: a1chzt <chizblank@gmail.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
22c1e3aff4 |
feat(web,landing): Workspace-for-Teams entry chip + enterprise lead page (#4737)
* feat(web,landing): Workspace-for-Teams entry chip + enterprise lead page Validate demand for a team/workspace edition. web (apps/web): - Add a neutral "Teams" chip to the Home top-right toolbar that opens the enterprise landing page in the external browser. The URL carries the client's active locale so the page opens in the same language (falls back to English for locales the landing site doesn't ship). Dev points at the local landing dev server; prod at open-design.ai. - New analytics element `workspace_teams` on home toolbar clicks. - i18n key `entry.workspaceTeams*` across all 19 locales. landing (apps/landing-page): - New /enterprise/ (+ localized [locale]/enterprise/) contact-sales page in the light Atelier Zero style: pitch + perks, a wordmark social-proof wall, and a lead form (name, work email, company, team size, multi-select use cases, role, message). Custom inline validation (no native browser bubbles). Page is intentionally outside every top-nav tab. - contact-sales Pages Function validates leads and fans them out to a Feishu custom-bot webhook with a Workers KV backup. contracts: - Add `workspace_teams` to HomeToolbarClickProps. * feat(landing): flesh out Workspace-for-Teams enterprise page - Real brand logo wall via Simple Icons CDN (denser 12-logo grid) - Custom styled Team size / Monthly budget dropdowns (no native popup) - Monthly budget question (USD ranges) wired into the lead payload - Early-access incentive callout - Use-case demo reuses the homepage Labs stage (magnifying dock, auto-cycle, video) - Discord community invite card in the left column (link, not a form field) - contact-sales: validate budget, drop joinDiscord, no-store header * feat(landing): instrument Workspace-for-Teams page with PostHog events Reuses the shared __odTrack delegate (sub-page-layout). page_view and the Discord-card join_discord click are already auto-captured; this adds the lead-funnel events the delegate can't see: - lead_submit_attempt / lead_submit_success / lead_submit_failed - lead_submit_invalid (with the missing field list) on validation block - demo_click when a user picks a Labs demo tile - data-od-id on the page/demos sections so ui_click area resolves correctly * fix(web,i18n): localize Workspace-for-Teams chip label and tooltip for zh The Teams chip label key was hardcoded to "Teams" in every locale, so the button never localized. Chinese now reads 团队版 (zh-TW 團隊版). Also reword the tooltip/aria away from the awkward "Workspace 团队版 — …告诉…诉求" mixed copy to "团队版工作区 — 告诉我们你的团队需求" (zh-TW 團隊版工作區 — …需求). * feat(landing): localize Feishu lead card to Chinese + require a business email The enterprise lead card now renders Chinese labels/values for the internal sales group, and the Cloudflare Function rejects free/personal mailboxes (Gmail/QQ/163/...) with error "personal_email" so every lead carries a company domain. * feat(landing): block personal mailboxes in the enterprise form client-side Mirrors the Function's business-email rule in the inline validator so a personal mailbox is caught before submit, and reworks the localized email error copy (en/zh/zh-tw) to explain why a work email is required. * chore(preview): redeploy to pick up corrected staging Feishu webhook * revert(landing): drop the business-email requirement — accept any valid email Per product: the enterprise lead form should accept ANY valid email, not just company domains. Removes the personal-mailbox blocklist + checks from both the Function and the client validator, and restores the original email error copy. The Chinese Feishu card localization is kept. * fix(web): keep the Teams entry reachable on narrow screens The topbar chip group is hidden under 900px, so the Workspace-for-Teams chip had no narrow-screen fallback (unlike Discord/X which live in the settings menu). Adds a Teams item to EntrySettingsMenu, extracts the enterpriseUrl helper into a shared module so both surfaces share one source, and allows element=workspace_teams on settings_popover tracking. Addresses review feedback on #4737. * fix(web): limit enterprise locale URLs --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> |
||
|
|
a915da2c66 |
feat(plugins): duplicate examples into projects (#4861)
* feat(plugins): duplicate examples as projects * fix(plugins): gate duplicate projects by HTML support * fix(plugins): preserve duplicate entry navigation |
||
|
|
41f11f478f |
feat(analytics): split project_kind by Home task card (wireframe/mobile/document) (#4769)
* feat(analytics): split project_kind by Home task card The created-project analytics `project_kind` collapsed several Home task cards into coarse buckets: Wireframe / Mobile / Live artifact all reported `prototype`, and Document reported `other`, so a created project's kind no longer matched the `task_chip` the user picked. Extend `TrackingProjectKind` with `wireframe` / `mobile` / `document` (`live_artifact` / `hyperframes` already existed) and derive the finer kind from the persisted project metadata — `projectKindToTracking` now reads `fidelity` / `intent` / `platform`/`platformTargets` (precedence live_artifact > wireframe > mobile), and `other` + `intent: 'document'` becomes `document`. A new `projectKindFromMetadataToTracking` helper threads this at every call site (web project_create_result / recent-projects / designs / in-project runs, daemon run_created / run_finished). Product behavior is unchanged: `metadata.kind` stays `prototype`/`other` (cards keep reusing the web-prototype seed); only the analytics dimension splits out. `intent: 'document'` is an analytics-only tag no product logic keys off. HyperFrames already resolved correctly via `videoModel === 'hyperframes-html'` and is preserved. Verified end-to-end against a live daemon: created one project per card via the real API, read back the persisted metadata, and confirmed each derives its expected fine kind (hyperframes/wireframe/mobile/document/live_artifact). * fix(analytics): keep design-system-apply target kind coarse Review catch: ProjectView's in-project design_system_apply_result emits `target_project_kind`, typed `TrackingDesignSystemApplyTargetKind` — a narrower enum without the prototype subtypes (wireframe/mobile) or document. Switching that site to the widened metadata helper let a Home-created Wireframe/Mobile/Document project emit a value outside the field schema (the `as` cast hid it). Derive the coarse kind here instead, so subtypes collapse back to `prototype`; the fine split stays on `project_kind` only. --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> |
||
|
|
273bacfc78 |
[codex] Add design-system tracking analytics (#4783)
* feat(analytics): design-system tracking foundation (project_kind, entry source, brand picker)
Implements the first slices of the design-system tracking spec:
- §1 project_kind: brand-extraction backing projects now report
project_kind=design_system so DS-project runs (creation + edits)
drill down cleanly instead of collapsing into 'brand'.
- U3/U4: report design_system_source from the real selection source
(request->user_selected, plugin->template_inherited, project->
project_saved, app-default->default) instead of the hard-wired
unknown/not_applicable; add design_system_kind (official/custom,
derived from the user:<id> shape) and design_system_slug (official).
- §3.1 create entry source: thread the real entry (onboarding /
design_systems_page / composer_picker / home_card / library /
project_canvas) from each navigate() call site into the create
page_view and create_result, replacing the binary heuristic.
- C7-C9 preset-brand picker: instrument the "Start from a brand"
trigger click, picker surface_view, and brand_pick with
preset_brand_category (never the domain).
Contracts: add TrackingDesignSystemKind, TrackingDesignSystemEditSurface,
DesignSystemsPresetBrandPicker{Click,SurfaceView}Props; extend
RunCreatedProps with design_system_kind/slug/edit_surface and both
entry_from enums with composer_picker/project_canvas/library.
Adds docs/design-system-tracking-spec.md.
Verified: contracts build + 170 tests, web typecheck + full suite (3463),
daemon typecheck + run-analytics/brand-extraction tests (70).
* feat(analytics): track AI-optimize (design_system_enrich) click
C13 (tracking spec §3.3): instrument the "AI Optimize" banner CTA on a
programmatically-extracted DS-as-project so the AI-conversion rate
(clicked ÷ programmatic creates) is visible.
Contracts: add design_system_enrich area, DesignSystemEnrichClickProps,
DesignSystemEnrichResultProps, and the design_system_enrich_result event
name (result event reserved for the C14 follow-up).
Adds an implementation-status table to the spec. C14 (enrich_result) and
C15 (ProjectMetadata.enrichmentStatus) remain follow-ups since they need
correlation with the async enrichment run's completion.
Verified: contracts build, web typecheck, brand-enrichment + analytics
tests (89).
* feat(analytics): report all design-system create sources (ds_source_origins)
Comment ②: multi-source creates were flattened to a single `mixed`
design_system_source, hiding which sources combined. Add a comma-joined
`ds_source_origins` (e.g. `source_url,local_code`) on create_result that
lists every source actually used, keeping the singular field for
back-compat. Mirrors the target_platforms/connectors multi-value convention.
Verified: contracts build, web typecheck.
* feat(analytics): distinguish onboarding build-DS vs skip-to-home (C2)
C2 (tracking spec §3.1): the final onboarding step forks into "Build a
design system" and going straight Home, but both reported
completion_type=completed_without_design_system, so the skip rate was
invisible. Parameterize runOnboardingCompletion so the build fork reports
completed_with_design_system and the home forks report
completed_without_design_system. Update the EntryShell onboarding test,
which clicked "Build a design system" yet asserted the without value.
Verified: web typecheck, EntryShell onboarding tests (24).
* feat(analytics): instrument design-system edit lifecycle (E1-E3)
Tracking spec §3.4/§3.6:
- E3 (direct module edits, ui_click area=design_system_edit, all carrying
edit_surface=direct_module + artifact_kind=design_system + the DS id):
DesignSystemsTab general ops (edit-with-agent / refresh / download / reset),
DesignKitView module buttons via the parent handlers (logo/font/image
upload incl. paste, color edit, logo/image delete, design_md edit-save),
and the BrandPreviewCard three actions (use-in-chat / open-project / delete).
- E1 (agent-routed edits): a DS-project run editing an EXISTING design system
now carries edit_surface — comment/mark from their entry_from, otherwise
chat. First-generation runs get none.
Contracts: DesignSystemEditClickProps (+ union); reuses the
TrackingDesignSystemEditSurface added earlier and RunCreatedProps.edit_surface.
Not wired (enum reserved / no handler today): kit_import, kit_open (external
link in the stateless kit view), design_md_copy/upload.
Verified: contracts build; web + daemon typecheck; DS/Brand/Kit web tests
(89) + daemon run-analytics tests (44).
* feat(analytics): AI-optimize result + enrichment status (C14/C15)
Closes the programmatic-vs-ai_refined comparison gap (tracking spec §6):
- C14: the AI-optimize run is tagged (analyticsHints.dsEnrichment) from the
ProjectView banner CTA; when it settles, the daemon emits
design_system_enrich_result (result / design_system_id / project_id /
run_id / duration_ms / error_code).
- C15: on a successful enrichment run the daemon flags the backing project
metadata enrichmentStatus='ai_refined' + enrichmentCompletedAt, so the two
DS cohorts can be split for retention/usage analysis.
Contracts: ProjectMetadata.enrichmentStatus/enrichmentCompletedAt; reuses
the design_system_enrich_result event + DesignSystemEnrichResultProps added
earlier. ProjectChatSendMeta gains dsEnrichment.
Verified: contracts build; web + daemon typecheck; daemon run-analytics +
brand-extraction tests (70); web brand-enrichment tests.
* fix(contracts): declare dsEnrichment hint in ChatAnalyticsHints
The daemon run route reads analyticsHints.dsEnrichment and ProjectView's
AI-optimize path sends it, but the shared ChatAnalyticsHints DTO did not
declare the flag — leaving the web/daemon HTTP shape ahead of the contract
layer so typed ChatRequest callers could not discover or type-check the hint
that drives design_system_enrich_result and the ai_refined metadata stamp.
Declare dsEnrichment?: boolean with analytics-only semantics and add a
request-shape test that compile-fails if the field is dropped again.
Addresses review feedback on PR #4740.
* chore: ignore .playwright-mcp capture output
Local Playwright MCP runs write YAML page snapshots and PNG screenshots to
.playwright-mcp/. These are ad-hoc local visual-check output (including local
UI/session state), not a maintained test fixture, and per the root AGENTS.md
local runtime data must stay out of git. Add the ignore rule so the directory
is never accidentally staged again.
* fix: repair AI optimize enrichment tracking
Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
* fix: track design kit module edit clicks
Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
* fix: stabilize project P0 catalog loading
Stub unrelated catalog endpoints in the scoped project workspace and runtime Playwright flows so the P0 checks assert their target behavior instead of waiting on large registry responses.\n\nGenerated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
* fix: guard design-system tracking repairs
Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
---------
Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
Co-authored-by: Looper <looper@noreply.github.com>
|
||
|
|
dd02f9bc7c |
Update official social links in settings menu (#4779)
Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local> |
||
|
|
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 ( |
||
|
|
ccfdc50929 |
feat(analytics): first-call cache-hit + is_followup_turn telemetry (session-reuse baseline) (#4725)
* feat(analytics): measure first-call cache hit + is_followup_turn on run_finished
Instrumentation-first (telemetry before optimization) for the session-reuse
work: ship a v0.12.0 baseline that measures the cache-hit rate of the FIRST
model call of each turn, then compare it against the post-optimization release.
Why first-call: the existing run_finished usage scan is a REVERSE scan that
takes the LAST usage event of the turn. Within a turn, every call after the
first re-reads the growing prefix, so the last/aggregate cache-hit ratio is
high regardless of whether the session was reused — it masks the signal. The
session-reuse win shows up only on the OPENING call of a follow-up turn.
- scanRunEventsForUsageAnalytics now also forward-scans for the first usage
event and emits first_call_input_tokens / first_call_cache_read_input_tokens
/ first_call_cache_hit_ratio (anthropic vs openai cache accounting mirrored
from the existing last-call path). The group is gated on a real first-call
input so cache-only alias payloads don't emit a dangling cache_read.
- run_finished carries is_followup_turn (a prior completed assistant turn
exists) so first_call_cache_hit_ratio can be sliced to the turns where
session reuse actually applies.
- The first-call fields are reliable only for per-call-usage agents
(claude / opencode / codebuddy / pi). codex reports a single cumulative
turn.completed usage, so its first stream event is the whole-session
aggregate, not the opening call — gated out of the stream path here; its
per-call number must come from the rollout last_token_usage (follow-up).
* feat(analytics): recover codex first-call cache hit from the rollout
Completes the first-call cache-hit telemetry for codex. codex emits only a
cumulative `turn.completed` usage on the exec/json stream the daemon consumes,
so the stream cannot reveal the cache-hit of a turn's OPENING model call — the
number that moves when a session is reused. codex does record per-call usage in
its rollout JSONL (`token_count.info.last_token_usage`), so we read it there.
- codex-rollout-usage.ts: pure `extractCodexLastTurnFirstCallUsage` takes the
first `token_count` after the last `task_started` (the turn the daemon just
finished), skipping zero-input placeholder calls. Validated against real
codex 0.133.0 rollouts: a warm resume turn reads ~95-96% cache hit, a cold
open ~15-18% — the exact baseline-vs-optimized signal. `readCodexRolloutFirstCall`
locates the rollout best-effort (newest-first walk of
`$CODEX_HOME/sessions/**/rollout-*-<thread_id>.jsonl`) and never throws.
- json-event-stream.ts surfaces codex's `thread.started` thread_id on the same
`sessionId` status channel claude uses, so run_finished can find the rollout
(and a future native-resume path can reuse it). `codexSessionIdFromRunEvents`
recovers it from the stored run events.
- run_finished now resolves first-call usage per agent: codex from the rollout,
every other agent from the stream forward-scan, behind one `firstCallUsage`
object. CODEX_HOME comes from the drift-proof `spawnEnvForAgent('codex')`
resolver so it honors sandbox relocation.
* fix(analytics): default codex rollout home to ~/.codex when CODEX_HOME unset
End-to-end testing through a real daemon surfaced this: codex's first-call
fields were silently dropped on every non-sandboxed run. The daemon only sets
CODEX_HOME when sandbox mode relocates it; with sandbox off, `spawnEnvForAgent`
returns an empty CODEX_HOME, codex itself falls back to ~/.codex and writes the
rollout there — but readCodexRolloutFirstCall bailed on the empty value instead
of mirroring codex's `$CODEX_HOME || ~/.codex` resolution.
Verified against a real daemon-driven codex run: the emitted run_finished now
carries first_call_input_tokens / first_call_cache_read_input_tokens /
first_call_cache_hit_ratio alongside is_followup_turn. Adds hermetic tests for
the locate-and-read layer (plant a rollout under <home>/sessions/.../, read it
back by session id) which previously had only the pure extractor covered.
* fix(analytics): harden first-call telemetry per release-readiness review
Adversarial review of the first-call cache-hit telemetry before a release
cherry-pick surfaced four fixable items; all are read-only/additive and keep
the feature best-effort:
- Contain throws in the codex run_finished branch. spawnEnvForAgent /
agentCliEnvForAgent ran outside any local guard, so a throw there bubbled to
the outer run_finished `.catch` and would have dropped the ENTIRE completion
event for that run (not just the codex first-call fields). Wrap them so a
failure degrades to "no first-call fields".
- Match the anthropic first-call denominator to the last-call one: include
cache_creation_input_tokens in the effective-input sum. Previously first-call
omitted it, skewing the very cross-turn comparison the metric exists for.
Added a regression test with non-zero cache_creation.
- Bound the codex rollout locator. On a miss (session captured but file
relocated/deleted) it walked the user's entire codex history; cap the scan to
the newest few day-dirs since the run's own rollout is always recent.
- Declare first_call_* and is_followup_turn on RunFinishedProps in
packages/contracts so the analytics shape is typed at the boundary.
* refactor(analytics): share one cache-token extraction for first/last-call
Review follow-up: the first-call cache-creation extraction only matched two of
the aliases the last-call path accepts (it missed cache_write_input_tokens,
cache_creation_tokens, and nested cache_creation.input_tokens), so a provider
emitting one of those on the opening usage event would compute
first_call_cache_hit_ratio with too small a denominator and overstate the cache
hit — the exact baseline metric this PR adds.
Extract one `extractUsageCacheFields` helper carrying the full provider alias
matrix and route BOTH the last-call reverse scan and the first-call forward
scan through it, so the two effective-input denominators can never drift. Adds
a fixture using the nested `cache_creation.input_tokens` alias to lock it.
|
||
|
|
e899fb93fe |
feat(packaged): main-process telemetry for pre-daemon startup failures (#4696)
* feat(packaged): main-process telemetry for pre-daemon startup failures The daemon hosts the PostHog client, so when daemon/web sidecars die before reporting status (issue #4638: better-sqlite3 vanished from the bundle -> ERR_MODULE_NOT_FOUND), nothing emits telemetry and the whole pre-daemon crash class is invisible on every dashboard. Add a main-process direct capture on the fatal-exit path: classify daemon-vs-web by log-path segment, parse the real error code + missing module from the sidecar log tail, scrub home dirs, and POST one packaged_runtime_failed event over plain fetch (no posthog-node SDK, no new dependency). Zero happy-path side-effects by construction: all logic runs only inside main().catch, fetch is wrapped in Promise.race with a hard timeout, errors are swallowed, and it is a no-op without a PostHog key. Crash telemetry follows the existing captureSafety policy (stability data is retained even for opted-out users); Settings -> Privacy copy follow-up tracked in the PR. Slice B of the crash-governance spec (PR #4694). * fix(packaged): Windows web-path classify + main-process installationId (review) Address PerishCode review on #4696: - classifyStartupFailure normalizes backslashes before the /web/ segment check so Windows web-sidecar failures aren't misreported as daemon-start. - resolveStartupDistinctId now takes an explicit installationRoot (threaded from paths.installationRoot); OD_INSTALLATION_DIR is only set in the daemon child env, so the main process previously always fell back to the synthetic id. Adds a Windows web-path fixture and an installationId-resolution test (18 total). * fix(packaged): raise crash-telemetry flush timeout 1.5s -> 5s (e2e finding) Real-machine e2e (local packaged build -> delete better-sqlite3 to reproduce issue #4638 -> launch -> query PostHog) showed the 1.5s cap silently DROPPED the event: a cold DNS+TLS handshake to us.i.posthog.com from a fresh main process exceeds 1.5s, so Promise.race timed out and process.exit(1) killed the in-flight POST. Confirmed with a standalone repro (1.5s never landed, 6s did). 5s lets the single request complete on a cold connection; an offline machine still exits fast because fetch rejects on DNS failure (send resolves early), so only a black-hole network pays the full bound — acceptable on a crash exit. * feat(packaged): stamp shared safety-event envelope on crash telemetry (review) Address PerishCode review on #4696: the direct PostHog emitter only sent the startup props + $os, omitting the envelope captureSafety always stamps. Mirror it so env/schema-filtered dashboards bucket packaged_runtime_failed beside the rest of the stream: event_schema_version, env, device_id, client_type (packaged_main), capture_source (packaged/startup), ui_version, event_id and $insert_id (dedup key). EVENT_SCHEMA_VERSION + the env resolver are replicated (packaged doesn't depend on contracts) with lockstep comments. Adds a typed PackagedRuntimeFailedProps + AnalyticsEventPayload entry in contracts, and a test asserting the envelope wire fields (19 tests). * fix(contracts): restore packaged_runtime_failed in AnalyticsEventName (review) The name union member was clobbered when the envelope change synced events.ts; only the AnalyticsEventPayload entry survived. PerishCode caught the regression — restore the canonical AnalyticsEventName member so the event name and its typed payload stay in lockstep. |
||
|
|
eef6efa5c7 |
[codex] show AMR wallet balance in Open Design (#4675)
* feat: show AMR wallet balance * fix: preserve AMR wallet reauth state * chore: retrigger PR validation * fix: bound AMR wallet balance fetch * chore: retrigger wallet validation * chore: bump bundled Vela CLI to 0.0.18 * chore: retrigger Vela CLI validation * chore(nix): refresh pnpm deps hash |
||
|
|
cfb210ec63 |
[codex] Split CLI and BYOK first-artifact handoff (#4634)
* Split CLI and BYOK artifact handoff * fix(daemon): neutralize filesystem fallback text Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(template): align web prototype filesystem handoff 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> |
||
|
|
0bf1b6d6b8 |
[codex] converge release workflows and stable dry-runs (#4390)
* fix(tools-pack): use junctions for Windows standalone peer deps * fix(desktop): expose IPC during startup * fix(tools-pack): preserve Windows inspect diagnostics * fix(tools-pack): report Windows inspect status errors * fix(packaged): use Electron net fetch for app protocol * fix(packaged): load Windows renderer from web sidecar * fix(desktop): show Windows packaged window during startup * fix(packaged): disable Windows GPU startup * fix(tools-pack): keep Windows core smoke observable * fix(packaged): remove Windows startup probes * fix(tools-pack): trace Windows desktop IPC status * fix(tools-pack): add Windows IPC diagnose loop * fix(release): default beta-s Windows updater feed * chore: clean merged test eof * refactor(release): unify prerelease channel model * chore(release): close prerelease doc escape hatches * refactor(release): converge release channel workflows * fix(release): install toolchain in metadata jobs * fix(release): build release package before contracts * chore(release): bump development version to 0.10.1 * fix(e2e): seed windows packaged smoke runtime config * fix(release): install toolchain for metadata publish * fix(release): materialize betas metadata checkout * chore(release): bump development version to 0.10.2 * fix(release): allow betas metadata cold start from s3 * fix(e2e): support betas packaged update scenarios * fix(release): pass betas channel into packaged smoke * fix(release): set betas channel during self-hosted builds * fix(release): verify counted channel reservations * fix(release): use pnpm cmd for betas windows publish * fix(release): add betas manifest artifact fallback * fix(release): skip beta-s public metadata fetch * fix(release): read beta-s manifests from storage * fix(release): cache beta windows tools-pack builds * fix(release): inline beta mac tools-pack builds * fix(pack): deep sign unsigned mac bundles * docs(pack): document payload-first beta updater validation * fix(release): align preview tools-pack cache flow * fix(release): align prerelease tools-pack cache flow * fix(release): pass github token to prerelease metadata * fix(release): setup pnpm before feishu notify * fix(release): add stable dry-run prepublish flow * fix(release): accept completed prerelease metadata gate * fix(release): require stable release branches * fix(release): converge r2 access checks * fix(updater): use release channel parser for defaults * fix(updater): harden windows payload relaunch * fix(release): converge updater smoke fixture contract * test(e2e): require silent updater fixture output * fix(release): align stable windows smoke build path * fix(ci): include release workspace in validation * fix(ci): repair release validation lanes Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(ci): restore zero-install Feishu notification Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
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> |
||
|
|
939e3d7526 |
fix(daemon): reclassify named causes out of opaque execution_failed (#3408 P1) (#4608)
* fix(daemon): reclassify AMR/vela upstream failures out of opaque execution_failed #3408 P1: the largest engineering-view failure bucket is the opaque `execution_failed` (~5,400/7d). Sampling real Langfuse error texts showed many are not engine bugs at all but product-view failures whose (often Chinese) upstream error text the English-only detectors miss, so they fall through to the catch-all. Pull the clearest, highest-confidence AMR/vela ones into their true categories: - vela Chinese pre-charge wallet failure ("预扣费额度失败" / "余额不足" / "额度不足") -> insufficient_balance (recharge), via classifyAmrAccountFailure. - Chinese / generic rate-limit text ("速率限制" / "控制请求频率" / "rate limit" / "too many requests") -> retryable rate_limit / rate_limit_429. - vela "model ... not in allowed list" rejection -> model_unavailable / model_not_found (switch_model). Verification: - Unit: vela-errors.test.ts covers the Chinese balance detection (the authoritative change); the classifier test covers rate-limit + model wiring (its vela-errors mock is updated to mirror the new Chinese signal). - End-to-end: a new run-failure-telemetry-smoke case drives a REAL daemon run whose agent emits each production text, then classifies the REAL recorded events.jsonl — proving the text flows through the run lifecycle into the classifier and lands in the correct category (not execution_failed), rather than only passing a hand-built unit input. Refs #3408 (P1 / execution_failed deep dive), #4502. * fix(daemon): also reclassify binary-not-found failures out of execution_failed Second P1 reclassification batch (same theme as the AMR/vela one): two install/env failure shapes that the agent emits and that currently leak into the opaque execution_failed bucket are an install/PATH problem, not an engine failure. Route them to the existing cli_not_installed detail: - Windows shell "'node' is not recognized as an internal or external command". - "Error: spawn <path> ENOENT" — the executable file does not exist (distinct from spawn EPERM/EBADF/ENOEXEC where the file exists but cannot run); real codex shape where its vendored sub-binary is missing. Red specs use the real production texts; the end-to-end smoke test now also drives these two through a real daemon run and classifies the recorded events.jsonl. (Note: a missing CLAUDE_BIN is a different scenario — it goes through the daemon's preflight path, not an agent-emitted ENOENT — so the e2e case emits the real codex spawn-ENOENT text via the agent, matching the production origin.) Refs #3408 (P1 / execution_failed deep dive), #4502. * fix(daemon): reclassify prompt-size / ACP-protocol / auth texts out of execution_failed Batch A of the P1 execution_failed reclassification — three more named causes sampled from Langfuse that currently leak into the opaque bucket, routed to existing categories (no new contract types): - "Prefill context too large" (local MLX runtime) -> prompt_too_large. - ACP "thread/start failed" -> agent_protocol_error. - vela "login fail: carry the API secret key" -> auth (auth_required). Red specs use the real production texts; the end-to-end smoke test drives all three through a real daemon run and classifies the recorded events.jsonl. Refs #3408 (P1 / execution_failed deep dive), #4502. * feat(analytics): classify local-model-not-loaded (LM Studio) out of execution_failed The single largest remaining slice of the opaque execution_failed bucket (#3408 P1, ~1/5 by Langfuse sampling) is opencode pointed at a local LM Studio provider that has no model loaded: "No models loaded. Please load a model … or use the 'lms load' command." Verified this is user-action, not a model-selection bug on our side: OD passes `-m <model>` to opencode only when the user picks one, and OD has no LM Studio integration (the user configures it as an opencode provider). The error is "No models loaded" (zero loaded), not "model not found" — independent of the model name we send, the local app returns this until the user loads a model. We cannot load it for them; the proportionate fix is to stop hiding it in the opaque bucket and give an actionable category. - New `TrackingRunFailureDetail` value `local_model_not_loaded`. - modelUnavailableDetail() maps "no models loaded" / "lms load" → it, so the existing model_unavailable block routes it (category model_unavailable, user_action switch_model). - Red spec + end-to-end smoke case (real daemon run, recorded events.jsonl). Refs #3408 (P1 / execution_failed deep dive), #4502. * feat(analytics): classify stale Claude session resume as session_resume_expired When `--resume <id>` targets a session that no longer resolves, the daemon already clears the stale id and tells the user to resend (a fresh session is re-seeded with the full transcript). This is a recoverable session-lifecycle failure, but it currently sits in the opaque execution_failed bucket (#3408 P1). Root cause verified (code + claude-code-guide): the stored id pointed at a real session (the daemon only persists it on success or a committed resumable failure), so "no conversation found" comes from Claude's own 30-day cleanupPeriodDays prune, a CLAUDE_CONFIG_DIR change, a cwd/worktree change, or a prior run killed before the session was flushed (`-p` mode has no fsync guarantee). All recoverable by a fresh session — which the daemon already does. - New `TrackingRunFailureDetail` value `session_resume_expired`. - Detector matches both the daemon's surfaced message and the raw Claude CLI shapes ("no conversation found with session id" / "no session found" / "session <id> not found"), tightened so it cannot bridge an unrelated "session …" and a far-away "404 Not Found". - Classified as process_exit / session_resume_expired, retryable, user_action retry (NOT resumable — isResumableFailure stays false, so we never retry by resuming a session that is already gone). - Red specs + end-to-end smoke case. This is the analytics/labelling half. Auto-recovery (transparently re-running with a fresh session so the user never sees a failure) is a separate, larger change tracked with the run-resume work. Refs #3408 (P1 / execution_failed deep dive), #4502. * refactor(daemon): narrow session_resume_expired daemon-message match Address review: '\bcould not be resumed\b' was broader than the rest of the helper's Claude session-miss shapes and runs before other process-exit fallbacks, so an unrelated future 'could not be resumed' stderr would be mislabeled. Anchor it to 'session could not be resumed' (matches the daemon's actual surfaced message 'The previous Claude session could not be resumed …'). |
||
|
|
dd3dafc1e0 |
fix: stamp effective design-system context (#4457)
* fix: stamp effective design-system context * fix(daemon): report injected design system on completion * fix(daemon): allow realpath opencode cwd aliases |
||
|
|
9267fffdf3 |
feat(daemon): add exponential backoff with jitter before same-run retry (#4588)
The safe same-run retry shipped for #3408 §3 restarts a transient failure immediately. For rate_limit / rate_limit_429 in particular, an instant same-model retry tends to re-hit the same limit, spending the single retry budget without giving the upstream any cooldown. #3408 §3 specified "retry 1-2 times"; the immediate restart under-delivers that intent. Add a category-aware exponential backoff with equal jitter, computed in the pure policy layer so it stays unit-testable: - run-retry-policy.ts: computeRetryBackoffMs() (rate_limit uses a larger base than other transient classes, exponential by attempt index, capped, equal jitter in [delay/2, delay]); jitter source is injectable for deterministic tests. decideSafeRunRetry returns retryDelayMs on the retry decision. - server.ts: split the synchronous restart into a synchronous teardown (kill child, move run to queued) plus a deferred re-spawn after the backoff. The teardown must stay synchronous: leaving the old child alive during the wait lets a follow-on signal (e.g. the inactivity watchdog SIGTERM) drive a second close-handler pass that finalizes the run as failed before the retry spawns. The deferred spawn is cancel-aware. - runtimes/runs.ts: clear a pending retry-restart timer on cancel/shutdown so a cancelled run is not resurrected after the timer fires. - contracts: add retry_delay_ms to run_retry_attempted so the backoff is observable in PostHog. The conservative single-retry default is unchanged; the backoff still applies before that one retry, which is the case that helps 429s most. |
||
|
|
f5db26ced6 |
feat(daemon): split spawn-to-first-token into auditable subsegments (#4600)
The run-finished telemetry reports the whole process-spawn -> first-token gap as a single `spawn_to_first_token_ms`, so when a run takes ~8-10s to first token there is no way to tell whether the time went to local CLI startup, the session handshake, or the provider model call. Split that span into `cli_ready_ms` / `session_init_ms` / `model_first_token_ms` plus a `spawn_to_first_token_remainder_ms` audit field, derived at the existing timing choke point so the four parts always sum back to `spawn_to_first_token_ms`. The `cli_ready_ms` marker is stamped per runtime family from its own decode point: first JSONL line (claude-stream-json), first decoded stream event (json-event-stream), first ACP JSON-RPC message (acp-json-rpc), or first non-empty stdout chunk (plain). `session_init_ms` is observed only where there is a real handshake to anchor to (the ACP session/new ack); for stream/plain families that time honestly folds into the remainder rather than a fabricated marker. The new fields ride the existing shared analytics contract, so they flow to both the PostHog run_finished props and the Langfuse trace mirror without a divergent surface. Pure observability, no behavior change. |
||
|
|
5d4f4a5a1d |
feat(daemon): split opaque execution_failed into close-reason sub-details (#4502)
`AGENT_EXECUTION_FAILED` whose text matches no classifier pattern currently collapses into a single opaque `failure_detail: execution_failed`. PostHog shows this is the largest current-version failure detail (~4.5k/wk across providers), and it hides three genuinely distinct shapes that the daemon already distinguishes via the `runtime_close` diagnostic's `rpc_close_reason`: mid-stream agent error (stream_error, ~51%), bare non-zero exit (exit_nonzero, ~44%), and ACP fatal close (fatal_rpc_error). That split lived only on PostHog's `rpc_close_reason` field, so the canonical `failure_detail` — and therefore the Langfuse sink, which classifies through the same `classifyRunFailure` — stayed opaque and the two sinks disagreed. `classifyRunFailure` already receives the run events, so it reads the `runtime_close` diagnostic and promotes the generic `execution_failed` detail to the specific close reason. Only the opaque catch-all is refined; specific details (exit_code, cli_not_installed, …) and retry behavior are untouched, so this is observational-only. This is the first step of the #3408 follow-up that turns the opaque bucket into named sub-buckets; mining the stream_error texts for real provider/tool error patterns is a Langfuse-backed follow-up. Relates to #3408. Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
d3896fa51a |
[codex] Clean deprecated launcher payloads on updater startup (#4524)
* fix(packaged): recover stale launcher and sidecar state * fix(packaged): write launcher cleanup from installer sync * perf(tools-pack): reuse windows payload caches * perf(tools-pack): avoid slow windows 7z filters * fix(tools-pack): bound windows payload archive hangs * test(packaged): stabilize sidecar tests on windows * fix(tools-pack): harden launcher runtime sync helper * fix(tools-pack): invalidate nsis cache for installer helpers * fix(tools-pack): hash nsis helper source in bundled cli * fix(packaged): restart when existing desktop has stale sidecars * chore(release): bump beta base to 0.11.1 * fix(e2e): seed windows smoke onboarding in runtime data * ci(release): let beta runs use default queueing * ci(release): restore beta concurrency guard * ci(release): enable beta mac x64 by default * ci(release): disable beta mac x64 by default * fix(updater): clean deprecated launcher payloads on startup * fix(e2e): align stable dry-run fixture version Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
ff41a437cc |
feat(daemon): guard against agents looping on failing tool calls (#3375)
* feat(daemon): guard against agents looping on failing tool calls Autonomous chat agents had no bound on repeated tool failures. An agent asked to move a button into a titlebar element it believed was called `titlebar-left` (the real class was `tb-left`) wrote a shell assertion for `titlebar-left`, it failed, the agent concluded the edit had not landed, re-read, and retried the same wrong assumption 80+ times with ~19 errors. The daemon streamed every attempt without noticing. The BYOK proxy path already bounds its tool loop at MAX_BYOK_TOOL_LOOPS; the autonomous agents had no equivalent. Add a run-scoped tool-loop guard that observes the normalized tool_use/tool_result events every agent path emits, so one instance covers Claude, Codex/OpenCode, Copilot, and ACP. Two triggers: N consecutive errored results (reset by any success), and K repeats of the same failing action signature (the fixation case). Two tiers: a one-shot `tool_loop` warning surfaced in the chat, then a hard-ceiling halt that terminates the run. Mode via OD_TOOL_LOOP_GUARD (halt|warn|off), default halt. - packages/contracts: `tool_loop` SSE payload and a TOOL_LOOP_DETECTED error. - apps/daemon: pure tool-loop-guard.ts plus tests, wired into both agent event paths in server.ts with an abortForToolLoop mirroring abortForRoleMarker. - apps/web: render the warning as a status line in the transcript. * fix(daemon): scope tool-loop repeated-failure to lack of progress, key on full signature Addresses the two blockers in the #3375 review. 1. The repeated-failure counter was cumulative across the whole run, so a progressing workflow (rerun the same check after each successful edit) could reach the halt ceiling even though each retry tested newly-written code. A successful tool result now clears the per-signature tallies, not just the consecutive streak, so repeated-failure means "failures since the last success." I pulled the conversation that motivated the guard: the loop was 11 back-to-back failing Edits with no success between them, so it still trips. 2. The signature was truncated to 160 chars before being used as the dedup key, so two distinct long commands sharing a prefix pooled each other's failures. computeToolSignature now returns the full signature for keying; a new displayToolSignature truncates only the form shown in the tool_loop event. Adds regression tests: same command with intervening successful edits does not halt, and two >160-char inputs sharing a prefix do not collide. * fix(daemon): route every agent runtime through one tool-loop choke point The Copilot (copilot-stream-json) and ACP (acp-json-rpc) branches emitted agent events with a bare send('agent', ev), bypassing observeToolEventForLoop. So a Copilot or ACP run could repeat a failing tool call indefinitely while Claude / OpenCode / Qoder runs were stopped, which contradicted the "every agent path" claim. Add an emitAgentEvent choke point that sends the event and then feeds it to the guard, and route every stream handler through it (sendAgentEvent, the Claude callback, Copilot, ACP). observe runs after the send so a tool_loop warning or halt follows the result that triggered it. A bare send('agent', ...) now only carries guard-internal events (the tool_loop verdict, role-marker warnings), which the guard ignores. Adds a structured-streams regression test that feeds repeated failing Copilot tool executions through the real parser into the real guard and asserts it halts. * fix(daemon): persist tool_loop warnings so they survive transcript replay The tool_loop event was mapped by the live web translator but dropped by the daemon's persisted-event path (daemonAgentPayloadToPersistedAgentEvent had no tool_loop branch), and send() persists every SSE event before emitting it. So the warning was transient: after a reload or history replay it was gone, and in OD_TOOL_LOOP_GUARD=warn there is no terminal TOOL_LOOP_DETECTED error to fall back on, so the user lost the only record that a loop was detected. Mirror the live status mapping in the persist path so the warning is stored as a warning status and replays identically. Export the mapper and add a regression test covering the warn and halt cases plus a malformed-event drop. (PR #3375 review follow-up, non-blocking.) * fix(daemon): only clear the tool-loop tally on progress, not on read-only successes The previous fix cleared failCounts on every successful tool result, which created a false negative: a stuck agent that alternates a failing Bash/Edit with a successful Read (re-reading the file and retrying the same wrong assumption) reset the tally every round and never tripped. That is the exact "re-read, retry the same wrong assumption" shape from the motivating report. A success now clears the repeated-failure tally only when it is real progress: a successful mutating tool (Edit/Write/apply_patch/...), or the failing action itself finally succeeding. A read-only success (Read/Glob/LS/Grep/TodoWrite) still breaks the strictly-consecutive streak but keeps the per-signature tally, so a re-read-between-failures loop still trips. The earlier edit -> rerun-check -> fix workflow stays safe because the edit is a mutating success that clears the tally. Adds regression tests: a failing call interleaved with successful reads still halts, and a mutating success clears the tally while a read does not. * fix(daemon): treat state-changing shell commands as tool-loop progress isMutatingToolName was a name-only allowlist that excluded Bash, but agents change state through the shell (sed -i, pnpm install, git commit, a build). So a failing check like `pnpm test`, fixed by a successful Bash command, then rerun, carried the old `Bash pnpm test` failures forward until the halt ceiling stopped a progressing run. Replace the allowlist with a progress classifier: read-only tools (Read/Glob/LS/Grep/TodoWrite) and pure-inspection shell commands (cat/ls/grep, git status/diff, sed without -i, python3 -c) are not progress; a state-changing shell command (sed -i, mv, install, build, a redirect to a file, git add/commit) and any other tool success are. The classifier is conservative: anything not recognised as a pure inspection counts as progress, so it never halts a run that is fixing things, while a re-read-between-failures loop (Read/cat/grep) still trips. Adds regression tests: a failing check fixed by successful mutating Bash does not halt, plus read-only vs state-changing command classification. * fix(daemon): default the tool-loop guard to warn, make halt opt-in The repeated-failure trigger classifies whether a success is progress, and review kept surfacing edge cases where a misclassification could halt a run that was actually making progress. Defaulting to halt put that risk on every run. Default to warn instead: the guard surfaces a one-shot heads-up when a run looks stuck (which already solves the original silent-grinding problem, since the operator sees it and can stop the run), and halting is opt-in via OD_TOOL_LOOP_GUARD=halt. A heuristic guard should warn before it terminates someone's run. The halt path stays fully implemented and tested; only the default changes. resolveToolLoopMode and createToolLoopGuard now default to warn. The halt-mechanism tests opt into { mode: 'halt' } explicitly, plus a test that the default warns but never halts. * fix(daemon): count inline python/node scripts as tool-loop progress isReadOnlyShellCommand treated every `python -c` / `node -e` snippet as inspection-only, but an inline script can write files (`python3 -c "Path(...).write_text(...)"`). A fix loop that uses one between failing checks would carry the old failure count forward toward the halt ceiling. Drop the interpreter special case so inline scripts fall through to the state-change default (progress), matching how any other non-inspection shell command is treated. Adds a regression test: a failing check fixed by a successful inline python write does not halt. Updates the classification matrix. * fix(daemon): unwrap env-prefixed shell commands in tool-loop classifier isReadOnlyShellCommand only looked at each segment's head, and `env` was in the read-only binary set, so `env CI=1 sed -i ...` and `env NODE_ENV=production pnpm install` were classified as read-only inspections. A real fix run that way between failing checks never cleared the repeated-failure tally, so a healthy run could still halt at repeatCount 8, the false positive this heuristic exists to avoid. Unwrap a leading `env [flags] KEY=VALUE...` prefix before classifying the real subcommand, and drop `env` from the read-only set so a bare `env` unwraps to nothing and stays an inspection. Adds the classification cases and an integration test that alternates a failing check with a successful `env ... sed -i` / `env ... pnpm install` fix and asserts no halt. Addresses the #3375 review note on the shell progress heuristic. * fix(daemon): count mutating find and unparseable shell segments as tool-loop progress isReadOnlyShellCommand treated find as a pure inspection and silently skipped any segment whose head it could not parse, so find ... -delete, find ... -exec rm, a subshell like (sed -i ...) && cat x, and quoted heads all read as read-only. In OD_TOOL_LOOP_GUARD=halt those successful fixes never cleared the repeated-failure tally, so a run actually changing files could still hit haltRepeat and be terminated as a false positive. Move find to its own branch that stays read-only only without a mutating action (-delete/-exec/-execdir/-ok/-okdir/-fprint/-fprintf/-fls), and treat a non-empty segment with no parseable head as a state change. Empty segments from trailing separators still skip. Adds matrix rows for these Bash-fix paths. Addresses review feedback on #3375. * fix(daemon): match the whole find -fprint* family in the tool-loop classifier The mutating-action matcher listed -fprint and -fprintf but missed -fprint0, because fprint\b has no word boundary before the digit. A successful find ... -fprint0 out.bin writes a file but read as a read-only inspection, so in OD_TOOL_LOOP_GUARD=halt it left failCounts intact and could false-halt a run making progress. Use -fprint\w* to cover -fprint / -fprintf / -fprint0 in one matcher. Adds matrix rows for -fprint0 and -fprintf. Addresses review feedback on #3375. * fix(daemon): route the tool-loop halt through signalChild so it kills the process group abortForToolLoop used a bare child.kill('SIGTERM'), unlike abortForRoleMarker, cancel, and the inactivity watchdog, which all go through design.runs.signalChild. signalChild escalates to the whole process group when one exists, while child.kill only signals the direct child. In OD_TOOL_LOOP_GUARD=halt that left Bash/build grandchildren alive to keep mutating the workspace until the forced-shutdown timer fired. Route the halt through signalChild to match the sibling shutdown paths. Addresses review feedback on #3375. --------- Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
2d2c22f9b9 |
fix(pi): raise detection timeouts and harden Windows npm prefix resolution (#3693)
* fix(pi): raise detection timeouts and harden Windows npm prefix resolution * fix(platform): make Windows npm-prefix handling additive to avoid POSIX regression |
||
|
|
36f57d5f9c |
fix(analytics): count run artifacts agent-agnostically + emit ACP tool events (#4520)
* fix(analytics): count run artifacts agent-agnostically + emit ACP tool events run_finished.artifact_count was 0 for every agent except claude_code. The counter reconstructs file writes from each agent's tool-call stream, but only claude-style tool names (Write/Edit/create_file) are recognized, and the ACP adapter never emitted tool_use/tool_result at all — so codex / opencode / gemini / cursor / amr / hermes / … all reported artifact_count: 0 and the behaviour funnel's "produced an artifact" step silently measured claude_code only. - ACP adapter (acp.ts) now mirrors artifact-write tool calls into canonical tool_use/tool_result events (also feeds UI / transcript / langfuse). - New agent-agnostic counter (run-artifact-fs.ts): snapshot the project's artifact files before the run and diff at finish. A created OR modified file counts, so an edit-only turn that rewrites an existing file still reports >0. Falls back to the tool-stream counter for no-project runs. - design_system_created and preview_module_count migrated to the same diff. - contracts: run_finished gains artifacts_created / artifacts_modified (created ~= activation, modified ~= iteration). * fix(daemon): count pathless ACP artifact writes in the no-project fallback Review follow-up (PR #4520, nettee): a pathless ACP write (title only, e.g. "edit", no locations/path) fell back to `file_path: toolCallId`, which has no artifact extension, so `countNewArtifacts` (`isArtifactPath`) rejected it. Since server.ts skips the filesystem baseline when cwd is null, no-project ACP runs kept reporting artifact_count: 0 for that shape. - acpArtifactWritePath now also extracts a path from rawInput and from a filename token in the title (real extensions let isArtifactPath correctly exclude non-artifact writes like config.json). - When still pathless, fall back to a synthetic `acp-write/<id>.html` so the already-classified artifact write stays countable (deduped per tool-call). - Tests: pathless ACP write counts; title naming a non-artifact file does not. * fix(daemon): defer ACP artifact tool_use to terminal frame; no synthetic .html Review follow-up (PR #4520, nettee, round 2): the previous fix emitted the mirrored tool_use on the first frame with a synthetic `.html` fallback, which (a) coerced generic pathless edits (config.json, README.md) into counted artifacts — false positives — and (b) locked in the guess so a real path on a later frame could not correct it. - Defer the mirrored tool_use/tool_result to the TERMINAL frame, accumulating the best concrete path across frames (ACP often sends `locations` only on the completing update). - Never fabricate an artifact extension: classification is left to `isArtifactPath`, which correctly excludes non-artifact edits and includes real artifacts. A write that never carries a concrete path stays keyed on its extension-less toolCallId and is simply not counted (honest under-count beats a false positive). - Tests: late-arriving path counts; truly pathless write is not coerced into an artifact; non-artifact filename stays uncounted. * fix(daemon): isolate concurrent same-cwd artifact diffs; hash same-size rewrites Review follow-up (PR #4520, nettee, round 3): Blocking — concurrent runs cross-attributed artifacts. The filesystem diff is taken over the whole project tree, and the daemon allows overlapping runs (cf. the antigravity lock), so two runs sharing a cwd would each claim the other's new/modified files. Introduce a baseline registry (createRunArtifactBaselines) that flags any run overlapping another in the same cwd as `contended`; contended runs skip the whole-tree diff and fall back to the per-run tool-stream count, so no run claims another's artifact_count / design_system_created / preview_module_count / activation milestones. Non-blocking — same-size rewrites. The size+mtime fingerprint could miss an edit that keeps the byte length and preserves mtime. Add a bounded content hash (sha1 for files <= 1 MiB) to the fingerprint so such rewrites are still detected; large media keep size+mtime to bound run-finish cost. Tests: concurrent same-cwd baselines are both flagged contended (distinct cwd stays clean); a same-size rewrite with mtime pinned back is detected as modified. * fix(daemon): normalize path separators in artifact diff for Windows runs Review follow-up (PR #4520, nettee, round 4): snapshotProjectArtifacts stores native paths (path.join → backslashes on Windows), but the diff classified them with isPreviewModulePath / isDesignSystemFile, which match forward slashes only. So design_system_created and preview_module_count silently reported false on Windows project runs. Normalize separators (\ → /) before those classifiers. Regression test builds a synthetic backslash snapshot and asserts DESIGN.md + preview/*.html are detected. --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> |
||
|
|
680cc818bb |
feat(analytics): runtime_type across the full event chain + BYOK run events (#4510)
* feat(analytics): runtime_type across the full event chain + BYOK run events Make runtime_type (amr_cloud / byok / local_cli) a first-class analytics dimension on every event so the behavioural funnel can split AMR / BYOK / CLI without static cohorts. - contracts: add runtime_type to AnalyticsConfigureGlobals (derived in deriveConfigureGlobals as the single active runtime — no 'both' cascade) and ChatAnalyticsHints; widen run agent_provider_id to allow BYOK providers. - daemon: stamp runtime_type on run_created/run_finished, with the web client's per-run hint overriding the BYOK-blind server derivation (runtimeTypeForRunAnalytics). - web: register runtime_type as a super-property so all client events carry it; emit run_created/run_finished for BYOK runs, which stream client-side and so never produced daemon run events before (they were invisible to the funnel). Red-spec first: apps/daemon/tests/run-runtime-type-analytics.test.ts pins the hint-override; apps/web/tests/byok-run-analytics.test.ts pins the BYOK builders. * fix(analytics): address review — pin BYOK runtime_type, guard run_finished, mode-first derivation - byok-run: stamp runtime_type='byok' on run_created/run_finished instead of relying on the mutable super-property, so a mid-stream mode switch can't split one run across runtime buckets. Adds optional runtime_type to RunCreatedProps for the per-event override (daemon already pins it). - ProjectView: wrap the artifact-count refresh in try/catch so a rejected refreshProjectFiles() no longer swallows the success run_finished. - deriveConfigureGlobals: check mode==='api' before agentId==='amr' so an AMR→BYOK switch (which leaves agentId='amr') reports runtime_type=byok, not amr_cloud. Adds a regression test. * ci: re-trigger after cooldown * ci: re-trigger after cancelling stuck run --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> |
||
|
|
169eb78a74 |
fix(platform): discover Windows fnm-managed Node in the toolchain bin list (#4535)
* fix(platform): discover Windows fnm-managed Node in the toolchain bin list A GUI-launched packaged app on Windows inherits a stripped PATH and reads no shell rc, so it relies on wellKnownUserToolchainBins to locate user toolchains. That list only probed POSIX fnm roots (~/.fnm and ~/.local/share/fnm) with [installation, bin] segments. fnm on Windows keeps Node under %APPDATA%\fnm\node-versions\<version>\installation with node.exe directly in `installation` (no POSIX-style bin subdir), so fnm-managed Node — and every agent CLI that needs it — was silently undetected. Probe %APPDATA%\fnm\node-versions (FNM_DIR overrides the root) with the Windows [installation] segment on win32, alongside the existing POSIX roots. * fix(platform): also probe %LOCALAPPDATA%\fnm and surface npm-global CLIs (#3062) fnm on Windows can root under %LOCALAPPDATA%\fnm as well as %APPDATA%\fnm. Probe both (FNM_DIR still overrides both). Because a globally `npm i -g`'d CLI shim such as codex.cmd lands in the node version's `installation` dir next to node.exe, surfacing that dir also makes those CLIs discoverable from a GUI launch — addressing the "Codex CLI not detected from Windows shell" report. |
||
|
|
41031d7bfb |
fix(platform): resolve pnpm via corepack when npm_execpath is absent (#4488)
On Corepack-only setups (e.g. native Windows PowerShell where `corepack pnpm` works but no standalone `pnpm` is installed on PATH), nested package-manager invocations from tools-dev/tools-pack failed: the no-`npm_execpath` fallback in createPackageManagerInvocation assumed a global `pnpm` binary. Route those invocations through `corepack pnpm …` instead — it runs the repo-pinned package manager out of the box, while a bare `pnpm` only resolves with a separate global install or a `corepack enable` shim. Applies to both POSIX and Windows. Fixes #2438 |
||
|
|
d335be493a |
Centralize reasoning provider policy enforcement (#4476)
* Add reasoning egress policy guard * Assert reasoning denials hide API keys * Rerun validation * fix(daemon): reject malformed reasoning egress policy * Fix Google reasoning allowlist canonicalization |
||
|
|
f62ea6db46 |
fix(daemon): classify high-confidence run failures (#4478)
Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
dc12e0938f |
fix(frontmatter): a lone quote character is no longer parsed as an empty string (#4182)
* fix frontmatter parser coercing leading-zero values to numbers The minimal YAML frontmatter parser (and its plugin-runtime mirror) coerced any `-?\d+` token to a Number, so a leading-zero value like `01234` (zip code, zero-padded id) silently became `1234`, and `007` became `7`. Per YAML 1.2 a leading-zero decimal is a string — the zero is significant. Restrict integer/float coercion to values without a redundant leading zero (`0` alone still coerces to the number 0). Also fix a related edge case where a value that is a single quote character (`"`) was treated as an empty quoted string (slice(1,-1) of a 1-char string), now requiring at least two characters before stripping quotes. Both parser copies are kept in sync per their "mirror" contract. Adds tests/frontmatter.test.ts for the daemon parser and extends the existing plugin-runtime parsers test. * fix(frontmatter): coerce leading-zero numerics per YAML 1.2 core schema Restore numeric coercion for leading-zero scalars (01234, 007, -01, 00.5) to match the YAML 1.2 core-schema int/float resolution, per review. The lone-quote guard (require length >= 2 before stripping matching quotes) stays in place. Tests updated to assert the numeric results. Applies to both the daemon parser and the plugin-runtime mirror. --------- Co-authored-by: Nagendhra <nagendhra405@gmail.com> |
||
|
|
b02d20e6b9 |
fix: unify Discord invite links (#4452)
Co-authored-by: koki yanlai xu <koki@kokideMacBook-Air.local> |
||
|
|
e1b8d9921e |
Merge pull request #4421 from nexu-io/release/v0.11.0
release: Open Design 0.11.0 — The Bazaar |
||
|
|
6b034db921 |
Add Projects refresh control (#3993)
* Add projects refresh control * Handle projects refresh failures * fix(web): stabilize projects auto refresh * fix(web): preserve projects on refresh failure |
||
|
|
41f87bad0e |
Add run result package endpoint (#4265)
* Model orchestrator scratch workspaces * Address scratch workspace contract review * Expose run workspace provenance * Type run workspace provenance responses * Add run result package endpoint * Address run result package review * Align result packages with run workspace snapshots * fix(daemon): harden orchestrator workspace metadata * fix(daemon): preserve scratch workspace provenance |
||
|
|
39cad2b8a5 | chore(release): bump version to 0.11.0 for release/v0.11.0 |