mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
main
306
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6132da1d79 |
[codex] Restore stable release publish mode (#5031)
* fix(release): restore stable publish dispatch mode * test(e2e): cover stable publish dispatch mode --------- Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
b2512e9a7e |
ci: block direct PRs into release branches except release automation (#4982)
Colleagues have been opening PRs straight into release/vX.Y.Z (e.g. into release/v0.13.0), bypassing main + the backport flow. Add a guard workflow: on a PR opened/reopened/edited with a release/** base, if the author is not a release-automation bot (open-design-release-bot, github-actions), post a comment explaining the backport flow (merge to main, add `backport release/vX.Y.Z`) and close the PR. Closing (not a failing check) is the only real gate here: release/* branches have no required status checks and no merge queue, so a red check would not block a merge. Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
6c424f4ad5 |
fix(release): populate github.commit in prerelease metadata so the changelog has a baseline (#4960)
The Feishu release card (notify-release-feishu.yml) builds its changelog as `git log <previous>..<current>`, reading <previous> from the published prerelease/latest/metadata.json `.github.commit`. That field is written by githubInfo(), which reads the RELEASE_COMMIT env. release-beta sets RELEASE_COMMIT on every build + publish job; release-prerelease set it on none, so every prerelease published an empty github.commit. Each prerelease then found no baseline and the card always rendered "首个 Prerelease 包,无上个版本可 对比" — the changelog was permanently empty, not just for a fresh release line. Add RELEASE_COMMIT to the four prerelease build jobs and the publish job, mirroring release-beta. Both are required: publish-metadata rejects a per-platform manifest whose github.commit disagrees with the metadata commit, so the build jobs (which write per-platform manifests) and the publish job (which writes the final metadata.json) must stamp the same built commit. Red spec in packaged-smoke-workflow.test.ts asserts each prerelease build + publish job carries RELEASE_COMMIT (red on main, green here). |
||
|
|
3fb3e3ca4a |
ci(release): finalize-release — bump main + drop backport label after a stable ships (#4936)
* ci(release): finalize-release — bump main + drop backport label after a stable ships * ci(release): arm merge queue + request core-maintainer review on the bump PR main enforces require_code_owner_review and the bump touches CODEOWNERS-owned release manifests, so a bot approval can't merge it hands-free (unlike backport-automerge / bake-plugin-previews-automerge, whose targets/diffs avoid code-owner review). Open the PR, arm the squash merge queue (gh pr merge --auto, proven on main by bake-plugin-previews-automerge), and request the @nexu-io/core-maintainers review that the code-owner rule needs. * ci(release): trigger finalize via workflow_run, not release:published release-stable.yml promotes its own draft to published with gh release edit using GITHUB_TOKEN, and a release event minted by GITHUB_TOKEN does not trigger other workflows — so a release:published trigger would never fire. React to release-stable completing instead (the backport-automerge / bake pattern), and gate on the open-design-vX.Y.Z release actually being published (draft=false) so dry-runs (metadata/prepublish) correctly no-op. * ci(release): resolve version from latest published release, not head_branch PerishCode review (blocking): release-stable supports building one ref while dispatched from another via its `ref` input, so workflow_run.head_branch can be the dispatch ref, not the built release branch — parsing it would miss-fire or read the wrong version. Read the version from the latest published release instead (release-stable marks it --latest), which is branch-independent. Drop the head_branch job-if guard; the draft/prerelease/X.Y.Z guards + bump idempotency keep dry-runs a clean no-op. * test(release): pin finalize-release workflow topology PerishCode review (blocking): .github/AGENTS.md asks cross-workflow behavior changes to update the topology tests, not rely on YAML review alone. Add a focused [P2] test in packaged-smoke-workflow.test.ts (alongside the backport / bake automerge coverage) that pins finalize-release's trusted-consumer contract: workflows: [release-stable] + success/dispatch gating, App-token use, version resolved via gh release view (latest/tag) with NO workflow_run.head_branch dependency, draft/prerelease/X.Y.Z + skip handling, forward-only synchronized bump, core-maintainer review request, and SHA-pinned merge-queue arming. --------- Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
110774e891 |
fix(ci): merge-queue needs-validation gate skips non-PR refs, not eject clean PRs (#4898)
* fix(ci): merge-queue needs-validation gate skips non-PR refs, not eject clean PRs The merge_group needs-validation gate enumerates queued PRs from three sources; source 2 scrapes a trailing `(#N)` from each commit subject. That `(#N)` can be an ISSUE reference an author put in the PR title, not a queued PR — e.g. #4833, titled "fix(plugins): ... (#4828)", where 4828 is the fixed issue. The gate then ran `gh pr view 4828`, which fails with "Could not resolve to a PullRequest"; as a plain assignment under `set -e` that ejected a clean, label-free, approved PR from the merge queue every time it entered — permanently unmergeable. Distinguish a definitive "not a PR" (skip — the number was a spurious reference) from a transient API failure (still fail closed → block), so real needs-validation PRs are still enforced while issue references no longer block clean merges. * fix(ci): resolve merge-group needs-validation per commit, not a flat union Addresses review: skipping a non-PR `(#N)` unconditionally could, in a batched merge group, drop the only candidate that would have checked a non-tip PR — so a non-tip PR still carrying `needs-validation` could slip through (#4736 hazard). Resolve each merge-group commit to at least one real PR from its own signals (subject `(#N)`, commits/<sha>/pulls, and — for the tip — the queue ref). A `(#N)` that does not resolve to a PR (e.g. an issue reference like #4833's "(#4828)") is ignored ONLY when its commit is still covered by another candidate that resolves to a real PR; a commit that resolves to no PR fails closed. Real needs-validation PRs are still blocked; clean PRs whose title references an issue are no longer ejected. Verified with a mocked gh harness: (A) tip "(#4828) (#4833)" → ignores 4828, checks 4833, clears; (B) batched non-tip whose only number is an issue ref + carries needs-validation → fails closed; (C) tip with needs-validation → blocks. --------- Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
211ec5df5a |
ci: make the Nix hash check advisory on PRs, enforce it only at merge time (#4896)
* ci: make the Nix hash check advisory on PRs, enforce it only at merge time A stale nix/pnpm-deps.nix hash is the routine, self-healing consequence of any pnpm-lock.yaml change: the nix_validation job already regenerates the hash and hands it to autofix.atom.yml, which pushes the refresh back to the PR branch. Failing the job on pull_request turned that transient, auto-fixed drift into a red check that flipped the PR to BLOCKED and read as a real CI failure to the review bot — the same deadlock the needs-validation gate already documents. The contributor then waited on the autofix round-trip every time the lockfile moved. Now nix_validation stays green on pull_request (the autofix still self-heals the hash in the background) and the hash is enforced as a hard gate only at merge time: merge_group and manual full runs fail closed, so a genuinely broken Nix build can never reach main. The Validate workspace gate ignores a red Nix job on PRs (both the blanket failure scan and the required-jobs check) and keeps requiring it everywhere else. * test(e2e): cover the Nix-advisory-on-PR Validate workspace gate Extract the two jq programs the `validate` job runs (the blanket failure scan and the required-jobs scan) straight out of ci.yml and feed representative `needs` JSON through the real `jq`, so the cross-workflow gate behavior is asserted rather than reviewed by eye: - pull_request + failed nix_validation -> gate passes (advisory), - merge_group / workflow_dispatch full + failed nix_validation -> gate fails closed, - pull_request + another failed required job -> gate still fails (the PR exception is scoped to nix_validation only). A future edit that makes Nix advisory on merge_group, or stops ignoring it on PRs, now fails this test instead of slipping through YAML review. |
||
|
|
cbf3882e7e |
[codex] Reset PR inactivity window on human activity (#4882)
* fix: reset PR inactivity window on human activity * fix: handle commit and failed CI blocker detection |
||
|
|
514ddba8ed |
ci: consolidate runner selection contract (#4803)
* ci: consolidate runner selection contract * test: stabilize connector settings privacy state |
||
|
|
8b9b169673 |
ci: add cost-sensitive runner tiering (#4764)
* test persistent contabo runner poc * test persistent runner web workspace poc * test persistent runner local cache setup * test persistent runner aggressive setup poc * test persistent runner web postinstall profile * test restore stable persistent runner poc * test keep persistent runner web tests default * test route persistent poc to zxiyun runner * test relax web vitest timeout on persistent poc * test harden web vitest cases for persistent runner * test add runner perf probe * test isolate runner perf probe dispatch * test pin runner perf probe node * test expand runner perf probe matrix * ci: route heavy jobs through runner modes * ci: keep web workspace tests on blacksmith * test: update web runner mode assertion * ci: route cost-sensitive runner tiers * ci: move preflight to hosted runner * ci: downshift blacksmith ui runners to 4vcpu * test: fix file viewer timeout after merge * ci: route e2e vitest to hosted default * ci: centralize runner profile selection * test: type runner profile assertions * ci: route e2e vitest to default blacksmith * ci: remove runner experiment probes * test: restore postinstall guard coverage Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * test: cover postinstall script contract * test: move postinstall guard to e2e --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
4c6f6549c6 | ci: add landing QA Feishu summary (#4763) | ||
|
|
a4dea37a22 | fix(ci): harden backport automerge provenance (#4761) | ||
|
|
60d187b79f |
feat(ci): auto-merge the rolling plugin-preview manifest PR on green CI (#4727)
* feat(ci): auto-merge the rolling plugin-preview manifest PR on green CI The post-merge bake opens ONE rolling PR (chore/plugin-previews) whenever a baked preview changes (spec slice 1). Its diff is pure generated data (data/plugin-previews/manifest.json; the clips already live on R2), yet someone still had to merge it by hand — the last manual step the pipeline rework left behind, and exactly the kind of bot PR that used to pile up. Two coupled changes: 1. bake-plugin-previews.yml now authors the rolling branch + PR with the release-bot App token instead of GITHUB_TOKEN. A GITHUB_TOKEN-authored push does not trigger workflows, so the manifest PR never ran `Validate workspace` (main's required check) and could not clear the merge queue at all — even manually. (PREVIEW_BAKE_TOKEN, the old fallback, was never configured.) 2. bake-plugin-previews-automerge.yml reacts to ci.yml completing for the rolling branch and, for a non-draft same-repo PR authored by the release bot with only bot commits (pristine) at the exact SHA CI passed, approves it as github-actions[bot] (main requires one review; an author cannot approve its own PR) and enqueues it with `gh pr merge --auto --squash --match-head-commit`. CI failure pings Feishu instead. Modeled on backport-automerge.yml, adapted for main: main has a merge queue, so the merge is GitHub-native --auto (enqueue) rather than a direct squash, and pristine checks the git committer email (the bot email maps to no GitHub user, so .committer.login is null). * fix(review): GitHub-backed pristine provenance + correct Feishu sign secret Address two blocking review findings on the auto-merge path: 1. pristine could be satisfied by spoofed commit metadata. The bake commit was authored with a plaintext `git config user.email "bot@open-design.ai"`, and the reactor trusted that raw committer email — any collaborator who can push to chore/plugin-previews could set the same email and fake pristine=true, landing unreviewed commits on main. Commit AS github-actions[bot] (its canonical noreply email) and gate on GitHub's resolved `.committer.login`, the same GitHub-backed field backport-automerge.yml trusts. 2. the failure Feishu ping signed with secrets.FEISHU_WEBHOOK_SECRET, but every other FEISHU_RELEASE_WEBHOOK caller (notify-release-feishu, notify-daily-feishu, backport-automerge) signs with FEISHU_RELEASE_SIGN_SECRET; the wrong secret resolves empty and the signed release webhook rejects the unsigned request. Use FEISHU_RELEASE_SIGN_SECRET and assert the pairing in the topology test. * test(ci): lock the auto-merge conclusion gates + bake producer invariants Address nettee's non-blocking coverage follow-up so a regression can't pass the topology test green: - Assert the approve and enqueue steps gate on `workflow_run.conclusion == 'success'`, and the Feishu step on `== 'failure'`, so dropping a conclusion predicate fails the test. - Add a companion `[P2]` block over bake-plugin-previews.yml asserting the three producer invariants the downstream pristine/auto-merge path depends on: the create-github-app-token mint, the App-token push URL, and the github-actions[bot] commit identity. Regressing the producer back to a GITHUB_TOKEN push or a different commit identity now fails here. * fix(review): bind the Feishu failure alert to the run's exact SHA A stale failed CI run can finish after the force-pushed rolling branch has advanced to a newer (green) head. The Feishu step only gated on author/cross + conclusion==failure, so it would page about a SHA that is no longer the PR head. Add the same steps.pr.outputs.head_oid == workflow_run.head_sha guard the approve/enqueue steps use, and lock it in the topology test. * fix(review): prove bot provenance with GitHub signatures, not git headers nettee correctly escalated: .committer.login is still a spoofable git-header identity. A collaborator who can push to chore/plugin-previews can mint a local commit with the bot's noreply email, GitHub then reports .committer.login == github-actions[bot], and pristine=true passes — landing unreviewed human changes on main. Switch to non-spoofable provenance: - bake-plugin-previews.yml now creates the manifest commit through the Git Data API (blob -> tree -> commit -> ref) with the App token, instead of a local `git commit` + push. A commit created through the API is signed by GitHub (verification.verified=true) and committed by the App bot (committer.type=Bot). Building the tree on the live main HEAD also rebases the rolling branch onto the latest main each run. - the reactor's pristine gate now requires EVERY commit to be both verification.verified==true AND committer.type==Bot. A human's locally pushed commit is unsigned (verified=false) even with the bot email; a human signing with their own key is committer.type=User. Neither can be both, so the pair is forge-proof. - topology test updated to lock the API-commit producer path (and forbid a regression to a local push) and the verified+Bot pristine check. * fix(review): parent the manifest commit on the rendered revision, not live main The previous change based the Git Data API commit on live main HEAD, but $NEW is rendered from the job's checkout at start. The bake can run ~90min and main can advance (unrelated merges) meanwhile, so basing the commit on live main would publish a manifest from an older checkout on top of newer commits — the rolling PR would look current while carrying stale preview data and could overwrite newer plugin-preview changes. Parent on `git rev-parse HEAD` (the exact revision that produced $NEW) instead, so GitHub correctly shows the PR as behind once main moves and the next bake re-renders on the newer HEAD. Topology test locks the rendered-revision base and forbids regressing to a live-main base. * fix(review): require the rolling PR's rendered base to equal main before merge main's merge queue does not require an up-to-date branch, so a bake rendered against an older main could be re-tested on the latest main and squash-merged with a stale manifest.json — overwriting a newer manifest that landed meanwhile. head_oid only proves the PR head is the SHA CI validated, not that it is current. Add a base-freshness gate: the rolling commit's first parent is the revision the manifest was rendered against, so require it to equal current main HEAD before approving/enqueuing. A behind PR is skipped and refreshed by the next bake (the nightly re-renders on the newer tip even if no plugin changed). Locked in the topology test. * fix(review): page Feishu on timed_out bakes too, not just failure workflow_run has terminal RED states beyond 'failure'. ci.yml defines many timeout-minutes jobs, so a timed-out run on the rolling manifest PR would block auto-merge with no Feishu page. Broaden the alert predicate to failure OR timed_out, and include the conclusion in the message. Deliberately exclude 'cancelled': a force-push of the rolling branch routinely cancels the prior in-flight run, so alerting on it would page on every normal refresh. Test locks the failure+timed_out set and the cancelled exclusion. * fix(review): gate auto-merge on a manifest-only diff, not commit signatures nettee is right that the Git Data API does not sign commits — I verified it empirically: a blob/tree/commit created via POST /git/commits comes back verification.verified=false / reason=unsigned (and the Contents API is unsigned too for non-web callers). So the previous verified==true && committer.type==Bot pristine gate would never pass and the rolling PR could never auto-merge. There is no per-commit field that is both bot-set and unforgeable without a signature, so stop trying to prove "the bot authored every commit" and instead bound what auto-merge can land: require the PR's changed files to be EXACTLY data/plugin-previews/manifest.json. A human with push access can still push to the rolling branch, but any commit that touches a code file (or anything but the manifest) makes the file set != the single manifest path, so pristine=false and the PR falls back to human review. The worst a forged manifest-only change can do is point a gallery preview at a bad video URL — generated data the next bake overwrites — never unreviewed code on main. With signing no longer relied upon, revert the producer from the Git Data API dance back to a plain App-token git push (still parented on the checked-out HEAD per the base-freshness fix, still pushed with the App token so CI runs). Tests updated to lock the manifest-only file gate and the simplified producer. * test(ci): update secretless-bake assertion for the App-token producer Merge main (#4691 landed the read-only pre-merge bake + its topology test). That test asserted the post-merge workflow contains PREVIEW_BAKE_TOKEN as the write-capable credential; this PR replaced that never-configured fallback with the release-bot App, so assert secrets.RELEASE_BOT_APP_ID (and that PREVIEW_BAKE_TOKEN is gone) instead. |
||
|
|
29b138f7a3 |
feat(brands): turn any brand into a reusable design system (#4691)
* Implement brand management routes and CLI support - Added `brand-routes.ts` to handle HTTP endpoints for brand operations: listing, extracting, retrieving details, deleting, and serving logos. - Introduced `brands-cli-help.ts` for CLI commands related to brand management, including usage instructions for listing, creating, retrieving, and deleting brands. - Updated `cli.ts` to integrate brand commands into the existing CLI structure, allowing headless management of brands via the command line. - Created supporting files for brand metadata handling, including `design-md.ts` for rendering brand information in markdown format and `index.ts` for the brand engine API. - Implemented `prefetch.ts` to fetch and process brand material from specified URLs, ensuring a streamlined extraction process. - Enhanced server setup in `server.ts` to register brand routes and manage brand-related data effectively. This commit establishes a comprehensive framework for managing brands within the application, facilitating both HTTP and CLI interactions. * Enhance memory management and onboarding experience - Introduced canonical profile labels to ensure consistent handling of user input in profile forms, preventing duplicate entries. - Updated the `parseProfileBody` and `captureProfileFromForm` functions to utilize the new canonical label matching. - Added a memory callout section in the onboarding view to highlight the benefits of memory usage, including personalized responses and reduced setup questions. - Implemented new UI elements in the onboarding view to improve user engagement with memory features. - Expanded i18n support for new onboarding messages related to memory benefits across multiple languages. * Refactor onboarding flow and enhance design system integration - Updated the onboarding process to include a new brand extraction step, replacing the previous newsletter step. - Adjusted the tracking logic to reflect the new onboarding steps, ensuring accurate analytics for user progress. - Improved the UI for the onboarding view, including new input fields for email collection during the brand extraction phase. - Refined the EntryShell component to remove outdated comments and clarify the onboarding renderer's purpose. - Enhanced CSS styles for the onboarding steps to improve layout and user experience. - Updated internationalization strings across multiple languages to reflect changes in the onboarding flow and brand extraction messaging. * Add brand management features and enhance font handling - Introduced new modules for managing brand assets, including `chrome.ts` for headless Chrome operations and `fonts.ts` for self-hosting web fonts. - Implemented `prefetch.ts` to streamline the brand material extraction process, allowing for efficient harvesting of colors, fonts, and logos. - Enhanced the brand system with new schema definitions in `schema.ts` to support brand color and font management. - Developed the `engine` module to integrate brand building and rendering processes, including token derivation and artifact generation. - Improved the overall structure and organization of brand-related files for better maintainability and scalability. * Enhance brand extraction and project management features - Updated `brand-routes.ts` to include new dependencies for project management, allowing for the registration of brand-related projects. - Modified the `extractBrand` function to support project ID and system files, improving the brand extraction process. - Enhanced the CLI commands in `cli.ts` to handle project IDs during brand creation, enabling better tracking of brand projects. - Updated the server setup in `server.ts` to register new project-related routes. - Improved the UI components to display project information associated with brands, including buttons for opening projects in the `BrandDetailView` and `BrandsTab`. - Added new metadata fields in the contracts to support project tracking and management for brands. This commit establishes a more robust framework for managing brand projects, enhancing both backend and frontend functionalities. * Enhance onboarding profile management and memory persistence - Added new canonical profile labels for 'Organization size', 'Use cases', and 'Discovery source' to improve user input consistency. - Introduced `OnboardingProfileState` type to manage onboarding profile data more effectively. - Implemented functions to build and persist the onboarding profile body to memory, ensuring user selections are saved accurately. - Updated the `OnboardingView` component to handle profile persistence during navigation and submission steps. - Enhanced tests to verify that user selections are correctly persisted to the memory profile. This commit improves the onboarding experience by ensuring that user inputs are consistently captured and stored, enhancing overall user engagement with the application. * Reflow brand extraction into an agent-driven, live flow Replace the deterministic SSE prefetch/preview/system pipeline with an agent-driven extraction: POST /api/brands now reserves the brand and stands up a backing project with the target site open in an in-app browser tab plus a seeded prompt, so the agent measures, synthesizes brand.json incrementally, and the user can clear anti-bot walls by hand. New /preview and /finalize routes let the agent render the kit page live and register the resulting user:<id> design system, so extracted brand facts persist as a structured, reusable brand kit instead of a one-shot deterministic guess. Adds the brand-extract skill (SKILL.md + brand-kit.html template), kit-render engine, brand-extraction-engine tests, brand project covers in the Designs tab, onboarding extract handoff, and the matching od brand extract/preview/ finalize CLI subcommands and contract updates. Co-authored-by: Cursor <cursoragent@cursor.com> * Sediment finalized brands into structured memory Reflow a finalized brand into the memory store (brandToMemoryEntries + reflowBrandToMemory) so future chats can ground vague requests in the brand's palette, type, voice and rules. finalizeBrand now wires through the runtime dataDir and best-effort persists the brand, MemoryChangeEvent gains a 'brand' source, and the brand kit render hardens its inline JSON escaping. Adds brand.previewEmpty / brand.viewDetails across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Implement logo fallback and imagery support in brand extraction - Introduced a deterministic logo fallback mechanism to ensure that brand extraction processes can retrieve and save site logos, even when the agent fails to do so. - Enhanced the `startBrandExtraction` and `finalizeBrand` functions to utilize the new logo fallback, allowing for better handling of logo assets. - Added support for imagery samples in brand validation, enabling the inclusion of representative images in the brand kit. - Updated the brand kit rendering to include self-hosted fonts and imagery, improving the overall presentation of brand assets. This commit strengthens the brand extraction workflow by ensuring that logos and imagery are reliably captured and displayed, enhancing the user experience in brand management. * Enhance memory management with rule proposal and verification features - Introduced new functionality for distilling annotations into rule proposals, allowing users to suggest rules based on in-canvas annotations through the `od memory rule suggest` command. - Implemented a verification system that programmatically enforces compliance with active rules during artifact generation, ensuring that all active rules are covered in the self-verify scorecard. - Added endpoints for managing verification outcomes, including listing, removing, and clearing verification records, enhancing the transparency of the verification process. - Updated the memory management system to support the retrieval of active rule entries, ensuring that only linked rules are considered during verification. - Enhanced tests for both rule proposal generation and verification processes to ensure reliability and correctness. This commit strengthens the memory management capabilities by integrating rule proposals and verification, improving the overall user experience in managing design rules and ensuring compliance. * Distill review annotations into memory and enforce self-verify scorecard Add distillAnnotationsToMemory to mine inline preview comments/highlights/ marks into durable feedback + rule memory via a dedicated distiller prompt, threaded through the existing extract pipeline with an 'annotation' change source. Tighten the self-verify prompt (daemon + contracts) to state the daemon programmatically checks the scorecard, so a missing or uncovered scorecard on an artifact turn is an enforcement failure. Cover the rule suggest and verification-history routes with tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply brand design system through web config on "Use in new chat" Thread onApplyDesignSystem from the entry shell into BrandsTab so the brand's registered design system is applied via the web config channel instead of a bare daemon PATCH that left the Home composer stale. Add a transient home-intent latch + event so the Brands tab can request the Prototype chip on the already-mounted HomeView, which consumes it once the plugin catalog loads. Co-authored-by: Cursor <cursoragent@cursor.com> * Wire annotation distillation into background memory extraction Add a background distill pass that mines inline review annotations (comments / highlights / drawn marks) from a turn into durable memory alongside the general LLM extraction, surface an `annotation` memory toast source in the web UI, and cover the flow with a unit test. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix brand design system not applying to composer on "Use in new chat" Selecting a brand's "Use in new chat" applies the brand's design system as the default and fires the Prototype chip intent in the same synchronous click handler. HomeView consumed that intent inside the event listener, so `pickChip` ran before React committed the config change and seeded the composer's design-system field from the stale (empty) default — the composer showed "No design system" instead of the brand until a reload. Split the intent handling: the listener now only bumps a tick, and a separate effect consumes the chip after the re-render lands, so the seeded design system reflects the freshly-applied brand. Add the previously-untracked home-intent latch test coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): rework Brands into Brand Kit and add Home create entry Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): harvest real cover/hero images for the Images module The brand kit's Images gallery only populated when the extraction agent remembered to save imagery — so a forgetful or bot-blocked agent (and the pre-imagery "Open Design" brand) left it empty. Add a deterministic, server-side imagery fallback (imagery-fallback.ts), mirroring the logo fallback: it parses og:image/twitter:image, large <img> (highest-res srcset/<picture>), <link rel=preload as=image>, and CSS background-image hero blocks, fetches candidates with browser-shaped headers, decodes PNG/GIF/JPEG/WebP dimensions to keep only big representative images (dropping icons/sprites/logos/tracking pixels), dedupes by content hash, and saves up to 8 of the largest into imagery/ with labeled samples. finalizeBrand runs it as a timeout-bounded, failure-tolerant safety net (injectable so tests stay offline) when the agent captured too few samples, first adopting any on-disk images. The extraction prompt and brand-extract SKILL now explicitly direct the agent to harvest the site's large/cover/hero images, filtered by rendered size. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(qa): implement deck layout validation and safety checks Add a new QA module for validating the layout of generated brand decks to ensure robustness against clipping and truncation issues. The `analyseDeckLayout` function checks for critical layout invariants, including the presence of `.slide` sections, correct container types, and necessary runtime layers. Introduce `assertDeckLayoutSafe` to enforce these checks during brand system rebuilds, preventing the deployment of decks that fail validation. Additionally, create comprehensive tests to verify the functionality of the new layout validation features. * fix(brands): apply deck shrink-to-fit synchronously so slides never clip The no-clip runtime scheduled its fit pass through requestAnimationFrame, whose callbacks are throttled while the deck is offscreen or occluded. A slide could therefore stay unscaled — and clip its content — until first paint. Fit synchronously on resize/load/fonts-ready with a trailing setTimeout settle pass for late reflow, removing the rAF dependency. Verified at the previously-broken 1024x620 viewport: container-type:size, zero truncations, runtime auto-applies scale (Problem 0.71, ASK 0.87, Product 0.97, Competition 0.97) and frame clip count drops to 0. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): let New Brand modal embed scrollable brand reference picker Add a fillHeight mode to BrandReferencePicker so the heading, quick-pick row and controls stay pinned while only the gallery scrolls inside a bounded-height parent. Wire it into NewBrandModal with a stable, spacious dialog and refresh the related newBrand/brandPicker copy across all 18 locales. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): enhance brand extraction with deterministic seed harvesting Introduce a new `seed-fallback` module to provide a server-side deterministic palette and typography seed during brand extraction. This ensures that the brand kit's initial display includes a harvested logo, an approximate color palette, and font families, improving the user experience by reducing the all-skeleton appearance during the first paint. Update the `startBrandExtraction` function to utilize this new module, allowing for a more seamless and visually appealing brand extraction process. Additionally, enhance the `BrandReferencePicker` component to reflect loading states and errors during brand extraction, ensuring users receive immediate feedback on their actions. Update related tests to verify the idempotency of the `finalizeBrand` function, ensuring that re-finalizing a brand correctly reuses the existing design system without duplication. * feat(brand-extract): enhance BrandReferencePicker and localization updates Updated the BrandReferencePicker component to reflect loading states and errors during brand extraction, improving user feedback. Added a new localization key for the brand extraction process and updated existing translations in English, Simplified Chinese, and Traditional Chinese to enhance clarity and user experience. Additionally, introduced new styles for better interaction with brand assets in the brand kit template. * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * i18n(web): add brandPicker.opening across remaining locales + picker test Completes the brand-reference picker i18n key that was committed only for en/zh-CN/zh-TW, so every locale satisfies the typed Dict, and lands the BrandReferencePicker extraction-feedback test left untracked by the concurrent worker. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(EntryShell): enhance AMR cloud card visibility post-detection Updated the EntryShell component to ensure the AMR cloud card remains visible after detection settles, even when the AMR runtime is unavailable. This change prevents the card from disappearing and allows it to degrade gracefully to fallback content and sign-in flow. Additionally, added tests to verify the new behavior, ensuring a better user experience during onboarding. * feat(library): OD Library asset registry + OD Clipper extension Add a global, cross-project asset registry (OD Library) and a Chrome MV3 capture extension (OD Clipper), wiring the full HTTP + CLI + Web UI three-track loop per specs/od-clipper.md. - contracts: LibraryAsset/Source/Kind, ingest, search, pairing, task DTOs - daemon: 6 additive SQLite tables, content-addressed owned storage, the idempotent registerLibraryAsset hook (hash dedup + append-source), programmatic enrichment (mime/size/image dims/domain/tags), pairing tokens with a persisted extension-origin allowlist, /api/library/* routes, and /api/tools/library/{search,apply} for in-task agent reuse - cli: `od library list|get|rm|search|import|pair` - web: Library tab (grid, source badges, filters, search, live SSE updates, extension pairing affordance) - clipper/: standalone MV3 extension (background SW, content toolbar, popup) - skills/library-curator: utility skill for agent-driven asset reuse Origin middleware now honors paired chrome-extension:// origins (seeded from SQLite on boot) and exempts the pairing-confirm handshake. Enrichment AI stages (caption/OCR/embedding) are recorded as skipped pending a configured model. * feat(brands): programmatic-first design system extraction + rename Make brand extraction two-phase so a usable design system is ready the moment the user enters a URL — the instant "aha" — instead of waiting on the AI agent: 1. PROGRAMMATIC-FIRST (synchronous): startBrandExtraction now harvests the site deterministically (logo, palette, typography, one-line description, cover imagery, source URL) via prefetchBrand, synthesizes a valid design system with brandFromMaterial (no LLM), and finalizes + registers it before returning. finalizeBrand is refactored into a reusable finalizeBrandCore shared by both the programmatic path and the agent path. 2. ASYNC AI ENRICHMENT: the seeded agent prompt is reframed to enrich the already-usable design system and re-finalize in place (same user:<id>), updating every artifact/template. Bounded + best-effort: a blocked/unreachable origin skips phase 1 and stays `extracting` for the agent to drive. Gated on userDesignSystemsRoot so the legacy agent-only path stays intact for tests. Also rename the user-facing "Brand Kit" surface to "Design System" across en + zh-CN strings, project names, and the enrichment prompt. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(library): enhance asset import and management features - Updated the `import` command to allow multiple local files and remote URLs, with restrictions on supported formats. - Added new commands: `apply` for copying assets into project design files, `edit-as-page` for converting HTML assets into editable projects, and `figma` for exporting Figma captures. - Introduced sidecar functionality for storing derived data alongside owned assets, including Figma capture IR and element HTML. - Enhanced server configuration to support larger ingest payloads for asset captures. - Improved error handling and user feedback during asset import and application processes. * feat(asset-management): enhance asset dropzone and introduce chat-to-design feature - Updated the DesignSystemAssetDropzone component to improve file preview handling with new functions for creating and revoking object URLs. - Adjusted CSS for better layout and spacing in the asset dropzone. - Added a new "Chat to design" button in the LibrarySection component, allowing users to send selected assets to the Home chat composer for project creation. - Updated localization strings across multiple languages to reflect changes in asset import terminology. - Enhanced the HomeView component to handle asset staging from the chat composer. * feat(library): enhance asset application with element markup support - Updated the `applyLibraryAsset` function to include an `includeElement` option, allowing the capture of element markup alongside assets. - Modified related components (e.g., `ChatComposer`, `LibrarySection`, `FileWorkspace`) to handle the new element markup feature, ensuring both asset paths and optional element paths are returned and processed. - Introduced a new function, `fetchLibraryAssetElementHtml`, to retrieve the captured HTML for element-pick assets. - Enhanced the UI to display element markup inline within the chat composer, improving user interaction with captured elements. - Updated API contracts to reflect changes in asset application responses, including optional element markup paths. * feat(library): enhance asset filtering and preview handling - Updated the LibraryPicker and LibrarySection components to implement a badge-aware kind filter, allowing for more precise asset filtering based on badge kind. - Introduced a new `matchesKindFilter` function to streamline the filtering logic across components. - Enhanced the DesignSystemAssetDropzone to ensure proper handling of image previews, addressing issues with broken thumbnails under React StrictMode. - Added CSS styles for kind badges to improve asset representation in the UI. - Implemented tests for the DesignSystemAssetDropzone to ensure correct preview lifecycle management. * feat(library): hydrate single asset on SSE ingest Add fetchLibraryAsset(id) so the Library grid can merge just the one asset an `ingest` SSE event references instead of refetching the whole list on every capture. Returns null on miss/error. * feat(clipper): richer in-page image picker Collect CSS background-image url()s in addition to <img> (so hero/section art painted as backgrounds is no longer silently missed), defer thumbnail decode to visible cells via IntersectionObserver, draw downscaled canvas thumbnails instead of second full-res decodes, and add locate-on-page highlighting so a picked image can be traced back to its DOM source. * feat(library): implement lazy loading for thumbnails and enhance asset filtering - Introduced a `LibraryThumb` component to lazily load heavy content (images, videos, iframes) only when they are near the viewport, improving performance. - Added a debounced search feature to optimize asset filtering, reducing unnecessary network requests during rapid input. - Enhanced the asset filtering logic to track active filters using a ref, ensuring efficient updates during live events. - Updated the `snapshotCardRects` and `cardIdsInBand` functions to support improved hit-testing for drag-and-drop interactions. * feat(library): lazy picker thumbnails + debounced search Extend the Library grid's lazy-thumbnail + 250ms debounced-search pattern to the composer LibraryPicker so opening it no longer fires one full-bytes request per asset, and tidy the clipper content-script image collection. * feat(clipper): compress and budget capture inlining Re-encode large raster images to downscaled WebP and inline smallest-first within a fixed budget, dropping only the secondary Figma IR past a safe body size, so an image-heavy page (e.g. a news front page) always saves as an editable HTML capture instead of 413-failing the ingest. * test(library): LibraryPicker debounce + lazy-thumbnail coverage Cover the composer picker's 250ms debounced search and its lazy <img> mount (deferred until the card is in view), matching the grid's perf test. * feat(design-system): enhance asset handling and UI for design systems - Updated the CLI to support additional asset kinds, including 'design-system'. - Enhanced the DesignSystemProvenance type to include source URLs, improving provenance tracking. - Modified the design system generation jobs to correctly summarize source links and GitHub repositories. - Updated UI components to reflect changes in asset handling, including new source link management in the DesignSystemFlow. - Improved tests to cover new functionality for adding source links and ensuring proper handling of design system assets. * refactor(library): rename 'design-system' to 'brand kit' and enhance thumbnail loading - Updated labels and filters in Library components to replace 'design-system' with 'brand kit'. - Introduced a shimmer skeleton for lazy-loaded thumbnails in the LibraryPicker to improve user experience during asset loading. - Enhanced the PickerCard component for better performance by memoizing individual asset cards. - Updated tests to ensure proper handling of brand kit assets and their visibility in the LibraryPicker. * feat(clipper): implement internationalization for toolbar and popup - Added i18n support to the clipper, enabling localization of UI elements and tooltips. - Introduced a new i18n.js file to manage translations for various languages. - Updated content.js and popup.js to utilize the i18n functions for dynamic text rendering. - Enhanced accessibility by ensuring aria-labels and tooltips are also localized. - Improved user experience by providing localized messages for actions and statuses. * feat(clipper): enhance brand kit extraction and localization support - Updated the brand kit extraction process to include improved handling of assets and localization for various UI elements. - Added internationalization support for the brand kit feature, allowing for dynamic text rendering based on user locale. - Enhanced the user experience by ensuring that all relevant messages and tooltips are localized. - Updated tests to cover new localization features and ensure proper functionality of the brand kit extraction process. * feat(clipper): enhance brand color derivation and update localization - Introduced new functions for color manipulation, including linear interpolation and clamping, to improve brand color derivation. - Updated the deriveBrandColors function to better map observed palettes to semantic roles, ensuring consistent brand representation. - Revised localization strings in i18n.js to reflect changes from 'brand kit' to 'design system', enhancing clarity and user experience. - Improved overall code organization and readability by refactoring existing functions and adding new utility methods. * refactor(clipper): update terminology from 'brand kit' to 'design system' - Replaced all instances of 'brand kit' with 'design system' across various components and localization files for consistency. - Updated UI elements, tooltips, and documentation to reflect the new terminology. - Enhanced user experience by ensuring clarity in the design system extraction process and related functionalities. - Adjusted localization strings in multiple languages to align with the updated terminology. * feat(clipper): enhance image fill handling and normalization - Introduced functions to normalize image fills by converting non-PNG/JPEG formats (SVG, WebP, GIF, AVIF) to PNG before import, ensuring all images are properly rendered in Figma. - Updated the UI to report the number of images converted and dropped during the import process, improving user feedback. - Enhanced the overall image processing workflow to prevent silent failures when unsupported formats are encountered. - Revised documentation to reflect the new image handling capabilities and supported formats. * feat(clipper): enhance UI kit and busy state feedback - Updated the UI kit to include new components such as inputs, selection, and overlays, improving the overall design system representation. - Enhanced the busy state feedback during capture processes with localized messages and a step-by-step progress indicator, providing users with clearer status updates. - Revised localization strings to support new UI elements and improve user experience across multiple languages. - Improved documentation to reflect changes in the UI kit and busy state handling. * fix(brands): restore design-systems nav entry + reconcile BrandsTab on re-activation Address review feedback on PR #4260: 1. EntryNavRail dropped the only control that reached view==='design-systems' when Brands replaced it in the rail, leaving the still-rendered/routed design-system manager deep-link only (the entry-nav-design-systems e2e specs assert this). Restore a reachable rail entry (blocks icon, existing navDesignSystems key) alongside Brands. 2. BrandsTab only fetched once on mount, but EntryShell keeps sub-views mounted and toggles visibility, so a brand finishing extraction in its backing project never reconciled until a full reload. Refresh whenever the Brands view becomes active again, and poll while any brand is extracting (torn down once settled / when hidden). Red spec: tests/components/BrandsTab.refresh.test.tsx (fails pre-fix: fetchBrands called once, not twice). * Update clipper/brand-capture.js * fix(clipper): improve busy state handling and UI feedback - Adjusted the spinner CSS to use flex properties for better layout control. - Enhanced the reclampIfMoved function to preserve user position during busy state transitions. - Added loading toast notifications for popup-launched captures to ensure progress visibility even when the on-page bar is hidden. * feat(daemon): add kiwi-schema dependency and enhance Figma API integration - Added kiwi-schema package to the daemon for improved schema handling. - Updated FigmaApiNode interface and related functions to support shared functionality with the offline decoder, ensuring consistency in node processing. - Refactored capture functions in clipper to maintain UI visibility during DOM/IR snapshots, enhancing user experience during capture operations. * fix(web): surface missing backing projects * fix(web): re-enable brand actions after use * fix(daemon): serve brand logos from data roots * fix(brands): reconcile failed extractions * feat(daemon): implement offline Figma import and decoding functionality - Added support for importing `.fig` files directly into the daemon, enabling offline processing without requiring a Figma account. - Introduced a new `fig-decode.ts` module for decoding `.fig` files, handling both ZIP-wrapped and raw formats. - Created `figma-import.ts` to orchestrate the import process, generating a canonical snapshot and associated metadata. - Enhanced the server to handle Figma file uploads and integrate with the new decoding logic. - Updated package dependencies to include `kiwi-schema`, `html2canvas`, and `jspdf` for improved functionality. - Added tests for the new Figma import features to ensure reliability and correctness. * feat(clipper): reload-proof capture progress badge on the extension icon The on-page progress strip dies if a page reloads itself mid-capture (aggressive paywall sites like economist.com do this), leaving no loading signal. Add a per-tab '•••' badge on the extension icon for the lifetime of any capture message — it lives on the action icon, so a page navigation can't take it down. Verified end-to-end via a real loaded extension. * feat(daemon): add export functionality for Figma and enhance PDF export process - Introduced `runFigma` command for importing Figma designs, supporting both local `.fig` files and Figma URLs. - Added detailed usage instructions for the `od figma import` command. - Implemented `runExport` command for programmatic export of HTML/deck artifacts to PDF, PPTX, or image formats. - Enhanced error handling and user feedback during export processes. - Removed obsolete `build-pptx-export-prompt` module and related tests to streamline the codebase. * feat(daemon): enhance library synchronization and export capabilities - Implemented `reconcileLibrary` to mirror design systems and agent-produced project deliverables into the Library as referenced assets. - Added support for programmatic export of artifacts via the `od export` command, including detailed usage instructions. - Introduced new functions for handling Figma imports and exports, improving integration with design workflows. - Enhanced error handling and user feedback during synchronization and export processes. - Added tests for new features to ensure reliability and correctness. * feat(web): PPTX export for any shareable artifact + Library toolbar tooltips * chore(nix): refresh pnpm deps hash * refactor(web): enhance onboarding view and file export progress indicators - Updated the onboarding view layout for improved accessibility and visual hierarchy, including adjustments to spacing, typography, and button styles. - Introduced a loading toast for file export progress, displaying elapsed time and estimated time remaining for slide exports. - Added new translation keys for export progress messages in multiple languages. - Refactored the export progress handling to provide real-time updates during the export process, improving user feedback and experience. * refactor(web): streamline export capture bridge and update connector styles - Removed unused loading logic for html2canvas in the export capture bridge, simplifying the code. - Updated CSS for the onboarding view connector to improve visual clarity and ensure it does not overlap with node numbers. * refactor(web): remove html2canvas dependency and enhance Figma URL handling - Removed the html2canvas package from the project, including its references in the lock file and related components. - Added functionality to manage Figma URLs within the Design System flow, allowing users to add, remove, and validate Figma file links. - Improved drag-and-drop handling to prevent unintended file navigation when dropping files outside designated areas. - Updated UI components to accommodate new Figma URL features, enhancing user experience and accessibility. * refactor(web): unify brand and design system flows - Merged the brand extraction process into the design system creation workflow, allowing users to start from a brand directly within the design system wizard. - Updated routing to redirect legacy brand links to the unified design systems tab. - Enhanced the onboarding experience by removing the separate Brand Kit tab and integrating brand selection into the design system creation process. - Improved UI components to reflect these changes, ensuring a seamless user experience across the application. * feat(web): introduce brand enrichment banner and picker modal - Added a new BrandEnrichmentBanner component to allow users to refine programmatically-extracted design systems with AI by selecting design-system skills. - Implemented a BrandPickerModal for selecting brands from a searchable gallery, enhancing the design system creation flow. - Updated ChatPane to conditionally display the enrichment banner for eligible brand projects, improving user engagement. - Enhanced the design system flow to support the new brand enrichment features, ensuring a seamless experience for users. * feat(web): enhance BrandPickerModal and DesignSystemAssetDropzone - Updated the BrandPickerModal to allow scrolling of the entire picker area, improving user experience by creating a unified scrolling surface. - Added new props to the BrandReferencePicker for action labels and scroll root reference, enhancing flexibility in brand selection. - Introduced a new DesignKitView component for rendering design kits consistently across different surfaces. - Enhanced the DesignSystemAssetDropzone to support a wider variety of file types with appropriate previews, improving asset management during design system creation. - Updated styles for better visual clarity and responsiveness across components. * feat(web): update Design Systems tab actions and enhance localization - Changed the button label in the DesignSystemsTab from "Edit" to "Open" for better clarity in user actions. - Added a new translation key for 'dsManager.openSystem' across multiple languages to support the updated button label. - Enhanced the FileWorkspace component to ensure the Design Files tab aligns correctly with the Design System tab, improving UI consistency. - Implemented a new design system editing feature that allows users to fetch and save design system content from DESIGN.md, enhancing the design workflow. * fix(merge): repair post-merge regressions after origin/main integration Follow-up fixes on top of the origin/main merge ( |
||
|
|
ed1df234ad |
fix(ci): needs-validation gate must resolve the queued PR from the merge_group ref (#4742)
* fix(ci): resolve merge_group PR from the queue ref, not commits/<sha>/pulls The needs-validation merge gate resolved PRs in the merge_group via commits/<sha>/pulls. The merge queue squashes, so the queued commits are NEW synthetic SHAs that GitHub does not associate back to the PR — the lookup returned no PRs, the gate logged "clear to merge", and a needs-validation PR (#4736) merged with no QA. (This was latent until the pull_request branch was removed and merge_group became the sole enforcement.) Resolve the PR number(s) from the queue ref name instead (refs/heads/gh-readonly-queue/<base>/pr-<N>-<sha>), which is stable regardless of squash. Fail closed if the ref yields no PR number. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): enumerate ALL PRs in a merge group (ref + commit subjects + commits/pulls) Addresses review: a batched merge group names only the tip entry in the queue ref, so parsing pr-<N> from the ref alone could miss earlier PRs in the batch (one could still carry needs-validation). Take the union of three sources — the ref pr-<N>, each commit subject's trailing (#N) (the queue's per-entry squash commit carries its own PR number, recovering non-tip batched PRs), and commits/<sha>/pulls (non-squash queues) — and fail closed if none resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): fail closed if the compare API (PR enumeration source) fails Addresses review: the compare call feeding msg_prs/assoc_prs used 2>/dev/null || true, so an API failure silently emptied those sources while ref_prs still held the tip — the -z check passed and only the tip PR was validated, letting a batched needs-validation PR merge. Capture compare once in a REQUIRED assignment (no || true) so failure trips set -e; derive subjects + shas from that parsed JSON; make commits/<sha>/pulls a required assignment too. Only grep no-match within a successful response is tolerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5360b5a4e9 |
fix(ci): enforce the needs-validation merge gate only in merge_group, keep the PR check green (#4714)
The needs-validation hard gate failed the validate job (Validate workspace) on pull_request, turning the PR's own required check red. A deliberately-red required check is indistinguishable from a real CI failure downstream: it flips mergeStateStatus to BLOCKED and reads as ci_status=failure to the review bot, which deadlocks the very QA/validation meant to clear the label — QA is never requested, the label is never removed, the PR is blocked forever. Move the gate to the merge_group (merge-queue) context only. main requires the merge queue, so every merge still passes through merge_group and a needs-validation entry is ejected there and can never land — but the merge_group run executes on the queue's transient ref, so its failure never appears in the PR head's status rollup. The PR's Validate workspace stays green, QA runs, removes the label, and only then does the queue let it through. No bot/charter change needed. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f4fe5ad757 |
feat(ci): implement the plugin-preview bake pipeline (spec 4548) (#4700)
* feat(ci): bake pipeline slice 1 — previews-diff guard + single rolling PR Per specs/change/20260618-plugin-preview-bake-pipeline/spec.md rollout step 1 (the smallest change that stops the bleeding on the stacked bake-PR backlog). - scripts/plugin-previews-diff.mjs: decide whether a manifest's `previews` subtree changed, ignoring the per-run `generatedAt` timestamp. node:test coverage in plugin-previews-diff.test.mjs (the #4261 timestamp-only noise case, entry change, add/remove, key-order stability). - bake-plugin-previews.yml: open a review PR only when `previews` actually changed (was: whole-file `git diff` that fired every run because of generatedAt), and reuse ONE rolling branch (chore/plugin-previews) / force-update the open PR in place instead of stacking chore/plugin-previews-<run_id> per run. - guard.ts: allowlist the two new CI-only .mjs scripts. Deferred to later slices (same spec): pre-merge same-repo coupling job, release-cut full bake, tag-union GC, directory-layered artifact keys. * feat(ci): bake pipeline slices 2-4 — pre-merge coupling, release full bake, GC, directory keys Completes the spec (specs/change/20260618-plugin-preview-bake-pipeline) on top of slice 1, all in one PR (GC ships dry-run so staged ENABLEMENT still holds): - .github/actions/bake-previews: composite action for the shared render + R2 upload core, so the three bake workflows stop duplicating it. - bake-plugin-previews.yml (post-merge): refactored onto the composite; role is now uploader + fork path + nightly backstop (rolling PR unchanged). - bake-plugin-previews-pr.yml (slice 2): pre-merge bake for SAME-REPO PRs — renders, uploads, and commits the manifest INTO the author's branch so it rides with the code change. Loop guard checks the head COMMIT author (git log -1 %ae of head.sha, fetched via full checkout), not head.user.login, plus a no-op previews-diff guard. Forks fall through to post-merge. - bake-plugin-previews-release.yml (slice 3): release-cut full bake committing the authoritative manifest onto release/**, with paths-ignore + bot-author loop guards. - scripts/bake-plugin-previews.mjs (slice 4a): directory-layered, content- addressed keys <id>/<fingerprint>/preview.mp4 (+ poster.jpg), prefix-relative in the manifest. Daemon consumer already resolves base+key / path.join(dir,key) so no consumer change; reused flat entries are left untouched (additive). - scripts/plugin-previews-gc.mjs (slice 4b) + .github/workflows/ bake-plugin-previews-gc.yml: weekly R2 GC. Protected set = keys referenced by every tag + live release/** HEAD + main; deletes orphans older than a 90d grace window. DRY-RUN by default (needs --delete AND GC_ENABLE_DELETE=1). Pure protected-set/orphan logic covered by node:test. - guard.ts: allowlist the new CI-only .mjs files. * fix(ci): capture diff-guard result on its own line so a helper error fails the step Per review: `if [ "$(node plugin-previews-diff.mjs ...)" != changed ]` swallows the helper's exit 2 (bad args / unreadable manifest) inside command substitution, so an error reads as empty string → 'unchanged' branch → the manifest PR/commit is silently skipped despite a successful bake. Capture into diff_result on its own line (so `set -e` aborts on a helper error) and `case` on the value, treating unexpected output as a workflow failure. Applied to all three bake workflows. * fix(ci): satisfy actionlint — quote gc description colon + route PR/dispatch context through env - bake-plugin-previews-gc.yml: quote the `delete` input description (the '(default: ...)' colon broke YAML parsing) and pass dispatch inputs via env (GC_DELETE/GC_GRACE_DAYS) instead of interpolating into the run body. - bake-plugin-previews-pr.yml: route head.sha/head.ref through HEAD_SHA/HEAD_REF env vars to avoid the script-injection lint on untrusted PR context. * fix(ci): hard-gate the release bake job to release/** branches workflow_dispatch can fire from any ref; the commit step pushes the authoritative manifest to the triggering ref with contents:write, so a dispatch on main would write straight to main and bypass the release back-merge. Add a job-level `if: startsWith(github.ref, 'refs/heads/release/')` guard. * fix(ci): GC fails closed on partial protected-ref data Per review (non-blocking but real once deletion is armed): the GC workflow's protected-ref fetches ended with '|| true', so a transient fetch failure could leave the tag/release/main protected set incomplete and, with GC_ENABLE_DELETE=1, prune clips a live release/main still references. Drop the '|| true' (fail the job if the protected refs can't be fetched), and add a script-side guard that refuses to delete when the protected set is empty or origin/main's manifest is unreadable. |
||
|
|
254fd7ddf0 |
fix(ci): only auto-merge a backport that is a pristine bot cherry-pick (#4705)
* fix(ci): only auto-merge a backport that is a pristine bot cherry-pick The auto-approve/merge premise is that a backport is the untouched cherry-pick of code already reviewed on main — so it can skip a fresh review. That breaks the moment a human pushes a commit (resolving a conflict, fixing a failing test): those commits were never reviewed, yet the existing gates don't catch them — the PR author stays the bot, a draft gets un-drafted, and head==CI-sha only guards post-green races, not bot-vs-human authorship. Gate auto-approve + auto-merge on pristine == 'true': every commit on the branch was committed by github-actions[bot] (what korthout's cherry-pick produces; the human author is preserved but the committer is the bot). Any human commit -> committer != github-actions[bot] -> pristine=false -> skip, and the PR falls back to human review (REVIEW_REQUIRED; release-gate blocks the release until it lands). Fail closed: any lookup miss / empty list -> not pristine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): paginate the pristine-commit check so a >100-commit backport can't pass on its first page per_page=100 without --paginate only validated the first 100 commits: a human commit at position 101+ would be missed and the PR would auto-merge unreviewed code (fail-open). Use --paginate to fold in every commit and fail if any committer is not github-actions[bot]; -F/-x match the literal bot login (the brackets are not a regex class). Fail closed on an empty list / lookup miss. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): use portable grep -c count for the pristine check, not grep -qv `! ... | grep -Fqvx` mis-evaluated to pristine=true on a human-touched branch under BSD grep (grep -q's early-exit status is unreliable across BSD/GNU) — the opposite of safe. Count the non-bot committer lines with grep -Fvxc, whose count semantics are portable, and require zero. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): fail closed when paginated commit lookup partially fails gh api --paginate can emit several pages and then fail on a later one; `|| true` kept that partial output, so an all-bot first page could read pristine=true without ever inspecting the commits on the failed page. Capture gh's exit status with `if !` and blank the list on any failure, so pristine requires a complete, successful enumeration of every commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(ci): assert the pristine check's actual shape (paginate + grep committer count) The topology assertion still referenced the old in-jq comparison '(.committer.login // "") == "github-actions[bot]"', which no longer exists after the check moved to extracting '.[].committer.login // "none"' and counting non-bot committers with grep -Fvxc across --paginate'd pages. Assert the shape that exists now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cbf7e881fa |
fix(ci): match the App author login gh actually returns (app/open-design-release-bot) (#4703)
The auto-approve/merge gates checked author == 'open-design-release-bot[bot]', but `gh pr view --json author --jq .author.login` returns 'app/open-design-release-bot' for an App-authored PR. The gate never matched, so every clean backport's approve + merge steps were skipped and the PR sat unmerged (e.g. #4697, #4698 — CI green, no approval). Match the actual value in all three gates + the topology test. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
fa852014df |
feat(ci): auto-approve the release bot's own clean backport to satisfy release/v* review rule (#4682)
release/v* requires one approving review, so backport-automerge could not merge the bot's own backport PRs (an app can't approve its own PR). Approve as github-actions[bot] — a separate identity — under the SAME gates as the merge (author == release bot, same-repo, non-draft, head == the CI SHA, CI green). The diff was already reviewed + approved on main; the backport is a mechanical, conflict-free cherry-pick, so this auto-approval is scoped strictly to the release bot's verified backports, then merged with the App token. Topology test updated. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c5ec410fd3 |
fix(ci): backport on a label added after merge (#4678)
* fix(ci): backport on a label added after merge, not only at merge time The backport workflow only triggered on pull_request_target closed, so a 'backport release/vX.Y.Z' label applied AFTER a PR was already merged never created a backport PR (a common slip). Add the labeled event; the existing 'merged == true' guard still skips still-open PRs, so labelling an open PR does nothing until it merges, while labelling a merged PR now backports it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): gate the labeled-trigger backport on the labeler's write+ permission Per review (PerishCode): the new labeled path let a triage-only user trigger the App-token backport on a merged PR, racing backport-label-guard.yml (which strips unauthorized labels in a separate workflow). Check the labeler's permission inside this workflow, before checkout/ backport-action: on a labeled event, require github.event.sender to have write+ (fail closed); the closed/merge path stays gated by the merger's write access. Also only run for backport-* labels, not every label on a merged PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
92f219d20b |
feat(ci): auto-merge clean backports on green CI (+ Feishu ping on failure) (#4671)
* feat(ci): auto-merge clean backports on green CI + Feishu ping on failure release/v* is PR-protected but has no merge queue and no required status check, so neither GitHub auto-merge nor enqueuePullRequest applies. backport-automerge.yml reacts to ci.yml completing for a backport-* branch: - success + clean (non-draft) PR -> squash-merge with the App token (the push to release/* re-triggers notify-release-feishu / the nightly) - failure -> Feishu ping for manual follow-up - conflict (draft) -> left for a human; release-gate blocks the release until it lands Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): harden backport auto-merge — verify head identity + bind to CI SHA Per review (PerishCode): - Only auto-merge PRs authored by the release bot from the same repo (author == open-design-release-bot[bot] && !isCrossRepository), so a stranger can't get a branch named backport-* auto-merged with the App token. - Bind the merge to the SHA CI actually passed on: gate on head_oid == workflow_run.head_sha and pass --match-head-commit, so a commit pushed after CI went green is never merged untested (release/v* has no required status checks to catch it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): restrict backport auto-merge to PR CI runs + gate the Feishu path too Per review (PerishCode): - Only run for workflow_run.event == 'pull_request' on a backport-* branch, so a push or manual dispatch can't reach the App-token / Feishu steps. - Apply the same author == bot && !isCrossRepository gates to the Feishu failure step, so a fork / non-bot PR named backport-* can't spam the release group. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(ci): add topology coverage for the backport-automerge trusted workflow Per review (PerishCode): packaged-smoke-workflow.test.ts registers backport-automerge.yml as a ci workflow_run consumer and asserts its trust gates — workflow_run.event == pull_request, the App-token mint, author == bot && !isCrossRepository && non-draft && release/v* base, the head_oid == workflow_run.head_sha + --match-head-commit binding, and the same identity gates on the Feishu failure path — so the security posture is covered by tests, not just YAML review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): resolve the backport PR by the CI run's SHA, not just the branch name Per review (PerishCode): bind the resolved PR to the completed run — select the open PR whose headRefOid == workflow_run.head_sha (and head branch + release/v* base), so a different/newer PR on the same branch is never acted on. Topology test asserts the SHA-bound resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): resolve backport PR from workflow_run.pull_requests, not branch name Per review (PerishCode): use the run's authoritative pull_requests association as the source of truth — select the entry whose base is release/* at exactly workflow_run.head_sha — instead of a gh pr list --head branch-name lookup. Empty for forks (never auto-merged). Topology test updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7229584ae4 |
fix(ci): block merge when a PR still has needs-validation (in ci.yml validate gate) (#4666)
* feat(ci): block merge when a PR still has the needs-validation label A required check on merge_group (and pull_request) that fails if any PR in scope still carries 'needs-validation'. On the merge queue this ejects the group; on the PR it shows red so an unvalidated PR can't be queued. Respects the skip-validation override implicitly (that override means needs-validation is never added). Enforcement requires adding this job as a required status check for main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): fold needs-validation gate into ci.yml validate job (fail closed) Per review (PerishCode): - Drop the standalone block-unvalidated-merge.yml; per .github/AGENTS.md, ci.yml is the low-privilege PR / merge-queue / validation gate, so the check belongs there. - Add it as a step in the existing 'validate' job (already a required check, so it now gates the merge queue with no extra branch-protection wiring). - Fail closed: drop the '|| true' suppressions; on pull_request read labels from the event payload (no API call), on merge_group resolve the group's PRs and let any lookup failure block the merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): make merge_group label lookup fail closed (out of the if condition) Per review (PerishCode): a gh call inside an if condition has its nonzero exit swallowed (read as 'no label'), so a failed lookup waved the merge through. Capture each gh result into a plain assignment so set -e / pipefail blocks on failure; the if only tests the captured value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0bf1b6d6b8 |
[codex] converge release workflows and stable dry-runs (#4390)
* fix(tools-pack): use junctions for Windows standalone peer deps * fix(desktop): expose IPC during startup * fix(tools-pack): preserve Windows inspect diagnostics * fix(tools-pack): report Windows inspect status errors * fix(packaged): use Electron net fetch for app protocol * fix(packaged): load Windows renderer from web sidecar * fix(desktop): show Windows packaged window during startup * fix(packaged): disable Windows GPU startup * fix(tools-pack): keep Windows core smoke observable * fix(packaged): remove Windows startup probes * fix(tools-pack): trace Windows desktop IPC status * fix(tools-pack): add Windows IPC diagnose loop * fix(release): default beta-s Windows updater feed * chore: clean merged test eof * refactor(release): unify prerelease channel model * chore(release): close prerelease doc escape hatches * refactor(release): converge release channel workflows * fix(release): install toolchain in metadata jobs * fix(release): build release package before contracts * chore(release): bump development version to 0.10.1 * fix(e2e): seed windows packaged smoke runtime config * fix(release): install toolchain for metadata publish * fix(release): materialize betas metadata checkout * chore(release): bump development version to 0.10.2 * fix(release): allow betas metadata cold start from s3 * fix(e2e): support betas packaged update scenarios * fix(release): pass betas channel into packaged smoke * fix(release): set betas channel during self-hosted builds * fix(release): verify counted channel reservations * fix(release): use pnpm cmd for betas windows publish * fix(release): add betas manifest artifact fallback * fix(release): skip beta-s public metadata fetch * fix(release): read beta-s manifests from storage * fix(release): cache beta windows tools-pack builds * fix(release): inline beta mac tools-pack builds * fix(pack): deep sign unsigned mac bundles * docs(pack): document payload-first beta updater validation * fix(release): align preview tools-pack cache flow * fix(release): align prerelease tools-pack cache flow * fix(release): pass github token to prerelease metadata * fix(release): setup pnpm before feishu notify * fix(release): add stable dry-run prepublish flow * fix(release): accept completed prerelease metadata gate * fix(release): require stable release branches * fix(release): converge r2 access checks * fix(updater): use release channel parser for defaults * fix(updater): harden windows payload relaunch * fix(release): converge updater smoke fixture contract * test(e2e): require silent updater fixture output * fix(release): align stable windows smoke build path * fix(ci): include release workspace in validation * fix(ci): repair release validation lanes Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(ci): restore zero-install Feishu notification Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
fa544f2836 |
[codex] Prune unused automation and repair metrics publishing (#4612)
* chore(workflows): prune unused automation * chore(workflows): update github app token action * chore(workflows): use github app client id |
||
|
|
a50e1f7b06 |
feat(release): automate release cut + backport + pre-release gate (#4640)
* feat(release): 自动化 release 切版 + backport + 准入闸 引入一套基于 GitHub Action 的 release 流程自动化(配套飞书《发布期间 fix PR 处理手册》): - cut-release: 每周二 09:00(北京)从 main HEAD 切 release/vX.Y.Z + bump 版本 + 建 backport label - backport: 合进 main 的 PR 带 backport label → 自动 cherry-pick 到 release 分支(冲突转 draft) - backport-label-guard: 校验贴标人 write+ 权限,否则撤销 label - release-gate: 发版前检查目标 release 分支无未落地 backport 全部用 GitHub App (open-design-release-bot) token,非 GITHUB_TOKEN, 以便其 push/PR 能正常触发下游 workflow(notify-release-feishu / ci)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(release): translate workflow comments and messages to English Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(release): validate cut-release version input via env; release-gate fails closed on missing branch Addresses review (PerishCode): - cut-release: pass manual version through env + strict x.y.z validation to prevent shell injection; downstream steps consume validated outputs via env (no inline ${{ }} in run). - release-gate: verify the release branch exists before checking PRs, so a mistyped / not-yet-cut branch fails the gate instead of falsely passing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(release): constrain backport/gate target to the release/vX.Y.Z grammar Addresses review (PerishCode, round 2): - backport: tighten label_pattern to ^backport (release/v[0-9]+\.[0-9]+\.[0-9]+)$ so a typo like 'backport main' / 'backport feat/foo' no longer matches and is never run by the bot. - release-gate: validate the input branch against ^release/vX.Y.Z before the existence check, so an arbitrary existing branch (e.g. main) with no open PRs cannot falsely pass the gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(release): cut-release always checks out main HEAD Addresses review (PerishCode, round 3): pin actions/checkout to ref: main so a manual workflow_dispatch from another branch still cuts the release branch from main HEAD, not from whatever ref the run was started on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
6cf48c29b5 |
ci: reduce Blacksmith usage and stabilize PR checks (#4603)
* ci: move lightweight workspace checks to hosted runners * ci: split UI P0 project runtime shard Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Stabilize UI P0 project runtime tests --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
358a426b2b | ci: rename atom workflows (#4522) | ||
|
|
c1701f709a |
[codex] codify workflow handoff capabilities (#4503)
* codify workflow handoff capabilities * Harden workflow marker jq lookups Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * harden autofix head validation * Preserve Nix hash fallback guidance Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Gate Nix hash fallback comments Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Clear stale Nix fallback comments after autofix Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
c782aeb3bb | ci: stop duplicate post-merge validation (#4469) | ||
|
|
b49c9b7932 | Refine CI hot and full validation modes (#4466) | ||
|
|
15d3759834 |
[codex] Optimize CI topology and Playwright suites (#4460)
* Consolidate PR validation into ci * Tune absorbed CI runner tiers * Fold visual validation into CI Playwright shards * Run PR shards during manual CI dispatch * Optimize CI topology and scopes * Increase UI P0 Playwright workers * Relax visual Playwright timeout |
||
|
|
8ff19c6b41 |
[codex] Add main-risk smoke and E2E coverage (#4322)
* test(e2e): cover daemon reload artifact recovery * test(e2e): expand critical flow coverage * test(daemon): add startup and recovery smoke coverage * test(e2e): add visual and desktop chrome smoke coverage * ci: gate packaged onboarding smoke to nightly * fix(web): repair artifact replay type narrowing Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): repair preflight smoke coverage checks Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): tighten artifact recovery polling Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): keep error recovery retries alive Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): relax brittle UI P0 assertions Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): recover pointer artifact outputs Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): stabilize manual edit reload smoke Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): avoid noisy win onboarding cleanup Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): stop failed artifact recovery polling Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(daemon): isolate startup smoke data root Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): recover standalone html artifacts Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): guard pointer artifact recovery by run time Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): narrow strict visual gate to critical smoke Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): limit PR visual capture to critical cases * fix(ci): keep PR visual capture complete * fix(web): keep reload artifact recovery retryable * fix(ci): clarify PR visual gate layering * fix(e2e): stabilize visual home P2 capture * fix(e2e): align visual gallery detail clicks * fix(e2e): preserve packaged install during onboarding cleanup * fix(ci): shard PR visual capture * fix(ci): remove duplicate nix validation from ci * fix(ci): keep visual capture screenshots flat * fix(ci): point nix hash autofix at ci-nix * fix(ci): stabilize PR P0 recovery gate * fix(web): ignore stale artifact pointers during recovery * fix(web): reset BYOK model after clearing key * ci: run critical UI P0 subset on PRs * ci: combine PR UI P0 critical shards * ci: combine PR visual capture shards * ci: preserve visual capture shard manifests * fix(web): clear ProjectView delayed timers on unmount * test(e2e): harden onboarding connect gate coverage * test(e2e): align AMR auth recovery and visual catalog timeout |
||
|
|
4b9a15734d |
[codex] Consolidate PR validation into CI (#4451)
* Consolidate PR validation into ci * Tune absorbed CI runner tiers * Fold visual validation into CI Playwright shards * Run PR shards during manual CI dispatch |
||
|
|
891981d460 |
[codex] Optimize CI runtime topology (#4450)
* Optimize e2e tools-dev runtime parallelism * Remove visual networkidle wait * Optimize e2e vitest parallelism * Optimize Nix flake caching * Test CI on Blacksmith runners * Allow parallel manual CI runs * Tier CI runner sizes * Temporarily narrow CI debug scope * Instrument watcher CI timeouts * Instrument watcher event diagnostics * Avoid default polling in watcher tests * Skip runtime trace during watcher debug * Probe ARM runner tiers for CI * Focus CI runner probe on x64 browser * Probe browser workers by runner size * Probe Playwright file parallelism * Probe Playwright worker scaling on 8v * Reshape CI topology * Fix split E2E Vitest CI lane * Simplify daemon CI topology * Optimize Windows payload CI setup * Revert "Optimize Windows payload CI setup" This reverts commit |
||
|
|
7abb7888df |
fix(deploy): align Docker defaults with GHCR releases (#4327)
* fix(deploy): align Docker defaults with GHCR releases Generated-By: looper 0.9.9 (runner=worker, agent=opencode) * fix(ci): publish stable Docker tags from release workflow Generated-By: looper 0.9.9 (runner=fixer, agent=opencode) * fix(ci): fold reusable workflow guard expression Generated-By: looper 0.9.9 (runner=fixer, agent=opencode) * fix(ci): gate Docker release publish Generated-By: looper 0.9.9 (runner=fixer, agent=opencode) * fix(ci): publish stable Docker tags after release Generated-By: looper 0.9.9 (runner=fixer, agent=opencode) * fix(ci): guard Docker latest tag enable expression Generated-By: looper 0.9.9 (runner=fixer, agent=opencode) * fix(deploy): update Helm chart GHCR defaults Generated-By: looper 0.9.9 (runner=fixer, agent=opencode) * fix(ci): publish latest from release workflow inputs Generated-By: looper 0.9.9 (runner=fixer, agent=opencode) |
||
|
|
a4abae3804 |
fix(release): build the daily beta from main, not the latest release branch (#4393)
* fix(release): build the daily beta from main, not the latest release branch
notify-daily-feishu picked the highest-semver release/vX.Y.Z branch and
built beta off it. Once that branch ships stable, its base version equals
the latest stable, so release-beta's strictly-greater-than-stable guard
rejects every scheduled run until someone hand-bumps the retired branch —
which is exactly how the daily beta + Feishu card silently stalled for
days after 0.10.1 shipped.
Beta is the daily R&D channel, so track the development tip (main), which
always leads stable. Keep the workflow_dispatch `ref` input for one-off
builds from a specific branch.
* test(release): scope daily-beta resolve check and assert ref override
Strengthen the regression test per review: scope the assertions to the
resolve job and prove both control-flow branches — the empty-input
default writes ref=main, and the workflow_dispatch override still
forwards ${{ inputs.ref }} verbatim — so a refactor can't keep the test
green while breaking the preserved override path.
|
||
|
|
eba8ea8cc3 | chore(release): register prerelease workflow placeholder (#4361) | ||
|
|
3ffc964045 | Rename CI provider workflows (#4342) | ||
|
|
8359fb6d2c |
landing: skill-spec restyle, upstream parity, newsletter + nav updates (#4158)
* landing: skill-spec restyle, upstream parity, newsletter + nav copy updates
Landing-page-only rebuild of the home-refresh branch on top of current
main (the previous branch carried unrelated apps/web changes and
conflicted with main):
- Homepage: newsletter band between CTA and FAQ (localized in all 18
locales, client-side stub until a provider lands), AMR logo in the
nav, hero subtitle now reads 'best open-source alternative' in every
non-English locale, hero/nav download labels swapped.
- /plugins/: upstream hub structure (3 tiles); /plugins/templates/ +
kind pages: breadcrumb and counter chip removed, scene chip
active-state contrast fix, tpl-card styles with token colors.
- /agents/: design-spec pass (token spacing incl. 68px max module gap,
font scale, 8px radius), 3-up card grid, list-indent fix; /solutions/,
/agents/, /plugins/ unified on one 1180px centered safe area.
- /blog/: cream/terracotta leftovers replaced with token colors, 8px
card radius.
- Header dropdowns: full 21-agent list, upstream caret/gap parity;
neutralized the homepage section padding leak inside info pages.
- package.json stays on main's shape; cobe / matter-js / @types/
matter-js added as exact pins (homepage globe + FallingText) with
matching pnpm-lock entries. nix/pnpm-deps.nix hash NOT refreshed
(no nix locally) — needs the Validate workspace gate or a follow-up.
* landing: fix CI gates while keeping the homepage refresh intact
Rebased onto current main and resolved the blocking review + red checks on
this branch without dropping the design refresh:
- Zero-client-JS gate: the cobe globe and matter-js FallingText enhancers
used ES `import`, so Astro emitted external `/_astro/*.js` module bundles
and tripped `Verify zero external JavaScript`. Both effects are preserved:
index.astro now reads each library's prebuilt runtime from node_modules at
build time and inlines it verbatim as a classic `<script is:inline>`
(cobe's single ESM default export is rebound to `window.__cobe`; matter-js
ships UMD), mirroring locale-switcher-script.astro. No bundled/module JS.
- Staging/preview noindex: restored the `staging-noindex-headers` Astro
integration that appends `X-Robots-Tag: noindex, nofollow` to `_headers`
for OD_LANDING_NOINDEX builds, so PR previews stay out of search indexes.
- Blob guard: removed 14 stray .bak/.prev/.leftpad backup files and 9
unreferenced PNG originals, converted 12 referenced >1MB PNGs to WebP
(each <1MB) with their refs updated, and recompressed lab-video.mp4 /
lab-hyperframes.mp4 under 1MB. No changed tracked file now exceeds 1MB.
- Residual-JS guard: allowlisted public/community/_site-nav.js, a verbatim
browser script for the static /community/ pages (same precedent as the
web notifications service worker).
- Homepage-art gate: the refresh serves its hero/gallery/method art as
optimized origin-hosted WebP instead of Cloudflare Image Resizing variants,
so the homepage no longer emits cdn-cgi URLs. The gate now requires the
same floor of 16 optimized WebP references (ci/staging/production) so a
regression to raw, unoptimized art is still caught.
* landing: stop 18x-localizing the skills/systems/templates catalog
The PR preview deploy hit Cloudflare Pages' 20,000-file-per-deployment cap:
the branch added `[locale]/{skills,systems,templates}` detail/listing
generators plus per-locale plugin previews, all fanned out across the 18
LANDING_LOCALES, producing ~29,300 output files.
This is exactly the locale-explosion the branch's own `public/_redirects`
already anticipated — its "Catalog migration" section says the standalone
skills/systems/templates generators "were removed" and are 301'd to
`/plugins/*`, but the generator routes were left in the tree, and the
localized 301 rules only covered 4 of the 18 locales.
Fix, matching that stated intent and Astro's guidance for bounded static
multilingual builds (prune the prerender matrix; signal i18n via
hreflang/sitemap, which this site already does — non-default locales are
sitemap-excluded):
- Remove the localized `[locale]/{skills,systems,templates}` detail, index
and facet generators, and the localized `[locale]/plugins/previews`
duplicate (previews are language-agnostic visual assets).
- Extend the localized catalog 301s in `public/_redirects` from {zh,zh-tw,
ja,ko} to every prefixed locale, and add `/<loc>/plugins/previews/*` →
the canonical English previews. Kept-localized plugin hub/detail pages
whose links degrade to these paths now resolve via a single 301 hop.
English catalog pages, all localized marketing pages, the localized plugins
hub, and the homepage design are unchanged. Output drops to ~19.2k files,
back under the deploy cap.
* landing: canonicalize legacy locale aliases in the header language switcher
The shared sub-page header language switcher builds its targets with
`localePath(target, Astro.url.pathname)`, and `stripLocaleFromPath` only
recognized canonical `LANDING_LOCALES`. The catch-all route still emits
legacy / alias locale pages (`app/_lib/i18n.ts` LOCALES: `/zh-CN/`, `/es-ES/`,
`/fa/`, `/hu/`, `/th/`, plus upper-cased `zh-TW`/`pt-BR`), so on those pages
the switcher produced doubled, non-existent targets like `/zh/zh-CN/blog/`.
Strip those alias prefixes too (case-insensitive, canonicalizing to the
default locale) so switching language from `/zh-CN/blog/` now lands on an
existing URL (`/blog/`, `/zh/blog/`, …). Verified against the static build:
the doubled `/<loc>/{zh-CN,es-ES,fa,hu,th}/…` targets are gone (0 matches),
and canonical `LANDING_LOCALES` pages are unchanged.
* landing: optimize homepage first-screen — fonts → subset woff2, hero → responsive webp
Resource-weight pass on the homepage, focused on the first screen.
Fonts (≈861 KB → ≈106 KB, and no longer render-blocking):
- Albert Sans regular/italic shipped as TTF (125 KB + 138 KB). Added woff2
builds (50 KB + 55 KB) as the primary `src`, TTF kept only as a fallback.
- `remixicon.ttf` was a 598 KB icon font loaded `font-display: block` (it hid
the icons — and text in the same paint — until the whole font downloaded).
The site only renders 11 of its glyphs, so it's now subset to a 1.3 KB woff2.
- All faces are `font-display: swap` now, and the above-the-fold Albert Sans
woff2 is `<link rel=preload>`ed at high priority, so text paints immediately
in the fallback and swaps without a blocking round-trip.
Hero imagery (responsive WebP instead of one oversized file for all devices):
- The hero backdrop was a single 326 KB / 2880px PNG served to every viewport.
Now responsive q90 WebP variants (960→33 KB, 1440→47 KB, 1920→89 KB,
2880→110 KB); `og:image`/`twitter:image` keep the PNG since social crawlers
don't reliably accept WebP. The LCP preload points at the WebP set with
`imagesizes="100vw"` to match the full-bleed `<img>`.
- The hero product shot now has a responsive srcset (800/1280/1920/2508);
the 2508px master is the pristine original (no re-encode), phones pull the
800px (66 KB) variant.
Quality is kept high (q90) — the savings come from format + right-sizing per
viewport, not heavier compression. Build output is unchanged structurally
(zero external/module JS still verified; ~19.2k files, under the deploy cap).
* landing: include all 25 used Remix Icon glyphs in the subset
The first subset only covered the 11 codepoints declared via CSS `content:`
and missed the 15 declared as JS \u escapes in page.tsx's `RI` glyph map
(download \uec5a, github \uedcb, star \uf18b, the arrow/chevron set, …), so
the hero download / Star CTA icons and several nav arrows rendered blank.
Re-subset to the full union of 25 glyphs (still ~2KB woff2).
* landing: add the locale-switcher caret glyph to the Remix Icon subset
A full scan of the built site (18,912 HTML files, every rendered Private-Use
codepoint) surfaced one more glyph the 25-glyph subset was missing: U+EA4E
(arrow-down-s-line), the language-switcher dropdown caret in header.tsx — it's
inserted as a literal char (not via the RI map), so the source grep missed it.
Re-subset to 26 glyphs (≈2.1KB); the same scan now reports zero missing
glyphs site-wide.
* landing: convert remaining raster art to WebP site-wide
After a site-wide image audit, converted the last referenced PNG/JPG raster
art to WebP (q88, no visible quality loss — the source files are lossless so
there's no generation loss):
- `lab-stage-bg` (homepage lab section): 710KB JPG → 323KB WebP.
- Blog study plates 09–12 and 16–22 (the hero/inline art in the Figma- and
Claude-alternative posts, referenced across every localized section):
~4.9MB of 1024² PNGs → ~2.6MB WebP. All `<img src>` references in the two
blog markdown files and the blog index were repointed; the originals are
removed.
Combined with the earlier hero/font pass, the homepage and blog now ship only
WebP raster art (PNG kept solely for the social `og:image`). Verified: build
clean, no dangling references to the removed files, zero external/module JS,
~19.2k output files (under the deploy cap).
* landing: make homepage og:image / twitter:image absolute URLs
`heroImage` is a same-origin path (`/hero-home.png?v=2`); social crawlers
(Facebook, Twitter, LinkedIn) need an absolute URL for rich link previews.
Resolve it against `Astro.site` at the metadata call site so the share card
renders as `https://open-design.ai/hero-home.png?v=2`. Per @PerishCode's
review note; non-blocking but correct.
* landing: absolute share image on sub-pages + canonical-locale plugin search.json
Two non-blocking output regressions from @PerishCode's review:
- sub-page-layout.astro wrote `og:image`/`twitter:image` from the raw
`ogImage ?? heroImage` (origin-relative), so every sub-page's share card
(plugins, agents, download, …) emitted `content="/hero-home.png?v=2"`.
Resolve against `Astro.site` like the homepage already does; `new URL()`
leaves an already-absolute `ogImage` untouched.
- [locale]/plugins/search.json.ts keyed off `_lib/i18n` PREFIXED_LOCALES
(the legacy alias table: zh-CN, es-ES, pt-BR …), so it emitted
`/zh-CN/plugins/search.json` whose entries linked to `/zh-CN/plugins/<slug>/`
detail pages that don't exist, while the canonical `/zh/plugins/search.json`
was missing. Drive it from `app/i18n` LANDING_LOCALES + localePath so each
endpoint matches the localized plugin detail routes and its hrefs resolve.
Verified: sub-page og:image is now absolute, /<canonical-locale>/plugins/
search.json exists with resolvable hrefs (legacy /zh-CN/ variants gone),
zero external/module JS, ~19.2k files.
* landing: preserve query + hash when switching language
The locale switcher's click handler only persisted the choice and let the
anchor's bare server-rendered href ('/<locale>/<path>/') drive navigation, so
a plain left-click dropped the current query string and hash — switching
language on '/plugins/templates/?kind=deck#gallery' landed on
'/<locale>/plugins/templates/'. Intercept the plain left-click and route
through the existing selectLocale(), which carries window.location.search +
hash across the switch via targetFor(); modified-click / non-left-button /
right-click still fall through to the real <a> (open-in-new-tab, etc.).
Per @PerishCode's review note. Verified: build clean, homepage still ships no
external/module JS, typecheck 0 errors.
* landing: handle legacy locale aliases in the switcher's path recompute
The previous commit routed plain locale-switch clicks through selectLocale(),
which recomputes the target via basePathFromCurrent(). That helper only treated
canonical LANDING_LOCALES as a strippable first segment, so on a legacy alias
page (/zh-CN/, /es-ES/, /fa/, /hu/, /th/ — still emitted by the catch-all)
clicking a language built a nested, non-existent URL like /zh/zh-CN/blog/.
Teach the switcher the same legacy-alias set as stripLocaleFromPath in
app/i18n.ts (zh-cn, es-es, fa, hu, th) via an isLocaleSegment() helper, so the
alias prefix is stripped before the target is rebuilt: /zh-CN/blog/ → ja now
yields /ja/blog/ (+ preserved query/hash), not /ja/zh-CN/blog/.
Per @PerishCode. Verified: build clean, switcher script carries the alias
handling, homepage still ships no external/module JS, typecheck 0 errors.
* landing: make plugin detail canonical-only + guard the deploy file count
The localized plugin detail wrappers (`[locale]/plugins/[...slug].astro` and
`[locale]/plugins/[slug]/index.astro`) regenerated the full ~450-plugin detail
set across all 17 non-default LANDING_LOCALES, so `build:static` emitted 19,234
files with `public/previews` empty — only ~766 below Cloudflare Pages' 20k-file
deploy cap, which the CI-generated preview thumbnails would erode further. This
is the same locale-explosion the PR set out to fix, left in the plugin subtree.
- Remove both localized plugin detail generators; plugin detail stays canonical
(English). Output drops 19,234 → 4,287 files (huge headroom).
- Add `/<loc>/plugins/* -> /plugins/:splat` 301s for every prefixed locale so
localized plugin links (still emitted by the kept localized hub pages) resolve
to the canonical detail page. Kept hub pages (/<loc>/plugins/,
/<loc>/plugins/{skills,systems,templates}/) have static files and win over
the redirect (static assets take precedence over _redirects on CF Pages).
- Add a post-build "deploy file count under Cloudflare Pages cap" step to the
ci / staging / production landing workflows (fail at >=19,000) so this limit
can never regress silently again.
Per @PerishCode. Verified: 4,287 files, localized hubs + canonical detail
intact, homepage zero external/module JS, typecheck 0 errors.
* landing: one canonical plugin detail route (drop namespace catch-all)
`plugins/[...slug].astro` keyed getStaticPaths off `plugin.slug`, which keeps
the registry namespace (`open-design/<name>`), so it emitted nested pages like
/plugins/open-design/<name>/ and self-canonicalized them via Astro.url.pathname.
But catalog cards and the search JSON link to `plugin.detailHref`
(/plugins/<last-segment>/), which the sibling `plugins/[slug]/index.astro`
already generates from `detailSlug`. The catch-all therefore produced ~440
orphan, indexable duplicate URLs that no surface links to — extra sitemap
entries and extra files against the Cloudflare Pages cap.
Delete the catch-all; `[slug]/index.astro` remains the single canonical detail
route at the exact `detailHref` URL the catalog/search already target. These
namespace URLs are net-new to this branch (never shipped/indexed), so no
redirect is needed. Output drops 4,287 → 3,848 files; canonical detail pages
(445) and the templates/[kind] hubs are intact.
Per @PerishCode. Verified: /plugins/open-design/* gone, canonical detail
renders, file-count guard passes, homepage zero external/module JS, typecheck
0 errors.
* landing: fix dead locale-prefixed links to canonical detail pages and migrated catalogs
Plugin/skill/template detail pages are canonical-only since the catalog
migration, but the card components still ran detailHref through
localizedHref, 404ing every card click on all 17 non-default locales
(430+ links per locale). The homepage lab dock, the design-systems stat
card, and the html-video CTA also still pointed at the legacy /skills
and /systems paths. Cards now link detail pages canonically; homepage
and html-video links target the /plugins/* catalogs and kind facets.
* landing: anchor the community dropdown into the hub sections again
Contributors / Ambassadors / Moderators in the header dropdown linked
straight to the standalone static pages, surfacing them on a plain
menu click. Restore upstream main's destinations — /community/#... hub
anchors — so the dropdown scrolls the hub; the standalone pages stay
reachable from the hub cards only.
---------
Co-authored-by: wangchenglong <879618852@qq.com>
Co-authored-by: lefarcen <ontf116@gmail.com>
|
||
|
|
571b43dce0 |
[codex] Expand UI E2E coverage and P0 CI signal (#4196)
* add PR UI P0 gate * fix(ci): keep critical UI check in required workflow Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * ci: split workspace restoration P0 shard * ci: reduce visual and daemon test wall time * ci: skip duplicate critical UI gate on PRs * ci: increase daemon test sharding * test(e2e): expand critical UI flow coverage * test(e2e): promote critical onboarding and automation flows * test: expand ui e2e coverage * test: repair PR P0 onboarding and media sync checks Generated-By: looper 0.9.7 (runner=fixer, agent=codex) |
||
|
|
8450a530fc |
Merge pull request #4115 from nexu-io/release/v0.10.0
release: Open Design 0.10.0 — the all-in-one Agentic design workspace, with a whole design team built in |
||
|
|
3c3d45aa3d |
fix(release): add skip_nightly_gate escape hatch for stable promotion (#4150)
The stable-promotion gate (validateStableNightlyMetadata) validates a long list of fields on the candidate nightly's metadata. After fixing stableVersion / github.* / manifest staleness, it still trips on r2.reportZipUrl (hard-coded null in publish-metadata.ts), with more fields (report.url, platforms.*) behind it. To stop the field-by-field whack-a-mole and ship 0.10.0, add a skip_nightly_gate input (default false) that bypasses the gate. The stable build still produces freshly built + signed artifacts and still runs publish-metadata's own per-platform manifest consistency checks; only the nightly-metadata precondition is skipped. |
||
|
|
1b35224471 |
fix(release): pass RELEASE_COMMIT/RUN_ID to every publish step (follow-up to #4144) (#4147)
* fix(release): pass RELEASE_COMMIT/RUN_ID to every publish step so manifests aren't stale Follow-up to #4144. Setting RELEASE_COMMIT/RELEASE_RUN_ID only on the publish-metadata step made publish-metadata.ts validateManifest() reject the per-platform manifests it aggregates: refusing stale mac_arm64 platform manifest for 0.10.0.nightly.39: github.runId=0 The staleness guard compares each platform manifest's github.{runId,commit} against the publish step's currentRunId/currentCommit. The platform build jobs write those manifests but never received the RELEASE_* env, so they stamped runId=0 while the metadata step now expected the real runId. Mirror release-beta.yml: set RELEASE_BRANCH/REPOSITORY/RUN_ATTEMPT/RUN_ID/ WORKFLOW workflow-wide so platform jobs inherit them, and pass RELEASE_COMMIT to every publish step (4 platforms + metadata) so manifest github.commit matches. * fix(release): use resolved branch for RELEASE_BRANCH on every publish step Address review: a workflow-wide RELEASE_BRANCH: ${{ github.ref_name }} stamps the caller's ref under workflow_call / inputs.ref, so per-platform manifests would disagree with the aggregate metadata (which uses needs.metadata.outputs. branch). Drop the top-level github.ref_name and pass RELEASE_BRANCH: ${{ needs.metadata.outputs.branch }} on each publish step instead, matching the resolved ref the metadata job built. Keep RUN_ID/RUN_ATTEMPT/REPOSITORY/WORKFLOW workflow-wide (identical for caller and callee). Regression assertion added. |
||
|
|
d055737de7 |
[codex] Add PR UI P0 gate (#4142)
* add PR UI P0 gate * fix(ci): keep critical UI check in required workflow Generated-By: looper 0.9.7 (runner=fixer, agent=codex) |
||
|
|
0cb06386cc |
fix(release): restore nightly stableVersion + github attribution for stable promotion (#4144)
The #3995 unified-publisher refactor dropped two fields the stable promotion gate (scripts/release-stable.ts) requires on a validated nightly: - publish-metadata.ts omitted stableVersion for the nightly channel, so no nightly carried the base version it is a candidate for. - release-stable.yml never passed RELEASE_COMMIT/BRANCH/REPOSITORY/WORKFLOW/ RUN_* to the unified publisher (release-beta.yml does), so metadata.github.* shipped empty. Both made every 0.10.0 nightly un-promotable. Restore the nightly stableVersion field and the RELEASE_* attribution env, mirroring release-beta.yml. |
||
|
|
537a0926ee |
ci: disable ci-gate automatic triggers (#4133)
* ci: disable ci-gate automatic triggers * ci: validate manual ci-gate override combos * ci: fix manual override shellcheck |
||
|
|
1eac8fcabf |
ci: demote legacy ci gate (#4122)
* ci: demote legacy ci gate * ci: make legacy gate manual only * ci: isolate ci-gate browser ports Generated-By: looper 0.9.5 (runner=fixer, agent=codex) * ci: keep legacy gate on automatic triggers * ci: isolate browser ports and gate check name * ci: document gate namespace split --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
c4a030fe18 |
ci: harden the structured ci-gate workflow stack (#4083)
* ci: add structured ci-gate workflow stack * ci: stop using setup-node on self-hosted runner * ci: harden ci-gate result transport * ci: record action step timings * ci: restore ci-gate step timeouts Generated-By: looper 0.9.5 (runner=fixer, agent=codex) * ci: make actionlint job avoid docker hub pulls Generated-By: looper 0.9.5 (runner=fixer, agent=codex) * ci: align ci-gate pull request target sha Generated-By: looper 0.9.5 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
88d1eab743 |
feat(landing): surface user-submitted tutorial issues and PRs in the daily digest (#4095)
* feat(landing): surface user-submitted tutorial issues and PRs in the digest User submissions lived only on GitHub and never entered the daily review flow. The tutorials-youtube-sync digest now also lists them so they're reviewed alongside auto-discovered YouTube candidates: - Submission issues: open issues from the 'Submit a tutorial' form (label tutorials). The card shows each with the video link extracted from the body; on approval a maintainer generates the entry and opens a PR (Closes #issue). - Contribution PRs: open PRs touching app/content/tutorials/** are auto-labeled 'tutorials' by a new labeler workflow, and listed in the card for review/merge on GitHub. Adds github-submissions.ts (search-API fetch + video-URL extraction) with unit tests, two new Feishu card sections, issues:read + pull-requests:read on the workflow, and the labeler workflow/config. Submission lookup is non-fatal — a failure skips those sections without affecting the YouTube candidates. * fix(landing): fail on submission lookup error; broaden YouTube URL parsing Per review: - A failed submission search no longer silently posts a digest without the issue/PR sections. It now aborts before posting (exit non-zero, observable); submissions aren't windowed so the next run re-queries them, and YouTube candidates are re-covered via the unchanged watermark — nothing is lost. - Extract a shared extractYouTubeId() (watch incl. m.youtube, youtu.be, embed, shorts, live) used by both the submission parser and generate-selected, so the digest no longer mislabels shorts/mobile links as 'no video link'. videoUrl is canonicalized to youtu.be/<id>. - Tests: shorts + mobile-watch extraction, and that a failed search rejects (is not swallowed). * fix(landing): host-anchor extractYouTubeId to real YouTube hosts Per review: the broadened matcher accepted ?v=<id> on any host, so a non-YouTube link like https://example.com/watch?v=... was canonicalized to a youtu.be URL and hid invalid submissions. Require a YouTube host (youtube.com / m. / music. / youtu.be) before accepting v=/shorts/embed/live. Add a regression test that a non-YouTube ?v= URL leaves videoUrl undefined. * fix(landing): validate YouTube host via URL hostname, not substring Per review: the substring host check matched lookalikes (notyoutube.com, evil-youtube.com). extractYouTubeId now parses candidate URLs with new URL() and checks hostname against an allowlist (youtu.be, youtube.com, www/m/music .youtube.com) before extracting the id. Regression fixtures for non-YouTube and lookalike hosts. * fix(ci): grant issues:write to labeler so it can create the tutorials label Per review: actions/labeler needs issue-label write to create the tutorials label when missing. The label is also pre-created in the repo so the issue-submission form can apply it; this makes first-run deterministic. * fix(landing): sync tutorials label to live diff; paginate submission search Per review: - labeler: sync-labels: true so the tutorials label is removed when a PR no longer touches the tutorials content dir, keeping the digest's is:pr label:tutorials query free of stale/non-tutorial PRs. - searchIssues paginates (per_page=100, loops until a short page, 10-page cap) so a backlog over one page is not silently truncated. Test stubs two pages. --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com> |
||
|
|
b6643698de |
feat(landing): narrow tutorial digest to same-day newly published videos (#4055)
* feat(landing): narrow tutorial digest to same-day newly published videos The daily Feishu digest defaulted to a 14-day window, so every run resurfaced the same two-week backlog. Default the lookback to 1 day so each digest only shows videos published since the previous run (still filtered against the catalogue). Consecutive daily runs tile back-to-back so nothing is missed; --days N is still available for manual catch-up sweeps. * fix(landing): derive digest window from last successful run, not wall clock Per review: a fixed 1-day wall-clock window assumed runs fire exactly 24h apart. Delayed or skipped scheduled runs would leave a gap — videos published between one run's window end and the next run's publishedAfter were never surfaced, and dedupe only covered already-published videos, not already-notified ones. notify-candidates now sets the window start to this workflow's last successful run (via the Actions API), with a 1h overlap for skew, so delayed/skipped runs stay gap-free and nothing is re-notified. Falls back to a 2-day window when no watermark is available (first run / local), clamped to 14 days max. --days still forces a fixed catch-up window. fetchCandidates now takes an explicit publishedAfter instead of a day count. Workflow gains actions:read + GITHUB_TOKEN to read its own run history. * fix(landing): don't clamp manual --days; fail on watermark lookup error Follow-up review: - An explicit --days override is no longer capped, so a manual catch-up after a longer outage (e.g. --days 30) actually covers the requested range instead of being silently limited. - A failed Actions API watermark lookup now fails the job instead of silently falling back to a short window — a wrong window would drop or duplicate notifications while the run looked green. The 2-day fallback is reserved for when there is genuinely no watermark source (local run, or no prior successful run yet). * fix(landing): use exact prior-run timestamp as window lower bound, no overlap Per review: the -1h overlap re-emitted candidates because there is no already-notified store, so a video in the overlap window reappeared the next day until published. Use the prior successful run's start as the exact lower bound; consecutive windows are contiguous ([T_prev, now] then [T_this, now]), so delayed/skipped runs stay gap-free without an overlap and nothing is duplicated. * docs(landing): document bounded residual overlap in window watermark The window lower bound is the prior run's start; the only residual overlap is the prior run's setup time (~1 min), which is bounded and cosmetic for a human-reviewed digest. Note it explicitly and scope a durable already-notified store as a follow-up rather than over-building this window-tuning change. * fix(landing): scope watermark lookup to the current ref Per review: lastSuccessfulRunStart() queried this workflow's successful runs across all refs. With workflow_dispatch enabled, a successful run on a feature branch could become main's watermark and permanently skip the range it 'covered' with possibly different code. Scope the Actions runs query to GITHUB_REF_NAME (fallback main) so only the canonical digest branch advances its own window. * fix(landing): validate --days override is a positive integer Per review: --days foo became NaN and crashed in isoDaysAgo (RangeError), and --days -1 silently produced a future publishedAfter (empty digest). Validate the parsed value and abort with a clear message before resolving the window. * fix(landing): abort on any partial search failure so the watermark holds Per review: a partial YouTube search failure (some queries fail, not all) still posted a digest and succeeded, advancing the run watermark past a window the failed query never covered — permanently skipping those candidates. Now any search failure aborts before posting (and before the gate), so the watermark does not advance and the next run re-covers the same window. * fix(landing): derive watermark from run_started_at, not created_at Per review (with evidence: a run where created_at=08:17 vs run_started_at=08:29 differ by ~12 min): created_at is the queue/creation time, so deriving the window from it re-emits candidates published while a run sat queued. Use run_started_at (actual execution start), falling back to created_at only if absent. Add a regression test covering a queued run where the two differ, plus current-run exclusion and the no-token case; guard the module entry so it is importable from tests without running main(). --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com> |