57 Commits

Author SHA1 Message Date
lefarcen 34ac405bfe fix(release): gate the Thursday patch cut on the previous patch, not the minor base (#7977)
* refactor(release): extract the patch-cut pre-flight out of inline shell

Faithful port, no behavior change: resolve-patch-cut.ts computes the same
version and the same X.Y.0 gate target the shell did, and runs the same
gh release lookup. Moving it out of YAML is what makes the gate decision
testable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(release): pin the patch cut on the previous patch, not the minor base

Red against the current guard: with release/v0.22.2 cut and unshipped, the
pre-flight gates open-design-v0.22.0, finds it published, and cuts v0.22.3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(release): gate the Thursday patch cut on the previous patch

The guard checked the minor base X.Y.0, so it only ever guarded a line's
first patch. Once X.Y.0 shipped, X.Y.2, X.Y.3, ... all passed no matter how
many unshipped releases had stacked up beneath them.

Gate on the release the cut actually stacks on — the highest release/vX.Y.Z
below it — which is the same target cut-release.yml uses for the Tuesday
minor, and still gives the answer the old minor-base comment wanted (cutting
0.15.1 checks open-design-v0.15.0, not the older 0.14.0).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(release): surface patch gate lookup failures

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 08:03:53 +00:00
open-design-crew[bot] dd278cea65 fix(release): allow publishing whats new from release branches (#7902)
Co-authored-by: zhouzheng <zhouzheng@refly.ai>
2026-09-08 13:25:44 +00:00
lefarcen 3ddff71205 feat(ci): publish the What's New card from the repository (#7892)
* feat(ci): publish the What's New card from the repository

Move the post-update highlight document into the repo and publish it from
GitHub Actions, so changing the card is a pull request instead of a task
that needs personal Cloudflare credentials.

- docs/whats-new.json is the source of truth, next to its spec doc.
- pnpm guard validates it through the daemon's own parser on every PR, so a
  document that would silently show no card cannot merge.
- whats-new-publish.yml uploads on merge to main (and on manual dispatch,
  with a read-only dry-run mode) and verifies the object by reading it back
  from the origin the daemon fetches, past the edge cache.

* docs: name the CI job that actually runs the guard check

* fix(ci): gate the What's New publish behind a main-only environment

A workflow_dispatch runs the workflow file from the ref it is dispatched
against, so the in-workflow main-only assertion was editable by whoever
triggered it: anyone with write access could publish unreviewed content to
every installed client from their own branch.

Split the workflow so credentials live in one place and that place is gated by
GitHub rather than by YAML. `validate` holds no secrets and runs on any ref;
`publish` declares the `whats-new-publish` environment, whose deployment-branch
policy allows main only and which owns the R2 secrets. Dropping the environment
declaration to escape the branch policy also drops the secrets.

Also points the card at the new Arena URLs supplied by product.

* fix(ci): pin the What's New publish trust boundary in a check that runs

The missing-credentials error still told operators the R2 keys are
repository secrets. Following that string is the exact setup the previous
revision closed: repository secrets are readable from any job on any
branch, so a modified workflow dispatched from an unreviewed ref could
publish to every installed client — or just take the keys. Point the
error at the `whats-new-publish` environment and refuse the repository
path in the same breath.

The boundary itself had no coverage: nothing read the workflow, so moving
`environment:` or a `secrets.*` expression back onto the arbitrary-ref
job would stay green while still publishing correctly from main.

That coverage cannot be lane-routed. Running the planner over
`.github/workflows/whats-new-publish.yml` selects neither
`web_tests_required` nor `ui_p0_validation_required`, so the `e2e_vitest`
workload does not run for a workflow-only edit — an assertion living only
there would be skipped for exactly the change class it guards. The check
therefore lives in `pnpm guard`, which CI invokes from the unconditional
preflight job, same reasoning as the document guard next to it.

It asserts that the credential-bearing job declares the environment by
its exact name, needs `validate` and is gated on its output with no
`always()`/`cancelled()` escape, reads all four bucket bindings, and that
no other job attaches an environment or reads any secret at all.

The e2e suite drives it over the real workflow mutated into each of those
regressions in turn, so every assertion is proven able to fail; gutting
the checker turns all eleven red.

* chore(whats-new): seed the document from the live card

Bringing the workflow in with new copy attached ties the infrastructure to
a content decision. The Arena CTA this document pointed at is not live —
`/llm-arena-for-design/` and `/zh/llm-arena/llm-arena-for-design/` both
404 — and the card renders regardless, so merging would have put a dead
button on every client's Home.

Seed the repository copy from the object that is already serving instead:
byte-identical to `https://whatsnew.open-design.ai/whats-new.json`, id
`0.21.0`. That puts the current state under version control and makes the
first automatic publish idempotent — it uploads exactly what is already
there, so the merge exercises the whole chain (validate, upload, read-back
against the origin the daemon fetches) without changing anything a user
sees.

The Arena copy is not lost; it moves to the PR body as a follow-up for the
release owner, who can now change the card through this workflow without
holding personal Cloudflare credentials.

* fix(ci): detect secrets by context, not by access shape

The guard added last round matched `${{ secrets.NAME }}` with a regex over
the raw workflow text. A guard whose whole job is to stop a secret reaching
the arbitrary-ref job had a blind spot for most of the ways to read one.

Verified against the previous implementation, each returning zero
violations while a secret reached `validate`: `secrets['NAME']`,
`secrets["NAME"]`, `secrets [ 'NAME' ]`, `secrets . NAME`,
`toJSON(secrets)`, a bare `secrets`, and `fromJSON(toJSON(secrets)).NAME`.

Extending the regex would have repeated the mistake that caused it. One
bypass proves why: a double-quoted YAML scalar may carry a backslash line
continuation, so

    STOLEN: "${{ sec\
      rets.NAME }}"

parses to a live secret read while the source text contains no such
substring. No text scanner can see that, however many shapes it lists.

So the workflow is read through a YAML parser, and detection is a single
property instead of an enumeration: GitHub's expression language has no
string-to-context evaluation, so a secret cannot be reached without naming
the `secrets` context as an identifier. Finding that identifier inside any
`${{ … }}` expression covers every access form, including ones nobody has
thought of.

Parsing also retires the hand-rolled job splitter and its own failure
modes, and lets the check distinguish "no violations" from "nothing was
examined" — an unparseable document, a flow-style jobs mapping, or a
renamed publish job now fail closed rather than passing quietly.

Two holes the shape list never covered are closed as a side effect: a
job-level `secrets:` key passes credentials to a called workflow with no
expression at all, and workflow-level `env:` is inherited by every job.

`yaml@2.9.0` becomes a root devDependency. It was already in the lockfile
via `tools/release` and pinned repository-wide by the existing pnpm
override, so the lockfile gains only the root importer entry.

Test coverage moves with it: 26 of 46 cases now go red when the checker is
gutted, up from 11.

---------

Co-authored-by: lefarcen <ontf116@gmail.com>
2026-09-08 11:14:16 +00:00
lefarcen a07dc736fd perf(ci): take prerelease validation off the release pipeline, and report it on one live card (#7842)
* perf(ci): make prerelease mac Intel opt-in

build_mac_intel is the prerelease critical path: ~33 min on the
macos-15-intel fleet against ~21 min for mac arm64 and Windows, which
stretches every release-branch push to ~56 min end to end.

Gate it behind a new `enable_mac_x64` input that defaults to false, the
same shape build_linux already uses, and let publish tolerate a skipped
Intel job. Push-triggered prereleases now finish in ~21 min; a manual
dispatch can still turn Intel back on.

The stable promotion gate is unchanged and still hard-requires
platforms.macIntel in the promoted prerelease metadata, so an Intel-less
prerelease fails promotion loudly rather than shipping unvalidated.

* perf(ci): stop the prerelease pipeline waiting on work it does not need

Run 34102146769 took 56m, 42m of which survived turning macOS Intel off.
Four things in that 42m were wall clock nobody was buying anything with.

Tests no longer sit in front of packaging. functional_e2e, e2e_vitest,
daemon_unit_tests and verify take ~11 min; packaging takes ~21 min, and the
build jobs needed all four before they could start. They now need only
metadata and run alongside the tests. Nothing about the safety of a release
changes: `publish` is what writes metadata.json, and it already required all
four gates to be `success` — a red suite still cannot ship. What it costs is
CI minutes, since a failing gate now stops builds that have already run.

The Feishu card stops cloning the repository to read one `git log`. That
checkout was `fetch-depth: 0` against a 2.2 GiB pack and took 4m09s to
produce a `PREV..CUR` commit list. The list now comes from the compare API,
reproducing `git log --no-merges --pretty=format:'%s (%h)'` line for line —
verified against 35 commits of real history, where the only difference was a
leading space that feishu.ts trims anyway. Every fallback the old script had
(no baseline, unreachable commit) still produces an empty changelog rather
than a failed notification.

Packaged smoke and Apple notarization become opt-in, like Intel already is.
Smoke is advisory — `continue-on-error`, so it has never blocked publication
— and costs ~2.5 min on the Windows critical path. Notarization is a
round-trip to Apple that no prerelease consumer needs. Signing is NOT
optional and stays on: prepare-stable.ts requires platforms.mac.signed ==
true, so an unsigned prerelease could never be promoted. `mac_sign_mode: no`
is offered for parity with beta and now correctly reports signed=false
rather than lying in the metadata.

Windows drops setup-node's pnpm store cache and every job checks out
shallow. Measured across four prerelease runs the store cache missed 3 times
out of 4, costing 100s/66s/43s to save with no install speedup, and on its
one hit traded a 32s restore for a 36s faster install — while competing for
the 10 GB cache budget against tools-pack caches worth ~11 min each.
Nothing in this workflow reads git history.

* perf(ci): let a prerelease ship on the build's schedule, not the tests'

prerelease is the internal channel. Its job is to put a package in a
colleague's hands so a human can test it; automated CI is a second, parallel
opinion about the same commit. Wiring the two in series meant a single flaky
suite cost the team a whole day's package — the expensive failure, and the
one this pipeline kept producing.

The four test jobs (functional_e2e, e2e_vitest, daemon_unit_tests, verify)
now gate nothing. They are absent from `build_*` and from `publish`. They
still run on every prerelease and their results leave the workflow as
outputs — functional_e2e_result and friends, plus a tests_all_green /
failed_test_jobs rollup — so the notification layer can raise the alarm
without holding the package hostage. Stable is untouched: its promotion gate
reads the prerelease metadata and runs its own checks.

The packaged smoke suites move for the same reason. They were already
`continue-on-error`, so a smoke failure never failed the job — but they sat
between the build and the R2 upload, which meant the package waited on them
and a hung smoke could still eat the job. Every smoke now runs AFTER
"Publish <platform> platform" and "Upload <platform> publish manifest", and
every step after that manifest upload is continue-on-error, so nothing
post-delivery can fail a build job and retroactively block publish.

The results travel through a `test_signals` job rather than the
`jobs.<job_id>.result` expression GitHub documents for workflow_call outputs,
because actionlint has rejected that property since 2023
(rhysd/actionlint#343) and ci.yml runs actionlint at a pinned version.

One consequence to hand to the notification work: a failed advisory job now
makes the overall workflow conclusion red even though the package shipped, so
`needs.build.result` is no longer the right thing to colour a card by.
`version_metadata_url` is the authoritative "did a package ship?" signal and
is documented as such.

* perf(ci): move prerelease validation out of the pipeline it validates

release-prerelease.yml holds one repository-wide concurrency group with
cancel-in-progress: false, so any job that outlives `publish` keeps that
group held and stops the NEXT prerelease from starting. Making the test
jobs advisory did not fix that: they still held the group for 45 minutes
after the package had shipped.

The pipeline is now metadata -> build_* -> publish plus two dispatcher
jobs that nothing depends on. The four code suites, the packaged
mac/Windows smoke, and the release card each run in a workflow of their
own, keyed on the origin run.

The packaged smoke no longer installs electron-builder's output
directory on the machine that produced it. It reads the published
version metadata, downloads the DMG / setup.exe out of R2, verifies its
sha256 sidecar, and writes it to the single path
`tools-pack <platform> install` reads - which is why the specs need no
changes. That is both a better test and what lets the smoke live on a
different runner at all.

The release card becomes progressive. A Feishu application bot can PATCH
a message it sent, so one card is created when the first platform lands
in R2 and edited in place as the rest of the platforms, the code tests,
and the smoke report in. A PATCH replaces the whole card, so exactly one
job may write it: the watcher owns the message and polls the three runs.
The card is withheld until a package exists, because a "building..."
card posted at dispatch time becomes a permanent lie when a build dies -
with total build failure as the one exception, where silence would hide
an incident.

macOS Intel goes back to on-by-default: stable ships Intel and stable's
gate is a validated prerelease artifact, so an Intel-less prerelease
cannot be promoted at all. The switch stays, forwarded as
`github.event_name != 'workflow_dispatch' || inputs.enable_mac_x64` -
`inputs.enable_mac_x64 || true` would be unturnoffable, because a
dispatch with the box unchecked also yields true.

Also fixes the signal batch 2 left wrong: a reusable workflow reports
failure if any job inside it failed, including work that runs after
publication, so `needs.build.result` painted the download card red while
its own buttons worked. `version_metadata_url` is non-empty only when
publish wrote metadata, and now drives both the header colour and the
smoke annotations.

Two topology tests were already red on this branch - batch 2 changed the
workflow without updating them. Both now assert the new invariant.

The existing webhook card stays until the new one has carried a couple
of real releases; a gap in release notification is worse than a
duplicate.
2026-09-07 17:38:33 +00:00
lefarcen d18ada9e35 fix(ci): make the DSH upstream drift card readable (#7824)
The maintainer who received this alert could not tell what it was. The
headline jumped straight to a verdict, so nothing said an upstream release
had been detected or where the message came from; the body opened with
semver theory; the blocking branch never mentioned the version we pin,
making the most urgent card the least informative one; and nothing said
when upstream shipped, so there was no way to tell a release from this
morning apart from one that had been sitting for a fortnight.

The headline now names the check and only the check — severity travels in
the header template, which Feishu renders before any text. The first
sentence is three facts with no reasoning attached: what upstream
released, when, and what we still pin, on both severities. The
consequence follows, and the semver rationale is compressed into a
parenthetical behind it. The three-file checklist is untouched.

Reading the publish date means asking npm for the full packument: the
compact one (`application/vnd.npm.install-v1+json`) carries no `time` map
at all. `readPublishedAt` degrades to null for every shape short of that,
because the date is one sentence of context on an alert whose entire
reason to exist is being sent.

Also replaces a self-check assertion that could never fail:
`String(card.header).length === 0` is "[object Object]".length whatever
the card says. It now reads the fields a human reads.

Claude-Session: https://claude.ai/code/session_01B8MaA1ABkvbnfaYUKw3UH3
2026-09-07 07:01:15 +00:00
PerishFire 8ab7c3d5f1 feat(standalone): establish the exact lifecycle and Terminal carrier (#7716)
* feat(standalone): add shell-neutral exact lifecycle

* feat(terminal): add native exact cold-start carrier

* test(terminal): exercise mutable exact channel heads

* feat(standalone): model synced blobs and Shell updates

* feat(terminal): exercise the complete Shell lifecycle

* feat(release): add the independent exact Terminal line

* test(exact): run the full local Terminal release line

* feat(terminal): harden exact Sidecar lifecycle

* feat(standalone): prove exact update transactions

* refactor(standalone): separate shell lifecycle dimensions

* refactor(e2e): decouple terminal exact drivers

* refactor(terminal): isolate exact sidecar fixture host

* feat(terminal): gate handoff on physical retirement

* refactor(release): gate exact activation on public acceptance

* test(terminal): formalize guarded lifecycle algebra

* fix(terminal): guard shell installer handoff

* feat(terminal): add exact bootloader handoff

* feat(terminal): integrate exact sidecar lifecycle

* refactor(exact): rename validation channels

* test(terminal): harden exact release acceptance

* fix(postinstall): build standalone dependencies first

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(ci): build the native Terminal scene

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* test(ci): cover exact convergence handoff routing

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(exact): isolate terminal validation from main CI

* fix(terminal): complete shell update confirmation

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(exact): bind acceptance to installed manifest

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(standalone): block occupied shell installs

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-09-03 03:50:34 +00:00
Marc Chan 1d2150007b chore: remove extracted landing-page from the monorepo (#7727)
* chore: remove extracted landing-page from the monorepo

The marketing site now lives in a separate repository. Drop the
Astro app, its GitHub workflows/scripts, and the remaining
monorepo coupling so this workspace no longer builds or deploys it.

* fix: drop leftover landing-page coupling from Teams and skills

Point development enterprise URLs at the deployed marketing origin,
remove nextjs-app skill outputs that no longer emit a page, and stop
advertising apps/landing-page in plugin marketplace metadata.

Generated-By: looper 0.12.0 (runner=fixer, agent=omp)

* fix(ci): restore DSH installer E2E Vitest routing

Removing dsh-bootstrap-identity dropped the only planner route from
tools/release/resources/dsh-bootstrap/ to web_tests_required, so
installer-only PRs skipped e2e/tests/dsh-installer-version-policy.test.ts.

Generated-By: looper 0.12.0 (runner=fixer, agent=omp)

* docs: drop leftover landing-page paths from template assets

The skill contract no longer advertises the extracted Astro app, but
styles.css, compose.ts, and example.html still named deleted
apps/landing-page sources. Describe the standalone template behavior
instead and keep the bundled plugin example in sync.

Generated-By: looper 0.12.0 (runner=fixer, agent=omp)

* docs: keep landing-page off review and test surfaces

Add apps/landing-page to the forbidden-surface review list and stop naming the extracted path in plugin registry acceptance tables.

Generated-By: looper 0.12.0 (runner=fixer, agent=omp)

* fix: drop leftover landing-page refs and bump plugin version

Stop naming the deleted apps/landing-page in the landing-deck demo and
plugin-registry plan. Bump example-open-design-landing to 0.1.1 and keep
the official catalog version aligned with the bundled manifest.

Generated-By: looper 0.12.0 (runner=fixer, agent=omp)
2026-09-03 03:21:08 +00:00
Marc Chan 241f520037 feat(release): publish versioned catalog and DSH snapshots (#7617)
* feat(release): publish versioned catalog and DSH snapshots

Add tools-release catalog export/pack/publish and a canonical DSH
bootstrap source so landing can later consume immutable R2 snapshots
without globbing the monorepo tree.

* fix(release): harden catalog snapshot publishing

* fix(release): use portable tar ownership flags

* fix(release): resolve catalog root from git checkout

* fix(release): stabilize catalog preview assets

* fix(release): close catalog publish determinism gaps

* fix(release): remove mutable catalog runtime metadata

* fix(release): prevent stale catalog pointer updates

* fix(release): fail all-fallback catalog previews

* fix(release): fail previews with blocked dependencies

* fix(release): export localized design system bodies

* fix(release): pin catalog preview environment

* fix(release): close catalog and bootstrap rollback gaps

* fix(release): fail partial catalog preview renders

* fix(release): render remote catalog entries locally

* fix(release): keep DSH bootstrap publishes fresh

* fix(release): parallelize catalog snapshot uploads

* fix(release): preserve hidden catalog assets

* fix(release): trigger catalog publish on storage helpers

Include common.ts and s3-upload.ts in catalog-publish and catalog-validate
path filters so publisher CAS, signing, and retry changes republish.

* fix(release): gate catalog provenance and DSH helper triggers

Validate complete provenance against the packed bundle before any
immutable upload. Include common.ts and s3-upload.ts in the standalone
DSH publisher path filter.

* fix(release): match provenance recordCounts to catalog

Derive counts from catalog.records and require an exact match before
any immutable upload so empty or stale provenance cannot become latest.
2026-09-01 03:25:37 +00:00
nettee 49cc51058f chore(nix): retire official Nix distribution (#7644) 2026-08-31 08:08:33 +00:00
PerishFire d03bf69d58 feat(standalone): complete Terminal exact lifecycle (#7319)
* feat(standalone): bootstrap terminal exact lifecycle

* feat(standalone): complete terminal exact lifecycle

* fix(ci): build standalone during postinstall

* fix(standalone): repair exact lifecycle recovery

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(standalone): make exact updates replay-safe

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(terminal): enforce replay-safe exact carrier

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(standalone): align active update replay state

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(standalone): preserve exact channel lifecycle

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-08-25 03:56:31 +00:00
lefarcen fb26b5cb8a fix(runtimes): support the dsh release line users are actually served (#7339)
* fix(runtimes): support the dsh release line users are actually served

Two days after #7153 widened the version policy from one release candidate
to the `0.1.0-rc.N` line, upstream published `0.1.1-rc.1` and then
`0.1.1-rc.2`, and made the latter the `latest` tag. Scoping to `0.1.0-rc.N`
was still a pin, just a longer one: every user on what npm now serves is
told their CLI is untested, our installer hands out a version two releases
behind, and the connection component's peers do not resolve against the
host they land on.

That last one is not a warning. semver only lets a prerelease satisfy a
range when some comparator carries the SAME major.minor.patch tuple AND a
prerelease tag, so `>=0.1.0-rc.6` matches nothing on the 0.1.1 line — a
verified `satisfies('0.1.1-rc.2', '>=0.1.0-rc.6') === false`, the same rule
that made the original pin uninstallable.

Move all three to the 0.1.x line: the policy accepts any `0.1.N`, release
candidate or stable; both installers pin `0.1.1-rc.2` with the resolution
window reset to just after its publish time; the peers gain a comparator
for the new line.

This will expire again. A comparator per prerelease line is what semver
allows, so `0.1.2-rc.1` will not resolve until someone adds one, and
nothing in the repo notices when upstream moves. The check that catches
installer/policy drift only compares the two against each other.

* ci: watch upstream dsh releases instead of waiting for a bug report

Twice in one week `@deepseek-ai/dsh` published a new release line while
this repo kept pinning the old one, and both times the detection mechanism
was a person reporting that their install did not work.

The check added with the first fix compares our installers against our
agent def. Both are our own config, so it stays green precisely when they
are equally out of date — which is the state that shipped, twice. Nothing
here looks at the registry.

Add a daily scheduled comparison against npm's `latest` that separates the
two failures worth telling apart: `behind` means our installer hands out an
older version than the registry serves, everything still works;
`unsupported` means a user who has what npm serves is outside what we claim
to support, which is an "untested" warning in Settings and peer ranges that
do not resolve. The card names all three files that must move together,
because forgetting the peer ranges is the one that breaks an install rather
than warning about it.

Standalone and outside the merge gate, like nix.yml and docker-image.yml:
an upstream release is not a reason to fail someone's PR. The workflow runs
`self-check` before touching the registry so a broken checker is loud
rather than quietly in-sync.

* fix(ci,runtimes): stop the drift watch losing what it exists to report

Four defects from review, each of which would have made this quieter than
having built nothing.

The widened policy over-claimed. `/^0\.1\.\d+(?:-rc\.\d+)?$/` accepts
`0.1.2-rc.1`, which the peer ranges cannot install — so the daemon would
suppress the untested warning for a version whose companion fails to
resolve, which is worse than the warning and silent. Narrow prerelease
acceptance to the tuples and floors the peers actually carry; stable
releases on the line need no comparator and stay accepted. This also
retires an over-claim that predates the branch: `0.1.0-rc.2` and `rc.3`
are published and below the `>=0.1.0-rc.6` floor.

`e2e/tests/dsh-installer-version-policy.test.ts` now runs both authorities
over a version matrix and fails when the def accepts anything the peers
reject, so the two cannot drift apart again.

Importing the script ran the CLI. Collecting the new test fetched the live
registry — measured at 354ms with the verdict printed — and once drift
existed would have attempted a real Feishu post before any assertion. Move
the dispatch behind a direct-entry check and prove it with a test that
stubs fetch and re-imports.

Feishu answers a rejected webhook with HTTP 200 and a nonzero code, so
treating every 2xx as delivered discards the alert and logs success doing
it. Mirror the landing notifier's contract: code 0 or absent is delivered,
429/5xx/9499 retry, anything else raises.

The workflow chose the landing webhook but signed with an unpaired secret,
so a bot that verifies signatures would reject the card. Pair them the way
the landing workflow does, and pin the mapping in the topology test.

* test(e2e): check each dsh peer range on its own

The compatibility guard scanned the whole manifest and merged every
comparator into one tuple table, so a single updated peer answered for the
other four. A bump that updates one range and forgets the rest — the
realistic way this goes wrong — read as compatible, which is the partial
state the guard exists to catch.

Parse `peerDependencies`, keep the `@deepseek-ai/dsh-*` entries, and require
every one of them to admit a version before calling it installable.
`cordis` is excluded: it has its own version line and never moves with dsh.

Two cases cover it: a fixture where one peer carries the new comparator and
the others do not, and the floor itself, since a comparator for the right
tuple still rejects a candidate below it. Verified against the real
manifest by downgrading four of the five ranges, which reds the matrix with
`expected [ '0.1.1-rc.1', '0.1.1-rc.2' ] to deeply equal []`.
2026-08-24 17:24:12 +00:00
PerishFire 993f2e1a9e feat(ci): add workload convergence shadow (#7311)
* feat(ci): add workload convergence shadow

* fix(ci): identify convergence public reads

* refactor(ci): move convergence policy into scripts

* fix(ci): fail open on malformed convergence reads

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(ci): preserve safe convergence observations

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-08-24 06:17:23 +00:00
PerishFire f3e4a3abbc ci: refine plan orchestration and Windows payload routing (#7223)
* docs(ci): define test set orchestration

* docs(ci): make orchestration guidance incremental

* ci: separate merge policy from validation

* ci: narrow Windows payload validation

* docs(ci): restructure orchestration methodology

* docs(ci): treat mapping shape as architecture feedback
2026-08-21 05:33:02 +00:00
PerishFire 84381efab9 feat(ci): add global hash skip planning (#7185)
* feat(ci): add global hash skip planning

* refactor(ci): remove superseded hash skip cache

* refactor(ci): keep scope trust in planner

* fix(ci): run scope goldens for planner changes

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(ci): publish hash state after successful validation

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-08-21 00:12:33 +08:00
PerishFire f1d6c5c4e4 feat(ci): preload hash skip and notification primitives (#7064) 2026-08-18 11:42:51 +00:00
PerishFire ab77eafeb1 refactor(release): retire dedicated betas and preview lanes (#7058)
* refactor(release): retire dedicated betas and preview lanes

* fix(updater): consume dynamic release channels

* fix(e2e): narrow counted release versions structurally
2026-08-18 11:27:28 +00:00
Joey-nexu f1a0b60c6c chore: unify product name display to OpenDesign repo-wide (#6998)
* chore: unify product name display to OpenDesign repo-wide

Rename the user-facing product name from "Open Design" to "OpenDesign"
across documentation, web/desktop UI strings, i18n locales, prompts,
plugin/skill/design-system content, changelogs, and specs, matching the
landing-page rename already in flight.

Deliberately excluded so installed-app identity stays untouched:
- packages/release PRODUCT_NAME and everything derived from it
  (app bundle names, installer/registry identity, updater feeds)
- tools/pack, tools/release, apps/packaged, landing page (PR #6983)
- mocks/ recorded traces and LICENSE
- test fixtures and comments that encode real on-disk install paths
  (Open Design.app, Program Files\Open Design, AppData paths)

Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host
test runs over the renamed strings.

* fix(web): keep two-letter author monogram for CamelCase brand names

authorInitials split words on whitespace only, so the renamed single-token
author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper
CamelCase boundaries as word breaks so the avatar fallback stays "OD".

* fix(release): restore spaced Windows source artifact names in win.ps1

tools/pack still derives builder outputs from the unchanged PRODUCT_NAME
("Open Design"), so the asset script must look for the spaced filenames.
Matches the mac/mac-intel/linux asset scripts, which were already kept on
the original names.

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-18 07:09:45 +00:00
lefarcen dba64f2e45 fix(landing): short HTML edge TTL + host purge after production deploy (#6552)
* fix(landing): shorten HTML edge TTL and host-purge after production deploy

Long s-maxage=3600 + stale-while-revalidate=86400 left locale HTML
(/zh/pricing/, /de/pricing/, …) on different edge objects after
landing-page-production, so some languages looked deployed and others
did not. Cap HTML/edge docs at s-maxage=60 without multi-hour SWR, and
purge open-design.ai by hostname after pages deploy (token needs Zone
Cache Purge). Hashed _astro assets stay immutable.

* ci(landing): add manual host-purge workflow for edge cache recovery

Allows testing CLOUDFLARE_API_TOKEN Cache Purge permission and one-click
recovery without a full production rebuild. Production deploy still purges
after pages deploy.

* ci(landing): support purge-only production workflow mode

Lets us exercise CLOUDFLARE_API_TOKEN Cache Purge without a full rebuild,
and keeps host purge as a post-deploy job after a real promote.

* ci(landing): move edge-cache purge helper to workflow-owned TypeScript

pnpm guard rejects project-owned .mjs, so the new purge helper blocked
Validate workspace. Convert it to TypeScript and move it next to the other
landing workflow helpers under .github/scripts/, invoked with
--experimental-strip-types like landing-page-deploy-feishu.ts.

Also drop the purge-only production mode: blog-indexing-on-deploy listens on
successful landing-page-production runs and cannot tell a cache-only recovery
run from a real deploy. Purging without a rebuild already has a dedicated
entry point in landing-edge-cache-purge, so production purge now runs only
after a successful deploy.
2026-08-13 14:46:04 +00:00
Amy 028bde50ad fix(release): harden prerelease E2E validation (#6755)
* fix(release): harden prerelease E2E validation

* fix(daemon): refresh active run tool tokens

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)

* chore(release): keep Feishu notification unchanged

* fix(daemon): retain media tasks for active runs

Associate token-authored media tasks with their owning run and defer terminal cleanup while that run still has an active tool token. Correct the prerelease status document to reflect the final Feishu notification topology.

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)

* test(e2e): defer system theme coverage

* test(e2e): remove retired design system settings flows

* fix(e2e): close prerelease coverage gaps

* fix(release): remove unrelated UI changes

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)

* fix(e2e): restore remaining prerelease coverage

* docs(e2e): record remaining scoped gaps

* test(e2e): isolate workflow command fixtures

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)

* test(e2e): preserve Node in workflow fixture PATH

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)

* fix(ci): skip Playwright apt deps on Blacksmith runners

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)

* Revert "fix(ci): skip Playwright apt deps on Blacksmith runners"

This reverts commit 201f3f3ee3.

* fix(plan): reliably open generated artifacts

* fix(plan): reject unconfirmed artifact paths
2026-08-12 11:29:11 +00:00
Ray Xi 7ecd35a746 fix(release): guard automated release trains (#6729)
* fix(release): guard automated release trains

* fix(release): resolve beta recovery before checkout

* style(release): group workflow outputs

* fix(release): publish daily beta snapshots without promotion
2026-08-11 06:06:53 +00:00
Marc Chan 4e47119edb ci: right-size UI P0 runners to medium (#6502)
Measured UI P0 shards fit nexu-runners-medium; drop the blanket xlarge
assignment and keep only project-collab on ui_p0_heavy (xlarge) because
multi-client collab has hit large/xlarge memory ceilings.
2026-08-06 21:17:25 +08:00
PerishFire 2a971e4f0e ci: add Blacksmith runner mode (#6533) 2026-08-06 18:44:44 +08:00
Marc Chan fef0ddf88e ci: auto-rerun once on runner/spot cancel (#6453)
* ci: auto-rerun once when runner/spot cancel kills ci jobs

Add rerun.atom.yml as a trusted workflow_run consumer that requests one
`gh run rerun --failed` when completed pull_request/merge_group ci runs
lose leaf jobs to infra cancel (shutdown signal / cancelled runner),
while skipping ordinary assertion failures, stale heads, and ejected
merge-queue entries.

* fix(ci): grant rerun atom read scopes and refuse mixed failures

Add checks/pull-requests read permissions so the helper can fingerprint
infra cancels, and require every non-success leaf to be infra-classified
before authorizing gh run rerun --failed.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fix(ci): require open PR for live-head stale gate

Stop treating a same-repo branch tip as live-PR evidence. Closed PRs can
leave the source branch at the failed run SHA with no open association;
query open pulls by repository-qualified head ref instead, and cover the
closed-PR + unchanged tip shape in self-check.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fix(ci): require infra marker for cancel rerun

Bare cancelled jobs without shutdown/spot annotations are ordinary
(manual cancel) and must not authorize gh run rerun --failed.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fix(ci): ignore aggregate gate jobs in infra-cancel classify

Validate workspace and Runtime summary fail as always() mirrors of leaf
outcomes and never carry runner/spot markers. Counting them as ordinary
blocked pure infra-cancel reruns whenever the workflow still reached the
gate. Exclude those names from classification and cover the shape in
self-check.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fixer: address PR #6453 follow-up items

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fix(ci): refuse mixed ordinary+infra job annotations

Jobs that record both a real failure annotation and a later runner
shutdown marker must classify as ordinary, not pure infra-cancel, so
gh run rerun --failed cannot auto-retry assertion failures.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fix(ci): paginate check-run annotations before infra classify

GitHub defaults annotations to 30 results; a later ordinary failure must still
block gh run rerun --failed. Walk pages like paginate_jobs and cover the split
with a multi-page request fixture.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fix(ci): drop tracked pycache from infra-cancel helper

PerishCode BLOCKING on #6453: remove the accidental CPython 3.14
bytecode under .github/scripts/__pycache__/ and ignore __pycache__/
plus *.py[cod] so local self-check/compile output stays out of git.

* fix(ci): open-PR merge_group freshness; ignore warning annotations

Merge-group infra-cancel retries no longer require the synthetic head to
still sit in the merge queue — workflow_run completed fires after required
checks already ejected it. Freshness is an open PR still associated with
the run (commit→pulls or gh-readonly-queue pr-N ref).

Warning-level annotations (e.g. scopes.ts full-plan fallback) no longer
count as ordinary failures that suppress pure infra-cancel retry.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)

* fix(ci): match merge_group PR heads and arm rerun e2e Vitest

Compare the live open PR head with the PR head SHA encoded in the
merge-queue branch before authorizing infra-cancel retry, and enroll the
rerun atom surfaces in scopes so PR CI runs the topology self-check.

Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)
2026-08-06 01:38:59 +00:00
Marc Chan 8e3853f0bc ci: route UI P0 jobs onto nexu-runners-xlarge (#6466)
UI P0 (especially project-collab) regularly drives the large runner
container working set to the 16Gi limit. Add a dedicated ui_p0 profile
on nexu-runners-xlarge and keep lighter browser jobs on large.
2026-08-05 11:41:14 +00:00
Marc Chan d87ba8e5b9 ci: route Linux validation to Nexu runners (#6407)
* ci: route UI P0 to Nexu runners

* ci: route Linux validation to Nexu runners

* ci: size Nexu runners by workload

* ci: preserve economic runner bootstrap

* test: budget scope process coverage

* ci: run browser validation on large runners

* ci: align runner mode bootstrap semantics

* ci: cap Playwright workers on large runners

* ci: match hosted Playwright concurrency

* test(desktop): accept full retry after interrupted download

* test(web): stabilize privacy choice click

* ci: use preseeded Playwright on Nexu runners
2026-08-04 18:09:58 +00:00
lefarcen 088ea9041f ci(landing): post a Feishu card after production deploys (#5290)
`landing-page-production` promotes the live site (open-design.ai) but
never announces it — the landing/QA group only learns the site changed
by noticing. This adds a `notify` job that fires once the deploy settles
(on success AND failure) and posts an interactive Feishu card, the
production-deploy analog of notify-release-feishu.

The card carries: status (green/red), the exact deployed commit + its
HEAD subject, who promoted it, their "why promote now?" reason, a live
"打开 open-design.ai" button, and the landing-relevant changelog since
the previous successful production deploy (non-landing commits on main
are filtered out as noise). Reuses FEISHU_LANDING_WEBHOOK with the
release webhook as fallback, matching landing-page-daily-feishu.

Card generation lives in .github/scripts/landing-page-deploy-feishu.ts
with a `self-check` command (asserts copy, sanitization, and the
failure/cold-start fallbacks) run as a workflow step before posting.
2026-07-08 06:20:47 +00:00
PerishFire e0d7a77230 fix(release): align stable notes public origin (#5037)
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-07-02 06:10:41 +00:00
PerishFire 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
PerishFire 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
lefarcen 4c6f6549c6 ci: add landing QA Feishu summary (#4763) 2026-06-25 07:59:45 +00:00
PerishFire 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
PerishFire 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
PerishFire 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
PerishFire 7a1ad1a438 feat(packaged): add launcher payload updates (#3812)
* feat(packaged): add launcher payload runtime

* ci: focus launcher payload windows smoke

* ci: publish beta mac x64 launcher payload

* test(e2e): harden payload updater smoke

* fix(pack): inject launch config for installed windows smoke

* fix(daemon): allow packaged payload resource roots

* fix(updater): relaunch after payload update

* test(e2e): refresh windows updater UI status

* test(e2e): ensure windows smoke reaches main shell

* test(e2e): skip onboarding before windows updater smoke

* fix(updater): detach windows payload relaunch helper

* test(e2e): run payload smoke without updater dry run

* fix(desktop): relaunch windows payload updates on quit

* fix(updater): preserve installed payload relaunch target

* fix(updater): require launcher target for payload updates

Generated-By: looper 0.9.5 (runner=fixer, agent=codex)

* test(e2e): align mac x64 artifact mode assertion

* test(e2e): canonicalize launcher relaunch root

* fix(updater): validate launcher payload config before activation

Generated-By: looper 0.9.5 (runner=fixer, agent=codex)

* fix(updater): require live launcher target for payload updates

Generated-By: looper 0.9.5 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-07 07:38:57 +00:00
PerishFire 8f6cdaaba1 Harden release-beta-s self-hosted beta lane (#3641)
* Fix Windows smoke launcher payload layout

* Harden release-beta-s runner probe

* Use cmd shell for release-beta-s probe

* Add win beta build script for self-hosted lane

* Keep release-beta-s checkout lightweight

* Use explicit local beta version for release-beta-s

* Install dependencies in beta build script

* Repair Electron dist before Windows beta build

* Add release-beta-s timings and diagnostics

* Polish release-beta-s observability guardrails

* Trace tools-pack cache materialization

* Reuse materialized workspace build cache

* Trace Windows packaging segments

* Trace Windows archive process details

* Reuse installer unpacked materialization

* Preserve reusable Windows unpacked projection

* Add fast smoke telemetry for self-hosted beta

* Cache Windows archive outputs for beta lane

* Tighten fast Windows smoke lane

* Scope Windows beta artifact reporting by target

* Clean stale Windows target artifacts

* Wire Windows Authenticode signing

* Expose self-hosted beta validation modes

* Add core Windows smoke profile

* Decouple core smoke from updater fixture version

* Trace Windows smoke lifecycle steps

* Skip unpacked materialization on NSIS cache hit

* Add Nexu S3 beta publisher

* Harden self-hosted Windows beta lane

* Use Windows PowerShell for publish probe

* Run publish probe via repo script

* Require explicit self-hosted S3 public origin

* Run real installer acceptance for external feeds

* Skip ready-prompt checks for external feeds

* Relax optional self-hosted update inputs

* Tolerate BOM in self-hosted publish probe

* Converge self-hosted beta metadata and signing flow

* Run self-hosted beta metadata prep on the runner lane

* Use Windows PowerShell for self-hosted beta metadata checks

* Bypass execution policy in self-hosted beta metadata checks

* Split self-hosted beta metadata publish

* Suppress mc output in beta publish helpers

* Split Windows NSIS payload cache layers

* Require fresh tool dist metadata

* Preserve Windows overlay payload paths

* Default beta-s gate to core

* Optimize win beta packaging reuse

* simplify windows full smoke lifecycle

* preserve onboarding state after windows upgrade smoke

* fix windows tools-pack test portability

* recover stale tools-pack locks

* Allow auto signing fallback for beta builds

* Add self-hosted beta mac arm64 lane

* Quote self-hosted signing choices

* Run self-hosted beta metadata on mac lane

* Use shallow metadata checkout on self-hosted beta

* Use sparse beta metadata checkout

* Make mac release profile bash-compatible

* Allow file sparse metadata checkout

* Isolate self-hosted metadata checkout

* Clear sparse skip-worktree before mac build

* Isolate self-hosted mac build checkout

* Run self-hosted beta publish without aws cli

* Use unsigned beta prefix for mixed platform publish

* Sparse checkout self-hosted beta publish

* Publish mac beta platform before metadata

* Pass mac publish release profile

* Probe mac signing keychain import

* Expand mac signing preflight variants

* Use sudo keychain helper for self-hosted mac signing

* Route self-hosted mac codesign through sudo wrapper

* Normalize mac signing identity name

* Harden self-hosted mac notarization

* Optimize self-hosted mac release cache

* Fix mac signing helper cleanup

* Retry transient mac notarization uploads

* Keep mac notarization fail-fast

* Reduce mac symlink diagnostics noise

* Normalize self-hosted beta platform inputs

* chore: bump beta base version to 0.9.1

* chore: tune release tools-pack cache retention

* test: add temporary cache validation marker

* chore: key windows nsis base payload by content

* Revert "test: add temporary cache validation marker"

This reverts commit e8487bfd3e.

* chore: probe windows nsis payload cache before materializing

* chore: align self-hosted beta smoke modes

* chore: skip mac smoke report upload when disabled

* chore: default self-hosted beta to publish both platforms

* chore: make beta signing modes explicit

* Add release report artifacts and metatool metadata

* Fix Windows release report zip publishing

* Remove artifact handoff from release beta self-hosted lane

* Fix release beta merge follow-ups

* Fix release beta install regressions

* Restore web build dependencies

* Route Windows beta downloads through mirrors

* Use PowerShell 7 for Windows beta lane

* Fix release beta preflight gates

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Fix Windows beta smoke update reuse

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Fix release beta review follow-ups

* Refresh Nix pnpm dependency hashes

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Fix Windows signtool fallback resolution

* Reject stale beta platform manifests

* Validate beta platform manifest run identity

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Validate beta manifest commit identity

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Allow beta metadata publish reruns

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Build tools-pack fully in beta Windows bootstrap

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Emit r2 metadata for Windows beta publish

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Stabilize beta publish browser test

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Stabilize Windows beta metadata smoke test

Generated-By: looper 0.9.4 (runner=fixer, agent=codex)

* Restore multi-platform Vela CLI package

* Refresh Nix pnpm dependency hashes

Generated-By: looper 0.9.5 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-05 03:00:55 +00:00
lefarcen 2305a35768 ci(release): split Feishu streams — daily=beta, release-push=nightly (#3474)
Follow-up to #3462. Two corrections:
- The per-push release/** stream must build the nightly channel (release-stable),
  not beta. The daily 09:00 stream stays on the lighter beta channel.
- The nightly card was missing the macOS Intel download button; release-stable
  builds mac arm64 + mac Intel (signed) + win unconditionally, so switching that
  stream to it adds the Intel button for free.

Changes:
- release-stable.yml: add workflow_call (channel/nightly_version/ref inputs +
  outputs incl. per-platform download URLs and previous_commit); add ref to every
  checkout; pin GITHUB_SHA to the resolved build HEAD in metadata/publish; capture
  the previous nightly commit before publish for the changelog baseline.
- release-beta.yml: expose mac_arm64_url + win_url as workflow_call outputs (the
  rest of its workflow_call interface from #3462 is unchanged; daily still uses it).
- notify-release-feishu.yml: call release-stable channel=nightly; CHANNEL_LABEL=Nightly.
- notify-daily-feishu.yml: keep calling release-beta; CHANNEL_LABEL=Beta.
- notify.ts: channel-agnostic title/changelog via CHANNEL_LABEL; download buttons
  from explicit per-platform URL outputs (mac_arm64/mac_intel/win/linux) instead of
  re-fetching metadata.json — guarantees the Intel button on nightly.

Ref plumbing mirrors release-beta: scheduled callers run on main but build a
release branch, so checkouts take inputs.ref and metadata/publish pin GITHUB_SHA
so metadata.json (and the next run's changelog baseline) records the real commit.
2026-06-02 07:17:21 +00:00
lefarcen 6ffa75aa5b ci(release): build beta + notify Feishu on release push and daily (#3462)
Reuse the release-beta pipeline as a workflow_call and add two thin caller
workflows that build a full beta ("nightly") package and post a Feishu card
with download links and the changelog since the previous beta:

- notify-release-feishu: triggers on every push to release/**, debounced
  (concurrency cancel-in-progress), notifies FEISHU_RELEASE_WEBHOOK.
- notify-daily-feishu: cron 01:00 UTC (09:00 Asia/Shanghai) + manual; resolves
  the active release branch (highest-semver release/vX.Y.Z) and builds it via
  the new ref input, notifies FEISHU_DAILY_WEBHOOK.

release-beta gains a workflow_call entry (enable_* booleans + optional ref +
outputs) so callers reuse one build source. Because scheduled callers run on
main but must build a release branch, every checkout takes inputs.ref and the
metadata/publish jobs pin GITHUB_SHA to the resolved build HEAD so the published
metadata.json records the real built commit (and the next run's changelog
baseline stays correct).

The changelog needs zero extra state: the baseline is the previous beta's commit
read from beta/latest/metadata.json before this run overwrites it; download links
come from this build's version metadata.json. notify.ts renders a Feishu
interactive card and supports optional signed bots (FEISHU_*_SIGN_SECRET).
2026-06-02 04:41:50 +00:00
lefarcen 551f967d2c ci(agent-pr-explore): rewrite prompt — non-lazy disposition + mandatory probe list (#3156)
* ci(agent-pr-explore): rewrite prompt — non-lazy disposition + mandatory probe list

Background: on PR #2355 (large AMR runtime add) the agent stopped at smoke level
because the positive path was gated on a missing `vela` binary. The old prompt
explicitly instructed "if setup prerequisites block, return inconclusive
immediately" + "do not spend more than two attempts on test data" + "do not run
arbitrary host shell commands", which made the agent give up rather than:
- use the PR's own `tests/fixtures/fake-vela.mjs`
- set the `VELA_BIN` env the runtime reads
- probe `/api/integrations/vela/*` directly via fetch

This rewrite shifts disposition + adds 4 structured unblock steps:

- **Mindset**: each /explore is a precious, expensive run; be thorough, not lazy.
- **STEP 0** Read PR body for `## Test Plan` section — declared cases = MUST-COVER.
- **STEP 1** Extract diff-driven probe list (new routes / components / env vars /
  fixtures / CLI flags). Anything skipped requires explicit written reason.
- **STEP 2** Before giving up, try (a) PR-provided fixtures, (b) build minimal stub
  inside container, (c) probe APIs directly via page.evaluate fetch,
  (d) search repo / related PRs / docs for unknown terms.
- **STEP 3** 4-7 cases for substantive PRs (was hard cap 3).
- **STEP 4** Login / multi-tab / OAuth — use Playwright multi-page handling;
  read creds from env, never echo.
- **SECURITY** strengthened: env vars matching common secret patterns are
  confidential; never echo / log / write to file / page.evaluate / report.
- **Report** new required §Mitigations Attempted for Inconclusive verdicts —
  must list what was tried + why each didn't unblock.

Kept unchanged: 3-min keepalive constraint, untrusted-data rule, no-host-shell
rule, report markdown structure (✅/⚠️/❌/⚪ + case emoji).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(agent-pr-explore): truncation-aware + soft-request protocol + capability fix

Addresses review feedback from @mrcfps:

(1) STEP 1 — diff probe list was instructed to enumerate "every new
    route/component/env/fixture/flag" without acknowledging that the harness
    already truncates the diff upstream (file_patch_max_chars + context_max_bytes).
    On exactly the large PRs this prompt targets, the agent only sees a slice.

    Fix: prompt now explicitly tells the agent the context MAY be truncated and
    to (a) note the truncation in §🧭 Scope and (b) emit §📎 Needs to ask the
    maintainer to attach the missing source files into the private workspace
    for the next run.

(2) STEP 2 — old text told the agent to "create stubs inside the sandbox
    container", "rewire env / PATH", and "run gh / grep searches". The harness
    does not expose docker exec or arbitrary shell to the agent (capabilities
    are fs:write on host + Playwright on host driving the dockerized app via
    HTTP). The instructions promised things the agent literally cannot do.

    Fix: STEP 2 now spells out the actual capabilities (host fs:write, Playwright
    page.evaluate / page.request, host-side $WORKSPACE_DIR if maintainer pre-
    attached one). The "unblock by stub" path is rewritten honestly: build a
    host-side stub if useful, but acknowledge container env is fixed at
    docker run and signal what's needed via §🔑 Needs / §📎 Needs for the next
    iteration. The "search repo for unknown term" step (which required gh/grep)
    is dropped in favor of using $WORKSPACE_DIR materials.

(3) Soft-request protocol (new):

    The agent is READ-ONLY for secrets and workspace — it cannot self-attach.
    But it can SIGNAL what was needed via two new optional report sections:

    - §🔑 Needs — secret request ("VELA_RUNTIME_KEY: needed to verify ...")
    - §📎 Needs — workspace file request ("amr-auth-spec.md: clarifies ...")

    The dashboard (synclo platform; see nexu-io/synclo#79 RFC §6.8) will parse
    these structurally and surface as one-click attach hints to the maintainer
    on the run detail screen. Pure passive signal; no auto-action; zero prompt-
    injection risk (no code path takes the values).

    Hard rules:
    - No pasting of existing secret values (security)
    - Each item MUST tie back to a specific blocked case in §🧪 Cases — not
      speculative
    - Workspace privacy: agent may reference workspace files BY PURPOSE
      ("verified positive-1 from test-plan.md") but NEVER paste their content
      into the report

This commit is non-trivial (~65 lines net) but the changes are tightly scoped:
honesty about capabilities + a new signaling channel that replaces the
impossible direct-action promises.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(agent-pr-explore): remove gh-search + container-exec language from prompt

MINDSET bullet: replace 'gh search issues/prs/code' with in-scope
materials (PR body, diff context, workspace files) plus
page.request/page.evaluate probes, matching actual harness capabilities.

SECURITY bullet: replace the contradictory 'You may run commands INSIDE
the sandbox container' with a clear statement that the agent has no shell
or container exec access, only the Playwright browser.

Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)

* ci(agent-pr-explore): fix Needs section misuse and unawaited fetch body

Move fixture env-var wiring request from §🔑 Needs to §📎 Needs and
remove the concrete host path from the example; §🔑 Needs is
secret-name-only and must not carry filesystem paths. Await r.text() in
the page.evaluate fetch example so the body field resolves to a string
instead of an unresolved Promise.

Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)

* ci(agent-pr-explore): broaden 📎 Needs to cover env/config wiring alongside file attachments

Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)

* ci(agent-pr-explore): fix step-2b Needs routing and split AMR_USER/AMR_PASS example

Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)

* ci(agent-pr-explore): fix mindset bullet — env var cannot be set mid-run

Replace "set the env var" in the MINDSET mitigations list with
"identify the env var and request the needed startup wiring in §📎 Needs"
to match the actual capability boundary: container env is fixed at
docker run time and cannot be changed by the agent mid-run.

Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:51:19 +00:00
lefarcen ae7a417208 ci: add idempotent provision script for the agent-pr-explore runner (#3122)
* ci: add idempotent provision script for the agent-pr-explore runner

The self-hosted runner's setup was hand-assembled and easy to lose on a
rebuild — most dangerously the codex-acp pin: expect-cli bundles
codex-acp 0.10, which is incompatible with ChatGPT-account auth (every
model rejected); we run 0.15, but any expect-cli reinstall silently
reverts it and breaks the agent.

Add a self-contained, idempotent provision script that brings the
runner's config layer back to a working state and is safe to re-run:
codex model pin (gpt-5.4), the codex-acp 0.15 pin (npm pack + extract +
chmod), deploy-key generation, base-repo git mirror seed/refresh,
pnpm-store/reports dirs, the weekly image-refresh helper + cron, and the
readiness self-check helper. The header documents the manual/secret
steps it intentionally does not automate (base toolchain + colima, the
interactive `codex login`, registering the deploy key on the repo, and
registering the Actions runner service).

Verified idempotent against the live runner (all checks pass, no config
disturbed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: provision — update codex model key in place, don't truncate config.toml

Review: step 2 overwrote the whole ~/.codex/config.toml with just the
model line whenever the exact pin wasn't already present, dropping any
other Codex settings on a re-run — destructive, contradicting the
idempotent goal. Now: replace an existing `model =` line in place (sed),
append only when the key is absent, and leave the rest of config.toml
untouched. Verified preservation locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: provision — create ~/.ssh before ssh-keygen on fresh host

Review: on the fresh-rebuild path this script targets, ~/.ssh usually
does not exist, so `ssh-keygen -f ~/.ssh/od_agent_deploy` fails with
"No such file or directory" and the deploy key (and downstream mirror
bootstrap) never gets created. mkdir -p the key's parent dir (chmod 700)
before keygen, and only print the pubkey when it actually exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:51:59 +00:00
lefarcen 54f225d6b3 ci: retry PR-context gh calls so a transient API blip doesn't abort the run (#3128)
* ci: retry PR-context gh calls so a transient API blip doesn't abort the run

The early PR-context gathering calls `gh pr diff`, `gh pr view`, and
`gh api .../files`. gh hits api.github.com under the hood, and a single
transient timeout/5xx there aborts the whole run before any exploration
(seen on #3083: "could not find pull request diff: Get \"https://api.
github.com/...\": net/http timeout"). These were the only network calls
in the run without a retry (source fetch + npm already retry).

Add a small gh_retry helper (4 attempts, linear backoff) and wrap the
three read-only context calls. gh writes nothing to stdout on a failed
API call, so retrying is safe even for the calls piped into the context
file; the retry warning goes to stderr.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: address review — buffer gh retries to files (no paginated duplication)

Review (Siri-Ray): wrapping `gh api --paginate` retries inline while the
context block is redirected to the file means a mid-pagination failure
leaves partial pages in the context, and the retry appends them again —
duplicating the patches section and burning the context budget the agent
reads.

Replace the in-pipe gh_retry with gh_retry_file: each call buffers to its
own file per attempt (`>` truncates on open, so a failed/partial attempt
is discarded before the next), and the context block just cats the
finished files. Fetch PR body + patches to files up front, then assemble.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:42:08 +00:00
lefarcen a6a56099ca ci: show per-case pass/fail status emoji in agent report (#3118)
Reviewers asked for at-a-glance outcomes. Instruct the agent to begin each
"Cases Tested" bullet with a status emoji (✅ pass / ❌ fail / ⚠️ warning /
⚪ inconclusive) and a bold case name, so the report shows which checks
passed or failed without reading each line.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:34:29 +00:00
lefarcen bf61a39cb5 ci: clean agent report (write-to-file) + slim artifacts/uploads (#3116)
* ci: clean agent report (write-to-file) + slim artifacts/uploads

Four related cleanups to the agent PR exploration output:

1. Clean report. The PR comment / report.md was assembled by dumping the
   entire verbose expect.log (ACP init logs, "Git failed" warnings, the
   ~24KB echoed prompt, ANSI codes, progress checklist) under the trace
   header -- ~28KB of noise. Instead, instruct the agent to write its
   final Markdown report to a file via its file-write tool, and have the
   runner read that file directly. Verified: Codex writes a clean report
   to the given absolute path. Falls back to an inconclusive note if the
   agent did not finish.

2. Drop duplicate trace/video. The script copied
   playwright-smoke-trace.zip -> playwright-trace.zip (a ~28MB legacy
   duplicate) and the webm likewise, and uploaded both to R2. Keep only
   the canonical smoke-named artifacts.

3. Slim the GitHub artifact. The trace zips and videos are already on R2;
   exclude *.zip / *.webm from the uploaded artifact so it drops from
   ~56MB to <1MB (report + logs only).

4. Persist report on the runner. Copy the report / agent-report /
   expect.log / trace URL to a stable host dir
   ($HOME/.cache/agent-pr-explore/reports/pr-<n>) so dry runs
   (skip_comment) can be inspected without downloading the artifact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: address review — keep advisory reports + recursive artifact excludes

Review findings on the report/artifact cleanup:

1. Regression fix: the non-app-surface and deterministic-verifier branches
   write their pre-baked advisory report (Inconclusive / Pass / Fail) and
   never run the agent, so they don't produce agent-report.md. After
   switching write_agent_report_artifact to read only agent-report.md they
   fell through to the "agent did not write a final report" fallback,
   dropping the real advisory (and mis-reporting on .github-only PRs like
   this one). Fix: those branches now write their advisory directly to
   $agent_report_file — single source of truth for the report body.

2. Recursive artifact excludes: the source Playwright recording lives at
   artifacts/playwright-video/<uuid>.webm; non-recursive !*.webm / !*.zip
   didn't match the subdirectory. Use **/*.zip and **/*.webm so the slim
   actually holds.

3. Drop the now-dangling summary.legacyTrace field (the legacy trace copy
   is no longer produced), matching the legacyVideo removal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:30:05 +00:00
lefarcen 995601de9c ci: make agent exploration finalize promptly (avoid inactivity abort) (#3100)
Real Codex runs (#3060) explored correctly — verifying 3-4 UI cases with
DOM evidence — but Codex over-planned (6 steps), executed the high-value
ones, then went silent chasing a remaining planned step and tripped
expect-cli's ~180s no-output watchdog, aborting the turn before it emitted
a final report. The run then fell back to an advisory artifact, so the
real findings never reached report.md.

Tighten the prompt so Codex finishes and submits before going idle:
- cap at 3 cases (was 6) and target 2-3, quality over breadth;
- add a CRITICAL instruction stating the runner aborts with no report
  after ~3 min of no output, so Codex must stop after 2-3 cases and emit
  the complete report in one final turn rather than leaving planned steps
  pending or retrying silently.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 17:15:06 +08:00
lefarcen fed464509b ci: drive agent PR exploration with the Codex ACP backend (#3086)
expect-cli defaults to the Claude Code ACP provider, which is not
installed on the self-hosted runner, so the exploration step errored
(AcpProviderNotInstalledError) and fell back to a reachability-only smoke
trace instead of real UI exploration.

Pass `-a codex` to expect-cli so it drives the Codex agent (installed on
the runner, authenticated via CODEX_HOME). Configurable via
OD_EXPECT_AGENT (set to empty to use expect-cli's default). When the
agent is unavailable the existing smoke-trace fallback still applies, so
this is safe even before Codex is authenticated.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:05:03 +08:00
lefarcen 114be63a4e ci: route agent sandbox installs through the China npm mirror (#3084)
After fixing source acquisition (#3078), the #3060 validation run reached
the container and got through most of `pnpm install`, then failed building
the better-sqlite3 native module: prebuild-install could not reach github
releases and the node-gyp fallback could not fetch node headers from
nodejs.org (ECONNRESET). The electron postinstall hits the same blocked
hosts, and package tarballs from npmjs were throttled to ~20 KB/s.

The runner's network to npmjs / nodejs.org / github releases is throttled
or reset by GFW; the China npm mirror (npmmirror.com) is fast and complete
(verified from the runner: registry ~2.4 MB/s, node headers ~3.6 MB/s,
better-sqlite3 prebuilt present). Point the in-container install at it via
registry + disturl (node-gyp headers) + electron / electron-builder /
better-sqlite3 binary mirrors + Playwright download host.

Package integrity is still verified against the lockfile, so the mirror
only changes transport. Once a native module builds, pnpm's side-effects
cache in the persistent store keeps it warm for later runs.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:30:59 +08:00
lefarcen 12141648e4 ci: fetch agent sandbox PR source on the host over SSH via a local mirror (#3078)
The sandbox checked out PR code with `git fetch https://github.com/...`
*inside* the container. The self-hosted runner's bandwidth to github.com
is throttled across every transport (HTTPS/SSH/codeload/API, all
~30-90 KB/s) and the HTTPS handshake is frequently RST'd, so a
from-scratch fetch of this ~200MB repo is impractical and unreliable per
run (run 26491460889 failed here with repeated GnuTLS resets).

Move source acquisition to the trusted host and make it incremental:

- Keep a persistent bare mirror of the base repo
  ($HOME/.cache/agent-pr-explore/open-design.git, overridable via
  OD_SANDBOX_REPO_MIRROR). Each run fetches only the PR's delta via
  `refs/pull/<n>/head` over SSH -- the one transport GFW doesn't reset --
  using a read-only deploy key (OD_SANDBOX_GIT_SSH_KEY).
- Take the head from the BASE repo's pull ref so fork PRs work without
  depending on the head fork, and verify it equals the resolved HEAD_SHA.
- Check the PR head into a per-run worktree and mount it read-only into
  the container; the container copies it into a writable workdir and no
  longer needs (or has) any github access.

The deploy key stays on the trusted host and is never exposed to the
untrusted PR code. The mirror must be seeded once on the runner (the
error message prints the exact clone command if it is missing).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 05:36:13 +00:00
lefarcen 2ed93e9c5d ci: reuse cached docker image and persist pnpm store for agent sandbox (#3074)
* ci: skip docker pull when agent sandbox image is already cached

The agent PR exploration script ran an unconditional `docker pull
"$image"` before `docker run`. Under `set -e`, a transient registry
timeout (the self-hosted runner's network to docker.io is unreliable)
aborts the whole run even when the base image (node:24-bookworm) is
already cached locally — which is what happened on run 26490782540.

Skip the pull entirely when the image is already present, and only pull
when it is missing. This avoids both the failure and the wasted pull
timeout on every run, and keeps a run's base image stable. Refreshing
the cached image is a separate, explicit operation on the runner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: persist agent sandbox pnpm store across runs

The pnpm store was placed under $RUNNER_TEMP, which the Actions runner
wipes per job, so every agent exploration re-downloaded all dependencies
from the npm registry — slow, and as fragile as the runner's docker.io
access (the same network class that already broke the docker pull).

Move the store to a persistent host path ($HOME/.cache/agent-pr-explore/
pnpm-store, overridable via OD_SANDBOX_PNPM_STORE) so a warm,
content-addressed store is reused across runs. `rm -rf "$root"` no longer
touches it since it lives outside the per-run root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:49:26 +08:00
lefarcen 7b8bf0d9fb ci: map agent trace upload to existing R2 secrets (#3013)
* ci: map agent trace upload to existing R2 secrets

* ci: make agent report comments macos-compatible

* ci: ensure Playwright browsers for agent traces
2026-05-27 03:01:36 +00:00
lefarcen b5bf28060b Add sandboxed agent PR exploration (#2604) 2026-05-26 07:52:42 +00:00
PerishFire bfcafc81fd feat(pack): add Windows portable zip target alongside NSIS installer (#2937)
Adds a new `--to zip` (and `--to all`) tools-pack Windows build target that
produces a portable `.zip` from the cached `win-unpacked` tree using the
bundled 7z. The zip lays files at the archive root so users can extract it
anywhere and launch `Open Design.exe` without going through the NSIS
installer, addressing the no-install download request.

Release plumbing is updated to publish the portable zip and its sha256
beside the existing installer on R2 for beta, preview, and stable channels
(default on, gated by `WINDOWS_INCLUDE_ZIP`/`WIN_INCLUDE_ZIP`). The
electron-updater `latest.yml` feed continues to point only at the
installer; the zip is a manual-download convenience and is intentionally
excluded from the in-app updater.

Closes #1121

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

Co-authored-by: libertecode <libertecode@proton.me>
2026-05-26 06:14:44 +00:00