mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
main
254
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fcd1c5f0b3 | fix(release): keep smoke report uploads advisory (#9002) | ||
|
|
417b81c752 |
[OPEND-4240] chore(tooling): migrate workspace to TypeScript 7 (#8957)
* chore(tooling): migrate workspace to TypeScript 7 * fix(docker): stage plugin manifests before native web type checking * perf(guard): batch native parsing with bounded AST retention * fix(tooling): port main's AST tests to native TypeScript |
||
|
|
6ef59618e9 |
[OPEND-4274] fix(ci): isolate E2E startup from Next registry lookup (#8993)
* fix(ci): configure prerelease E2E worker budget * fix(e2e): isolate Next version lookup from runtime startup --------- Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
75693e95f4 |
[OPEND-3114] feat(od-next): open registered agents and CLI BYOK (#8702)
* feat(od-next): open registered runtimes and CLI BYOK Use the effective runtime registry for OD Next admission and remove retired capability qualification. Keep native observation and existing resume/cold recovery, with the evidence graph owned by observability contracts. Add run-scoped BYOK connection flags with caller-selected environment key input and reuse the existing provider bridge. Accept equivalent frozen Skill JSON and Base64 representations while preserving content and ownership boundaries. Validated with focused contracts, runtime, public Run and CLI regressions; eight real native agents and a real BYOK request/production pair; guard and workspace typecheck. Refs #8701. * fix(web): describe BYOK project file tools Explain that API-provider project runs use OpenCode file tools and require OpenCode to be available. Update the existing notice in all 19 locales and its component/browser regressions. Validated with red/green notice checks, real settings and preview follow-ups, guard and workspace typecheck. Refs #8701. * fix(od-next): preserve hidden prototype form feedback Keep HTML hidden semantics within the shared layout layer without overriding authored product CSS or hidden-until-found behavior. This fixes name errors remaining visible after a successful registration. Validated with a red/green Chromium regression, three resource-boundary checks, a real Claude follow-up with desktop/mobile form interaction, guard and workspace typecheck. Refs #8701. * test(od-next): make Run fixtures select their execution path Explicitly select ordinary Runs for retry persistence, plain stdout replay and the compatibility single-run telemetry case. Keep task hierarchy and terminal fallback telemetry cases active, and assert the selected path. Restore owner write permission to the leftover-agent fixture's frozen directories only during teardown so recursive deletion works on POSIX. Skip symlinks and preserve cleanup errors and every original behavior check. Reproduced all four CI failures before the fix. Focused daemon and E2E checks passed (12 tests), along with guard, workspace typecheck and focused LSP diagnostics. Refs #8701. * fix(daemon): avoid passing Pi resource directories as prompt files Pi reads existing --append-system-prompt paths as files. Stop passing skills, design systems and task input directories there; their resource paths already travel in the composed RPC prompt. Validated with 104 focused runtime/RPC tests, a real Pi 1.1.0 offline startup red/green probe, workspace typecheck and guard. * fix(web): coalesce streamed calls in generation progress Fold in-flight snapshots and terminal inputs through the existing tool-event reducer before rendering the Design Files progress feed. Keep the latest input, stable call identity and the limit of twelve distinct calls. Reproduced the duplicate React key warning before the fix. Runtime and progress-feed regressions now pass alongside existing in-flight tool-row tests. * fix(web): preserve forms and browser storage in prototype previews Permit client-side form submission in opaque interactive previews. Route Web Storage artifacts through the existing isolated powered preview, including daemon hints for large files, and retain guards when isolation is unavailable. Verified URL and srcDoc form interaction, persisted values after reload, and host isolation with focused Chromium regressions. Focused viewer/runtime and daemon tests, workspace typecheck and guard passed. * test(web): keep srcDoc fixtures on the opaque preview path Use an existing non-storage sandbox-shim signal in transport lifecycle fixtures now that Web Storage artifacts select the isolated preview origin. Preserve every reload, activation, recovery and manual-save assertion. Validated all 338 cases in the three affected FileViewer test files, workspace guard and typecheck, and focused diagnostics. * fix(byok): accept keyless IPv6 loopback providers Recognize the bracketed IPv6 hostname returned by WHATWG URL parsing in both settings validation and the OpenCode BYOK runtime. Keep remote hosts key-required. Validated with focused daemon and Web tests, settings browser checks, workspace typecheck and guard. * fix(daemon): restore Pi sessions and preserve completed RPC turns Load prior Pi history with switch_session and capture the authoritative sessionFile through nonblocking get_state. Treat a host-requested SIGTERM after a clean completed turn as shutdown, including Pi builds that report exit 143, while preserving real errors and cancellation. Validated with focused protocol and public Run lifecycle tests, a real Pi generation and native resumed edit, workspace typecheck and guard. * fix(preview): isolate project storage and preserve hidden controls Give powered previews stable per-project loopback origins and reserve those hosts for matching powered GETs before API or static exceptions. Advertise the capability so newer Web clients retain the opaque fallback with older daemons. Reuse the active powered transport in Present. Share a low-specificity hidden-control guard across URL and srcDoc previews without overriding explicit author rules or until-found; preserve doctype mode when synthesizing a missing head. Validated with actual two-project storage, Workers, reload, Present and API isolation browser witnesses, form/hidden regressions, focused Web and daemon checks, workspace typecheck and guard. * test(e2e): align public canaries with best-effort OD Next Read the remaining recipe identity marker without the retired serialized package hash. Make the fake OpenCode help probe finite. Exercise the current public question-form action, completed question turns/new answer tasks, and no-artifact normal endings while retaining cancellation, artifact and deck assertions. Validated with six fake-runtime cases and focused active/off public browser canaries with explicit rollout flags; no production completion policy or timeout was relaxed. * fix(od-next): guide focus restoration after list updates Prefer native modal lifecycle and restore focus to a current control after list rerenders, falling back to the primary action when a filtered item disappears. Real OpenCode and Pi prototypes exposed detached-trigger focus loss. Keep this as best-effort build guidance, without output contracts or automatic repair rounds. Validated the actual artifacts through normal public edit tasks and browser interaction checks; profile resource tests passed. * fix(od-next): retain focus through inline task updates Extend the existing live-control guidance to inline list actions. Real Copilot and Qoder boards also lost keyboard focus when status updates rebuilt their cards; hidden filtered items need a visible fallback. * fix: align Discovery integration tests and presentation storage * test(daemon): tolerate transient memory origin cleanup races --------- Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
341641d71a |
[OPEND-3962] fix(motion): 修复预览声音与模板配乐约束 (#8777)
* fix(motion): avoid audio seek thrashing during preview stalls * fix(motion): keep in-progress video previews silent * fix(motion): let user briefs override template demo soundtracks * fix(motion): keep project grid covers muted All Projects grid and recent-project hover covers loaded motion sources without odMotionThumbnail, so the embedded player autoplayed the soundtrack. Covers now always request thumbnail mode: animation keeps playing, audio stays muted until the project preview is opened. --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> |
||
|
|
2ba458503f |
[OPEND-4236] perf(pack): remove redundant checks and defer cache persistence (#8955)
* fix(pack): notarize macOS builds only once * perf(pack): separate type validation and defer bounded Windows caches * test(e2e): bound BuildKit cleanup job away from pack cache save The deferred Windows pack-cache job checkouts the repo, so slicing clean-closed-pr-buildkit to EOF made the no-checkout assertion fail. Generated-By: looper 0.16.1 (runner=fixer, agent=omp) * test(ci): bound cache cleanup assertions to their job * fix(ci): preserve cache provenance when the release branch advances * perf(ci): reduce compression work in Windows cache handoff * fix(ci): align Windows cache restore and persistence paths * perf(ci): retain latest complete Windows artifacts for repeat builds * fix(ci): keep newer pack caches when ancestor saves finish later Prefix pruning in cache-maintenance used cache creation time, so an older ancestor finishing after a newer same-ref release deleted that newer cache. Prune only when the validated source SHA is the current commit. Generated-By: looper 0.16.1 (runner=fixer, agent=omp) |
||
|
|
3dfd41c6b3 |
[OPEND-2800][OPEND-4117] feat(od-next): integrate Discovery delivery and validated prototype fixes (#8804)
* fix(od-next): continue plans with keyed markers and honor explicit deliverables * fix(od-next): retire output contracts and hand off continued tasks * fix(od-next): record round settlement and recover production cold * fix(od-next): resume marker sessions with context deltas * fix(od-next): separate completion facts and honor plan-only intent * fix(od-next): preserve physical failures and legacy chat compatibility * test(web): align run recovery fixtures and await campaign mount * fix(od-next): tolerate terminal production marker formatting * fix(od-next): unblock follow-ups after a stop or unreadable history (#8423) Treat a previous task this build cannot read as absent so the follow-up starts a new task, and stop counting a Run with a pending cancel as holding its conversation. Add end-to-end coverage for the marker continuation flow and restore the remote Workspace directory gate cases. * feat(od-next): add deliverable discovery and ten-type routing * fix(od-next): refine discovery and live delivery evidence * feat(observability): expose actual task delivery receipts for evaluation * fix(observability): settle successful headless task telemetry * docs: clarify focused local validation Keep local daemon and web validation scoped to affected files and integration boundaries. Reserve full suites for PR CI unless the user explicitly requests them. * feat: add Motion Design skill and OD Next task route * fix(web): rename motion design entry and place it second * fix(web): reconcile motion design catalog consumers * feat(web): add motion design templates to Home and Community Five motion design example templates (3D procedural island, explainer, product launch, showreel, data story), chosen by community reach, under the Home motion-design chip and a new Community "Motion design" type. Each template follows the bundled example layout: an OD-generated example.html with its runtime libraries under assets/, an HTML preview baked by the existing preview pipeline, and a SKILL.md that carries example.html as the reference when the card is picked on the OD Next motion-design route. Home cards ship curated copy for every UI locale. Motion templates stay off the Video and HyperFrames chips. * fix(skills): keep motion-design procedural layers visible in video export The MP4 exporter seeks timelines with callbacks suppressed (renderSeek(t, { suppressEvents: true })), so content drawn only from a GSAP onUpdate callback is missing from exported video. Recommend a per-frame draw loop that reads the timeline clock, and state that visible content must not depend on timeline callbacks. The OD Next task asset is re-synced with scripts/sync-motion-design-skill.ts. * fix(motion): preserve reference assets and exported animation frames * feat(skills): integrate curated motion helpers with complete runtime files * feat(skills): add Huashu, Oil Motion and Motion.Lab adapters * test: classify animate design guidance in narration guard * feat(motion): refine five community templates and portable previews * fix(motion): refine launch transitions and center gallery previews * fix: allow free-form briefs on automatic motion task route * feat(motion): add template soundtracks and audio export * fix(motion): require rendered MP4 alongside editable video source * test(motion): align strategy activation expectation with package version * fix: scope motion delivery and make editable sources playable * fix(motion): bundle source dependencies and valid closing protocol * feat(motion): rebuild the five templates on the most-viewed community posts Re-select topics by single-post views in the latest awesome-opus-5.5-video snapshot and anchor each template to the top eligible post of its genre: camera-focus explainer, showreel, product launch, procedural island and a new wordless animated short that replaces the data story. All five example.html files are hand-authored canvas/Three.js films drawn by one render(t) from the GSAP master timeline, so preview, scrubbing and MP4 export produce identical frames. Each ships an original procedural soundtrack, updated SKILL.md and 19-locale copy; Home order follows reach. * fix(plugins): load bundled fonts in plugin previews Plugin previews blocked every bundled font: the preview CSP had no font-src, and relative url() references inside inline <style> blocks kept pointing at the preview URL instead of the plugin folder. Allow same-origin fonts, map inline-style url() references through the existing asset route (escapes, absolute and data: URLs stay untouched), and serve font files with font MIME types. * feat(community): make motion template Use match the Home example pick Community 「做同款」 on a motion design template now binds it as that type's own example card, carrying the type's project metadata, so the run takes the same OD Next motion-design route as a pick from Home's motion design list and delivers the MP4 together with the editable source. Other task types keep the existing plugin-pinning hand-off. Community use clicks now record use_source (card | detail_modal) and use_action (use | use_with_query); the details modal Use is tracked for the first time. The Home hand-off no longer records a second community_gallery click for uses Community already tracked. * fix(plugins): count the five motion templates in the official registry metadata * fix(media): keep HyperFrames audio filters compatible with FFmpeg 4.1 CI's bundled Linux FFmpeg 4.1 rejects adelay all=1 and amix normalize=0. Make each track stereo and delay both channels explicitly, skip amix for a single track, and undo amix averaging with a gain for several tracks. * fix(motion): keep template previews bakeable on slow CI renderers CI's software-GL baker skipped the 3D island (preview readiness timeout): under slow frames GSAP lag smoothing slowed the timeline, so the 8 s poster frame never arrived within the baker's 15 s wait. Play every template on the wall clock and move the island poster to 6 s. With simulated 300 ms frames all five now report ready within 10 s. * feat(motion): add the kinetic typography template Template selection now follows the motion design spec's four content types (brand and product films, kinetic typography, explainer animation, procedural art). Kinetic typography takes the fifth slot, replacing the animated short. The new template is a 15-second, 120 BPM type film with an original soundtrack and 19-locale copy. * fix(motion): let the project motion player fit template stages Inside a project the motion source player wraps the composition, fits it and adds its own controls. The templates also scaled their stage, so editable sources from Remix and Community use rendered at a fraction of the preview. Templates now keep their own fit and controls only outside that player. * feat(motion): restage the 3D island as a midday lagoon The island moves from golden hour to a bright tropical midday closer to its anchor post: a shader lagoon coloured by a baked depth map with ripples, shallow-water shimmer and shore foam, a wide white-sand beach, palms and instanced bushes, a village with a pier, soft sun shadows and a lower camera path. The CPU wave mesh is gone, so frames cost no more than before. Copy is updated in all 19 locales. * fix(motion): keep each soundtrack inside the composition root A Community use regenerated the island source tail and dropped the root <audio> element, so its MP4 had no sound. Templates now place the element inside the composition root next to the stage. * docs(motion): state the template selection rule as complete works * fix(motion): enforce video clocks and bound content verification * feat(motion): replace the motion templates with remakes of viral posts The five Motion design templates are now frame-faithful remakes of viral motion posts, so each preview shows the look users came for: the 15-second motion designer showreel, a high-energy motion reel (copy set in English), a one-shape UI motion piece, a pixel-art wizard and a 20-second electric-blue tech reel. Each ships open-licensed fonts, an original soundtrack, the creator's published prompt where one exists, and credits its source post. The explainer, product launch, 3D island and kinetic type templates are removed. Tests follow the new set; a backward-seek check now covers hidden WebGL layers. * feat(motion): feature The Future Is Already Here first and start preview sound with the film * chore: ensure PR CI * fix(motion): match The Future Is Already Here controls and reorder the motion templates * fix(motion): make the one-shape UI preview controls legible * fix(motion): hide The Future Is Already Here controls inside the project player * fix(od-next): maintain discovery prompt assets in English * fix(motion): plan suitable sound and synchronize source audio playback * feat(motion): keep remake soundtracks by default and fade exported audio where it stops * test(motion): cover audible source controls in Chromium * fix(test): use explicit ESM imports in audio player regression * fix(od-next): preserve planning intent and make agent CLI probes inert * fix(motion): align preview track discovery with video export * feat(motion): autoplay looping HTML without a player bar * fix(test): consume the built motion playback contract * docs(motion): state explicit soundtrack rules for make-same and the music line in replies * chore(motion): version the audible looping delivery profile * docs(motion): let template sound rules lead the general policy and freeze the audio contract * fix(motion): scope autoplay to declared video motion sources * fix(motion): reject audible MP4s under an explicit silent contract * docs(motion): drop the routine music note from make-same replies * test(motion): align delivery regressions with looping source playback * fix(od-next): route refreshable sources and preserve live templates * fix(od-next): distinguish source refresh from preview reload * fix(motion): isolate preview and audio export from legacy HyperFrames * fix(od-next): retain live list containers for empty source data * test(motion): retain the existing desktop legacy audio limitation * fix(motion): suspend audio when previews leave the active workspace * feat(motion): replace two motion templates with a product promo and a visual lesson; sign the showreel as Open Design * fix(discovery): distinguish reference recreation from new prototypes * fix(discovery): require skill reads before copying reference artifacts * feat(motion): give the showreel the Open Design palette and a full-size stacked wordmark * feat(motion): give the product promo a livelier mascot girl * refactor(motion): rebuild The Future Is Already Here as readable, editable scene code * fix(motion): pick gallery cover frames that survive the baker's capture delay * feat(motion): sharpen the showreel opener, depth, truchet and end card * fix(discovery): avoid implementation-only skills and false video delivery * feat(motion): make the product promo mascot talk, add a speaking score and the flow pop * fix(discovery): distinguish video rendering from editable animation delivery * feat(motion): give the visual lesson the original's paper-collage texture * fix(motion): bundle gallery covers for fresh branch checkouts * feat(motion): feature and refine Bring Ideas to Life Adapt the paper story around OpenDesign capabilities, with short rounded hands, clearer timing, continuous paper actions, and a larger audience invitation. Feature it first in Motion and synchronize localized names, metadata, cover, guidance, and regressions. Keep product promo audio music-only with sparse percussion. * fix(motion): stabilize artifact cards and capture declared covers * fix(discovery): retain WebGL intent and isolate reference-only skills * fix(discovery): complete authorized plans and verify audible delivery * fix(discovery): gate skill reads on current production authorization * fix(web): share creation type order with community * fix(discovery): stop required-model attempts on authentication failure * fix(discovery): retain required model provider and billing channel * fix(discovery): stop opaque video failures without diagnostic generations * fix(discovery): distinguish local state and tighten output adherence * fix(preview): preserve local forms and hash routes in strict sandbox * fix(prototype): keep validation feedback selectors consistent * fix(prototype): preserve native hidden feedback states * fix(discovery): align local routes and live filter states * fix(prototype): preserve dynamic controls and dialog visibility * fix(prototype): keep review and confirmation state coherent * fix(web): stabilize preview activation and retain scoped local progress * test(web): verify persistent previews through activation messages * feat(web): refine task type selection and keyboard navigation * fix(discovery): distinguish execution from requested delivery and expose recovery * fix(web): use final run delivery state in folded task history * revert(web): restore todo-based continuation controls * revert(od-next): remove experimental continuation recovery changes * fix(telemetry): bind Task traces to authenticated Vela identity * test(telemetry): establish accepted Task roots in bridge fixtures * test(discovery): align required CI fixtures with integrated behavior * fix(web): preserve succeeded messages when run probe is unavailable * test(discovery): verify selection clearing and production turn recovery --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> Co-authored-by: Cheems <hihicheems@gmail.com> Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> Co-authored-by: zhouzheng <zhouzheng@refly.ai> Co-authored-by: William <zhouhaoran@refly.ai> Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com> Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> |
||
|
|
23cc8cb6a8 |
fix(e2e): compare motion assets with Buffer.equals (#8871)
Co-authored-by: ivy-ting <234137810+ivy-ting@users.noreply.github.com> |
||
|
|
88b3d99314 |
fix(ci): harden Blacksmith Playwright apt setup (#8862)
Co-authored-by: ivy-ting <234137810+ivy-ting@users.noreply.github.com> |
||
|
|
2b41bc5c3d |
feat(share): publish files as public links with stop/resume and synced comments (#8408)
* wip: wire deleted-file stop retry and cover owner-scoped HTTP behavior
* wip: persist published comment backfill state
* wip: connect backfill delivery receipts and preserve completed batches
* wip: expose exact-file backfill state and retry through HTTP and CLI
* wip: wire scoped comment sync pause into preview panel
* test: cover backfill terminal rejection and stale publication receipts
* wip: separate published content from unavailable viewer origin
* feat: distinguish publication success from unavailable viewer links
* fix(web): gate export share guide on authoritative project history
* test: restore publish backfill HTTP proof with current publisher pipeline
* fix(web): show authoritative stopped-share guidance
* fix(web): restore publish help before creating a share
* fix(web): show unread comments on collapsed rail
* fix: share annotated selectors across URL and srcdoc bridges
* test: verify saved comment selector reanchors in Chromium
* fix(web): warn about active shares before project deletion
* fix: authorize and bootstrap public share project bindings
* wip: prepare dispatched live share-chain probe (not run)
* fix(web): deliver per-file share residuals after deletion
* fix(web): prevent stopping shares in read-only viewers
* fix(e2e): retain complete share probe HTTP evidence
* fix(web): name shared projects in delete confirmation titles
* docs(e2e): clarify live share acceptance and human login boundaries
* fix(e2e): preserve share query context in diagnostic evidence
* fix(web): restore share-panel copy and comment-pin visuals per board8
chain1-publish G4: delete the stale "仅支持分享单个文件,内嵌图片等本地
资源暂不支持" sentence from fileViewer.publishSingleFileDescription (19
locales) — it contradicts the current multi-resource share and G4 required
its removal.
chain1-publish S1: add the missing "链接访问" title row + its static
description ("接收者可查看预览和已有评论,不包含对话和代码。") to
ShareTab's public-share section, as new fileViewer.linkAccessTitle /
linkAccessDescription keys (19 locales). No switch added — that control
overlaps the existing "停止分享" button and is left as a 需确认 item.
chain4-owner 4.2b: canvas comment pins go from 20x20/weight 600/2px white
ring/fixed #282828 to 28x28/weight 500/no ring, and now derive their
background/foreground from the same per-author color used by that author's
sidebar avatar (commentAuthorAvatarColor), both pure local CSS/TSX with no
packages/ or protocol change.
chain4-owner O7: formatCommentTime now buckets by calendar day (adds
"昨天", drops the "N 天前/N 周前" buckets, switches the hour bucket to
same-day instead of <24h elapsed, and formats older dates via
toLocaleDateString with year only when it differs from the current year),
and comment-side-time gets a title attribute with the full absolute
timestamp on hover.
Verification: pnpm --filter @open-design/web typecheck passes. Not run
through a real renderer — no pixel-level claim.
* fix(web): share entry icon, delete-confirm title, OP2 read-only hint
chain1-publish E0: swap the share toolbar entry's icon from RemixIcon
share-forward-line to the board's own upload-arrow glyph (13px path
"M12 4v11M7 9l5-5 5 5M5 14v5h14v-5") — same size/shape as the board's
.shareBtn icon, glyph only, no behavior change.
chain6-dialogs D-04: drop the extra "确定" from designs.deleteConfirm's
zh-CN/zh-TW title ("确定删除「{name}」?" → "删除「{name}」?") to match
the board and every other locale, which already read "Delete "{name}"?"
without an "are you sure" prefix.
chain4-owner OP2: add the missing "分享页评论不可编辑" explanation line
under a comment's textarea specifically when the viewer can't edit it
AND the comment is a share-page (external) one — team-member-reads-
external-comment (OP3) is unaffected, matching the board's OP2-only
scope. New comment.sharePageCommentReadOnly key, 19 locales.
Verification: pnpm --filter @open-design/web typecheck passes. Not run
through a real renderer — no pixel-level claim.
* test(web): update FileViewer specs for the G4/O7/4.2b restorations
Three specs encoded the exact pre-fix behavior the previous two commits
intentionally changed, so they went red:
- "reads the publish help without publishing" located the help bubble by
the now-deleted stale sentence ("Only a single file can be shared for
now..."); relocates it by the sentence that remains.
- "keeps relative comment-time boundaries stable across clock boundaries"
asserted the old <24h-elapsed hour bucket; the O7 rewrite buckets by
calendar day, so the fixture's 23h-ago comment (which crosses this
fake clock's midnight) now correctly reads "昨天" instead of "N 小时前".
- "keeps D1-D4 marker and deck-page presentation on the rendered
selectors" asserted the old 20px/weight 600/white-ring pin CSS; updated
to the board's 28px/weight 500/no-ring geometry.
Verification: pnpm --filter @open-design/web typecheck passes;
tests/components/FileViewer.test.tsx: 334/334 passed (via
node_modules/vitest/vitest.mjs --pool=forks, run directly against the
node 24.18.0 binary — this shell's `npx`/`node` wrap nvm in a way that
blows bash's FUNCNEST under vitest's fork pool).
* feat(web): draw the export guide's countdown, title and description
The 10s countdown already existed as state - remainingMs, checkedAt and
paused were computed and consumed - but nothing rendered it, so the guide
showed no sign that it was about to dismiss itself.
The bar is one CSS width transition per run rather than a per-frame timer:
the remaining time is already known when the run starts, so animating to
zero over exactly that duration needs no re-render, and a pause just freezes
the transition.
Board copy added across all 19 locales.
* test(web): update AfterExportShareGuide specs for the P1 card restoration
Three assertions encoded the pre-restoration visual/DOM shape that
|
||
|
|
1eaa2381aa |
feat(release): validate version-only bumps before approval-exempt queueing (#8800)
Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
6410d3622a |
fix(od-next): restore production follow-up host instructions (#8770)
* fix(od-next): restore production follow-up host instructions Compose production host instructions with the same fresh key persisted on the run, including native resume, cold-start input, and UI locale. Keep the instructions independent of retired model state declarations and exclude the nonce-bearing text from internal continuation retry identity. Verify optional suggestions, message persistence, and repeated preparation through the real server and run registry. Validated with main red specs, focused regression tests, guard, and the workspace typecheck. * test(od-next): preserve production test entry points Keep the existing production test header and fixture setup while sharing host-protocol assertions with a separate suggestions case. This avoids adding a conflict with the pending agent rollout tests. Validated with the focused real-server regressions, guard, and the full workspace typecheck. |
||
|
|
784025c04a |
fix(daemon): resolve the AMR default model from the caller's catalog (#8519)
* fix(daemon): resolve the AMR default model from the caller's catalog A run that asked for `default` before the caller's `vela model list` had arrived was concretized from the bundled `vela model preset` seed. The seed is the same for every plan and OD orders deepseek-v4-flash first, so free-tier callers (entitled only to deepseek-v4.1-flash since 09-24) had Vela reject `session/set_model` before any work began. On 0.24.1 that was 22 runs from 21 users in the last 7 days, 13 of 16 bundled runs showing the user's choice as `default`. - When `default` is requested and only the preset is cached, wait up to 12s for the caller's remote catalog (`getAuthoritative`). - Remember remote catalogs separately; a later preset seed never displaces one when a default model is chosen. - Order deepseek-v4.1-flash first: it is the one chat model every plan, including free, is entitled to, so a flagless list still lands on it. * fix(daemon): bound the AMR default-model catalog wait per refresh CI follow-up. A `default` run looked the catalog up twice (the resume probe and the run itself) and each lookup waited up to 12 s for the caller's remote catalog, so a slow or broken `vela model list` held the run queued for up to 24 s. The AMR e2e runs timed out at 20 s that way. - The wait is counted from the start of the remote refresh, so one run, or several concurrent runs, share a single wait. - When the last refresh failed, the preset is used at once; the next refresh still starts in the background. - e2e fake vela: answer `model list --all --format json`, the form the daemon actually sends. It only matched the flag-less form and hung. * test(e2e): let the fake vela answer the daemon's `model list --all` probe The catalog probe spawns `vela model list --all --format json`. The matcher stripped `--all` with its preceding space only, so `' --format json'` never equalled `'--format json'`: the probe fell through to the login check, the cache recorded a failed refresh, and default runs settled on the preset. Match the subcommand and the `--format json` flags in any order, as fake-vela.mjs and turn.test.ts already do, and cover the exact daemon argv for both the catalog and the 401 path. |
||
|
|
14fee234e6 |
feat: add motion design workflow and curated motion skills (#8619)
* feat: add Motion Design skill and OD Next task route
* fix(web): rename motion design entry and place it second
* fix(web): reconcile motion design catalog consumers
* feat(web): add motion design templates to Home and Community
Five motion design example templates (3D procedural island, explainer,
product launch, showreel, data story), chosen by community reach, under the
Home motion-design chip and a new Community "Motion design" type.
Each template follows the bundled example layout: an OD-generated
example.html with its runtime libraries under assets/, an HTML preview baked
by the existing preview pipeline, and a SKILL.md that carries example.html
as the reference when the card is picked on the OD Next motion-design route.
Home cards ship curated copy for every UI locale. Motion templates stay off
the Video and HyperFrames chips.
* fix(skills): keep motion-design procedural layers visible in video export
The MP4 exporter seeks timelines with callbacks suppressed
(renderSeek(t, { suppressEvents: true })), so content drawn only from a GSAP
onUpdate callback is missing from exported video. Recommend a per-frame draw
loop that reads the timeline clock, and state that visible content must not
depend on timeline callbacks. The OD Next task asset is re-synced with
scripts/sync-motion-design-skill.ts.
* fix(motion): preserve reference assets and exported animation frames
* feat(skills): integrate curated motion helpers with complete runtime files
* feat(skills): add Huashu, Oil Motion and Motion.Lab adapters
* test: classify animate design guidance in narration guard
* feat(motion): refine five community templates and portable previews
* fix(motion): refine launch transitions and center gallery previews
* fix: allow free-form briefs on automatic motion task route
* feat(motion): add template soundtracks and audio export
* fix(motion): require rendered MP4 alongside editable video source
* test(motion): align strategy activation expectation with package version
* fix: scope motion delivery and make editable sources playable
* fix(motion): bundle source dependencies and valid closing protocol
* feat(motion): rebuild the five templates on the most-viewed community posts
Re-select topics by single-post views in the latest awesome-opus-5.5-video
snapshot and anchor each template to the top eligible post of its genre:
camera-focus explainer, showreel, product launch, procedural island and a
new wordless animated short that replaces the data story.
All five example.html files are hand-authored canvas/Three.js films drawn by
one render(t) from the GSAP master timeline, so preview, scrubbing and MP4
export produce identical frames. Each ships an original procedural
soundtrack, updated SKILL.md and 19-locale copy; Home order follows reach.
* fix(plugins): load bundled fonts in plugin previews
Plugin previews blocked every bundled font: the preview CSP had no font-src,
and relative url() references inside inline <style> blocks kept pointing at
the preview URL instead of the plugin folder. Allow same-origin fonts, map
inline-style url() references through the existing asset route (escapes,
absolute and data: URLs stay untouched), and serve font files with font
MIME types.
* feat(community): make motion template Use match the Home example pick
Community 「做同款」 on a motion design template now binds it as that type's
own example card, carrying the type's project metadata, so the run takes the
same OD Next motion-design route as a pick from Home's motion design list and
delivers the MP4 together with the editable source. Other task types keep
the existing plugin-pinning hand-off.
Community use clicks now record use_source (card | detail_modal) and
use_action (use | use_with_query); the details modal Use is tracked for the
first time. The Home hand-off no longer records a second community_gallery
click for uses Community already tracked.
* fix(plugins): count the five motion templates in the official registry metadata
* fix(media): keep HyperFrames audio filters compatible with FFmpeg 4.1
CI's bundled Linux FFmpeg 4.1 rejects adelay all=1 and amix normalize=0.
Make each track stereo and delay both channels explicitly, skip amix for a
single track, and undo amix averaging with a gain for several tracks.
* fix(motion): keep template previews bakeable on slow CI renderers
CI's software-GL baker skipped the 3D island (preview readiness timeout):
under slow frames GSAP lag smoothing slowed the timeline, so the 8 s
poster frame never arrived within the baker's 15 s wait. Play every
template on the wall clock and move the island poster to 6 s. With
simulated 300 ms frames all five now report ready within 10 s.
* feat(motion): add the kinetic typography template
Template selection now follows the motion design spec's four content types
(brand and product films, kinetic typography, explainer animation, procedural
art). Kinetic typography takes the fifth slot, replacing the animated short.
The new template is a 15-second, 120 BPM type film with an original
soundtrack and 19-locale copy.
* fix(motion): let the project motion player fit template stages
Inside a project the motion source player wraps the composition, fits it and
adds its own controls. The templates also scaled their stage, so editable
sources from Remix and Community use rendered at a fraction of the preview.
Templates now keep their own fit and controls only outside that player.
* feat(motion): restage the 3D island as a midday lagoon
The island moves from golden hour to a bright tropical midday closer to its
anchor post: a shader lagoon coloured by a baked depth map with ripples,
shallow-water shimmer and shore foam, a wide white-sand beach, palms and
instanced bushes, a village with a pier, soft sun shadows and a lower
camera path. The CPU wave mesh is gone, so frames cost no more than before.
Copy is updated in all 19 locales.
* fix(motion): keep each soundtrack inside the composition root
A Community use regenerated the island source tail and dropped the root
<audio> element, so its MP4 had no sound. Templates now place the element
inside the composition root next to the stage.
* docs(motion): state the template selection rule as complete works
* fix(motion): enforce video clocks and bound content verification
* feat(motion): replace the motion templates with remakes of viral posts
The five Motion design templates are now frame-faithful remakes of viral
motion posts, so each preview shows the look users came for: the 15-second
motion designer showreel, a high-energy motion reel (copy set in English), a
one-shape UI motion piece, a pixel-art wizard and a 20-second electric-blue
tech reel. Each ships open-licensed fonts, an original soundtrack, the
creator's published prompt where one exists, and credits its source post.
The explainer, product launch, 3D island and kinetic type templates are
removed. Tests follow the new set; a backward-seek check now covers hidden
WebGL layers.
* feat(motion): feature The Future Is Already Here first and start preview sound with the film
* chore: ensure PR CI
* fix(motion): match The Future Is Already Here controls and reorder the motion templates
* fix(motion): make the one-shape UI preview controls legible
* fix(motion): hide The Future Is Already Here controls inside the project player
* fix(motion): plan suitable sound and synchronize source audio playback
* feat(motion): keep remake soundtracks by default and fade exported audio where it stops
* test(motion): cover audible source controls in Chromium
* fix(test): use explicit ESM imports in audio player regression
* fix(motion): align preview track discovery with video export
* feat(motion): autoplay looping HTML without a player bar
* fix(test): consume the built motion playback contract
* docs(motion): state explicit soundtrack rules for make-same and the music line in replies
* chore(motion): version the audible looping delivery profile
* docs(motion): let template sound rules lead the general policy and freeze the audio contract
* fix(motion): scope autoplay to declared video motion sources
* fix(motion): reject audible MP4s under an explicit silent contract
* docs(motion): drop the routine music note from make-same replies
* test(motion): align delivery regressions with looping source playback
* fix(motion): isolate preview and audio export from legacy HyperFrames
* test(motion): retain the existing desktop legacy audio limitation
* fix(motion): suspend audio when previews leave the active workspace
* feat(motion): replace two motion templates with a product promo and a visual lesson; sign the showreel as Open Design
* feat(motion): give the showreel the Open Design palette and a full-size stacked wordmark
* feat(motion): give the product promo a livelier mascot girl
* refactor(motion): rebuild The Future Is Already Here as readable, editable scene code
* fix(motion): pick gallery cover frames that survive the baker's capture delay
* feat(motion): sharpen the showreel opener, depth, truchet and end card
* feat(motion): make the product promo mascot talk, add a speaking score and the flow pop
* feat(motion): give the visual lesson the original's paper-collage texture
* fix(motion): bundle gallery covers for fresh branch checkouts
* feat(motion): feature and refine Bring Ideas to Life
Adapt the paper story around OpenDesign capabilities, with short rounded hands, clearer timing, continuous paper actions, and a larger audience invitation. Feature it first in Motion and synchronize localized names, metadata, cover, guidance, and regressions.
Keep product promo audio music-only with sparse percussion.
* fix(motion): stabilize artifact cards and capture declared covers
* fix(web): share creation type order with community
* test(motion): exercise audio controls through project UI lifecycle
* fix(motion): scope live cover behavior to actual video sources
* test(motion): use real HEAD responses in cover regressions
---------
Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
Co-authored-by: William <zhouhaoran@refly.ai>
Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: Amy <1184569493@qq.com>
|
||
|
|
3faaab0f20 | fix(e2e): align packaged Codex fixture with recipe identity (#8727) | ||
|
|
e0b250663d |
fix(release): allow re-cutting an unchanged packaged version (#8725)
Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
014bffbf77 |
fix(collab): synchronize owner devices with overwrite consent (#8683)
* fix(collab): synchronize owner devices with overwrite consent * docs(collab): add owner device synchronization QA handoff * chore(collab): move delivery reports and screenshots to Plane * fix(collab): preserve owner pull fallback and ghost heal Generated-By: looper 0.16.1 (runner=fixer, agent=codex) * fix(collab): probe null owner heads for retractions Generated-By: looper 0.16.1 (runner=fixer, agent=codex) * fix(collab): preserve manual checks across coalescing Generated-By: looper 0.16.1 (runner=fixer, agent=codex) * feat(collab): hide update check behind a dismissed-version badge Show the header entry only when the owner skipped a cloud version, as a "Cloud update available" badge that re-opens the overwrite dialog. Rewrite the dialog description in plain product language. * fix(collab): clarify device sync copy and place entry before present * fix(collab): silence update reminders without automatic overwrite --------- Co-authored-by: Looper <looper@noreply.github.com> Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com> |
||
|
|
424f5c68cc |
fix(od-next): remove output-contract gates and unblock continuation (#8356)
* fix(od-next): continue plans with keyed markers and honor explicit deliverables * fix(od-next): retire output contracts and hand off continued tasks * fix(od-next): record round settlement and recover production cold * fix(od-next): resume marker sessions with context deltas * fix(od-next): separate completion facts and honor plan-only intent * fix(od-next): preserve physical failures and legacy chat compatibility * test(web): align run recovery fixtures and await campaign mount * fix(od-next): tolerate terminal production marker formatting * fix(od-next): unblock follow-ups after a stop or unreadable history (#8423) Treat a previous task this build cannot read as absent so the follow-up starts a new task, and stop counting a Run with a pending cancel as holding its conversation. Add end-to-end coverage for the marker continuation flow and restore the remote Workspace directory gate cases. * docs: clarify focused local validation Keep local daemon and web validation scoped to affected files and integration boundaries. Reserve full suites for PR CI unless the user explicitly requests them. * fix(web): scope folded task completion to its final run Keep the final physical Run key and conclusion together when OD Next history is folded or accumulated live. Reuse the existing predecessor offsets while retaining the full display history and remaining Todo snapshot. Add regression coverage for completed and unfinished production, live continuation, multiple Runs, legacy history, wrong and quoted markers, question handoff, and failed or canceled Runs. Validated the parent regression before the fix, 165 focused Web tests, workspace typecheck, and guard. * fix(od-next): compact historical context without restricting clarification Reuse confirmed artifact summaries for old assistant history, preserve cross-agent plans and original requests, and represent earlier questionnaires as neutral historical information without inferring answers. Keep user replies, skips, changed answers, unpersisted bodies, examples, and unfamiliar content intact. Do not add question repetition checks, output gates, repair rounds, or limits on clarification. Validated red regressions, shared context and Web provider tests, focused handoff/resume/cold-production coverage, real server and fake CLI input, workspace typecheck, and guard. The repeated-question server fixture remains successful. --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> Co-authored-by: Cheems <hihicheems@gmail.com> Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> |
||
|
|
39e8301764 |
fix(daemon): drive Antigravity through agy's stream-json stdin channel (Windows ENAMETOOLONG) (#8594)
* fix(daemon): deliver Antigravity prompt on stdin to avoid Windows ENAMETOOLONG
agy received the full composed OD prompt as the -p argument. On Windows
that routinely exceeds CreateProcess's 32,767-char command line, so the
spawn failed with ENAMETOOLONG before agy started and users saw a generic
"This task failed to run" (OPEND-3495).
Pass a short fixed pointer as the -p value and deliver the prompt on stdin,
which agy print mode reads (verified on agy 1.3.1).
* fix(daemon): drive Antigravity through agy's stream-json stdin channel
The previous attempt passed `-p "Follow the instructions provided on
stdin."` and wrote the prompt to stdin, but agy ignores stdin whenever a
prompt is given with `-p` (agy 1.1.1), so the model never received the
OpenDesign prompt. Earlier "successes" came from the model reading files
in its working directory on its own.
agy's documented headless stdin channel is
`--input-format stream-json --output-format stream-json`, which reads
one NDJSON `{"event":"user","message":{"content":...}}` message per
turn. The prompt now travels only through that frame, handed over as a
complete file-backed stdin at spawn, so argv no longer grows with the
prompt (fixes Windows `spawn ENAMETOOLONG`, OPEND-3495).
- add an `encodeStdinPrompt` runtime hook used by the chat-run spawn and
the connection test
- parse agy stream-json events (text deltas, tool steps, terminal
result, usage) in json-event-stream, falling back to the Gemini JSONL
branch for frames without `event`
- drop the antigravity-only plain-stdout buffering, Gemini re-parse and
log-file empty-output classification
- recognise agy's headless signed-out output as AGENT_AUTH_REQUIRED and
keep the per-model quota guidance for `Individual quota reached`
- fixtures recorded from agy 1.3.1 plus the samples in agy's headless docs
* fix(web): show Antigravity quota guidance instead of the generic run-failure copy
The Antigravity RATE_LIMITED card had no message key, so it rendered
chat.runError.fallbackMessage ("This task failed to run. Please retry")
-- the exact sentence in the OPEND-3495 report -- with no hint that the
per-model quota was the cause. Give it its own copy (switch model in the
model menu or via agy /model) and the quotaExhausted title, in all 19
locales.
* Revert "fix(web): show Antigravity quota guidance instead of the generic run-failure copy"
This reverts commit
|
||
|
|
d9dd6fefa7 |
feat(release): add one-dispatch withdrawal for an unshipped cut (#8509)
* feat(release): add one-dispatch withdrawal for an unshipped cut A release that was cut but will not ship had no undo. cut-release creates a branch, a backport label, a Feishu card, and — through the prerelease build its push triggers — moves prerelease/latest onto the new line. Abandoning that version meant reversing four surfaces by hand, and the one that actually matters is easy to forget: internal validators auto-update, so they sit on a package nobody intends to release. release-withdraw reverses each, in the order that leaves the least mess if a later step fails: repoint prerelease/latest at an older published prerelease, delete the label, delete the branch, post the matching withdrawal card. The branch delete uses the release App token because the release/v* ruleset blocks deletion and that App is one of its bypass actors — the same token that created the branch. It refuses more than it does. A version that reached stable is not an unshipped cut, and the error names release-rollback instead. A branch carrying any commit that is not on main and not from the release bots would lose work, so it stops. Open PRs targeting the branch, or still carrying its label, stop it. The prerelease pointer is read live rather than trusted from the input, and is only moved when it actually belongs to the withdrawn version — at which point a target becomes mandatory rather than silently skipped. dry_run defaults to true: a withdrawal is never the urgent case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): make the withdrawal queue behind the pointer's other writer, and give the dry run teeth Both findings from review. Concurrency. `open-design-release-withdraw` was a group of one, so it serialised this workflow against nothing, and the header comment claimed a guarantee it never had. `release-prerelease.yml` is the only other writer of `prerelease/latest` and holds `open-design-release-prerelease`; the withdrawal now joins it. The publish this races is the ordinary cut timeline, not a manual re-run: the cut push and the preview-manifest push that follows each start `notify-release-feishu` -> `release-prerelease`. Three failures came from the unique group. Reading the pointer before an in-flight publish finishes resolves `belongs=false`, so the repoint is skipped and the publish then installs the withdrawn version after a green run. Interleaved writes mix `latest`, because the publisher writes metadata first and feeds second while `applyLatestRollback` writes feeds first and metadata last, and every PUT is unconditional. And a publish landing *after* a repoint restores the abandoned pointer, because the publisher's CAS refuses only backward moves. The group is workflow-level and must stay that way, which is the part worth keeping in mind: it is held through the branch delete, so a queued trailing publish cannot enter the publisher until `release/vX.Y.Z` is gone and then fails closed on a checkout of a deleted ref. A job-level group would release the lock first and lose that. A P0 assertion pins the shared string against release-prerelease — renaming the publisher's group now breaks a test rather than the invariant — and pins the single workflow-level block. `cut-release.yml` stays out of the group: it writes no R2 object of its own, and what needed serialising was the publisher it triggers. The branch also gets a compare-and-delete, re-reading the tip and refusing if it has moved off the SHA that "nothing but cut commits" and "no open PRs" were established against. Not a substitute for the lock, which is what fixes the publish race — this covers the likelier window the lock does not see, a backport landing while a human reads the dry run. GitHub's ref delete takes no expected-SHA precondition, so the compare is explicit. Dry run. `Setup pnpm`, `Install dependencies` and `Repoint prerelease latest` were all gated on `!inputs.dry_run`, which removed the credentialed half of the validation from the mode that exists to validate. `prerelease_to_version` only had to be syntactically plausible: an absent, incompletely published or feed-less target, or one below the prerelease launcher floor, passed the plan and failed for the first time during the real run. It still fails before the label and branch deletes, so nothing was destroyed by it — but the dry run is the path this workflow tells the operator to trust, and `[P0] plans before it destroys` required `!inputs.dry_run` on the repoint step, locking the skip in. The three steps now run whenever the pointer needs moving and the planner takes `RELEASE_DRY_RUN: ${{ inputs.dry_run }}`, matching how release-rollback already wires it. "The dry run passed" now means the real run has nothing left to discover, and the header and the step summary say so. The two deletions and the Feishu card stay gated on the input. Each of the five behaviours was mutation-checked: reverted at exactly one site (three for the `if:` gate), exactly one test went red. Workflows re-linted with the CI-pinned pair, actionlint 1.7.12 with shellcheck 0.11.0 on PATH, proven wired by a probe workflow that reports SC2086. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
92fe7e87b1 | feat(release): decouple stable Docker reconciliation (#8517) | ||
|
|
5b19dfa435 |
fix(release): resolve the rollback tag from published metadata (#8508)
The first live dry run failed at the badge preflight: I had written the tag as v<version>, but prepare-stable.ts publishes open-design-v<version>. A hand-built prefix reads plausibly and is wrong, so take the tag from .versionTag in the restored release's own immutable metadata — the value the publisher recorded — probing the same prefix candidates rollback-latest.ts probes, and hand it to the badge step as a step output. The summary checklist had the same wrong shape; it now names the version rather than constructing a tag. The regression test pins both: the tag must come from .versionTag, and no step may rebuild one from the version input. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
54139504d4 |
feat(release): add one-dispatch stable rollback (#8507)
* feat(release): add one-dispatch stable rollback A stable release publishes its artifacts to an immutable stable/versions/<version> prefix and then copies the pointer objects into stable/latest. latest is therefore derived state, and retracting a release is copying an older version prefix over it again — but until now nothing in the repo could do that, so a bad stable release had no stop button. Add `tools-release rollback-latest` plus a release-rollback workflow that dispatches it. The CLI refuses unless the live pointer still serves the version the operator typed as from_version, the target published completely (not partial, not a dry run), every ready platform's manifest and updater feed exist in the target prefix, and the current installer floor is satisfiable by the target. It re-resolves control.launcher.version from live channel policy rather than restoring the snapshot's, records rollback provenance, writes metadata.json last so latest never advertises a version whose feeds it does not yet serve, and verifies every object through both the origin and the public URL. The workflow shares release-stable's publish concurrency group so a rollback cannot overlap a publish, moves only the GitHub Release "Latest" badge (never a release body), and ends with a checklist of what a rollback does not reach: the download page's version-pinned links, the What's New card, the group announcement, and above all the installs already on the retracted version — the updater never offers a version that is not strictly newer than the running one, so this stops a rollout and cannot recall one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): group the rollback summary fallback redirects shellcheck SC2129 — CI runs actionlint with shellcheck wired in, which my local actionlint could not see without it installed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): prove the restored tag has a release before writing The badge move runs after the R2 write, so a missing GitHub Release for the restored version would have failed the run with stable/latest already moved. Check it up front instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): pass the stable installer-floor policy into rollback resolveLauncherVersionFloor() reads process env, and repository variables are not process env. Without the handoff a rollback resolved no floor at all: it would strip an active control.launcher.version from the restored metadata and stop refusing targets below the current minimum — the safeguard the workflow documents as enforced. Latent today only because no stable floor is set. release-stable.yml passes the same pair to metadata publish and verify; mirror it, with a topology assertion so a later edit cannot drop it silently. Also stamp RELEASE_BRANCH/RELEASE_COMMIT so the rollback provenance is not blank. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): require the restored release to be badge-eligible gh release view succeeds for a draft or a prerelease, but GitHub will not put the Latest badge on either — so the preflight passed and the edit failed with the R2 rollback already done, which is the half-complete state the preflight exists to prevent. Check isDraft and isPrerelease too, and cover the eligibility guard in the workflow test alongside its ordering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b52fafd474 |
feat(ci): enforce reusable workload plans (#8393)
* feat(ci): enforce reusable workload plans * fix(ci): skip incompatible convergence upgrades |
||
|
|
eb27acd322 |
feat(billing): support Coding Plan usage and wallet fallback (#8339)
* feat(billing): support Coding Plan usage and wallet fallback * fix(web): keep AMR send off billing preflight Workspace send no longer waits on advisory vela billing preflight; recovery still requests it. Realign zero-wallet P0 oracles and the credits-card wallet label. Generated-By: looper 0.15.0 (runner=fixer, agent=omp) * fix(billing): serialize recovery preflight and bound hung vela Skip overlapping AmrBalanceDialog recovery ticks and stop the watch after ten minutes even if a preflight is still pending. Give vela billing preflight a 60s runVelaCommand timeout so a hung CLI cannot pin recovery. Generated-By: looper 0.15.0 (runner=fixer, agent=omp) * fix(web): restore coding plan text contrast in billing card * feat(web): render Coding Plan windows and Go tier in the credits panel - CodingPlanUsage renders one row per preflight window (name, used %, remaining USD, reset countdown + absolute time) with loading/unavailable states; refreshes once on mount and once at resetsAt (no 30s polling, setTimeout delay clamped to the 32-bit limit). - Pure model in coding-plan-usage-model.ts; creditsPerUsd comes from `vela billing summary` through WorkspaceBillingSummary and falls back to 10,000 only when the backend omits it. - PlanWordmark/EntryNavRail accept the `go` tier (placeholder wordmark, design asset still pending). - i18n: 7 new billing keys + entry.billingTierGo across all 19 locales; real translations for the #8339 keys; drop unused codingPlanNone / codingPlanRemaining. - CLI wording: unstarted window reads "not started yet". * build(pack): pin vela-cli 0.1.4-test.0 and gate on billing preflight The packaged client needs `vela billing preflight` for the Coding Plan window rows. Bump the bundled CLI to the npm `test` prerelease that ships it and add a third capability gate in tools-pack: a CLI without the subcommand prints the parent `billing` help and exits 0, so the gate checks the help markers (preflight, --workspace-id, --model, --format) instead of the exit code. TEMPORARY: replace 0.1.4-test.0 with the stable 0.1.4 once it is released; do not merge with a `test` prerelease pinned. * fix(web): make Coding Plan rows legible on the inverted credits card The credits card is a hard-coded #202020/#fafafa inverted surface, but CodingPlanUsage.module.css read page-level --text-* / --bg-fill-* tokens, so on the card the window names rendered #202020 on #202020 (1.00:1) and the stat/reset lines sat below WCAG AA. Derive every colour from currentColor instead (name 100%, stat 72%, reset/note 60%, separator ~50%, track 36%, exhausted = --red mixed toward currentColor); measured in Chrome: name 15.6:1, stat 8.7:1, reset 6.4:1, track 3.2:1, exhausted 6.5:1. Keep the stat on one line (nowrap) and let the name ellipsize instead of wrapping "60% / used" onto two lines; drop the block's 12px side padding so it aligns with the card's own rows. Also add a minutes tier to the reset countdown: the last hour before a window resets used to show no countdown at all. New key billing.codingPlanResetsInMinutes in all 19 locales. * feat(web): rebuild the plan panel 1:1 from design PR #8364 Replace the improvised Coding Plan block with the design's panel, measured against docs/ui-previews/plan-panels/index.html in Chrome (computed styles and a 4x pixel diff of the Plus specimen): - Header: tier name + 16px wordmark, 升级 pill (管理 on the top tier). - One row 「7天额度免费用 · 已用 N% ›」 (7-day window only; rounded percent; 100% when spent, no red state) over a 5px #b1f38b track, then the separator and the 「钱包余额 · US$x.xx ›」 row. No headings, window names, remaining USD, reset time, note or unavailable line. - Team workspaces keep the pre-existing card unchanged (all team_* tiers); the quota row renders only for personal workspaces. - Free personal workspace: no quota row, but the header sells 升级 like the design (a personal workspace needs no billing permission; an account B reports without a plan upgrades as free). - Top-right pill shows the wordmark only, no balance. - Wallet always reads US$ regardless of locale (product ruling 2026-09-23). - zh-CN tier names follow the design: 免费版 / 标准版 / 专业版 / 旗舰版. - Drop the dead billing.codingPlan* keys and model helpers; add billing.codingPlanWeeklyAllowance and entry.creditsManage in 19 locales. * feat(web): show 管理 on personal Max per design PR #8364 The design's Max card sells 管理 (console auto-recharge settings) where every other tier sells 升级; product ruled on 2026-09-23 to follow the design for personal Max too, superseding the earlier "personal tiers are always upgradeable" ruling. The gate now asks isMaxPlanTier and 管理 wins over 升级 when both would show. * feat(web): route the plan card's three entries per product ruling 2026-09-23 ruling on the card's landings: 升级 opens the console's pricing dialog (billing=plan, unchanged), the allowance row opens the console dashboard (unchanged), the wallet row asks for the manual top-up dialog (new billing=recharge intent; B does not honor it yet and degrades to the dashboard), and 管理 on Max opens the plain dashboard instead of the auto-recharge dialog. * fix(web): align personal plan cards with v2 design and scoped loading * fix(web): center plan wordmarks inside the top-right pill * fix(web): reuse shared billing cache for plan hover cards * test(web): match personal billing preflight in avatar fixture * fix(billing): require usable quota evidence for funding recovery * test(billing): cover scoped preflight and quota event refresh end to end * feat(daemon): relay scoped coding plan usage events and health * fix(billing): refresh quota caches from scoped realtime events * test(daemon): accept capability query on mock event stream --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
00c2d8ae57 |
feat(plan): establish reusable workload and postinstall contracts (#8383)
* feat(plan): add verified atomic workload product restoration * perf(plan): probe product availability without downloading payloads * refactor(ci): formalize workspace initialization plans * feat(plan): add versioned workload execution contracts * refactor(plan): colocate postinstall defaults and docs * fix(convergence): enforce cache result integrity |
||
|
|
94f27cb0ab |
fix(e2e): align smoke fixtures with OD Next execution intent (#8329)
Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
792480fe21 |
fix(daemon): bound stored run events and keep crashed runs from acting on truncated prompts (#8170)
* test(daemon): red specs for unbounded persisted run events
Incident 2026-09-14: a cursor-agent conversation (~169 turns) stored every
unrecognised stdout line verbatim as a `raw` event. One `editToolCall`
completion repeats the edited file twice (~1.2 MB) and the `user` line echoes
the whole composed prompt, so each turn stored ~1.4 MB that nothing renders.
The renderer and daemon ran out of memory, and `GET .../messages` failed with
`RangeError: Invalid string length`.
These specs pin the invariants the fix must establish, against 0.22.x-shaped
rows (seeded straight into the daemon's tables) and the real parser +
persistence path:
- I1: every persisted run event fits a 64 KiB budget; meaningful events
(status, done_key, text, usage) are stored unchanged; nothing is dropped;
live `raw` lines are bounded too.
- I2: GET messages returns a bounded transcript; the PUT sibling-Run guard and
the project run-status projection keep their verdicts without any database
read handing a run's event log to JS.
- I3: a background, idempotent, one-row-per-transaction heal bounds existing
rows, skips active runs, and is safe to interrupt.
* fix(daemon): bound persisted run events and heal oversized rows
A persisted run event never carries an unbounded payload (I1):
`runtimes/run-event-payload-budget.ts` bounds every stored event to 64 KiB of
JSON. An oversized event keeps its kind, identity fields and count; its
payload is shortened deterministically to a 16 KiB head+tail with an inline
`[open-design: ...]` marker (bytes cut, original size, digest) and a
`truncated: { originalBytes }` field. cursor-agent `tool_call` lines drop their
full-file copies (`beforeFullFileContent`, `afterFullFileContent`, read
`content`) first so the retained JSON stays valid and meaningful; Write/Edit
rows keep their `+N -M` via `od_diff_stat`; text/thinking are never cut.
Applied at the json-event-stream source (live SSE + run buffer), at the
SSE->persisted conversion, and at every events_json/batch write.
Loading a conversation never materializes unbounded data (I2): GET messages
streams rows and bounds legacy rows on read; the PUT sibling-Run guard reads
each sibling's first done_key in SQLite (`listSiblingRunDoneKeys`); the
project run-status projection picks each project's latest run in SQLite and
reads only the events `eventsEndedWithUnfinishedWork` can see.
Existing oversized rows are healed once (I3):
`storage/message-event-payload-heal.ts` runs after the daemon is listening,
one row per transaction, streaming each row's events via json_each, skipping
queued/running runs, and records completion in `daemon_maintenance_passes`.
Contracts gain the optional `AgentEventPayloadTruncation` field on raw,
tool_use and tool_result events (and the SSE raw payload).
* fix(daemon): keep listMessages working on all()-only DB adapters
Streaming rows with `iterate()` is what keeps a pre-heal conversation read
from holding every row's legacy event log at once, but some integration
boundaries hand listMessages a lightweight adapter that models only
`prepare().all()` (as `hasMessageEventBatchStorage` already tolerates). Fall
back to `all()` there. Also prepare the run-status completeness statement once
per listing instead of once per project.
* test(daemon): red spec for unbounded live raw lines from every parser
claude-stream, copilot-stream and qoder-stream hand unrecognised stdout lines
to the SSE fan-out verbatim; only json-event-stream bounded them at the
source. Pin that every parser's live raw event fits the 64 KiB per-event
budget on both the newline and flush() paths, and that a line that fits is
passed through unchanged.
* fix(daemon): bound raw lines at the source in every stream parser
Apply boundedRawAgentEvent to the raw emitters of claude-stream,
copilot-stream and qoder-stream, as json-event-stream already does, so the
live SSE stream and the in-memory run buffer carry the same bounded line the
transcript stores. Live tool_use/tool_result stay unbounded on purpose:
Langfuse tool spans and artifact detection read them from the live run.
* fix(daemon): deliver agent prompts intact and reap leftover agents after a daemon crash
Plain-text-stdin runtimes (cursor-agent, codex, opencode, copilot, qwen,
amp, ...) received the composed prompt through an async pipe write. When
the daemon died right after spawn, only the kernel pipe buffer (64 KiB)
reached an agent that reads stdin late, and the detached agent (running
with --force) kept going on the truncated prompt, whose last visible
request was the conversation's oldest one.
- Deliver the prompt as a file-backed stdin: a 0600 file under the run
directory, passed as the child's stdin fd at spawn and deleted right
after. If it cannot be written, the run fails before any agent exists.
Claude's stream-json pipe is unchanged.
- Record each spawned agent (pid, process group, spawn window, daemon
identity) in its run directory. On the next daemon start, terminate
leftovers whose recording daemon is gone and whose identity still
matches, and sweep stale prompt files. A failed record write fails the
spawn instead of leaving an agent nothing can reap.
- Add process-identity helpers to @open-design/platform.
By design a leftover agent keeps running until the next daemon start;
the interrupted run reads back failed / DAEMON_RESTARTED.
Red first: with main's behaviour a late-reading agent received 65,536
bytes and its process group survived a restart. The agent-process specs
and e2e/tests/dialog/leftover-agent-reap.test.ts go red there and are
green on this change.
|
||
|
|
2acb7f6f69 |
chore(pack): upgrade bundled Vela CLI to 0.0.37 (#8224)
* chore(pack): upgrade bundled Vela CLI to 0.0.37 * test(amr): align Vela continuation contract with 0.0.37 |
||
|
+25 |
7245f543a7 |
feat(web): land the Home entry refresh (feat/home-entry-refresh → main) (#8208)
* feat(web): refresh the Home hero and composer (#7731) * feat(web): add the SiriOrb run indicator Ports the Siri Orb (SmoothUI, MIT) from #7635 as a standalone component: a rotating neon sphere with idle / thinking / streaming speeds, drawn entirely in its CSS Module. Unused by this PR; the rail run-status icon (PR-A of OPEND-2553) consumes it. * feat(web): vendor the composer running orb Adds the thinking-orbs 0.3.1 'solving' state at its 20px preset (MIT, attributed in the file) as a local component instead of a new runtime dependency: the composer needs one of the package's nine animations, pinned to the dark palette, and the transcription keeps the geometry, preset scaling, painter and frame loop of the published build so the canvas output is identical. Refs OPEND-2553 / #7635. * feat(web): add the rotating Home headline i18n keys homeHero.title now carries the {word} slot in every locale together with homeHero.titleWords (the comma-separated nouns the headline cycles), homeHero.subtitle and the attachment band's paging labels. Every locale owns its own sentence and noun list so the placeholder-parity check stays green (#7635 only rewrote en / zh-CN / zh-TW). Refs OPEND-2553. * test(web): re-pin the Home hero and composer specs to the refreshed surfaces Follows #7635's hero/composer contract: type row instead of the template dropdown, display+clear type pill, no Home mode picker, no resource submenus in the '+' menu, references in the working-directory row, lead-chip truncation, the 116px hero offset, the 220px execution-chip cap, the flat app wash and the stable send arrow. Carries over the Demo's new specs (dock collapse, Backspace-at-start, modelVersionLabel, icon-only design-system pill) and drops the ones for removed surfaces. Refs OPEND-2553. * test(e2e): follow the Home type row and composer menu Ports #7635's Playwright helper (homeTypeRow / clearHomeTemplate / pickHomeTemplate) and the home-hero-rail, entry-chrome-flows, project-management-flows, visual-navigation and entry-topbar changes that reach the composer through the type row, the working-directory group and the trimmed '+' menu. Refs OPEND-2553. * feat(web): refresh the Home hero and composer Replicates the Home hero + composer subset of #7635 on current main (OPEND-2553, PR-B): plain headline with the rotating noun and subtitle, a typeless cold Home whose type row (3 + 2 behind More) retires into the composer's display+clear pill, upload disc + design-system palette + type pill in the foot row, the supplied send mark and border beam, two-line staged-file chips in a paging band, the lead template chip with Backspace-at-start, references and linked checkouts in the working-directory row (one slot), the trimmed '+' menu without resource submenus or the mode picker, poster rows for examples, the model chip's brand mark and version-only label, the running orb + stop square in the project composer, and the flat app wash. HomeView gains the page/dock variant plumbing and drops the default type seed and the AppWashKineticGrid mount; the recent-projects grid stays for PR-C. Adds task_chip_clear to the composer click contract. * test(e2e): follow the Home composer and type-row refresh Migrates the Playwright specs that named surfaces #7635 removed or moved: the composer session-mode picker (every turn now carries the conversation's stored design mode, and design turns carry no message chip), the Home examples loading shell (a cold Home is typeless and shows only the type row while the catalogue loads), the fixed type row (three inline pills plus two behind More; every other create type reaches the hero through the cross-surface hand-off, which the shared pickHomeTemplate helper now drives), the working-directory row's single slot for references and linked checkouts, the '+' menu's working-dir group, the lead chip's eight-character title, the 32px send disc, and the flat app wash. The Home recent-projects specs keep main's shape (PR-C owns that move). Refs OPEND-2553. * test(e2e): repair the visual captures the composer refresh broke The projects captures clicked the rail's drafts item, which only renders with a workspace context the visual fixture never has; Home already shows the recent-projects strip, so the click is dropped. The integrations capture reached its page by direct load, whose default tab is MCP and whose cold mount outlasts a bare expect; it now clicks the connectors tab like the MCP capture does. The BYOK switcher capture expected the link glyph, but the chip leads with the selected model's brand mark and only falls back to the glyph for a vendor without one. OPEND-2553 (PR-B). * feat(web): add recent projects and run status to the entry rail (#7730) * feat(contracts,daemon): report awaiting-input projects on the runs list `ChatRunStatus` cannot express "waiting on the user": the run that asked a `<question-form>` reports `succeeded` and exits while the project stays blocked, so a client folding `GET /api/runs` into a per-project status shows such a project as finished. Ship `awaitingInputProjectIds` beside the runs, composed from the same `listProjectsAwaitingInput` read `GET /api/projects` uses and intersected with the projects the returned runs already reveal, so the field can never widen what a caller may see. Ported 1:1 from upstream #7635 for OPEND-2553 (PR-A). * feat(web): derive per-project run status from the runs feed `Project.status` only arrives on the unscoped `GET /api/projects`, so a workspace-scoped session cannot read it. Add the pure fold (`foldRunsToProjectStatuses`) that mirrors the daemon's composition, the per-project `listRunsForProject` read plus `useProjectRunStatuses` hook that keeps it live off `RUNS_CHANGED_EVENT`, the one glyph per status (`ProjectRunStatusIcon` over the SiriOrb), and the two conversation reducers (`latestUserPromptText`, `runProgressSteps`) the Design Files empty state will read. No visual change on its own. Ported 1:1 from upstream #7635 for OPEND-2553 (PR-A). * feat(web): list recent projects in the entry nav rail Adds the 最近浏览过 disclosure under 插件 in the signed-in rail: the head of the same recent catalog 全部项目 shows, newest first, capped at eight rows. Each row leads with the project's live run status (running / needs input / completed / failed) from the runs feed, opens through the shell's pull-first opener, floats a cover preview beside the rail on hover, and carries a ⋮ menu with rename, whole-project export and a two-step delete. An acknowledged ✓ is spent by opening the project and re-raised by the next run. The Home page's recent-projects grid stays; this only adds the rail entry. Row geometry, easing and colours are pinned to the Demo (upstream #7635) by `tests/styles/entry-rail-recent.test.ts`. OPEND-2553 (PR-A). * fix(web): acknowledge the rail ✓ per finished run The 最近浏览过 rows spent a project's ✓ by remembering the PROJECT id and only forgot it after observing a live queued/running status. The section polls only while it is open, so a run that started and finished while it was collapsed was never observed: on re-expanding, the stale acknowledgement still matched and the new completion's ✓ never showed. (The Demo, #7635, carries the same defect; this deliberately diverges from it.) Acknowledge by run identity instead. `foldRunsToProjectRunSummaries` reports the newest terminal run's id beside each project's status (the status-only fold is derived from it and unchanged for its callers), `useProjectRunSummaries` exposes it live, and the rail stores `{ projectId: acknowledgedRunId }`: a ✓ is acknowledged for one specific finished run, and a newer finished run is a new notice. The reset-on-running effect is gone. A store in the old array shape reads as nothing acknowledged, so at worst one already-seen ✓ shows once more. Red spec: tests/components/EntryNavRail.recent-section.test.tsx "re-raises the ✓ for a newer finished run even when its running phase was never seen". OPEND-2553 (PR-A). * fix(web): close the Home hero/composer acceptance issues against #7731 (OPEND-2553 PR-B2) (#7769) * fix(web): keep the staged attachment band in sight as the prompt scrolls The scroller sat on .home-hero__prompt-flow, which is also the attachment band's parent, so scrolling a long prompt carried the band away with the text: an attachment the user had just staged left the screen while they were still writing about it. Move the scroll down to a new .home-hero__prompt-surface holding the prompt and its lead chip and nothing else. That element keeps the position: relative the out-of-flow chip needs as a containing block, so the chip still scrolls with the text it leads rather than pinning itself to the top of the box. Ported from nexu-io/open-design#7635 ( |
||
|
|
dc61db8e03 |
fix(daemon): show Codex and OpenCode tools before arguments finish (#8117)
* fix(daemon): stream early Codex and OpenCode tool previews * fix(daemon): retry missing runtime versions for tool previews * test(amr): verify early tool previews through real Vela runs --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
d465086b2c |
fix(amr): align client links with Cloud dashboard routes (#8126)
* fix(amr): use canonical cloud dashboard URLs * test(e2e): align AMR recharge link assertion Generated-By: looper 0.13.1 (runner=fixer, agent=codex) * fix(amr): honor selected dashboard profile Generated-By: looper 0.13.1 (runner=fixer, agent=codex) |
||
|
|
1ff25e5b2b |
fix(packaged): preserve MCP runtime integrity and sidecar delivery (#8027)
* fix(daemon): keep packaged MCP discovery out of package managers * fix(packaged): preserve MCP payload identity and require sidecar supervisor * fix(pack): finalize runtime manifests and report entry recovery * ci: allow isolated full Windows smoke on dispatched revisions * test(packaged): derive recovery versions from the release channel * test(win): read portable installer logs and retain rollback diagnostics * test(win): distinguish portable startup reconciliation from installer sync |
||
|
|
8921ccac20 |
fix(daemon): exclude URLs from ACP title file inference (#8063)
Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
d06eb1efeb |
fix(daemon): fail blocked OD Next runs before finalization (#7942)
* fix(daemon): fail blocked OD Next runs before finalization * test: align run fixtures with OD Next terminal status Keep generic session and smoke fixtures on ordinary runs, and assert the blocked verdict in the strategy timing fixture. Generated-By: looper 0.13.0 (runner=fixer, agent=codex) * test(daemon): isolate Codex resume and retry fixtures from OD Next Select ordinary-run mode for plain-reply session and recovery fixtures, restoring the rollout override after each test. Generated-By: looper 0.13.0 (runner=fixer, agent=codex) |
||
|
|
c333324da0 |
test(amr): verify prerelease runtime identity and Vela contracts (#7957)
* test(amr): verify prerelease runtime identity and Vela contracts * docs(amr): bind continuation evidence to final upstream commit * docs(amr): track host cancellation integration dependency * docs(amr): record final Write test-only dependency head |
||
|
|
98f2fd1a46 |
fix(chat): hide undesigned cards and auxiliary message panels (#7956)
* fix(chat): render cards inside execution records Render supported od-card payloads inside execution records with the existing card components, instance scopes, and browser-assistance callback. Preserve Markdown code examples and isolate card payloads from later Markdown parsing. * test(web): await writable scope before deleting a conversation * fix(web): buffer streamed card opener prefixes * fix(web): remove undesigned ChatPanel cards (OPEND-2971) Remove task-brief and rule-proposal presentation and their exclusive UI, styles, and translations following the explicit product decision. Preserve quoted card examples, neighboring prose, and the three retained card kinds, including browser-assist callbacks. * fix(chat): stop generating undesigned brief and rule cards * test(web): use supported role query options for removed cards * fix(chat): hide retired brand browser assistance cards * fix(web): hide undesigned assistant message panels * test(web): specify the brand project kind in hidden UI fixture * style(web): remove trailing blank line after panel styles * fix(web): hide verification scorecards in ChatPanel * test(chat): align retired UI expectations with approved hiding * fix(chat): keep card payloads opaque to question form parsing * fix(web): preserve card parsing across replay metadata Keep marker masking aligned with physical run boundaries, and replace repeated Markdown tail scans with a forward indexed walk. Preserve actual forms and raw code examples with replay and paragraph-boundary regression coverage. * fix(web): keep form field syntax inside its payload Recognize complete forms through the existing parser before scanning their fields for card or code markers. Preserve sibling form actions and keep outer card/code ownership with callback-based parsing that avoids an import cycle. * fix(chat): share question form protocol context with host detection --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
34ac405bfe |
fix(release): gate the Thursday patch cut on the previous patch, not the minor base (#7977)
* refactor(release): extract the patch-cut pre-flight out of inline shell Faithful port, no behavior change: resolve-patch-cut.ts computes the same version and the same X.Y.0 gate target the shell did, and runs the same gh release lookup. Moving it out of YAML is what makes the gate decision testable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(release): pin the patch cut on the previous patch, not the minor base Red against the current guard: with release/v0.22.2 cut and unshipped, the pre-flight gates open-design-v0.22.0, finds it published, and cuts v0.22.3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): gate the Thursday patch cut on the previous patch The guard checked the minor base X.Y.0, so it only ever guarded a line's first patch. Once X.Y.0 shipped, X.Y.2, X.Y.3, ... all passed no matter how many unshipped releases had stacked up beneath them. Gate on the release the cut actually stacks on — the highest release/vX.Y.Z below it — which is the same target cut-release.yml uses for the Tuesday minor, and still gives the answer the old minor-base comment wanted (cutting 0.15.1 checks open-design-v0.15.0, not the older 0.14.0). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(release): surface patch gate lookup failures --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
365313443d |
fix(e2e): make packaged Home readiness timeout deterministic (#7952)
Use a fake clock to verify the existing 10ms deadline and the final readiness snapshot after multiple inspections. Cover a first inspection that consumes the whole budget, where a single observation is valid. Keep the readiness helper and application behavior unchanged. Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
21bba485b6 |
fix(whats-new): support localized Arena CTA labels (#7943)
Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
8be9b0fcaf |
fix(daemon): deliver artifacts with syntax warning telemetry (#7925)
* fix(daemon): deliver artifacts with syntax warning telemetry * docs: record main-based syntax warning acceptance * fix(daemon): surface internal syntax failures at delivery boundary --------- Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
35fa33b3c7 |
fix(e2e): align packaged Home smoke inputs and evidence (#7918)
* fix(e2e): use daemon-owned request input in Codex plans * fix(e2e): scope Codex smoke receipts to app-server processes --------- Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
d07102a3b9 |
feat(release): post the prerelease card on trigger and time every platform (#7916)
Reverses the "withhold the card until the first artifact publishes" rule. The card is now posted on the watcher's first poll, while every platform is still queued, and every later state edits that same message. The reason the card was withheld — a "构建中…" message left standing forever when a build dies — has since been answered by the machinery that grew around the card: a dead lane renders its own terminal state, a release where nothing shipped turns the card red, the watcher's 140-minute budget stamps the card instead of abandoning it, and a watcher that never delivered draws the fallback bot. Silence for the whole build window bought nothing. `shouldPostFirstCard` conflated two predicates. One half was already `anyPackagePublished`; the other is now `allPlatformsFailed`, and it moves from gating the post to being the card's red verdict — the header's old "some platform reported and none shipped" proxy is only equivalent while the card does not exist before total failure, and with the card posted early it reads 全部平台构建失败 the moment one lane fails, or red at t=0 because `build_linux` is skipped on every run. Each platform row now carries its build time (`用时 11m42s` once finished, `已用 18m` while running) and the footer carries the whole round's clock, both read from the `started_at` / `completed_at` already in the polled job list plus the origin run's `created_at`. Live numbers are quantized to the minute so a 30-second poll does not PATCH the card on every cycle. |
||
|
|
d232620c16 |
fix(e2e): align packaged Home smoke with Codex app-server (#7914)
Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
dd278cea65 |
fix(release): allow publishing whats new from release branches (#7902)
Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
25475defab |
fix(e2e): stop the packaged mac first-run gate failing on budget alone (#7899)
The `[P0] cold first Home run` packaged mac case has been red on almost
every beta build for eleven days (1 pass in 12 attempts) and nobody saw
it, because release-beta's smoke step is `continue-on-error: true`.
Four changes, in the order they matter:
1. Capture evidence before cleanup. A failing case uninstalled the app
and the next case deleted the runtime namespace, so the only surviving
trace was `logs: {skipped: true}`. The failure path now writes the
desktop log, a raw `/api/runs` diagnostics snapshot, and the error into
the release report before anything is torn down.
2. Give the wait a budget that matches the chain, split in two. 15s had to
cover cold project creation through rendered output; it is now 60s for
"the daemon owns a finished run" plus 30s for "the output surfaced", so
a timeout says which half broke instead of leaving three observations
that cannot falsify each other. Polling moved inside the page: every
sample used to cost a `tools-pack mac inspect` process, which ate about
a third of the old budget on its own.
3. Drop the injected 503. `POST /api/runs` does not answer
WORKSPACE_AUTHORITY_UNAVAILABLE — local run creation is deliberately
independent of Workspace membership availability — so the case spent
an extra create round trip plus a 500ms backoff on a branch production
cannot reach. Run-create retry semantics stay covered by
apps/web/tests/providers/sse.test.ts.
4. Make the exempted red audible. The exemption stays (at a 1/12 pass rate
a blocking gate would stall the daily beta), but `tools-release
write-report` now turns a failed smoke into a run annotation and a
caution banner at the top of the step summary.
|
||
|
|
3ddff71205 |
feat(ci): publish the What's New card from the repository (#7892)
* feat(ci): publish the What's New card from the repository Move the post-update highlight document into the repo and publish it from GitHub Actions, so changing the card is a pull request instead of a task that needs personal Cloudflare credentials. - docs/whats-new.json is the source of truth, next to its spec doc. - pnpm guard validates it through the daemon's own parser on every PR, so a document that would silently show no card cannot merge. - whats-new-publish.yml uploads on merge to main (and on manual dispatch, with a read-only dry-run mode) and verifies the object by reading it back from the origin the daemon fetches, past the edge cache. * docs: name the CI job that actually runs the guard check * fix(ci): gate the What's New publish behind a main-only environment A workflow_dispatch runs the workflow file from the ref it is dispatched against, so the in-workflow main-only assertion was editable by whoever triggered it: anyone with write access could publish unreviewed content to every installed client from their own branch. Split the workflow so credentials live in one place and that place is gated by GitHub rather than by YAML. `validate` holds no secrets and runs on any ref; `publish` declares the `whats-new-publish` environment, whose deployment-branch policy allows main only and which owns the R2 secrets. Dropping the environment declaration to escape the branch policy also drops the secrets. Also points the card at the new Arena URLs supplied by product. * fix(ci): pin the What's New publish trust boundary in a check that runs The missing-credentials error still told operators the R2 keys are repository secrets. Following that string is the exact setup the previous revision closed: repository secrets are readable from any job on any branch, so a modified workflow dispatched from an unreviewed ref could publish to every installed client — or just take the keys. Point the error at the `whats-new-publish` environment and refuse the repository path in the same breath. The boundary itself had no coverage: nothing read the workflow, so moving `environment:` or a `secrets.*` expression back onto the arbitrary-ref job would stay green while still publishing correctly from main. That coverage cannot be lane-routed. Running the planner over `.github/workflows/whats-new-publish.yml` selects neither `web_tests_required` nor `ui_p0_validation_required`, so the `e2e_vitest` workload does not run for a workflow-only edit — an assertion living only there would be skipped for exactly the change class it guards. The check therefore lives in `pnpm guard`, which CI invokes from the unconditional preflight job, same reasoning as the document guard next to it. It asserts that the credential-bearing job declares the environment by its exact name, needs `validate` and is gated on its output with no `always()`/`cancelled()` escape, reads all four bucket bindings, and that no other job attaches an environment or reads any secret at all. The e2e suite drives it over the real workflow mutated into each of those regressions in turn, so every assertion is proven able to fail; gutting the checker turns all eleven red. * chore(whats-new): seed the document from the live card Bringing the workflow in with new copy attached ties the infrastructure to a content decision. The Arena CTA this document pointed at is not live — `/llm-arena-for-design/` and `/zh/llm-arena/llm-arena-for-design/` both 404 — and the card renders regardless, so merging would have put a dead button on every client's Home. Seed the repository copy from the object that is already serving instead: byte-identical to `https://whatsnew.open-design.ai/whats-new.json`, id `0.21.0`. That puts the current state under version control and makes the first automatic publish idempotent — it uploads exactly what is already there, so the merge exercises the whole chain (validate, upload, read-back against the origin the daemon fetches) without changing anything a user sees. The Arena copy is not lost; it moves to the PR body as a follow-up for the release owner, who can now change the card through this workflow without holding personal Cloudflare credentials. * fix(ci): detect secrets by context, not by access shape The guard added last round matched `${{ secrets.NAME }}` with a regex over the raw workflow text. A guard whose whole job is to stop a secret reaching the arbitrary-ref job had a blind spot for most of the ways to read one. Verified against the previous implementation, each returning zero violations while a secret reached `validate`: `secrets['NAME']`, `secrets["NAME"]`, `secrets [ 'NAME' ]`, `secrets . NAME`, `toJSON(secrets)`, a bare `secrets`, and `fromJSON(toJSON(secrets)).NAME`. Extending the regex would have repeated the mistake that caused it. One bypass proves why: a double-quoted YAML scalar may carry a backslash line continuation, so STOLEN: "${{ sec\ rets.NAME }}" parses to a live secret read while the source text contains no such substring. No text scanner can see that, however many shapes it lists. So the workflow is read through a YAML parser, and detection is a single property instead of an enumeration: GitHub's expression language has no string-to-context evaluation, so a secret cannot be reached without naming the `secrets` context as an identifier. Finding that identifier inside any `${{ … }}` expression covers every access form, including ones nobody has thought of. Parsing also retires the hand-rolled job splitter and its own failure modes, and lets the check distinguish "no violations" from "nothing was examined" — an unparseable document, a flow-style jobs mapping, or a renamed publish job now fail closed rather than passing quietly. Two holes the shape list never covered are closed as a side effect: a job-level `secrets:` key passes credentials to a called workflow with no expression at all, and workflow-level `env:` is inherited by every job. `yaml@2.9.0` becomes a root devDependency. It was already in the lockfile via `tools/release` and pinned repository-wide by the existing pnpm override, so the lockfile gains only the root importer entry. Test coverage moves with it: 26 of 46 cases now go red when the checker is gutted, up from 11. --------- Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
819c10d49f |
fix(chat): make the failure card, the queued action and the fork note stop disappearing (#7871)
* fix(web): ellipsize the fork divider note instead of hard-cutting it The fork divider footnote lost its ellipsis when #7863 merged the two-block layout into one line. `.fork-note` became `display: flex` so the branch icon and the label could sit side by side, but the three truncation declarations (`overflow: hidden`, `text-overflow: ellipsis`, `white-space: nowrap`) stayed on `.fork-sep span` — the very same element. Bare text inside a flex container lives in an anonymous flex item, and `text-overflow` is not inherited, so the anonymous box falls back to `clip`: the 62% cap still clipped, just without the three dots. Move the label into its own `.fork-note-label` span and hang the truncation on that layer, with `min-width: 0` to release the flex item's default `min-width: auto`. `.fork-sep span` becomes `.fork-sep > span` so the inner label is not pinned to `flex: none` — a non-shrinkable flex item is always exactly as wide as its content and can never overflow. Also finishes OPEND-2714's copy: the 16 locales other than en / zh-CN / zh-TW still carried translations of the retired English sentence. They now say what `Continued from chat` says, each within 1.5x the English length so they stay inside the 62% cap. * test(web): witness the fork note ellipsis in a browser, not in chat.css apps/web/src/components/chat/AGENTS.md §5 forbids asserting CSS class names or declarations in chat tests, and the spec added in the previous commit did exactly that: it located the label by className, parsed chat.css, and pinned the resolved values of text-overflow, overflow, white-space, min-width and flex. Delete it. Replace it with a narrow browser witness. e2e/ui/fork-note-ellipsis.test.ts forks a conversation through the real daemon endpoint, renders it in the longest shipped locale (German), squeezes the chat panel until the label overflows, then checks two observable things: the label really is overflowing its box, and its paint differs from the same element forced to `text-overflow: clip`. Comparing the element against itself keeps the oracle baseline-free, and a build that still hard-cuts paints identically under both, so the check cannot pass vacuously. Asserting the declarations was the wrong instrument anyway: the broken build declared `text-overflow: ellipsis` too. What was wrong was the box it sat on, and only a laid-out browser can see that. The one structural fact worth keeping - the label is carried by its own element rather than as bare text beside the icon - moves into the existing fork-divider spec and keys off a data-testid instead of a class name. Enrolls the new file in the project-workspace UI P0 group. An unenrolled P1 would run only in the prerelease full pool, which means shipping a witness that nothing on the merge path ever executes. * test(e2e): wait for the fork divider before squeezing the chat panel The width squeeze is a raw custom-property write onto the split, which React's own width effect would overwrite if a late fetch re-rendered between the write and the measurement. Wait for the divider to paint at the default width first, then squeeze with nothing awaited in between. Also corrects a stale doc comment that named a helper this file never had. * fix(e2e): squeeze the chat panel without racing its width transition CI caught the guard doing exactly what it was written to catch: it failed with "content 161px vs box 161px", meaning the note never overflowed and the ellipsis check would have been measuring nothing. The cause was the squeeze, not the copy. `--project-chat-panel-width` is a registered `@property` with a 200ms transition, so writing it tweens from the default 460px and the measurement taken right after read the panel mid-flight. Suspend the transition the way the product's own resize handle does (`.is-resizing-chat`), and do the write and the read inside a single evaluate so no re-render can land between them. Also measures the label before and after the squeeze, so the next failure names its own cause: a panel that never narrowed is a fixture problem, a narrowed panel that still fits the text is a copy problem. The previous message could not tell those apart. * chore(web): drop the now-unused splitList export from the cascade helper It was exported only to feed the CSS-parsing spec that the previous commit deleted. Nothing outside the helper calls it any more, so put it back to a module-private function and leave the shared test infrastructure exactly as it was on main. * fix(web): collapse the queued row's leading action into one 「引导对话」 button The queued-send row's leading action had two faces: a 74px labelled 「引导对话」 while `canSteerCurrentTurn` held, and an unlabelled 22px "send now" icon otherwise. Both were wired to the same `sendQueuedChatSendNow` — the gate only changed the name, the tooltip and the analytics `element` value. Product ruled on 2026-09-08: 「引导对话就是原本的立即发送啊,只不过我们 换了个名字跟 codex 客户端对齐了下」. So the gate goes, the fallback face goes, and the row keeps one button that is always 「引导对话」. The delivered spec agrees. `729fa43ce7:docs/design/chat-panel-next.html` component 17 "Queue" draws exactly one action there — `aria-label="引导对话" data-tip="引导对话"` with `<svg/><span>引导对话</span>` — and never the icon-only send-now key. `data-tip` is the plain name, so `title` / `data-tooltip` / `aria-label` collapse from `chat.queuedSteerInterrupts` back to `chat.queuedSteer`; visible text and accessible name are now identical (WCAG 2.5.3 Label in Name). Order is untouched: steer → edit → delete (OPEND-2715). Analytics: this surface no longer emits `ui_click` with `element: 'send_now'`. The surviving button reports `'steer'`, matching the name it now carries. The `send_now` member stays in the contract union because PostHog still holds the historical events. Props: `onSteerQueuedSend` existed only to carry the gate, so it is removed from ChatPane, SideChatTab and both ProjectView call sites, along with `canSteerCurrentTurn`. `onSendQueuedNow` survives as the single handler — it has the second pass-through face in `SideChatTab`'s `controlledChat`, and it names what the handler actually does. `steerBlockedReason` is left alone: it had no producer before this change either, and retiring a deliberately parked placeholder is its own call. * fix(prompts): stop asking the model for a per-question help string OPEND-2707. The clarification card stopped rendering the per-question subtitle (`q.help`) in #7863, but the prompt layer kept asking the model to write one — so the model spent tokens on copy nobody would ever see, and believed it had already told the user something it had not. The user ruled on 2026-09-08 to go all the way and retire the prompt half too. Three prompt surfaces carried it: - `core-slim.ts` routed leftover context into "the individual question labels/help"; context now has one destination, the question label. - The UI locale override (daemon + contracts mirror) listed "helper text" among the question-form controls that must be localized — naming the field without naming it. The other four entries in that list all still render. - The ElevenLabs voice picker is the only form the repo itself authors, and it shipped the only `help` string in the tree. Its content is real information (the visible labels are voice descriptions; the submitted value is the voice_id), so it moves into the question label rather than being dropped. `FormQuestion` has no per-question `description`, and `placeholder` is already taken by "Choose a voice". That form is `JSON.stringify`d whole into the system prompt, so it doubles as the model's worked example of what a question may look like — leaving a `help` key there would have quietly undone the rest of the change. `e2e/tests/question-form-help-retired.test.ts` guards all seven authorized prompt paths, mirroring `question-form-visual-style-retired.test.ts`. It reads source bytes, so it also asserts the parser keeps tolerating `help` — the dormant field must not be removed along with the prompt guidance. Snapshot: only `totalChars` moves, on the three scenarios that compose the touched text; no section is added or removed. * docs(web): record FormQuestion.help as dormant, not dead OPEND-2707 follow-up. With the renderer, the CSS rule, the prompt guidance and the last host-authored producer all gone, `help` now has no producer and no consumer anywhere in the tree. It still stays. Removing `help?: string` would break `QuestionForm.no-question-subtitle.test.tsx` and `QuestionForm.test.tsx` — and the first of those is the only positive evidence that the subtitle is not rendered. Deleting a zero-cost field at the price of the test that guards the invariant is a bad trade. Cached prompts, older clients and the model's own memory of the old format can also still send a question carrying the key, and the parser should keep tolerating it rather than change parse shape as a side effect of this retirement. Same reasoning as the six `qf.visual*` i18n keys in `specs/current/chat-panel-decisions-sheet.md`. The field now documents when and why it went dormant, and how to bring the rendering half back (the pre-#7863 revision of both files). Also corrects `DesignDirection.mood` in both prompt mirrors: its docblock said the text is "shown to the user as `help`", which was already inaccurate — it is the `mood` blurb on a `direction-cards` option, and that renderer has had no call site since the visual-style question was retired. * fix(web): keep the disconnect failure card when no reconnect row is there A turn that failed with `DAEMON_STREAM_DISCONNECTED` left nothing at all on screen once the user stepped out of the project and came back: no failure card, no reconnect line, no way to retry. `anotherSurfaceOwnsFailure` in `ChatPane` handed the story to the reconnect line unconditionally, on the premise (stated in the comment right above it) that the reconnect line "is always there". It is not: `ProjectView` has a dedicated unmount effect that clears `reconnectView` on project change / screen exit, so on re-entry the card yields to a surface that has already left. The neighbouring balance rung already encodes the right invariant — `balanceCardCannotTakeTheHandoff` refuses the handoff when the upgrade card cannot be drawn. The same invariant was only written on one of the two rungs. Both rungs now name their handoff target first, then ask one shared question: is that surface actually present. Red spec: `ChatPane.error-card-ladder.test.tsx` — a disconnected run with no reconnect row must render the card, its own `chat.connectionDropped` copy, and a retry action. Reverse anchor (row present -> no card) is green on both sides of the fix. The two older cases in that file asserted "no card" while rendering *without* a reconnect row: they pinned this defect rather than the invariant. Both now put the handoff target on screen, so they pin what they were written for — that either signal (structured code, or a legacy detail-only row) is recognized as the disconnect failure. * fix(web): shorten the Cloud switch CTA to match the delivery spec The error card's primary button read 「切换到 OpenDesign Cloud 并重试」 while the delivery spec (docs/design/chat-panel-next.html, cell 79 "特殊错误 · 运行环境为 CLI / BYOK") writes 「切换到 Cloud」. Product ruled in person on 2026-09-08: "切换到 cloud 就行了,你怎么写那么 长的文案『切换到 Cloud 并重试』" and "具体的报错文案不一定跟设计稿对齐, 按钮文案对齐先". So only `chat.amrCard.switchCta` is aligned here — the card's title and body keep their own product copy and are deliberately left untouched, including the separate `chat.amrCard.switchBody` key. All 19 locales are shortened to the same "Switch to Cloud" register with the trailing "and retry" clause dropped. Comments in ChatPane.tsx and amr-guidance.ts that quoted the old label are updated so they stop describing copy that no longer exists; the reasoning around them is unchanged. Playwright assertions in e2e/ui that pinned the old label follow the new string. * docs(specs): record the three rulings taken on 2026-09-08 * fix(web): keep the failure card when a failed turn has no words for it A turn that ends in a terminal failure loses its entire recovery surface whenever the card cannot compute a description. The whole card — title, message, and all four actions (contact support, export logs, retry, switch to Cloud) — hangs off one `displayError` ternary, so a null description takes the explanation and every way out with it. What is left on screen is the execution shell's bare "run failed" line, written on the assumption that the card below it says why. The three description sources all go empty together more often than the shape suggests: the mapping table's own last-resort row carries no `messageKey`, the pane-level `error` state is reset when ProjectView unmounts on navigation, and the run's raw detail never reached the message for the `AGENT_UNAVAILABLE` / `BYOK_PROVIDER_REQUIRED` / `AGENT_RUNTIME_DEF_INVALID` family (daemon `fail()` emits without going through `persistRunEventToAssistantMessage`) or for the `runStatus:'failed'` rows ProjectView writes itself when self-healing a phantom running turn. `resolveRunErrorCardDescription` now takes `turnEndedInTerminalFailure` and falls back to `RUN_FAILURE_FALLBACK_MESSAGE_KEY` instead of rendering nothing. It is the last branch, so every handoff and every more precise source still wins ahead of it. The signal is `retryableAssistantMessage`, which already gates the recovery entries and already treats a delivery failure (`no_result` / `delivery_failed`) as terminal, minus the empty API response — that one has a surface of its own ("No output") and must not get a second one. Also stops an empty pane-error string from shadowing the run's own detail. That branch carried a comment saying the silence was a separate question; this is the same question, answered the same way. |
||
|
|
faba302437 |
feat(release): alert through the other Feishu bot when the prerelease card goes silent (#7873)
* feat(release): alert through the other Feishu bot when the prerelease card goes silent The progressive card is now the only prerelease notification, and every way it breaks is silent: the pipeline stays green, the packages ship, and the channel hears nothing. The watcher catches Feishu failures on purpose — one hiccup must not end a two-hour watch — so expired application-bot credentials look exactly like a healthy run. Adds a last-resort notice delivered through the custom-bot webhook, which is a different bot with a different secret, so a credential failure on the card side cannot take the alert down with it. It fires only when the card did not reach the group: the watcher now reports `card_delivered` for a card whose FINAL state is in the chat, and the gate reads that rather than the job result. Two callers, mutually exclusive so a release can never draw both: release-prerelease-card.yml's `fallback_notice` job covers a watcher that died or never delivered; steps at the end of release-prerelease.yml's `dispatch_validation` cover a card that was never dispatched. The latter are steps, not a job, because nothing may appear in a `needs:` on the dispatchers. * fix(release): keep the fallback notice reachable past the dispatch it reports The two steps that actually deliver the notice carried no status function, so GitHub's implicit success() skipped them whenever anything earlier in the job had failed. `dispatch-validation.sh` exits non-zero when it cannot dispatch the card on any ref — which marks the job failed — so on the one path the dispatch-side fallback exists for, it was silent. This PR was written to close the implicit-success() trap at the job level and walked into its step-level twin. Both consumers now carry `always() && steps.notice.outcome == 'success'`: the status function so a failed dispatch still reaches them, and the outcome check so neither a crashed nor a skipped composer can hand feishu-notice.ts an empty body. The composer writes `alert` last, after every field a poster reads, so `alert=true` in an append-only $GITHUB_OUTPUT means the whole notice is on disk. Adds the step-level reachability rule as a spec beside the existing job-level one, and puts the dispatched prerelease lanes into that job-level sweep — release-prerelease-card.yml was outside it, so `fallback_notice`, whose `if` hand-checks `needs.card.result`, was uncovered by the rule it has to obey. |
||
|
|
5a7bbe5b10 |
chore(release): stop blocking stable releases on missing release notes (#7886)
A stable release with no docs/CHANGELOG/v<version>/ directory hard-failed at the very first job. Fixing it mid-release is not "write two markdown files": the stable promotion gate pins github.commit to the promoted prerelease, so the changelog commit invalidates that artifact and forces a fresh ~27-minute prerelease round trip. Missing notes are now a loud warning instead of a failure. reviewReleaseNotePlanPolicy returns warnings for a stable release with no notes, or one missing zh-CN, and prepare-release-note surfaces them as a ::warning:: annotation plus a job summary entry. Notes that exist but cannot be consumed still throw: a channel mismatch, or a plan that omits the default locale every consumer falls back to. publish-metadata carried a second stable gate on the same condition. Removing only the first would have moved the hard failure past the GitHub draft release and asset upload, which is strictly worse, so both are gone. The rest of the chain already treated "absent" as a complete outcome — it is the beta and prerelease path — so publication, verification, and metadata assembly are unchanged. Non-stable channels behave exactly as before. |