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>
2026-07-02 04:30:58 +00:00
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>
2026-07-01 03:45:48 +00:00
lefarcenandGitHub 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).
2026-06-30 13:08:39 +00:00
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>
2026-06-30 09:04:49 +00:00
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>
2026-06-29 13:36:50 +00:00
lefarcenandGitHub 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.
2026-06-29 13:33:08 +00:00
lefarcenandGitHub 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
2026-06-29 07:30:11 +00:00
PerishFireandGitHub 514ddba8ed ci: consolidate runner selection contract (#4803)
* ci: consolidate runner selection contract

* test: stabilize connector settings privacy state
2026-06-26 04:33:11 +00:00
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>
2026-06-25 08:55:51 +00:00
lefarcenandGitHub 4c6f6549c6 ci: add landing QA Feishu summary (#4763) 2026-06-25 07:59:45 +00:00
lefarcenandGitHub a4dea37a22 fix(ci): harden backport automerge provenance (#4761) 2026-06-25 07:07:04 +00:00
lefarcenandGitHub 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.
2026-06-25 05:53:01 +00:00
Tom HuangGitHubCursorxne998808-aiPerishCodeopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>lefarcenSiri-Ray
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 (886f925cd) addressing
regressions the conflict resolution introduced. main's web suite is the
oracle (100% green); resolution principle was main's engine/backend +
HEAD's UI, unioned.

daemon:
- library-sync.ts: correct design-systems import to ./design-systems/index.js
  (design-systems became a directory module on main).
- tests/server-bootstrap-regression: add LIBRARY_DIR to the PathDeps fixture
  (main-added test x HEAD-added LIBRARY_DIR field).

web:
- WorkspaceTabsBar: union — restore main's onboarding Search-popover close
  behaviour + guards, keep HEAD's library/brands nav entries.
- HomeView: restore main's composer sending-state (await onSubmit, widen its
  return type to Promise<boolean>|boolean|void, pass submitting to HomeHero).
- MemorySection.test: take main's version to match main's two-loop memory
  component.
- i18n: restore dropped settings.onboardingRoleMarketing key across types.ts
  and all locales.
- App/BrandsTab/EntryNavRail/router/home-intent: union fixes restoring main
  features dropped during conflict resolution (needs_input handling, etc.).

Validation: pnpm guard + full pnpm typecheck (all 23 packages) green.
Known-red: EntryShell onboarding step 3 intentionally retains HEAD's "build"
step rather than main's brand-extract step; 8 EntryShell.onboarding /
App.onboarding-amr-e2e tests stay red pending that onboarding decision.

* fix(merge): keep HEAD's unified brand flow (revert main's separate Brands tab)

Follow-up to 688544ff7. Per the chosen product direction (brand creation
unified into the design-system create wizard, not a standalone Brands tab),
revert the brand-flow routing/nav that the post-merge repair had restored
from main:

- router.ts: keep HEAD's brand routing (brands folded into design-systems),
  drop main's standalone /brands and /brands/:id view routing.
- EntryNavRail.tsx: drop main's standalone "Brands" nav button.
- runtime/home-intent.ts: drop main's brand "Use in new chat" confirmation
  notice plumbing (tied to the separate Brands flow).

Kept from the repair commit (additive, non-conflicting): App.tsx
loadedActiveProject correctness, composer Sending… state, WorkspaceTabsBar
onboarding popover behaviour, two-loop memory test, restored i18n keys,
brand needs_input STATUS handling, server.ts plugin-route infrastructure.

* feat(library-ui): implement conditional rendering based on LIBRARY_UI_VISIBLE

- Updated router.ts to conditionally render the library view based on the LIBRARY_UI_VISIBLE flag.
- Modified ComposerPlusMenu.tsx, DesignFilesPanel.tsx, and DesignSystemAssetDropzone.tsx to show the "Select from library" button only when LIBRARY_UI_VISIBLE is true.
- Adjusted EntryNavRail.tsx and EntryShell.tsx to include the library navigation button and section conditionally based on the LIBRARY_UI_VISIBLE state.
- Enhanced HomeHero.tsx to allow starting a blank project directly, improving user experience by providing more options for project creation.

This commit introduces a feature toggle for the library UI, allowing for better control over its visibility during development and testing.

* feat(home-hero): implement edge auto-scroll for horizontal overflow

- Introduced `useEdgeAutoScroll` hook to manage auto-scrolling behavior for horizontally overflowing components in HomeHero.
- Updated `PluginPromptPresets` and `RailGroup` components to utilize the new auto-scroll functionality, enhancing accessibility for users without trackpads.
- Added `EdgeScrollZones` component to provide interactive edge zones for scrolling.
- Enhanced CSS styles to support the new scrolling layout and ensure proper positioning of elements.

This commit improves user experience by making overflow content more accessible and easier to navigate.

* feat(design-systems): add project creation from design system and enhance UI components

- Implemented `handleCreateProjectFromDesignSystem` function in `AppInner` to facilitate project creation directly from a selected design system.
- Updated `DesignKitView` to wrap the iframe in a span for better layout control.
- Refactored CSS for `BrandPreviewCard` and `DesignSystemsTab` to improve styling and responsiveness.
- Introduced a new `TemplatePicker` component in `HomeHero` for selecting project-type templates, enhancing user experience.
- Updated various components to support asynchronous handlers for design system actions, improving overall functionality.

This commit enhances the design system integration and user interface, making project creation more intuitive and accessible.

* feat(brand-routes): enhance brand reservation API and add DESIGN.md support

- Updated the POST /api/brands endpoint to accept optional fields: description and designMd, allowing for more flexible brand reservations.
- Modified validation to require either a URL or designMd for brand extraction.
- Introduced a new design-md-input module to handle parsing and validation of DESIGN.md content.
- Enhanced startBrandExtraction function to support processing of DESIGN.md, improving integration with design systems.
- Added utility functions for managing DESIGN.md input and output, streamlining the brand creation process.

This commit improves the brand extraction workflow by integrating DESIGN.md support, making it easier for users to create and manage brands.

* feat(chat-pane, design-kit-view): enhance chat functionality and design preview features

- Added `handleNextStepPromptAction` to `ChatPane` for setting draft prompts, improving user interaction.
- Introduced `nextStepVariant` to differentiate design system projects in `ChatPane`.
- Updated `DesignKitView` to include a button for previewing design kit covers, enhancing user experience.
- Implemented a modal for displaying design kit previews, allowing users to view content in a dedicated space.

These changes improve the chat interface and design kit interactions, making the application more intuitive and user-friendly.

* feat(brand-extraction): enhance DESIGN.md support and testing

- Added a new test case to validate brand extraction from DESIGN.md input without requiring a website.
- Implemented functionality to register brands directly from DESIGN.md, improving the brand creation workflow.
- Updated the `ChatPane` and `NextStepActions` components to handle design system-specific actions for projects, enhancing user experience.
- Enhanced localization files with new carousel hints and project brief options across multiple languages.

These changes streamline the brand extraction process and improve the overall functionality of the design system integration.

* feat(wireframe-examples): add annotated and greybox wireframe examples

- Introduced new wireframe examples for annotated and greybox styles, enhancing design system capabilities.
- Added HTML and JSON files for both wireframe types, providing templates for low-fidelity design mockups.
- Implemented SKILL.md documentation for each wireframe example, detailing usage and design specifications.

These additions improve the design toolkit, offering users more options for creating wireframes in various styles.

* feat(brand-extraction): refine Chrome fallback and enhance error handling

- Updated the Chrome fallback logic in the prefetch pipeline to clarify its purpose and usage as a diagnostic tool.
- Introduced environment variable checks to enable or disable system Chrome usage, improving control over the extraction process.
- Enhanced error messages in the DesignSystemCreationFlow component to provide clearer guidance on required inputs for creating a design system.
- Added regression tests to ensure that prompts do not instruct the agent to invoke a non-existent `brand-extract` skill, preventing potential failures during brand extraction.

These changes improve the robustness of the brand extraction process and enhance user experience by providing clearer instructions and error handling.

* feat(brand-extraction): enhance DESIGN.md input handling and introduce brand ready prompt

- Updated the BrandFromDesignMdInput interface to explicitly define the description property as optional with undefined.
- Enhanced the brand extraction prompts to clarify the inline brand-extract workflow, preventing confusion during the extraction process.
- Added a new BrandReadyPrompt component to notify users when a design system is ready for preview, improving user experience.
- Introduced CSS styles for the BrandReadyPrompt to ensure a visually appealing and user-friendly interface.
- Updated localization files to support new strings related to the brand ready prompt across multiple languages.

These changes improve the clarity and usability of the brand extraction process, providing users with timely feedback and a more intuitive interface.

* feat(brand-extraction): improve design system focus handling and localization updates

- Refactored the handling of browser tabs in the brand extraction tests to ensure proper validation of tab states.
- Enhanced the AppInner component to refresh design systems alongside templates, ensuring users see the latest updates without page reloads.
- Introduced a pending focus state in the DesignSystemsTab to manage design system selection more effectively after brand extraction.
- Added a BrandReadyPrompt in the ProjectView to notify users when a design system is ready for preview, improving user engagement.
- Updated localization files for Chinese (Simplified and Traditional) to reflect changes in terminology related to design systems.

These changes enhance the user experience by providing timely feedback and ensuring that the design system selection process is seamless and intuitive.

* fix(styles): adjust letter-spacing and enhance plus-menu trigger styles

- Set letter-spacing to 0 in design-system-flow.css for improved text clarity.
- Added styles for plus-menu trigger in plus-menu.css, including background, border, and hover effects to enhance user interaction and visual consistency.

These changes refine the design aesthetics and improve the usability of the plus-menu component.

* feat(tests): add design-system focus handoff tests

- Introduced a new test suite for validating the design-system focus handoff functionality.
- Implemented tests to ensure that the focus ID is correctly set, read, and cleared from session storage, preventing user selection hijacking.
- Added checks for scenarios where no focus ID is pending, enhancing test coverage for the design system's behavior.

These tests ensure the reliability of the design-system focus handling, contributing to a more robust user experience.

* feat(export): restrict image format options to PNG and JPEG

- Updated the image format options in the export functionality to only allow PNG and JPEG, removing WebP to prevent silent downgrades.
- Enhanced error handling to provide clear feedback when an unsupported image format is specified.
- Adjusted related documentation and comments to reflect the changes in supported formats across the application.

These changes ensure consistency in image export behavior and improve user experience by providing immediate validation errors for unsupported formats.

* feat(origin-validation): implement zero-config OD Clipper bypass for library requests

- Added a new function `isZeroConfigClipperLibraryRequest` to validate requests from locally-installed browser extensions targeting the `/library/` path.
- Updated the origin validation middleware to utilize this function, allowing unpaired browser extensions to access the `/api/library/ingest` endpoint while blocking other cross-origin requests.
- Enhanced tests to cover the new bypass functionality, ensuring correct behavior for both valid and invalid origins.

These changes improve the integration of browser extensions with the local daemon, enhancing user experience while maintaining security.

* feat(design-systems): add download functionality for design systems

- Implemented a new command `od design-systems download <id>` to allow users to download design systems as a .zip file, including all system files and a generated SKILLS.md usage guide.
- Updated the CLI help documentation to include usage instructions for the new download command.
- Enhanced the design systems API to support the download feature, ensuring only user design systems are accessible while handling errors for non-existent presets.
- Added localization strings for the new download functionality across multiple languages.

These changes enhance the usability of design systems by providing a straightforward method for users to obtain and share their design assets.

* feat(design-systems): enhance design system management and localization

- Introduced new UI components and styles for managing design systems, including buttons for downloading, refreshing, and resetting edits.
- Updated the DesignKitView to support direct actions for DESIGN.md editing, improving user interaction with design systems.
- Enhanced the DesignSystemDetail component to include download functionality and improved state management for design system edits.
- Added localization strings for new features, ensuring consistent user experience across multiple languages.
- Improved error handling and user feedback for design system operations, including download failures.

These changes streamline the design system management process, making it more intuitive and user-friendly while ensuring robust localization support.

* feat(tests): add comprehensive tests for design system archive functionality

- Introduced a new test suite for validating the `buildUserDesignSystemArchive` and `buildDesignSystemSkillsMarkdown` functions.
- Implemented tests to ensure correct packing of design system files, including the generation of a `SKILLS.md` guide and exclusion of internal metadata.
- Added checks for handling non-user IDs and scenarios where a design system already includes its own `SKILLS.md`.
- Enhanced the overall test coverage for design system functionalities, ensuring reliability and correctness in the design system archive process.

These changes improve the robustness of the design system features by ensuring thorough testing of critical functionalities.

* feat(figma-import): enhance CLI output and add Figma import endpoint

- Updated the CLI to conditionally log detailed import information based on the `--json` flag, improving usability for users who prefer JSON output.
- Introduced a new API endpoint for importing Figma files, handling file uploads and validating project existence, with appropriate error responses for missing files or invalid URLs.
- Added a dedicated route for the Figma import functionality, ensuring seamless integration with existing project workflows.

These changes improve the Figma import experience by providing clearer output options and robust error handling, enhancing overall user interaction with the CLI and API.

* feat(design-files): enhance DesignFilesPanel with new actions and styles

- Added new action buttons for opening a browser and creating a design system in the DesignFilesPanel, improving user interaction in the empty state.
- Updated styles for action buttons to enhance visual distinction and usability.
- Enhanced tests to verify the functionality of new actions in the DesignFilesPanel, ensuring they trigger correctly.

These changes improve the user experience by providing additional functionality and clearer visual cues in the design files interface.

* fix(ci): restore new project modal flow

* fix(ci): align design kit and onboarding checks

* fix(ci): sync bake preview workflow action

* fix(ci): include plugin preview helper scripts

* fix(review): harden brand source and preview flows

* fix(ci): stabilize web workspace tests

* fix(review): address latest blocking feedback

* chore(ci): retrigger validation after label update

* chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714)

* refactor(lightbox): implement portal for overlays to resolve z-index issues

- Updated the lightbox component to use React's createPortal for rendering overlays directly to the <body>, ensuring proper z-index stacking.
- Removed session mode toggle from HomeHero and adjusted related styles and tests accordingly.
- Cleaned up CSS by removing unused styles related to session mode toggle.
- Updated tests to reflect changes in the HomeHero component and its interaction with the design router.

* style(home-hero): remove focus halo from template search input

- Updated CSS to eliminate the global input focus outline and box-shadow for the template search field in the HomeHero component.
- Added a test to verify that the template picker search field maintains a clean appearance when focused.

* feat(design-system): add create design CTA and enhance design kit functionality

- Introduced a new `DesignSystemCreateCta` component to facilitate creating new designs from an active design system, enhancing user experience in the chat interface.
- Updated `ChatPane` to include the new CTA, allowing users to create designs directly from the chat.
- Enhanced `DesignKitView` with sticky header functionality for better accessibility while scrolling.
- Added new CSS styles for the `DesignSystemCreateCta` component to ensure a visually appealing and consistent design.
- Updated internationalization files to include new strings for the design system creation feature.

* feat(upload): enhance file upload handling and error recovery

- Introduced `sanitizePath` to preserve directory structures during file uploads, preventing issues with subdirectory paths.
- Updated `DesignKitView` and related components to utilize the new `sanitizePath` function for improved file name resolution.
- Added `KitErrorBoundary` component to gracefully handle rendering errors in the design kit, providing a user-friendly fallback.
- Implemented internationalization updates for new error messages and action confirmations related to uploads and error handling.
- Enhanced CSS styles for better visual feedback during error states and improved user experience.

* feat(design-kit): add keyboard shortcuts hint and enhance key handling

- Introduced a new keyboard shortcuts hint in the DesignKitView, providing users with quick access to essential actions (E edit, C copy, U upload, R refresh, ⌫ delete logo).
- Implemented a keydown event handler to manage keyboard shortcuts contextually within the design kit, improving user interaction and accessibility.
- Updated CSS for the shortcuts hint to ensure it remains low-contrast until hovered, enhancing the UI experience.
- Added internationalization support for new shortcut labels and hints across multiple languages.
- Adjusted DesignSystemsTab to prefer user logos for their systems, improving visual consistency.

* feat(design-system): introduce DesignSystemExtractionPanel and enhance design system interactions

- Added the `DesignSystemExtractionPanel` component to facilitate user interactions during design system extraction, providing a synthesized conversation view and next steps.
- Updated `ChatPane` to render the new extraction panel when a design system is active, enhancing user guidance.
- Introduced a new utility function `designSystemExtractionSource` to derive human-readable labels for design system sources.
- Enhanced internationalization support with new strings for extraction-related actions and prompts across multiple languages.
- Updated various components and tests to reflect changes in terminology and functionality, improving overall user experience.

* feat(project): add project deletion functionality and enhance design system interactions

- Introduced `onDeleteProject` prop in `ProjectView` to handle project deletion, improving project management capabilities.
- Updated `AppInner` to include the new delete project handler, enhancing user experience in project interactions.
- Enhanced `DesignKitView` and `DesignSystemsTab` with loading states and improved visual feedback during design system resolution.
- Removed deprecated `DesignSystemCreateCta` component and associated styles, streamlining the codebase.
- Updated internationalization files to reflect changes in project management terminology and actions.

* feat(design-kit): enhance internationalization and user feedback in DesignKitView

- Updated various labels and error messages in the DesignKitView to utilize internationalization functions, improving accessibility and user experience.
- Enhanced color input validation messages and added confirmation prompts for design system deletions in DesignSystemsTab and FileWorkspace.
- Introduced new props for handling design system project deletions, streamlining project management.
- Updated internationalization files to reflect new strings and translations for improved user guidance across multiple languages.

* refactor(design-kit): remove keyboard shortcuts hint and streamline header menu

- Eliminated the keyboard shortcuts hint from the DesignKitView, simplifying the header menu.
- Updated the sticky-header overflow menu to exclude upload, full-system preview, and shortcut help actions, focusing on essential project operations.
- Adjusted related tests to reflect the removal of the shortcuts hint and ensure accurate menu item visibility.

* feat(brand-routes): add extract-from-html endpoint for brand extraction

- Introduced a new POST endpoint `/api/brands/:id/extract-from-html` to re-run brand extraction using HTML rendered from the in-app browser after clearing anti-bot walls.
- Implemented error handling for missing HTML and brand not found scenarios.
- Enhanced the `extractBrandFromHtml` function to process the provided HTML and optional CSS, integrating it into the existing brand extraction workflow.
- Updated `prefetch` functionality to support extraction from pre-rendered HTML, improving the overall brand data retrieval process.

* chore(nix): refresh pnpm deps hash

* feat(brand-cli): add extract-from-html command for brand extraction

- Introduced a new CLI command `od brand extract-from-html` to facilitate brand extraction from pre-captured rendered HTML, allowing users to bypass anti-bot walls.
- Enhanced the command to accept optional CSS and base URL parameters, improving flexibility in extraction scenarios.
- Implemented error handling for missing HTML input and invalid brand IDs, ensuring robust user feedback.
- Updated the `BRAND_USAGE` documentation to reflect the new command and its usage details.
- Adjusted server configuration to accommodate larger payloads for the new extraction endpoint.

* feat(design-system): enhance design system extraction and browser tools

- Added a new script to collect CSS styles from rendered pages, improving brand extraction capabilities by capturing computed styles from cross-origin stylesheets.
- Removed the `DesignSystemExtractionPanel` component and its associated styles, streamlining the codebase.
- Updated `ProjectView` and `FileWorkspace` components to enhance design system interactions and improve user experience.
- Introduced new internationalization strings for design system phases and actions, ensuring better user guidance across multiple languages.

* feat(brand-assist): implement browser assist for brand extraction

- Added support for a client-side confirmation mechanism for the brand-browser-assist od-card, allowing users to extract brand information from the unblocked browser DOM.
- Enhanced the `ProjectView`, `ChatPane`, and `AssistantMessage` components to handle the new assist functionality, improving user interaction during brand extraction.
- Introduced new internationalization strings for browser assist prompts and messages, ensuring clarity and guidance across multiple languages.
- Updated the `useBrandReadyPrompt` hook to manage the state of the browser assist, providing a seamless user experience when dealing with anti-bot walls.

* feat(brand-prompt): enhance BrandReadyPrompt with refinement options

- Updated the BrandReadyPrompt component to include options for AI optimization and manual editing, allowing users to refine extracted brand systems.
- Added a new refinement nudge to inform users that automatic extraction may miss details, improving user guidance.
- Adjusted styles for the prompt and dismiss button for better alignment and visual consistency.
- Introduced new internationalization strings for the refinement features, ensuring clarity across multiple languages.
- Removed deprecated PPTX export functionality from the FileViewer component, streamlining the export options.

* refactor(export): remove PPTX export functionality and streamline export options

- Eliminated PPTX export support across various components, including CLI, desktop, and web, to simplify export formats.
- Updated documentation and help messages to reflect the removal of PPTX, ensuring clarity for users.
- Adjusted export-related types and constants to focus on PDF and image formats only, enhancing code maintainability.
- Improved user experience by refining export options and related UI elements.

* refactor(export): remove PPTX references and update export functionality

- Removed all instances of PPTX export functionality from the codebase, including related dependencies and comments.
- Updated export options to focus solely on PDF and image formats, enhancing clarity and maintainability.
- Adjusted UI components and tests to reflect the removal of PPTX, ensuring a streamlined user experience.
- Improved internationalization strings and documentation to align with the new export capabilities.

* chore(nix): refresh pnpm deps hash

* fix(onboarding): preserve selected runtime

* fix(brand): localize generated kit copy

* fix(onboarding): align first-run flow with main

* fix(nav): use palette icon for design systems

* fix(analytics): use design system onboarding step

* fix(ui): remove design system guide toggle

* fix(ui): position design system ready prompt

* fix(ui): space plugin task notice

* fix(web): restore home ask mode and design kit preview

* test(e2e): align onboarding visual capture

* test(e2e): align amr onboarding checks

* fix(brand): remove blocked reference brands

* feat(onboarding): show profile choices as chips

* fix(home): prefer design system cover art in recents

* test(e2e): select onboarding profile chips

* feat(brand-extraction): implement programmatic extraction transcript and UI enhancements for design systems

* feat(brand-extraction): enhance programmatic extraction with transcript agent support and UI improvements

* feat(brand-extraction): add transcript agent resolution and improve message handling in brand extraction

* fix(design-systems): stabilize loading state coverage

* test(e2e): align design system detail visual

* fix(brand-extraction): backfill programmatic transcripts

* fix(web): refresh ready brand design systems

* fix(brands): stabilize extraction handoff and seed colors

* fix(brands): return extraction transcript immediately

* fix(web): open new project modal from entry rail

* fix(editing): expose content edits for plain targets

* feat(file-viewer): implement manual edit draft dirty state tracking and reset logic

* feat(design-system): enhance project creation flow with conversation ID handling

* feat(brands): implement light theme handling for color extraction and seed generation

* feat(brands): add finalizeBrandProject function for brand project completion

* feat(file-workspace): add designSystemBrandId prop and update DesignSystemProjectPanel to use it

* Fix manual editing for brand kits

* fix(design-system): wait for project refreshes

* fix(web): open new project modal from rail

* fix(web): restore home ask mode toggle

* fix(web): sync brand color edits with seeds

* fix(web): stabilize design system workspace tests

* test(tools-pack): relax Windows resource cache timeout

* chore(pr): retrigger review after validation

* fix(web): surface design kit action progress

* fix(web): clarify brand next-step actions

* fix(web): cancel programmatic brand extraction

* fix(web): add design systems tab action feedback

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: xne998808-ai <xne998808@gmail.com>
Co-authored-by: PerishCode <perishcode@gmail.com>
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
Co-authored-by: lefarcen <935902669@qq.com>
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-06-25 03:56:14 +00:00
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>
2026-06-24 09:51:32 +00:00
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>
2026-06-24 01:10:43 +00:00
lefarcenandGitHub 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.
2026-06-23 15:39:16 +00:00
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>
2026-06-23 15:34:40 +00:00
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>
2026-06-23 13:13:09 +00:00
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>
2026-06-23 08:41:22 +00:00
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>
2026-06-23 08:08:18 +00:00
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>
2026-06-23 07:17:07 +00:00
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>
2026-06-23 06:40:45 +00:00
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>
2026-06-23 06:13:21 +00:00
PerishFireandGitHub 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
2026-06-23 04:01:49 +00:00
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>
2026-06-23 02:15:31 +00:00
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>
2026-06-22 04:51:04 +00:00
PerishFireandGitHub 358a426b2b ci: rename atom workflows (#4522) 2026-06-19 03:22:43 +00:00
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>
2026-06-18 08:37:18 +00:00
PerishFireandGitHub c782aeb3bb ci: stop duplicate post-merge validation (#4469) 2026-06-17 10:04:03 +00:00
PerishFireandGitHub b49c9b7932 Refine CI hot and full validation modes (#4466) 2026-06-17 09:32:29 +00:00
PerishFireandGitHub 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
2026-06-17 08:24:52 +00:00
AmyandGitHub 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
2026-06-17 16:01:53 +08:00
PerishFireandGitHub 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
2026-06-17 14:21:04 +08:00
PerishFireandGitHub 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 5cbc48c0af.

* Cache better-sqlite3 Nix binding separately

* Revert "Cache better-sqlite3 Nix binding separately"

This reverts commit 0384e3787e.

* Remove unused Nix cache setup from CI

* Use Blacksmith ARM for lightweight CI jobs
2026-06-17 12:53:06 +08:00
Marc ChanandGitHub 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)
2026-06-17 12:25:18 +08:00
lefarcenandGitHub 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.
2026-06-16 13:00:24 +00:00
PerishFireandGitHub eba8ea8cc3 chore(release): register prerelease workflow placeholder (#4361) 2026-06-15 14:07:44 +00:00
PerishFireandGitHub 3ffc964045 Rename CI provider workflows (#4342) 2026-06-15 11:49:58 +00:00
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>
2026-06-12 12:11:56 +00:00
AmyandGitHub 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)
2026-06-12 04:41:49 +00:00
lefarcenandGitHub 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
2026-06-11 19:32:11 +08:00
lefarcenandGitHub 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.
2026-06-11 18:44:40 +08:00
lefarcenandGitHub 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.
2026-06-11 18:02:22 +08:00
AmyandGitHub 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)
2026-06-11 09:09:00 +00:00
lefarcenandGitHub 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.
2026-06-11 16:45:41 +08:00
PerishFireandGitHub 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
2026-06-11 07:33:50 +00:00
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>
2026-06-11 04:55:36 +00:00
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>
2026-06-11 02:39:32 +00:00
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>
2026-06-10 14:18:29 +00:00
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>
2026-06-10 09:41:10 +00:00