mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
dceac12f70df2b9eb0fae9c90aff1fe75da309c0
256
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
176fae3dc6 |
ci: constrain Actions cache writes (#6270)
* fix(tools-pack): carry workspace build key into tarballs * ci: constrain Actions cache writes |
||
|
|
809d45f27c |
feat(integrations): add the local Open Design Cloud flow for Codex (#6055)
* feat(daemon): expose Vela login through CLI and MCP * feat(mcp): add interactive local brief card * feat(packaged): bootstrap local MCP headlessly * feat(daemon): secure local BYOK credential profiles * feat(cli): manage secure BYOK profiles * feat(web): store BYOK keys in secure profiles * fix(runs): deduplicate cloud generation requests * feat(daemon): add end-to-end plugin observability * fix(pack): include native PTY runtime in desktop packages Lazy-load node-pty in the daemon so missing native binaries only disable Terminal and interactive Keychain operations instead of crashing startup. Ship and validate target prebuilds in macOS and Windows packages. Repair the macOS spawn helper before signing, and invalidate stale Windows packaging caches. Cover the packaged PTY path in macOS and Windows smoke tests. Require Vela CLI for every beta desktop build target. * fix(mcp): localize and stabilize plugin briefs Keep collect and confirm on one MCP Apps card, report intrinsic size changes, and remove the fragile widget-state fallback. Localize brief forms, summaries, status, and public errors from the current request or Host locale while preserving stable protocol ids and selections. Keep internal runtime identifiers out of user-facing MCP guidance. * fix(packaged): register live Studio URLs with daemon Register the packaged web sidecar loopback origin over namespace-scoped daemon IPC after dynamic binding. Invalidate install-info by the live web port and remove the second MCP URL cache so run and project responses use the current Studio address. Validate loopback origins and cover registration, rebind, and cache refresh. * fix(mcp): stabilize brief confirmation lifecycle Keep confirmed briefs locked after Host publication failures and retry only the cached follow-up message instead of confirming twice. Replace blank initialization with a compact loading state, accept immediate and delayed tool results, and bump the MCP Apps resource to v3 so Codex does not reuse the stale card. Add an executable JSDOM Host bridge harness covering double-submit, publication retry, delayed results, and repeated draft notifications. Validated with the full daemon suite, workspace typecheck, and guard. * fix(mcp): prevent brief app resize feedback loops Replace the self-observed intrinsic-height loop with deduplicated body measurements so Codex MCP app iframes do not repeatedly resize themselves and fail sandbox startup. Bump the brief resource URI to v4 to avoid cached v3 cards and cover the regression with the focused brief-app spec. Validated with daemon tests/build, workspace guard/typecheck, a verified beta DMG, and a direct packaged MCP resource smoke. * fix(mcp): use the final Open Design plugin identity Make open-design the only accepted external plugin ID across MCP tools, run attribution, artifact version origins, and telemetry. Rename the Brief MCP App resource to v5 and reject the unpublished old ID instead of aliasing it. Validated with 171 daemon tests, 5 web tests, daemon/web typechecks, and guard. * chore(pack): bump Vela CLI to 0.0.27 * chore(nix): refresh pnpm dependency hashes * fix(mcp): honor host and plugin request contracts Respect independently advertised MCP Apps capabilities and prefer the Codex-native follow-up bridge so confirmed briefs render as readable user turns instead of context-only messages. Bump the brief resource to v6 to invalidate stale cards.\n\nMark Vela login and account probes as open-world interactions, and require stable request IDs for attributed generation so lost responses cannot create a second logical run. * fix(cli): support keyless BYOK profiles Add an explicit --no-api-key save mode for local and self-hosted providers while keeping credential-bearing profiles stdin-only. Reject missing or conflicting credential modes and cover both paths in the CLI tests. * fix(pack): replace headless owner when desktop opens Advertise headless packaged runtimes as having no visible window, then shut them down before a normal desktop launch continues instead of treating SHOW as a successful focus. Validated with the focused packaged headless and launcher lifecycle tests (20 passed). * fix(pack): clean up failed headless startup Acquire the headless identity, sidecars, and IPC server through one failure-atomic lifecycle so later bootstrap errors close every previously acquired owner in reverse order. Cover MCP-install and web-identity publication failures; validated with 22 focused tests and the packaged typecheck. * fix(byok): remove raw provider request contract Expose only daemon-owned BYOK profile references from shared run types and the web transport, while retaining the daemon's fail-closed rejection for legacy or untyped raw credential payloads. Validated with 76 web provider tests plus web and contracts typechecks. * fix(daemon): preserve interrupted run idempotency Hydrate queued or running durable requests as DAEMON_RESTARTED failures before idempotency reuse, preserving the original request and workflow mappings instead of creating a second cloud operation. Share the restart transition with boot reconciliation and expose synthetic terminal events to reattaching clients. Validated with 48 focused tests and the daemon typecheck. * fix(byok): roll back failed credential metadata writes Snapshot secure-store values before mutation and compensate create, update, and delete operations when profile metadata cannot be persisted, preventing orphaned or lost credentials. Validated with nine focused credential-service tests and the daemon typecheck. * fix(web): reconcile selected BYOK profile metadata Treat the daemon's selected secure profile as authoritative for protocol, endpoint, model, and API version so CLI profile edits cannot leave the browser showing stale execution settings. Validated with 66 config tests and the web typecheck. * fix(byok): migrate legacy credentials before cleanup Import each supported legacy browser credential into a stable daemon-owned secure profile before removing plaintext copies. Keep the original browser record on secure-store or unsupported-provider failures and surface a Settings recovery path. Cover successful migration, duplicate projections, and injected secure-backend failure while preserving existing Bedrock downgrade behavior in memory. * fix(byok): secure Windows profiles with DPAPI Dispatch native Windows BYOK profiles to a CurrentUser DPAPI backend rooted under the resolved daemon data directory. Secrets enter PowerShell only through stdin, encrypted blobs stay separate from non-secret profile metadata, and API responses remain secret-free. Add platform dispatch coverage, a Windows-only real DPAPI round-trip test, and the packaged Windows smoke path for create, resolve, response-redaction, and delete behavior. * fix(web): preserve config writes during hydration * fix(byok): persist secure credentials across entry flows * test(byok): cover secure onboarding persistence --------- Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
cf3ca4fc3d | ci: close packaged leaf test coverage (#6102) | ||
|
|
fac10139c0 |
perf(ci): narrow zero-effect queue validation (#6100)
* perf(ci): narrow zero-effect queue validation * docs(ci): keep scope spec state-focused |
||
|
|
f9a6e18c11 |
feat(ci): promote the certain-exempt docs core to merge-queue trust (#6047)
* feat(ci): promote the certain-exempt docs core to merge-queue trust Split the exempt surface into a certain-tier core (docs/, apps/landing-page/, .vscode/, .idea/, .github/ISSUE_TEMPLATE/, LICENSE, CODEOWNERS) and the medium residue (global markdown regex, workflow exacts, nix). A merge-queue group confined to the core now drops to the unconditional floor lanes instead of running everything; replaying the last 398 first-parent merges puts that at 12.3% of queue traffic. The promotion is backed by a new floor-lane guard check, "certain-exempt surface consumption": no skippable-lane source may reference a certain-exempt path (dot-relative literals flagged everywhere; bare repo-relative literals flagged outside test fixtures). guard.ts gains --list-checks as the machine registry, and the rule-table invariant test now requires every certain rule's guard field to resolve to a live check. * docs(ci): record the scope confidence methodology in specs/current/ci.md The living framework for confidence-tier changes: medium-tier cheap loop vs certain-tier deliberate loop, the three-sentence promotion discipline, guard strength levels and floor placement, evidence recipes (replay, offline plan, queue trace extraction), tooling graduation criteria, and the open questions deliberately left undecided. Indexed from AGENTS.md and .github/AGENTS.md required reading. * fix(ci): narrow certain-exempt fixture handling Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): pin the trae-cli consumption exception to the daemon-lane wiring The review round established that apps/daemon/tests/runtimes/trae-cli.test.ts genuinely reads docs/agent-adapters.md. Its exemption from the consumption guard was justified by a wiring fact (the ci.yml daemon lane runs only project-watchers.test.ts) stated as prose, which would rot silently if the lane ever widened. Make the exception conditional on that exact ci.yml needle: when the daemon lane changes, the exemption disappears and the guard reports the read, forcing reclassification instead of trusting a stale rationale. * docs(ci): codify exception-precondition binding in the methodology Backfill the pattern the first review round produced: guard allowlist entries justified by local definitional facts may stay prose, but entries justified by remote mutable facts (lane wiring, gate topology) must be conditional on a checked precondition so the exception dies loudly with its premise. Records the trae-cli/daemon-lane needle as the worked example. * fix(ci): fold static paths in exempt consumption guard Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): require exclusive daemon test wiring Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): recognize daemon pnpm selector forms Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): tokenize daemon workflow test commands Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
de2020fb88 |
docs: reconcile code-evolution drift (#6083)
* docs: reconcile code-evolution drift * fix(web): preserve generic-disconnect recovery retries Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(web): retry capped reattach probe after cleanup Generated-By: looper 0.11.0 (runner=fixer, agent=codex) |
||
|
|
a2ba3ee2ad |
feat(updater): manual cache clear, installer-reinstall floor, and crash-recovery lifecycle (#6032)
* feat(updater): manual cache clear + installer-reinstall floor on the installed outer version
Two disaster-recovery capabilities for the packaged updater:
- Reuse control.launcher.version.min properly: the gate now compares the
physically installed outer package version (read at check time from the
outer bundle's open-design-config.json via the launcher launch path;
OD_UPDATE_INSTALLED_VERSION overrides for tests), not the running payload
version. When the floor trips - or min is set but the outer version is
unreadable (conservative) - the updater selects the installer artifact and
offers a same-version installer reinstall when no newer release exists
(clamped to min <= candidate so the offer always converges). The status
snapshot carries a reinstall block {reason, installedVersion, minVersion,
url}; UpdateDialog/UpdaterPopup render reinstall copy with an optional
operator jump link. tools-release publish-metadata publishes the block from
RELEASE_LAUNCHER_VERSION_MIN(_URL) (per-channel repo vars with a _STABLE
fallback wired in all release workflows) and hard-fails when min exceeds
the release version; verify-metadata re-checks the published block and
summary-metadata surfaces the floor.
- Manual clear-cache action (od:update:clear-cache IPC, sidecar clear-cache
action, Settings > About row with a two-stage inline confirm): resets
one-shot update state (downloaded release, install freeze) back to idle,
purges releases/staging/downloads/.back, removes a stale launcher
attempt.json plus any non-confirmed desktop-handoff journal, and deletes
non-retained payload versions. Runtime active/lastSuccessful versions,
retained cleanup entries, install.json, and a confirmed handoff journal
are never touched; locked files defer through the existing cleanup.json
retry machinery.
Also: tools-pack --update-action now validates through sidecar-proto's
exported isDesktopUpdateAction so clear-cache reaches the ops surface;
tools-serve updater fixture gains control min/url knobs and CLI flags;
9 new i18n keys across all 19 locales; tools/pack/AGENTS.md documents the
gate semantics and clear-cache boundaries.
Validated locally per the updater matrix (desktop/web/tools-serve/tools-pack
suites, guard, typechecks) plus a mac release-beta closed loop: platform full
spec, same-version reinstall against a real outer/payload version split,
clear-cache recovery, and a real-feed payload update to 0.15.1-beta.4.
* test(updater): close Windows recovery acceptance
* test(updater): mirror Windows same-version reinstall recovery on the mac lane
Port the Windows runSameVersionUpdaterRecoveryAcceptance shape into the mac
full profile: after the payload cold-start acceptance, restart the packaged
app against a fixture publishing an installed-outer floor at the running
payload version and assert the same-version installer reinstall snapshot
(reason/installedVersion/minVersion/url), the popup reinstall learn-more
link, clear-cache back to idle with launcher pointers untouched, explicit
re-check recovery, and the installer open in dry-run mode — macOS has no
silent DMG install to execute, unlike the Windows NSIS transaction lane.
Extends the popup eval expression/validator with reinstallLinkVisible and
records an updaterRecovery block in the smoke summary. Full-profile test
timeout raised 240s -> 360s for the extra app restart.
Validated locally: release-smoke full profile green in 80s with the recovery
summary populated (reinstall block exact, cleared idle, recovered dmg,
dryRun install).
* refactor(release): centralize launcher version floor channel policy in one resolver
Channel policy for control.launcher.version.{min,url} is now managed as one
repo-vars pair per channel (RELEASE_LAUNCHER_VERSION_MIN[_URL]_<CHANNEL>).
Workflows pass the channel pair plus the STABLE pair through verbatim — the
|| fallback expressions are gone from YAML — and the shared resolver in
tools/release/src/storage/launcher-version-floor.ts owns resolution and
validation: pair-level stable fallback for non-stable channels (never mixing
one channel's min with another's url), version format, http(s) url, orphan
url rejection, and the floor <= releaseVersion guard. publish-metadata and
verify-metadata consume the same resolver, so verify re-derives the expected
block from the same channel policy instead of trusting a pre-resolved env.
release-workflows.test.ts pins the verbatim passthrough keys in all five
release lanes and rejects any reintroduced YAML fallback.
* feat(updater): let clear-cache rebuild a provably-owned corrupt update store
A corrupt store is one of the blocking scenarios the manual clear exists to
recover from, yet the action previously bailed on the very store errors it
should heal. clear-cache now rebuilds the update root when — and only when —
ownership is provable: the current-generation sentinel is the proof, and
everything else inside an owned root is updater cache by definition. Roots
without a sentinel, with a foreign-generation marker, or failing IO are never
touched; the original store error surfaces unchanged. Four specs pin the
boundary (corrupt metadata rebuilt, stray entries rebuilt, unowned root
refused with files intact, foreign-generation marker refused).
* fix(launcher): pre-arm delegated payload launches so pre-bookkeeping crashes roll back
A payload that spawned successfully but died before reaching its own launcher
bookkeeping left no attempt.json behind, so every later cold start re-selected
the same broken active pointer and re-delegated to the dead payload — a
permanent dead loop the installer fallback could never surface (found while
designing the crash-rollback acceptance).
The delegating parent now arms attempt.json BEFORE spawning the payload and
passes the pointer as --od-launcher-delegated-* argv. Selection gains an
active-delegated reason: an attempt matching active AND the delegated pointer
is the launch in progress, not a previous failure; only an exact active match
qualifies, so a stale pointer cannot defeat the rollback. Rollback
(last-successful) delegations deliberately carry no marker and never re-arm —
the attempt on disk IS the rollback evidence the child must re-derive.
Covered end to end: packaged outer delegation pre-arms (and skips pre-arm on
rollback), the updater's payload activation pre-arms and threads the pointer
through the deferred relaunch, and confirm clears the attempt for delegated
launches. launcher-proto 18, packaged 237, desktop 264 all green.
* fix(updater): scrub a stale payload install freeze after a rollback
A payload install freezes the updater with installResult.activeVersion
awaiting the relaunch. When that payload crashed and the launcher rolled
back, the app restarts on the OLD version with the freeze still persisted —
every later check became a frozen no-op, so a rolled-back install never saw
another update offer (the crash-rollback acceptance exposed this in its
self-heal phase). Cold-start restore now detects the broken promise —
installResult.activeVersion newer than the running version on a live process
— and drops the freeze and stale install result while keeping the verified
downloaded release actionable. The installer-artifact freeze (no
activeVersion) keeps its existing restore semantics. Red spec:
'clears a stale payload install freeze when the promised relaunch never
became the running version'.
* test(updater): full-lifecycle acceptance — silent startup, crash rollback, coverage map
Two new packaged acceptance tests on both platform lanes (mac validated
locally; win symmetric for the Windows box):
- 'applies a downloaded payload silently on the next cold start': enables the
daemon-owned allowSilentUpdates through the production GET+PUT
/api/app-config path, restarts, and asserts the first scheduler tick
installs, quits, and relaunches with converged pointers — no updater action
issued.
- 'rolls back a crashing payload and self-heals on the next good update':
repacks the real payload archive with a crash-before-bookkeeping stub
(mac: shebang script via ditto; win: a real executable — System32 where.exe
— via the installed app's bundled 7z, since a script cannot stand in for an
exe), drives the update through the popup, asserts the pre-armed attempt
survives the crash as rollback evidence, cold-starts into the rolled-back
base version (degraded steady state pinned: broken pointer stays active
with its attempt), then self-heals via a version-bumped healthy payload —
real recovery releases ship as version+1.
Spec hygiene: resetPackagedRuntimeState wipes launcher/runtime/daemon state
before each lifecycle test (uninstall keeps runtime data by design, and
leftover pointers silently delegate fresh installs onto stale payloads);
mac waitForHealthyDesktopVersion gains win's requireSettledLauncher opt-out
for rollback waits.
docs/testing/updater-lifecycle.md is the new lifecycle-to-test coverage map
(incl. deliberate manual-only nodes), linked from tools/pack/AGENTS.md and
the e2e coverage status doc.
* fix(updater): harden Windows full-lifecycle acceptance
* test(release): make channel-prepare hermetic against local git tags
prepare-preview derives its latest-stable floor from the clone's real
open-design-v* tags, so the channel-prepare tests were green on tagless CI
checkouts but permanently red on any developer clone once shipped stable
tags advanced past main's package version (0.15.1 vs v0.15.1/v0.16.0 today).
The spawned scripts now run with GIT_DIR pointing at a throwaway repo
holding exactly the tags each test declares, and a new negative case pins
the floor rejection itself (packaged version below a v99.0.0 stable tag).
* test(e2e): model delegated payload launch
Pass the updater's pre-armed payload pointer into the cross-runtime launcher simulation so it represents the spawned payload process instead of a later crash-recovery launch.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* test(web): wait for question form readiness
Wait for the asynchronously derived submit-disabled state before asserting it, removing the suite-order race observed by the Web workspace tests check.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(updater): preserve recovery state across locks and restarts
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
---------
Co-authored-by: Looper <looper@noreply.github.com>
|
||
|
|
14fcc411cd |
feat(ci): confidence-tiered scope rule table with merge-queue shadow evaluation (#6029)
* chore(scripts): make root scripts/ test-free and purify the guard entrypoint
Root scripts/ is now source-only, enforced by a new "scripts test-free"
guard check (any *.test.{ts,tsx,js,jsx,mjs,cjs} under scripts/ fails
pnpm guard). The root guard script becomes a pure tsx ./scripts/guard.ts
invocation instead of chaining a hand-maintained node --test file list.
Script behavior-contract coverage follows the boundary e2e/AGENTS.md
already declares for e2e/tests/scripts/: the fork PR approval security
tests migrate there (converted to Vitest, 24 tests passing), and the
remaining script unit tests are deleted — five of them (batch-design-
system-test, check-design-system-package-quality, plugin-previews-diff,
plugin-previews-gc, migrate-to-plugins/example) were orphans no runner
ever executed; the guard-chained checker tests covered logic whose
repo-wide enforcement remains in guard.ts checks. Coverage with a real
test claim gets rebuilt in e2e/tests/scripts/ on demand.
* test(ci): pin characterization goldens for scopes.ts scope detection
Golden tests run scripts/scopes.ts as a subprocess exactly the way ci.yml
does, with gh stubbed through the existing OPEN_DESIGN_GH_NODE_SCRIPT seam
(hermetic stub materialized per run), and pin the complete 20-key output
plan for a matrix of changed-file sets across pull_request,
workflow_dispatch (hot/full), and merge_group events.
They deliberately pin current quirks (skills/*.md arming daemon+web without
the ui-critical fallback; e2e/ui/*.md arming ui_p0 with workspace validation
off; dispatch-hot skipping the workspace_validation re-derivation) so the
upcoming rule-table restructuring can prove behavior preservation
byte-for-byte.
* refactor(ci): restructure scopes.ts into a confidence-tiered rule table
Scope detection becomes data plus a pure evaluator: every changed file is
classified by an additive rule table (union of matched effects, minimum
confidence across matched rules), and an evaluation context brings a trust
threshold — files below it, or matching no rule, escalate fail-closed to
the full radius. Today's behavior is exactly the degenerate case of this
structure (all rules medium, certain set empty): pull_request/hot believes
everything, the merge queue believes nothing and stays full. The
characterization goldens pass unchanged, byte-for-byte.
The exempt surface becomes an explicit empty-radius rule instead of a
special case, so exemptions can be split, rated, and promoted per rule.
Promotion to "certain" requires a guard field naming the enforcing check
(enforced by a rule-table invariant test).
New offline entry points for rule iteration: `plan --context
pr|merge-queue|full --files ...` evaluates without any GitHub environment
and prints { plan, trace }; `rules` lists the table. Every evaluation now
returns a decision trace (per-rule hit counts, per-file escalations, and
an ifTrustAll shadow plan that shows what medium-confidence rules would
have allowed — the evidence stream for future certain-tier promotions);
github-output mode logs it and writes a step-summary table without
touching the frozen 20-key output contract.
The legacy dispatch-hot asymmetry (no workspace_validation re-derivation)
is preserved behind an explicit option and pinned by the goldens.
* feat(ci): evaluate merge-group union diffs at the certain threshold with shadow trace
The merge queue now resolves the queued group's union diff
(compare base_sha...head_sha from the merge_group event payload) and
evaluates it through the rule table at the "certain" trust threshold.
While the certain rule set is empty every file escalates and the queue
keeps running everything — the goldens pin this byte-for-byte — but the
evaluation's ifTrustAll shadow column now records, for every queued
group, what medium-confidence rules would have allowed: the evidence
stream that future certain-tier promotions will cite.
Queue lanes derive from the evaluated outputs (ci_mode stays reported as
"full", but lanes are no longer unconditionally forced), so promoting a
rule to "certain" later is a pure rule-table change with no further
structural work.
Fail-safe behavior is explicit and tested: resolution errors and empty
resolutions fall open to the full plan without failing the scopes job
(queue throughput must never depend on this evaluation), while
pull_request resolution failures keep failing the run as before.
Changed-file resolution no longer filters out removed files (manual hot
runs previously did): deleting a runtime source file must trigger the
same validation scopes as editing it. This is the only behavior change,
and it is strictly fail-closed.
* fix(ci): preserve guard contracts and fail closed on compare limits
Restore the eight guard behavior suites in the e2e script lane and make merge-group compare resolution treat GitHub's 300-file ceiling as incomplete.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(ci): enforce extension-agnostic script test boundary
Match every suffix after .test. and cover TypeScript, MTS, and non-test filenames in the e2e script suite.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(ci): route script contract tests to e2e
Ensure changes under e2e/tests/scripts arm E2E Vitest with the same daemon and web scope effects as their root script counterparts, and pin the behavior with a PR golden.\n\nGenerated-By: looper 0.11.0 (runner=fixer, agent=codex)
---------
Co-authored-by: Looper <looper@noreply.github.com>
|
||
|
|
702d6f3a0a | fix(ci): make static gate runner-independent (#6039) | ||
|
|
b4f2677618 |
fix release: notarize prerelease mac builds (#5906)
* fix release: notarize prerelease mac builds * fix(release): inject Apple notary env on prerelease mac retry steps The cache-miss retry steps for both mac_arm64 and mac_x64 prerelease builds pass --notarize but lacked the APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID env block present on the primary build steps. notarize.cjs fails closed when those vars are missing, so a recovery build could not produce a notarized candidate. Mirror the primary step env on both retry steps and assert the notary env appears twice per mac job in release-workflows.test.ts. Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
20c6c0a6ed |
ci(e2e): shard the full UI functional pool into a merge-ready matrix (#5883)
* ci: benchmark deduplicated UI merge sentinel * ci: benchmark restoration-hosted sentinel * ci: benchmark distributed P0 sentinel * ci: benchmark split P0 sentinel * ci: serialize split sentinel benchmark * ci: finalize deduplicated UI merge topology * ci: prototype all-shades UI domain topology * ci: bound full UI runtime by shade * test(e2e): stabilize full UI suite * fix(web): unclip DS picker popover and propagate patched project metadata Two regressions surfaced by re-enabling the full UI pool: - The New Project design-system popover lost its portal and was clipped by short viewports and the modal body. Render it through createPortal with a viewport-aware anchor that flips upward when space below runs out. - After a working-dir pick, onProjectMetadataChange forwarded only the metadata onto a stale project copy, so ProjectView.currentProject's recency comparison let an older useProjectDetail snapshot shadow the accepted PATCH and the picker label never updated. The callback now carries the daemon's authoritative post-patch project (fresh updatedAt included), matching the design-system switch path, and ProjectView hands it straight to onProjectChange. * test(e2e): repair remaining stale full-suite UI contracts Second repair batch for the failures surfaced by full diagnostic run 29480362163, plus one CI-invisible case that was serially skipped behind the BYOK failure in the same file: - Point the working-dir metadata spec at a directory that exists on the runner instead of a hardcoded absolute path, and assert the label from the picked path (this spec is red without the ProjectView patched-project fix). - Navigate back to All project files before asserting the plugin-folder cards, since a finished run auto-opens the produced file tab. - Resolve the create-run response race in real-daemon-run's sendPrompt via a single waitForResponse alongside the click. - Refresh selectors and copy for entry topbar, home hero rail, chat todo autoscroll, project management, workspace keyboard, design files, manual edit, restoration, and DS picker clipping flows to match the current UI contracts while keeping each spec's oracles. * ci: distribute the full UI suite over a generic 8-shard matrix Replace the diagnostic four-domain P0/P1/P2 serial topology with a single all-shades Playwright invocation per shard (--shard=N/8, two fully parallel workers, 20-minute cap) and drop the scheduled trigger; full runs stay manual until the merge-queue wiring lands. Update the workflow topology guard accordingly. * ci: serialize full UI shards and widen the matrix to twelve The eight-shard two-worker experiment left six light shards healthy (260-477s test steps) but both shards holding runtime-heavy files degraded: repeated two-minute timeouts with retry recovery in one, and a consistent create-run response starvation failure in the other. This matches the earlier finding that runtime-heavy scenarios cannot share a runner under two fully parallel workers. Run each shard with a single worker and rebalance to twelve shards; fully-parallel stays on only for test-granularity sharding. * fix(web): preserve prompt-extracted plugin inputs across apply reconcile The active-plugin apply reconcile rebuilt the inputs from the bind-time optimistic snapshot, so edits the user typed during the apply roundtrip (already extracted into active.inputs by handlePromptChange) were silently reverted to defaults — the submitted pluginInputs then disagreed with the visible prompt. Merge the apply result's defaults into the current inputs inside the functional updater instead, mirroring the guard the prompt-text reconcile already had. * test(e2e): stabilize home gallery flows for all-shades execution Generic all-shades sharding executes tests in orders the shade-filtered domain topology never produced, exposing a home-surface remount window: the async projects fetch can mount or remount HomeTemplatesReveal after a test starts interacting, collapsing the gallery (inert) under the test's cursor. Stabilize the shared helpers instead of individual specs: - gotoEntryHome arms a projects-list response waiter before navigating and holds until it settles. - revealHomeTemplates requires the revealed state (or a true pass-through) to survive a settle window, retrying when the wrapper mounts late; a card's isVisible() is true inside the collapsed container, so openHomePluginDetails now always routes through the reveal path. - The browse-registry spec reveals before clicking. - The DS-picker options moved out of the new-project-modal subtree with the portal fix; query the page-level popover per the clipping spec's convention. - real-daemon-run's sendPrompt reports on timeout whether POST /api/runs was ever issued, so the CI-only BYOK failure can be split into swallowed-click vs unanswered-request on the next reproduction. * test(e2e): make BYOK availability hermetic and reveal remaining gallery clicks The BYOK missing-provider spec could only pass on machines with a real `opencode` binary: daemon detection maps byok-opencode's availability through the `opencode` agent's configured cli env, and the web composer refuses to POST /api/runs for an unavailable agent (the sendPrompt diagnostic confirmed "was never issued" on CI). Point the helper's agentCliEnv at the fake opencode runtime — the run still fails pre-spawn on the missing provider config, so the fake binary never executes. Also route the two remaining collapsed-container gallery clicks (the browse-registry starter journey and the detail-use force-click) through revealHomeTemplates; an inert container swallows force-clicks silently. * ci: probe a 24-shard full UI matrix against the four-minute wall target The first green 12-shard run put eleven shards at 141-354s test steps but concentrated the contiguous runtime-heavy chain in one 894s shard, setting a ~16-minute wall tail. With ~3568 total test-seconds and ~90s per-job overhead, 24 shards should land every job near four minutes. * ci: settle the full UI matrix on twelve shards The 24-shard probe stayed green but proved the runtime-heavy chain is a serial group (test.describe.configure serial in real-daemon-run), which Playwright keeps atomic within one shard: the tail stayed ~16 minutes while the extra twelve cold starts raised aggregate runner time from ~76 to ~102 minutes. Twelve shards dominate; lowering the wall below the serial-chain floor is a de-serialization follow-up, not a shard-count knob. * test(e2e): drop serial mode from shard-splittable files Playwright keeps a serial group atomic within one shard, so real-daemon-run's file-level serial made its ~16-minute chain the floor of the full UI matrix wall regardless of shard count. Every test in the three serial files already performs its full setup per test (config reset, route stubs, own project), so serial only added skip-after-failure and shard atomicity. Verified order independence by running each file's two standalone shard halves locally (fully parallel sharding, one worker): 21/21, 57/57, and 12/12 across the halves. * test(e2e): settle the projects fetch in real-daemon-run home navigation With serial mode gone, the plugin-authoring spec runs after arbitrary shard predecessors, so the daemon holds more projects when it navigates home; the late-resolving projects list re-renders the hero and left the shortcuts trigger unstable for the whole click timeout on CI. Port the projects-settle waiter already used by entry-chrome-flows into this file's gotoEntryHome. * test(e2e): name the plugins-load gate in the plugin authoring spec The home shortcuts trigger is disabled while the plugins list loads, and this spec's mid-flow reload re-fires that fetch after gotoEntryHome's settle has already passed — on CI the click then times out opaquely for 120 seconds. Arm a response waiter across the reload and assert the trigger's enabled state explicitly, so a slow or hung /api/plugins fails with the actual cause named. * test(e2e): enter plugin authoring through the plugins page create button The home shortcuts trigger sits disabled on CI runners while a home plugin apply hangs (plugins list resolves, the pending-apply gate never clears); three reproduction attempts confirmed the anomaly is CI-only and independent of this journey. The create button on the Plugins page drives the same queuePluginAuthoring flow, and this spec's oracle is the generated scaffold plus its action cards, so route the entry there and track the disabled-trigger anomaly separately. * ci: let configure-ci-parallelism drive full UI shard workers The single-worker override predates the repairs that explained the two-worker instability (BYOK agent availability and the reveal remount race, both test-side and since fixed), so it was pinning throughput on a stale diagnosis. Drop it and let the repo's nproc/2 convention apply — two workers on the four-core ui_hot profile — halving the per-shard test step if the runtime holds. * ci: probe three full UI workers against Blacksmith's actual vCPU capacity The ui_hot profile resolves to blacksmith-4vcpu runners that reportedly provide six actual vCPUs while nproc's cgroup view says four, so the nproc/2 convention derives two workers and idles capacity. Pin three workers for this probe and print the CPU topology alongside for verification. * Revert "ci: probe three full UI workers against Blacksmith's actual vCPU capacity" This reverts commit 63c8296ae0ead2c3dadfbfda41f6e068d58331ef. * ci: skip the critical fallback for pure packaged-leaf changes Playwright starts tools-dev web (daemon + web runtimes) and never launches the desktop, packaged, or tools-pack entrypoints, so PRs confined to apps/desktop, apps/packaged, or tools/pack were paying the two-job critical fallback (5.4-13.4 minutes of wall each) for coverage their package tests and preflight already provide — seven of the eight fallback PRs in the latest 100-merge sample were such false positives. Add a fail-closed ui_critical_validation_required scope: it clears only when every non-exempt changed file sits under one of those three leaf roots. tools-dev, any transitive package (including the undeclared metatool edge), scripts, runtime resources, unknown roots, and mixed leaf + runtime changes keep the fallback armed; root manifests keep enabling P0 with the existing critical/P0 mutual exclusion; merge-group full mode is unchanged. * test(e2e): align question-form specs with inline Studio discovery Rebasing onto main picked up #5496, which moved clarification forms inline into the chat flow and removed the legacy Questions workspace tab. Re-point the submit-persistence spec at the inline form, and fix the oracles that no other lane had verified since: the answered summary echoes the picked visual-style card title (not the option label), and the submitted answer message carries the card title plus its value id. * test(e2e): absorb post-rebase drift in BYOK gating and zh-CN hero copy Two more contracts only the full pool guards moved under the rebase: - The composer's BYOK gate silently swallows submits until the incremental /api/agents stream has delivered byok-opencode's availability, so a fast submit races the stream (the sendPrompt diagnostic reports the POST was never issued). Retry through that window; runErrorCard reads the last card, so swallowed attempts cannot shadow the provider-validation oracle. The gate erroring instead of waiting while agents are still loading is a product-side follow-up. - The home hero title copy changed in every locale; re-point the zh-CN P2 smoke at the current translation. * test(e2e): align gallery specs with the all-types default slice The Community gallery now defaults to an All category pill (#5759) instead of pre-selecting Deck: assert the new default state in the hero and category-tab specs, and have the facet-filter spec pick Deck explicitly before asserting the deck-only visibility set. * test(e2e): align the first-run reveal spec with the all-types default slice Same #5759 drift family as the entry-chrome-flows gallery specs, missed by the first sweep because it lives in home-hero-rail: the revealed gallery now shows the All pill selected instead of hiding it with live-artifact pre-selected. * docs(e2e): add UI test stability rules distilled from the full-pool work Capture the invariants a UI test must hold to stay green under the generic sharded full pool, learned from stabilizing that suite and two rebases: - order independence (no serial groups; setup is per-file, not universal), and that neither shade-filtered local runs nor merge-path P0 prove it; - retry-only passes are an order-leak/readiness signal, not flake; - settle async surfaces (best-effort waiter + a state-survives-a-window check) before interacting; - an enabled control is not a ready control when a streamed precondition (e.g. BYOK-OpenCode agent availability over the /api/agents SSE stream) gates it; - never force-click into inert/collapsed reveal containers; use actionability, not visibility, as the readiness signal; - hermetic agent availability via route mocks (with the terminal SSE `done` event) or fake CLI runtimes + agentCliEnv, never host binaries; - realign stale oracles against the product and confirm them in the lane that actually executes the case, since a merged PR does not verify P1/P2 or unregistered-file P0 oracles; - name failure causes so a CI-only reproduction carries its own evidence. Cross-linked from the top-of-file coverage-status pointer. * test(e2e): realign BYOK missing-provider spec to the #5745 client-side preflight main #5745 added a client-side BYOK preflight that blocks a missing-provider run before any POST and opens the execution Settings section, instead of letting the run reach the daemon and fail there. Rewrite the spec to the new "blocked before spawn" behavior: submitting opens the execution-mode dialog, no create-run request is issued, and no artifact is produced (persisted across reload). This drops the now-obsolete daemon-failed-run assertions and the SSE-availability retry loop, which the preflight makes unnecessary. * docs(e2e): explain why the functional config ignores visual specs Clarify that visual-*.test.ts are owned by playwright.visual.config.ts and its playwright_visual lane, so excluding them from the functional config is intentional scope (it keeps a bare test:ui or the full-pool shard run from executing visual specs without their visual config), not a disabled lane. * fix(web): portal the New Project brand-preview flyout with the popover The earlier DS-picker unclip portaled the popover list but left its companion brand-preview flyout on the old inline `position: absolute` against the trigger wrapper. Once the popover moved to a body portal, the flyout for a finalized `user:<id>` brand was still clipped by the modal body and no longer anchored to the popover. Portal the flyout too and place it beside the popover with the same viewport-aware logic (right of it when the 320px card fits, else flipped left, then clamped), mirroring the popover's up/down vertical anchor. Red spec: e2e/ui/new-project-ds-picker-clipping.test.ts "finalized-brand preview flyout is not clipped by the modal body" — red with the popover portaled but the flyout inline, green with this change. |
||
|
|
3447f60a34 |
fix packaged payload desktop handoff (#5678)
* fix packaged payload desktop handoff * fix beta mac full smoke fixture wiring * fix tools-pack mac payload cold start * test payload desktop update restart * fix Nix daemon launcher protocol build * fix(pack): harden Windows long-jump updates * fix(pack): harden mac long-jump acceptance * fix(handoff): defer armed journal writes until spawn and shutdown succeed Previously executeLegacyPayloadDesktopHandoff wrote the armed handoff journal, attempt, and runtime state before spawning the payload desktop and before the old desktop accepted shutdown. When spawn() threw or the shutdown request failed, the armed journal was stranded on disk, so the next cold start bailed out of prepareLegacyPayloadDesktopHandoff() with reason "already-armed" and the install stayed pinned to the old desktop generation indefinitely. Move the three writes to after both the payload child has spawned and the shutdown has been accepted. The old desktop is still alive while it acks the shutdown, and the payload waits for the old pid to exit before resuming, so the state still lands before the payload reads it. Add regression coverage for both the spawn-failed and shutdown-failed paths, asserting the journal stays "prepared" and a later cold start can retry. Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code) * fix(updater): await payload spawn acceptance * fix(packaged): advance confirmed handoff recovery --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
6b90486c97 |
docs: reconcile stale documentation with current code (#5829)
* fix(docs): align question guidance with form flow * docs: reconcile stale documentation with current code * fix(docs): remove retired template metadata from guides Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(e2e): stabilize visual workspace helper Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(docs): align localized template guide metadata Generated-By: looper 0.10.6 (runner=fixer, agent=codex) |
||
|
|
94d48f41f2 |
feat(plugins-home): order the template gallery by usage (OPEND-449) (#5106)
* feat(plugins-home): order the template gallery by usage (OPEND-449) Non-prototype facets (Slides, Image, Video, …) now lead by real usage — a trailing 28-day blend of distinct-users + runs from `run_finished`. The Prototype facet keeps its curated editorial order; the default mode-seeds (Web Prototype, Simple Deck) and no-preview / manually-sunk tiles drop to the bottom of their facet and the All view. Ordering lives in one shared comparator (`comparePluginGalleryOrder`) used by the grid and the Home rail; the All view keeps its visual-appeal order with sunk tiles pushed last. A weekly in-repo workflow rebuilds the popularity snapshot from PostHog and opens a self-PR; a committed snapshot ships the ordering immediately. * test(plugins-home): make Live Artifact grouping assertions order-insensitive The rail + Community-section tests hardcoded the curated Live Artifact preset order. Facet order is now usage/sink-driven (OPEND-449), so these grouping/ routing tests assert membership via a sorted comparison instead. --------- Co-authored-by: Scarlett Huang <scarletthuang@Scarletts-MacBook-Air.local> |
||
|
|
6e38058dcf |
feat(web): add agent-assisted home headline (#5699)
* feat(web): add time-aware home greeting * feat(web): add contextual greeting emoji * feat(web): use Agent-focused home headline * fix(web): refine agent-focused home copy * fix(web): keep German home copy informal --------- Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com> |
||
|
|
925db8d2af |
ci: move nix and docker off the merge validation path (#5682)
* ci: move nix and docker off the merge validation path Core ci.yml / Validate workspace / merge_group no longer schedule or require Nix flake check or Docker image builds. Packaging lives on two standalone workflows: nix.yml (flake check) and docker-image.yml (PR smoke build + tag/release publish). Update scopes, topology tests, and agent docs. * fix(ci): detect Docker publish from release inputs, not workflow_call event Reusable workflow callers keep their own github.event_name (usually workflow_dispatch), so matching event_name == workflow_call never fires and stable GHCR publish would silently smoke-build only. Gate publish on tag push or non-empty release_version / publish_latest inputs instead. * fix(ci): keep smoke-only Docker sha tags off the publish path Gate pr-/manual-sha metadata tags on publish != true so release-stable workflow_dispatch callers do not push extra manual-sha-* tags to GHCR. |
||
|
|
37515a4864 | fix docker ghcr public pull verification (#5016) | ||
|
|
d7151b2b60 |
feat(analytics): link downloads and first-party browser opens (#5442)
* feat(analytics): attribute desktop downloads * fix(analytics): initialize attribution routes after HTTP deps * feat(analytics): bridge first-party browser opens * feat(analytics): bridge first-party anchor opens * fix(analytics): make attribution claims idempotent * fix(analytics): record pending attribution results * chore(analytics): bind landing attribution KV * fix(ci): remove landing preview wrangler config override * fix(landing): resolve download attribution function * fix(analytics): harden attribution token consumption * fix(analytics): preserve same-installation claim retries * fix(analytics): merge recovered attribution retries * fix(analytics): preserve attributed browser links * fix(analytics): clear attribution on privacy reset * fix(analytics): bridge stopped AMR console links * fix(ci): deploy attribution schema before Pages * fix(ci): run attribution migrations with Wrangler 4 --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: PerishCode <perishcode@gmail.com> |
||
|
|
d954bee805 | Extract telemetry worker from monorepo (#5565) | ||
|
|
3e09ebf282 |
feat(release): publish localized changelog metadata (#5520)
* feat(release): publish changelog metadata * fix(release): verify release notes from stored bytes Generated-By: looper 0.10.3 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
fc5b276f7d |
feat(ci): surface merge-queue needs-validation ejection as a PR comment (#5519)
A PR ejected from the merge queue by the needs-validation gate leaves no trace on the PR itself: the gate fails on the queue's transient ref, the PR's own checks stay green, and mergeQueueEntry silently goes null. The only way to learn why the PR fell out of the queue is to dig through merge_group workflow runs, which nobody watches (observed on #5329). Producer: the merge_group gate in ci.yml now emits a handoff/comment artifact targeting the labeled PR right before it blocks, and uploads it on the failure path. Consumer: comment.atom.yml accepts merge_group workflow_run events; since a merge_group run's head_sha is the queue's synthetic merge commit, artifacts from those runs are bound to their producing run by run_id, and the base-freshness skip is waived (PRs ahead in the queue move the base while the run completes). The live-PR head check still guards staleness. Topology coverage pins the new boundary: comment.atom consumes pull_request + merge_group; autofix/report stay pull_request-only. |
||
|
|
edb6292f1c |
feat: post-update what's new card on home with release feed highlights (#5071)
* add post-update what's new card on home with release feed highlights
After an update, the home surface shows a one-time bottom-right card with
the release highlights (title, copy, optional image) and a "See what's new"
link. Content ships in the release feed metadata.json as an optional
whatsNew block sourced from tools/release/whats-new/<baseVersion>.json;
versions without highlights fall back to generic copy linking the release
notes. The card shows once per version, keyed on a locally stored
last-seen version, and never on fresh installs.
- packages/contracts: WhatsNewContent/WhatsNewResponse DTOs + analytics props
- daemon: GET /api/whats-new (channel feed fetch, 1h cache, stale/null
degradation) + od whats-new CLI mirror
- web: WhatsNewPopup card (CSS Module, i18n chrome in 19 locales,
surface_view/ui_click tracking), lastSeen decision logic in lib/whats-new
- release tooling: publish-metadata forwards the optional whatsNew block and
fails loudly on malformed files; docs in tools/pack/AGENTS.md and
tools/release/whats-new/README.md
* fix: gate whats-new card on active home view, soften Escape, validate locale overrides
Address review feedback on the post-update what's-new card:
- EntryShell passes `active={view === 'home'}` into WhatsNewPopup, and the
component defers its fetch/show decision (and `page_name: 'home'`
analytics) until Home is the active entry view, so direct routes like
/projects no longer surface the card or emit home-page events.
- The document-level Escape listener now ignores already-handled events
(`defaultPrevented`) and hides the card without marking the version as
seen or firing the dismiss analytics event, so a stray Escape no longer
permanently spends the once-per-version card.
- publish-metadata validates the optional `locales` block at publish time
(plain-object shape, non-empty `title`/`body`, HTTPS `linkUrl`) so
malformed locale overrides fail the publish loudly instead of being
silently dropped by the daemon parser; README documents the contract.
Generated-By: looper 0.9.11 (runner=fixer, agent=claude-code)
* fix(release): validate whats-new URLs by parsing, not prefix check
Replace the startsWith("https://") checks in readWhatsNewBlock with a
shared assertHttpsUrl helper that trims, constructs new URL(...), and
requires protocol === "https:", mirroring the daemon readHttpsUrl parser
so values it would silently drop (e.g. "https://") fail the publish
loudly for imageUrl, linkUrl, and locale linkUrl.
Generated-By: looper 0.9.11 (runner=fixer, agent=claude-code)
* fix(web): stop whats-new card fetch race from swallowing the card on first Home load
The card's fetch effect armed its once-guard when the fetch STARTED, and the
effect teardown re-armed it — so React StrictMode's double-invoke (dev) or a
Home toggle mid-fetch cancelled the first fetch, the second effect run saw the
guard still set and bailed, and the resolved-but-cancelled fetch re-armed the
guard without ever showing the card. The card then never appeared until the
Home view toggled again.
Arm the guard only once a decision is actually REACHED (fetch resolved with a
payload); keep the `cancelled` flag purely as a state-update guard that no
longer re-arms the fetch. A superseded fetch drops and the surviving one
records the decision, so the card shows on first Home activation and a genuine
mid-fetch Home exit still retries on the next activation.
Red spec: apps/web/tests/components/WhatsNewPopup.test.tsx renders under
StrictMode and asserts the card appears — red on the previous effect, green now.
* feat: add silent updater preference
* fix: block posthog in beta dry runs
* refactor(whats-new): drive the home card from a dedicated R2 document
Source the post-update "what's new" card from a single hand-curated document
on a dedicated R2 bucket (https://whatsnew.open-design.ai/whats-new.json,
overridable via OD_WHATS_NEW_URL) instead of per-version release metadata.
Operators edit that one file after a release; there is no publish tooling.
- Show-once is keyed on the document's `id`, not the app version: the card
shows whenever the id differs from the last one the client recorded, first
sight on a fresh profile included. This removes the version gating that could
never tell a fresh install from an existing profile upgrading across the
feature boundary (the reviewer's blocking finding) -- content identity
settles it instead.
- Daemon service just fetches the document and parses {id, ...content}; no
channel feed or release-version matching. Graceful degrade to no card on any
failure is preserved.
- Drop the now-dead release publish tooling: tools/release/whats-new/*, the
whatsNew forwarding in publish-metadata.ts, and its tests. Ops flow documented
in docs/whats-new.md.
- Remove the generic no-content fallback card and its two i18n keys; the card
shows only when the document has content.
Contracts, `od whats-new` CLI, and analytics updated to the {version,id,content}
shape.
* style(whats-new): align the home card with the marketing design
Match the reference card: brand serif body copy (var(--serif), same face as
the home hero), body + CTA stacked in a left column with the illustration
stretching to the card's full height on the right so there is no dead space,
a subtle CTA, and a slightly wider, softer card. CSS-module + markup only;
no behavior change (test ids and flow are untouched).
* style(whats-new): drop the title row, float the close over the card
Per design feedback the "Design system sync" label row is removed so the serif
body copy starts at the top edge; the ✕ now floats top-right as a bordered chip
that stays legible over the illustration. The title still labels the dialog via
aria-label for screen readers. Test asserts on the body + dialog name instead.
* style(whats-new): version eyebrow + release title as the card copy
Per design feedback: the top row is the "Open Design x.y.z" version eyebrow
(left) with the close button (right); the release title renders as the main
serif line (1-2 lines) and the illustration stretches to the card height; the
long body is dropped from the card. CTA + release-notes action unchanged.
* fix(whats-new): gate the card to release channels so it can't intrude on CI
The R2 rearchitecture dropped the daemon's channel gate, so development/CI
builds fetched the live document and showed the card on Home. That broke the
P0 "settings dialog reachable" smoke (getByRole('dialog') matched both the
settings modal and the what's-new card, which also carries role="dialog") and
would have dirtied every Home visual baseline. Restore the gate: only
beta/prerelease/preview/stable fetch the document; other channels resolve to no
card and never touch the network. OD_WHATS_NEW_URL still opts any channel in
for e2e fixtures that exercise the card on purpose.
* fix(whats-new): use role="complementary" instead of "dialog" for the toast
The card is a non-modal, dismissible announcement — not a dialog. As
role="dialog" it collided with getByRole('dialog') in the P0 Settings smokes
(settings-dialog-reachable, AMR reopen, Local CLI Codex fallback), which
strict-mode-failed the moment the card rendered on Home. A complementary
landmark is the correct a11y role for supporting content and never matches a
dialog query, so those P0s pass regardless of whether the card is showing. The
daemon channel gate (
|
||
|
|
d5aeab77fc |
fix(ci): push the release-cut baked manifest as a ruleset bypass bot (#5358)
* fix(ci): push the release-cut baked manifest as a ruleset bypass bot bake-plugin-previews-release.yml renders every preview on a release/** cut and pushes the authoritative manifest straight onto the release branch. But release/** is guarded by the 'Protected branches (preview/*, release/v*)' ruleset whose pull_request rule rejects direct pushes — and the workflow pushed with PREVIEW_BAKE_TOKEN||github.token, i.e. github-actions[bot], which is NOT a bypass actor. So the writeback failed with 'GH013: Repository rule violations found for refs/heads/release/vX.Y.Z' (observed on release/v0.14.2), and the release branch's bundled data/plugin-previews/manifest.json was never refreshed — the packaged app then falls back to rendering each preview as a live iframe (pathological for the WebGL example templates). Mint an open-design-bot token (app id 3640364 — already a bypass actor on that ruleset, same App the metrics/blog/contributors workflows use) after the render and push with it via an explicit token URL. persist-credentials: false on checkout so the leftover github.token extraheader can't override the inline bot token on push (same failure mode fixed in #5357). No ruleset change required — the bypass actor already exists; the workflow just wasn't using it. * test(ci): lock the release-cut bake's ruleset-bypass push contract Per .github/AGENTS.md, cross-workflow auth changes must update topology coverage in e2e/tests/packaged-smoke-workflow.test.ts rather than rely on YAML review. Add a companion assertion (alongside the existing bake-plugin-previews.yml one) that locks bake-plugin-previews-release.yml's three co-dependent auth invariants: persist-credentials: false on checkout, the open-design-bot token mint (BOT_APP_CLIENT_ID/BOT_APP_PRIVATE_KEY — the App that is a ruleset bypass actor, not RELEASE_BOT_APP_ID), and the explicit x-access-token:${BOT_TOKEN} push. A refactor dropping any one silently reintroduces the GH013 rejection that stranded release/v0.14.2's manifest. |
||
|
|
6cf1221cde |
fix(ci): push the rolling plugin-preview manifest as the release-bot App (#5357)
The post-merge bake force-pushes the chore/plugin-previews branch with the release-bot App token so the rolling PR's sync is attributed to app/open-design-release-bot and its CI runs automatically — bake-plugin-previews-automerge.yml then approves + enqueues it. But the Checkout step persisted the default GITHUB_TOKEN as an http.https://github.com/.extraheader Authorization header, which overrides the inline App token on push, so the force-push authenticated as github-actions[bot]. A github-actions[bot]-attributed pull_request run is held as action_required (GitHub's bot-recursion gate, 0 jobs, no REST approve for non-fork runs), so the rolling PR's CI never ran without a manual 'Approve and run'. With no green CI the automerge reactor (gated on workflow_run.conclusion == 'success') stayed skipped and the manifest PR sat BLOCKED — main's baked-preview manifest went stale and freshly-baked clips (e.g. the WebGL example templates) never surfaced in the gallery. Set persist-credentials: false so the App token is the sole push credential and the rolling PR is attributed to the App again. Nothing between checkout and the push needs the persisted credential. |
||
|
|
d41cdd9404 |
feat(release): post an immediate "branch cut" Feishu card on both release cuts (#5323)
Cutting a release branch (Tuesday minor via cut-release, Thursday patch via
cut-patch-release) pushes to release/**, which triggers notify-release-feishu to
build a full prerelease before it posts the download card — 20-40 minutes during
which the team has no signal the cut even happened.
Post an eager green "🌿 已切出 release 分支" card the moment the branch exists, from
the cut workflow itself, reusing feishu-notice.ts (node:crypto only, so the
existing setup-node suffices). The card names:
- the branch and whether it is a 大版本 (minor) or 小版本 (patch),
- the exact backport label (`backport release/vX.Y.Z`) so people know which
label to apply for backports,
- a note that the prerelease build is running and a download card will follow.
The download card from notify-release-feishu is unchanged; each cut now yields
an immediate ack card plus the eventual download card. The patch workflow's eager
card only fires on the happy (published) path, alongside the existing skip card.
Topology test asserts both workflows post the eager card after the branch push and
name the backport label.
|
||
|
|
dbc70663aa |
fix(release): let the Thursday patch cut survive an already-matching version (#5320)
The first scheduled cut-patch-release run (2026-07-09) failed at "Create branch + bump version + push" with "nothing to commit, working tree clean". The guard had passed (open-design-v0.14.0 published → happy path) and it computed patch v0.14.1, but main's apps/packaged/package.json was already 0.14.1: in steady state main leads stable by exactly one patch (finalize-release bumps main to X.Y.(Z+1) after X.Y.Z ships), and the Thursday patch equals that same X.Y.(Z+1). So `npm pkg set version=0.14.1` is an idempotent no-op, `git commit -am` finds an empty tree, and under `set -euo pipefail` the step dies before the push — no branch cut, no prerelease, no Feishu card. This collides every Thursday the version already matches, so it is structural, not a one-off. Commit the release marker with `--allow-empty` so the branch is always cut and pushed (which is what triggers notify-release-feishu); a real version bump still commits the change normally. Add a topology assertion that the release commit tolerates an empty tree. Verified locally: with a real `npm pkg set version=<same>` (idempotent, no diff) the old `git commit -am` fails exactly as CI did while `--allow-empty` succeeds with a marker commit; a differing version still commits the bump. |
||
|
|
b0e4f83e85 |
ci(release): Thursday patch-release cut, gated on the Tuesday minor shipping (#5294)
* ci(release): add Thursday patch-release cut gated on the Tuesday minor shipping cut-release cuts a minor every Tuesday (from main HEAD) and, via the release/** push, fires notify-release-feishu to build a prerelease and post a Feishu card. Add cut-patch-release: the same flow one weekday later (Thursday 09:00 Asia/Shanghai) with three deltas: - Bumps the PATCH of the highest release/vX.Y.Z branch (X.Y.(Z+1)), still cut from main HEAD like cut-release. - Gates on this week's minor having actually shipped: the line's minor base X.Y.0 must be a PUBLISHED stable GitHub Release (open-design-vX.Y.0, isDraft=false, isPrerelease=false — i.e. release-stable ran `gh release edit --draft=false --latest`, which rolls the tag back on failure). If it is not published, no branch is cut and nothing is built; instead a Feishu notice card announces the skip. - On the happy path it is otherwise identical to cut-release: create the branch, bump apps/packaged, App-token push (which triggers notify-release-feishu), and create the backport label. feishu-notice.ts is a lightweight sibling of feishu.ts for non-build events (here: the skip card). It reuses the same signed-webhook + retry transport and depends only on node:crypto, so it runs under the existing setup-node with no pnpm install. packaged-smoke-workflow.test.ts locks the guard: Thursday cron + patch bump, the published-stable predicate, and that every branch-cutting step is gated so the skip path posts the notice without cutting or building. * fix(release): gate the patch cut on the final version's minor, not the highest branch The guard target MINOR_BASE was derived from the highest release/v* branch before the manual workflow_dispatch `version=` override was applied. On the auto path V comes from that same branch, so it matched — but a manual `version=0.15.1` while the latest branch is release/v0.14.0 would check open-design-v0.14.0 (the wrong minor), letting the workflow cut release/v0.15.1 even when 0.15.0 has not shipped (and the inverse could wrongly block a valid manual patch on another line). Decide V first (validated override wins, else patch-bump the highest branch), then derive MINOR_BASE from V's own major/minor. Add topology assertions that MINOR_BASE comes from V and never from the highest branch, covering the manual dispatch path. |
||
|
|
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. |
||
|
|
390fcf88f3 |
fix(plugin-previews): keep Community gallery previews in sync with shipped plugins (#5262)
* fix(plugin-previews): validate baked preview metadata against shipped plugins The Home Community gallery renders whatever data/plugin-previews/manifest.json names, and nothing kept that metadata in sync with reality: #4815 deleted 19 example plugins but left all 19 manifest entries behind, #4040 shipped a card showing another plugin's imagery, a failed re-bake silently leaves the old clip serving outdated content, and durationMs recorded the intended walk time rather than the encoded file (23 entries carried holdMs > real duration, so the card's idle loop pointed past the end of the clip). - add scripts/check-plugin-preview-manifest.ts to pnpm guard: every manifest entry must name a shipped plugin, own its clip keys (<id>/<hash>/... or the legacy <id>.<hash>....), carry a coherent fingerprint, and never share a clip with another entry; prune the 19 orphaned entries it caught - bake validation (BAKE_VERSION 5): reject blank clips (whole-clip luma range via ffprobe signalstats), record durationMs from the encoded file, clamp holdMs to it, and write a bake-report.json naming skipped/stale/blank plugins instead of swallowing the failure - strict mode (--strict / PREVIEW_STRICT=1) fails the run when a bake left broken metadata behind; the pre-merge validation workflow now runs strict while the post-merge/nightly publishers stay lenient * fix(plugins): make the hyperframes example self-contained The example loaded cdn.tailwindcss.com (a render-blocking head script the page never used — every style is inline) plus Google Fonts. Inside the sandboxed gallery iframe or on a slow network the stalled script showed a long blank instead of the composition, and the CI bake raced the webfonts. Drop both: the page now renders from local system font stacks with zero external requests, so the live preview, the detail iframe, and the baked clip all show the same content. * fix(plugin-previews): address review — fail fast on probe errors, widen strict trigger paths - probeClipMs/clipLumaRange now throw instead of returning null: ffprobe is required validation infrastructure, and a swallowed probe error silently disabled exactly the checks this PR adds. A thrown error becomes an 'error …' skip, is recorded in bake-report.json under 'errors', and fails strict mode — while the routine skips every sweep has (non-html plugins 404ing the preview route) still never trip strict. - bake-plugin-previews-pr.yml now also triggers on plugins/community/**, .github/actions/bake-previews/**, and itself, so community-plugin preview changes and bake-recipe changes cannot merge without the strict pre-merge validation. * fix(plugin-previews): bake static pages as a held still instead of skipping forever The strict pre-merge bake on this PR caught five manifest entries that could NEVER refresh: example-dating-web, example-html-ppt-zhangzara-8-bit-orbit, example-frame-logo-outro, od-new-generation, example-orbit-notion. All five are (near-)static pages — the CDP screencast only delivers frames when something repaints, so they produce 1-4 frames and the old 'frames < 5' guard skipped them on every bake, nightly included, leaving their committed entries permanently stale (their old clips are the 103-210ms degenerate files in the manifest today). Encode <5-frame captures as a still instead: hold the last frame for the idle-loop span (half before the trailing repeated concat entry, half inherited by it, summing to ~HOLD_MS). Only a zero-frame capture is a real failure now. The blank-luma check still rejects stills of nothing. * fix(plugin-previews): never persist a manifest entry without a fingerprint When the preview fingerprint fetch failed, the loop still rendered and — on an otherwise successful bake — persisted an entry with hash: null and un-fingerprinted keys: metadata the content-hash reuse skip can never match again and the new manifest guard rejects. Treat a missing fingerprint as an infrastructure error instead: skip the plugin, record it under errors in bake-report.json (strict mode exits non-zero), and leave any committed entry untouched for the next sweep. --------- Co-authored-by: Tuola Ge <gexingli@refly.ai> |
||
|
|
e0d7a77230 |
fix(release): align stable notes public origin (#5037)
Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
6132da1d79 |
[codex] Restore stable release publish mode (#5031)
* fix(release): restore stable publish dispatch mode * test(e2e): cover stable publish dispatch mode --------- Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
b2512e9a7e |
ci: block direct PRs into release branches except release automation (#4982)
Colleagues have been opening PRs straight into release/vX.Y.Z (e.g. into release/v0.13.0), bypassing main + the backport flow. Add a guard workflow: on a PR opened/reopened/edited with a release/** base, if the author is not a release-automation bot (open-design-release-bot, github-actions), post a comment explaining the backport flow (merge to main, add `backport release/vX.Y.Z`) and close the PR. Closing (not a failing check) is the only real gate here: release/* branches have no required status checks and no merge queue, so a red check would not block a merge. Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
6c424f4ad5 |
fix(release): populate github.commit in prerelease metadata so the changelog has a baseline (#4960)
The Feishu release card (notify-release-feishu.yml) builds its changelog as `git log <previous>..<current>`, reading <previous> from the published prerelease/latest/metadata.json `.github.commit`. That field is written by githubInfo(), which reads the RELEASE_COMMIT env. release-beta sets RELEASE_COMMIT on every build + publish job; release-prerelease set it on none, so every prerelease published an empty github.commit. Each prerelease then found no baseline and the card always rendered "首个 Prerelease 包,无上个版本可 对比" — the changelog was permanently empty, not just for a fresh release line. Add RELEASE_COMMIT to the four prerelease build jobs and the publish job, mirroring release-beta. Both are required: publish-metadata rejects a per-platform manifest whose github.commit disagrees with the metadata commit, so the build jobs (which write per-platform manifests) and the publish job (which writes the final metadata.json) must stamp the same built commit. Red spec in packaged-smoke-workflow.test.ts asserts each prerelease build + publish job carries RELEASE_COMMIT (red on main, green here). |
||
|
|
3fb3e3ca4a |
ci(release): finalize-release — bump main + drop backport label after a stable ships (#4936)
* ci(release): finalize-release — bump main + drop backport label after a stable ships * ci(release): arm merge queue + request core-maintainer review on the bump PR main enforces require_code_owner_review and the bump touches CODEOWNERS-owned release manifests, so a bot approval can't merge it hands-free (unlike backport-automerge / bake-plugin-previews-automerge, whose targets/diffs avoid code-owner review). Open the PR, arm the squash merge queue (gh pr merge --auto, proven on main by bake-plugin-previews-automerge), and request the @nexu-io/core-maintainers review that the code-owner rule needs. * ci(release): trigger finalize via workflow_run, not release:published release-stable.yml promotes its own draft to published with gh release edit using GITHUB_TOKEN, and a release event minted by GITHUB_TOKEN does not trigger other workflows — so a release:published trigger would never fire. React to release-stable completing instead (the backport-automerge / bake pattern), and gate on the open-design-vX.Y.Z release actually being published (draft=false) so dry-runs (metadata/prepublish) correctly no-op. * ci(release): resolve version from latest published release, not head_branch PerishCode review (blocking): release-stable supports building one ref while dispatched from another via its `ref` input, so workflow_run.head_branch can be the dispatch ref, not the built release branch — parsing it would miss-fire or read the wrong version. Read the version from the latest published release instead (release-stable marks it --latest), which is branch-independent. Drop the head_branch job-if guard; the draft/prerelease/X.Y.Z guards + bump idempotency keep dry-runs a clean no-op. * test(release): pin finalize-release workflow topology PerishCode review (blocking): .github/AGENTS.md asks cross-workflow behavior changes to update the topology tests, not rely on YAML review alone. Add a focused [P2] test in packaged-smoke-workflow.test.ts (alongside the backport / bake automerge coverage) that pins finalize-release's trusted-consumer contract: workflows: [release-stable] + success/dispatch gating, App-token use, version resolved via gh release view (latest/tag) with NO workflow_run.head_branch dependency, draft/prerelease/X.Y.Z + skip handling, forward-only synchronized bump, core-maintainer review request, and SHA-pinned merge-queue arming. --------- Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
110774e891 |
fix(ci): merge-queue needs-validation gate skips non-PR refs, not eject clean PRs (#4898)
* fix(ci): merge-queue needs-validation gate skips non-PR refs, not eject clean PRs The merge_group needs-validation gate enumerates queued PRs from three sources; source 2 scrapes a trailing `(#N)` from each commit subject. That `(#N)` can be an ISSUE reference an author put in the PR title, not a queued PR — e.g. #4833, titled "fix(plugins): ... (#4828)", where 4828 is the fixed issue. The gate then ran `gh pr view 4828`, which fails with "Could not resolve to a PullRequest"; as a plain assignment under `set -e` that ejected a clean, label-free, approved PR from the merge queue every time it entered — permanently unmergeable. Distinguish a definitive "not a PR" (skip — the number was a spurious reference) from a transient API failure (still fail closed → block), so real needs-validation PRs are still enforced while issue references no longer block clean merges. * fix(ci): resolve merge-group needs-validation per commit, not a flat union Addresses review: skipping a non-PR `(#N)` unconditionally could, in a batched merge group, drop the only candidate that would have checked a non-tip PR — so a non-tip PR still carrying `needs-validation` could slip through (#4736 hazard). Resolve each merge-group commit to at least one real PR from its own signals (subject `(#N)`, commits/<sha>/pulls, and — for the tip — the queue ref). A `(#N)` that does not resolve to a PR (e.g. an issue reference like #4833's "(#4828)") is ignored ONLY when its commit is still covered by another candidate that resolves to a real PR; a commit that resolves to no PR fails closed. Real needs-validation PRs are still blocked; clean PRs whose title references an issue are no longer ejected. Verified with a mocked gh harness: (A) tip "(#4828) (#4833)" → ignores 4828, checks 4833, clears; (B) batched non-tip whose only number is an issue ref + carries needs-validation → fails closed; (C) tip with needs-validation → blocks. --------- Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
211ec5df5a |
ci: make the Nix hash check advisory on PRs, enforce it only at merge time (#4896)
* ci: make the Nix hash check advisory on PRs, enforce it only at merge time A stale nix/pnpm-deps.nix hash is the routine, self-healing consequence of any pnpm-lock.yaml change: the nix_validation job already regenerates the hash and hands it to autofix.atom.yml, which pushes the refresh back to the PR branch. Failing the job on pull_request turned that transient, auto-fixed drift into a red check that flipped the PR to BLOCKED and read as a real CI failure to the review bot — the same deadlock the needs-validation gate already documents. The contributor then waited on the autofix round-trip every time the lockfile moved. Now nix_validation stays green on pull_request (the autofix still self-heals the hash in the background) and the hash is enforced as a hard gate only at merge time: merge_group and manual full runs fail closed, so a genuinely broken Nix build can never reach main. The Validate workspace gate ignores a red Nix job on PRs (both the blanket failure scan and the required-jobs check) and keeps requiring it everywhere else. * test(e2e): cover the Nix-advisory-on-PR Validate workspace gate Extract the two jq programs the `validate` job runs (the blanket failure scan and the required-jobs scan) straight out of ci.yml and feed representative `needs` JSON through the real `jq`, so the cross-workflow gate behavior is asserted rather than reviewed by eye: - pull_request + failed nix_validation -> gate passes (advisory), - merge_group / workflow_dispatch full + failed nix_validation -> gate fails closed, - pull_request + another failed required job -> gate still fails (the PR exception is scoped to nix_validation only). A future edit that makes Nix advisory on merge_group, or stops ignoring it on PRs, now fails this test instead of slipping through YAML review. |
||
|
|
cbf3882e7e |
[codex] Reset PR inactivity window on human activity (#4882)
* fix: reset PR inactivity window on human activity * fix: handle commit and failed CI blocker detection |
||
|
|
514ddba8ed |
ci: consolidate runner selection contract (#4803)
* ci: consolidate runner selection contract * test: stabilize connector settings privacy state |
||
|
|
8b9b169673 |
ci: add cost-sensitive runner tiering (#4764)
* test persistent contabo runner poc * test persistent runner web workspace poc * test persistent runner local cache setup * test persistent runner aggressive setup poc * test persistent runner web postinstall profile * test restore stable persistent runner poc * test keep persistent runner web tests default * test route persistent poc to zxiyun runner * test relax web vitest timeout on persistent poc * test harden web vitest cases for persistent runner * test add runner perf probe * test isolate runner perf probe dispatch * test pin runner perf probe node * test expand runner perf probe matrix * ci: route heavy jobs through runner modes * ci: keep web workspace tests on blacksmith * test: update web runner mode assertion * ci: route cost-sensitive runner tiers * ci: move preflight to hosted runner * ci: downshift blacksmith ui runners to 4vcpu * test: fix file viewer timeout after merge * ci: route e2e vitest to hosted default * ci: centralize runner profile selection * test: type runner profile assertions * ci: route e2e vitest to default blacksmith * ci: remove runner experiment probes * test: restore postinstall guard coverage Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * test: cover postinstall script contract * test: move postinstall guard to e2e --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
4c6f6549c6 | ci: add landing QA Feishu summary (#4763) | ||
|
|
a4dea37a22 | fix(ci): harden backport automerge provenance (#4761) | ||
|
|
60d187b79f |
feat(ci): auto-merge the rolling plugin-preview manifest PR on green CI (#4727)
* feat(ci): auto-merge the rolling plugin-preview manifest PR on green CI The post-merge bake opens ONE rolling PR (chore/plugin-previews) whenever a baked preview changes (spec slice 1). Its diff is pure generated data (data/plugin-previews/manifest.json; the clips already live on R2), yet someone still had to merge it by hand — the last manual step the pipeline rework left behind, and exactly the kind of bot PR that used to pile up. Two coupled changes: 1. bake-plugin-previews.yml now authors the rolling branch + PR with the release-bot App token instead of GITHUB_TOKEN. A GITHUB_TOKEN-authored push does not trigger workflows, so the manifest PR never ran `Validate workspace` (main's required check) and could not clear the merge queue at all — even manually. (PREVIEW_BAKE_TOKEN, the old fallback, was never configured.) 2. bake-plugin-previews-automerge.yml reacts to ci.yml completing for the rolling branch and, for a non-draft same-repo PR authored by the release bot with only bot commits (pristine) at the exact SHA CI passed, approves it as github-actions[bot] (main requires one review; an author cannot approve its own PR) and enqueues it with `gh pr merge --auto --squash --match-head-commit`. CI failure pings Feishu instead. Modeled on backport-automerge.yml, adapted for main: main has a merge queue, so the merge is GitHub-native --auto (enqueue) rather than a direct squash, and pristine checks the git committer email (the bot email maps to no GitHub user, so .committer.login is null). * fix(review): GitHub-backed pristine provenance + correct Feishu sign secret Address two blocking review findings on the auto-merge path: 1. pristine could be satisfied by spoofed commit metadata. The bake commit was authored with a plaintext `git config user.email "bot@open-design.ai"`, and the reactor trusted that raw committer email — any collaborator who can push to chore/plugin-previews could set the same email and fake pristine=true, landing unreviewed commits on main. Commit AS github-actions[bot] (its canonical noreply email) and gate on GitHub's resolved `.committer.login`, the same GitHub-backed field backport-automerge.yml trusts. 2. the failure Feishu ping signed with secrets.FEISHU_WEBHOOK_SECRET, but every other FEISHU_RELEASE_WEBHOOK caller (notify-release-feishu, notify-daily-feishu, backport-automerge) signs with FEISHU_RELEASE_SIGN_SECRET; the wrong secret resolves empty and the signed release webhook rejects the unsigned request. Use FEISHU_RELEASE_SIGN_SECRET and assert the pairing in the topology test. * test(ci): lock the auto-merge conclusion gates + bake producer invariants Address nettee's non-blocking coverage follow-up so a regression can't pass the topology test green: - Assert the approve and enqueue steps gate on `workflow_run.conclusion == 'success'`, and the Feishu step on `== 'failure'`, so dropping a conclusion predicate fails the test. - Add a companion `[P2]` block over bake-plugin-previews.yml asserting the three producer invariants the downstream pristine/auto-merge path depends on: the create-github-app-token mint, the App-token push URL, and the github-actions[bot] commit identity. Regressing the producer back to a GITHUB_TOKEN push or a different commit identity now fails here. * fix(review): bind the Feishu failure alert to the run's exact SHA A stale failed CI run can finish after the force-pushed rolling branch has advanced to a newer (green) head. The Feishu step only gated on author/cross + conclusion==failure, so it would page about a SHA that is no longer the PR head. Add the same steps.pr.outputs.head_oid == workflow_run.head_sha guard the approve/enqueue steps use, and lock it in the topology test. * fix(review): prove bot provenance with GitHub signatures, not git headers nettee correctly escalated: .committer.login is still a spoofable git-header identity. A collaborator who can push to chore/plugin-previews can mint a local commit with the bot's noreply email, GitHub then reports .committer.login == github-actions[bot], and pristine=true passes — landing unreviewed human changes on main. Switch to non-spoofable provenance: - bake-plugin-previews.yml now creates the manifest commit through the Git Data API (blob -> tree -> commit -> ref) with the App token, instead of a local `git commit` + push. A commit created through the API is signed by GitHub (verification.verified=true) and committed by the App bot (committer.type=Bot). Building the tree on the live main HEAD also rebases the rolling branch onto the latest main each run. - the reactor's pristine gate now requires EVERY commit to be both verification.verified==true AND committer.type==Bot. A human's locally pushed commit is unsigned (verified=false) even with the bot email; a human signing with their own key is committer.type=User. Neither can be both, so the pair is forge-proof. - topology test updated to lock the API-commit producer path (and forbid a regression to a local push) and the verified+Bot pristine check. * fix(review): parent the manifest commit on the rendered revision, not live main The previous change based the Git Data API commit on live main HEAD, but $NEW is rendered from the job's checkout at start. The bake can run ~90min and main can advance (unrelated merges) meanwhile, so basing the commit on live main would publish a manifest from an older checkout on top of newer commits — the rolling PR would look current while carrying stale preview data and could overwrite newer plugin-preview changes. Parent on `git rev-parse HEAD` (the exact revision that produced $NEW) instead, so GitHub correctly shows the PR as behind once main moves and the next bake re-renders on the newer HEAD. Topology test locks the rendered-revision base and forbids regressing to a live-main base. * fix(review): require the rolling PR's rendered base to equal main before merge main's merge queue does not require an up-to-date branch, so a bake rendered against an older main could be re-tested on the latest main and squash-merged with a stale manifest.json — overwriting a newer manifest that landed meanwhile. head_oid only proves the PR head is the SHA CI validated, not that it is current. Add a base-freshness gate: the rolling commit's first parent is the revision the manifest was rendered against, so require it to equal current main HEAD before approving/enqueuing. A behind PR is skipped and refreshed by the next bake (the nightly re-renders on the newer tip even if no plugin changed). Locked in the topology test. * fix(review): page Feishu on timed_out bakes too, not just failure workflow_run has terminal RED states beyond 'failure'. ci.yml defines many timeout-minutes jobs, so a timed-out run on the rolling manifest PR would block auto-merge with no Feishu page. Broaden the alert predicate to failure OR timed_out, and include the conclusion in the message. Deliberately exclude 'cancelled': a force-push of the rolling branch routinely cancels the prior in-flight run, so alerting on it would page on every normal refresh. Test locks the failure+timed_out set and the cancelled exclusion. * fix(review): gate auto-merge on a manifest-only diff, not commit signatures nettee is right that the Git Data API does not sign commits — I verified it empirically: a blob/tree/commit created via POST /git/commits comes back verification.verified=false / reason=unsigned (and the Contents API is unsigned too for non-web callers). So the previous verified==true && committer.type==Bot pristine gate would never pass and the rolling PR could never auto-merge. There is no per-commit field that is both bot-set and unforgeable without a signature, so stop trying to prove "the bot authored every commit" and instead bound what auto-merge can land: require the PR's changed files to be EXACTLY data/plugin-previews/manifest.json. A human with push access can still push to the rolling branch, but any commit that touches a code file (or anything but the manifest) makes the file set != the single manifest path, so pristine=false and the PR falls back to human review. The worst a forged manifest-only change can do is point a gallery preview at a bad video URL — generated data the next bake overwrites — never unreviewed code on main. With signing no longer relied upon, revert the producer from the Git Data API dance back to a plain App-token git push (still parented on the checked-out HEAD per the base-freshness fix, still pushed with the App token so CI runs). Tests updated to lock the manifest-only file gate and the simplified producer. * test(ci): update secretless-bake assertion for the App-token producer Merge main (#4691 landed the read-only pre-merge bake + its topology test). That test asserted the post-merge workflow contains PREVIEW_BAKE_TOKEN as the write-capable credential; this PR replaced that never-configured fallback with the release-bot App, so assert secrets.RELEASE_BOT_APP_ID (and that PREVIEW_BAKE_TOKEN is gone) instead. |
||
|
|
29b138f7a3 |
feat(brands): turn any brand into a reusable design system (#4691)
* Implement brand management routes and CLI support - Added `brand-routes.ts` to handle HTTP endpoints for brand operations: listing, extracting, retrieving details, deleting, and serving logos. - Introduced `brands-cli-help.ts` for CLI commands related to brand management, including usage instructions for listing, creating, retrieving, and deleting brands. - Updated `cli.ts` to integrate brand commands into the existing CLI structure, allowing headless management of brands via the command line. - Created supporting files for brand metadata handling, including `design-md.ts` for rendering brand information in markdown format and `index.ts` for the brand engine API. - Implemented `prefetch.ts` to fetch and process brand material from specified URLs, ensuring a streamlined extraction process. - Enhanced server setup in `server.ts` to register brand routes and manage brand-related data effectively. This commit establishes a comprehensive framework for managing brands within the application, facilitating both HTTP and CLI interactions. * Enhance memory management and onboarding experience - Introduced canonical profile labels to ensure consistent handling of user input in profile forms, preventing duplicate entries. - Updated the `parseProfileBody` and `captureProfileFromForm` functions to utilize the new canonical label matching. - Added a memory callout section in the onboarding view to highlight the benefits of memory usage, including personalized responses and reduced setup questions. - Implemented new UI elements in the onboarding view to improve user engagement with memory features. - Expanded i18n support for new onboarding messages related to memory benefits across multiple languages. * Refactor onboarding flow and enhance design system integration - Updated the onboarding process to include a new brand extraction step, replacing the previous newsletter step. - Adjusted the tracking logic to reflect the new onboarding steps, ensuring accurate analytics for user progress. - Improved the UI for the onboarding view, including new input fields for email collection during the brand extraction phase. - Refined the EntryShell component to remove outdated comments and clarify the onboarding renderer's purpose. - Enhanced CSS styles for the onboarding steps to improve layout and user experience. - Updated internationalization strings across multiple languages to reflect changes in the onboarding flow and brand extraction messaging. * Add brand management features and enhance font handling - Introduced new modules for managing brand assets, including `chrome.ts` for headless Chrome operations and `fonts.ts` for self-hosting web fonts. - Implemented `prefetch.ts` to streamline the brand material extraction process, allowing for efficient harvesting of colors, fonts, and logos. - Enhanced the brand system with new schema definitions in `schema.ts` to support brand color and font management. - Developed the `engine` module to integrate brand building and rendering processes, including token derivation and artifact generation. - Improved the overall structure and organization of brand-related files for better maintainability and scalability. * Enhance brand extraction and project management features - Updated `brand-routes.ts` to include new dependencies for project management, allowing for the registration of brand-related projects. - Modified the `extractBrand` function to support project ID and system files, improving the brand extraction process. - Enhanced the CLI commands in `cli.ts` to handle project IDs during brand creation, enabling better tracking of brand projects. - Updated the server setup in `server.ts` to register new project-related routes. - Improved the UI components to display project information associated with brands, including buttons for opening projects in the `BrandDetailView` and `BrandsTab`. - Added new metadata fields in the contracts to support project tracking and management for brands. This commit establishes a more robust framework for managing brand projects, enhancing both backend and frontend functionalities. * Enhance onboarding profile management and memory persistence - Added new canonical profile labels for 'Organization size', 'Use cases', and 'Discovery source' to improve user input consistency. - Introduced `OnboardingProfileState` type to manage onboarding profile data more effectively. - Implemented functions to build and persist the onboarding profile body to memory, ensuring user selections are saved accurately. - Updated the `OnboardingView` component to handle profile persistence during navigation and submission steps. - Enhanced tests to verify that user selections are correctly persisted to the memory profile. This commit improves the onboarding experience by ensuring that user inputs are consistently captured and stored, enhancing overall user engagement with the application. * Reflow brand extraction into an agent-driven, live flow Replace the deterministic SSE prefetch/preview/system pipeline with an agent-driven extraction: POST /api/brands now reserves the brand and stands up a backing project with the target site open in an in-app browser tab plus a seeded prompt, so the agent measures, synthesizes brand.json incrementally, and the user can clear anti-bot walls by hand. New /preview and /finalize routes let the agent render the kit page live and register the resulting user:<id> design system, so extracted brand facts persist as a structured, reusable brand kit instead of a one-shot deterministic guess. Adds the brand-extract skill (SKILL.md + brand-kit.html template), kit-render engine, brand-extraction-engine tests, brand project covers in the Designs tab, onboarding extract handoff, and the matching od brand extract/preview/ finalize CLI subcommands and contract updates. Co-authored-by: Cursor <cursoragent@cursor.com> * Sediment finalized brands into structured memory Reflow a finalized brand into the memory store (brandToMemoryEntries + reflowBrandToMemory) so future chats can ground vague requests in the brand's palette, type, voice and rules. finalizeBrand now wires through the runtime dataDir and best-effort persists the brand, MemoryChangeEvent gains a 'brand' source, and the brand kit render hardens its inline JSON escaping. Adds brand.previewEmpty / brand.viewDetails across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Implement logo fallback and imagery support in brand extraction - Introduced a deterministic logo fallback mechanism to ensure that brand extraction processes can retrieve and save site logos, even when the agent fails to do so. - Enhanced the `startBrandExtraction` and `finalizeBrand` functions to utilize the new logo fallback, allowing for better handling of logo assets. - Added support for imagery samples in brand validation, enabling the inclusion of representative images in the brand kit. - Updated the brand kit rendering to include self-hosted fonts and imagery, improving the overall presentation of brand assets. This commit strengthens the brand extraction workflow by ensuring that logos and imagery are reliably captured and displayed, enhancing the user experience in brand management. * Enhance memory management with rule proposal and verification features - Introduced new functionality for distilling annotations into rule proposals, allowing users to suggest rules based on in-canvas annotations through the `od memory rule suggest` command. - Implemented a verification system that programmatically enforces compliance with active rules during artifact generation, ensuring that all active rules are covered in the self-verify scorecard. - Added endpoints for managing verification outcomes, including listing, removing, and clearing verification records, enhancing the transparency of the verification process. - Updated the memory management system to support the retrieval of active rule entries, ensuring that only linked rules are considered during verification. - Enhanced tests for both rule proposal generation and verification processes to ensure reliability and correctness. This commit strengthens the memory management capabilities by integrating rule proposals and verification, improving the overall user experience in managing design rules and ensuring compliance. * Distill review annotations into memory and enforce self-verify scorecard Add distillAnnotationsToMemory to mine inline preview comments/highlights/ marks into durable feedback + rule memory via a dedicated distiller prompt, threaded through the existing extract pipeline with an 'annotation' change source. Tighten the self-verify prompt (daemon + contracts) to state the daemon programmatically checks the scorecard, so a missing or uncovered scorecard on an artifact turn is an enforcement failure. Cover the rule suggest and verification-history routes with tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply brand design system through web config on "Use in new chat" Thread onApplyDesignSystem from the entry shell into BrandsTab so the brand's registered design system is applied via the web config channel instead of a bare daemon PATCH that left the Home composer stale. Add a transient home-intent latch + event so the Brands tab can request the Prototype chip on the already-mounted HomeView, which consumes it once the plugin catalog loads. Co-authored-by: Cursor <cursoragent@cursor.com> * Wire annotation distillation into background memory extraction Add a background distill pass that mines inline review annotations (comments / highlights / drawn marks) from a turn into durable memory alongside the general LLM extraction, surface an `annotation` memory toast source in the web UI, and cover the flow with a unit test. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix brand design system not applying to composer on "Use in new chat" Selecting a brand's "Use in new chat" applies the brand's design system as the default and fires the Prototype chip intent in the same synchronous click handler. HomeView consumed that intent inside the event listener, so `pickChip` ran before React committed the config change and seeded the composer's design-system field from the stale (empty) default — the composer showed "No design system" instead of the brand until a reload. Split the intent handling: the listener now only bumps a tick, and a separate effect consumes the chip after the re-render lands, so the seeded design system reflects the freshly-applied brand. Add the previously-untracked home-intent latch test coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): rework Brands into Brand Kit and add Home create entry Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): harvest real cover/hero images for the Images module The brand kit's Images gallery only populated when the extraction agent remembered to save imagery — so a forgetful or bot-blocked agent (and the pre-imagery "Open Design" brand) left it empty. Add a deterministic, server-side imagery fallback (imagery-fallback.ts), mirroring the logo fallback: it parses og:image/twitter:image, large <img> (highest-res srcset/<picture>), <link rel=preload as=image>, and CSS background-image hero blocks, fetches candidates with browser-shaped headers, decodes PNG/GIF/JPEG/WebP dimensions to keep only big representative images (dropping icons/sprites/logos/tracking pixels), dedupes by content hash, and saves up to 8 of the largest into imagery/ with labeled samples. finalizeBrand runs it as a timeout-bounded, failure-tolerant safety net (injectable so tests stay offline) when the agent captured too few samples, first adopting any on-disk images. The extraction prompt and brand-extract SKILL now explicitly direct the agent to harvest the site's large/cover/hero images, filtered by rendered size. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(qa): implement deck layout validation and safety checks Add a new QA module for validating the layout of generated brand decks to ensure robustness against clipping and truncation issues. The `analyseDeckLayout` function checks for critical layout invariants, including the presence of `.slide` sections, correct container types, and necessary runtime layers. Introduce `assertDeckLayoutSafe` to enforce these checks during brand system rebuilds, preventing the deployment of decks that fail validation. Additionally, create comprehensive tests to verify the functionality of the new layout validation features. * fix(brands): apply deck shrink-to-fit synchronously so slides never clip The no-clip runtime scheduled its fit pass through requestAnimationFrame, whose callbacks are throttled while the deck is offscreen or occluded. A slide could therefore stay unscaled — and clip its content — until first paint. Fit synchronously on resize/load/fonts-ready with a trailing setTimeout settle pass for late reflow, removing the rAF dependency. Verified at the previously-broken 1024x620 viewport: container-type:size, zero truncations, runtime auto-applies scale (Problem 0.71, ASK 0.87, Product 0.97, Competition 0.97) and frame clip count drops to 0. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): let New Brand modal embed scrollable brand reference picker Add a fillHeight mode to BrandReferencePicker so the heading, quick-pick row and controls stay pinned while only the gallery scrolls inside a bounded-height parent. Wire it into NewBrandModal with a stable, spacious dialog and refresh the related newBrand/brandPicker copy across all 18 locales. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): enhance brand extraction with deterministic seed harvesting Introduce a new `seed-fallback` module to provide a server-side deterministic palette and typography seed during brand extraction. This ensures that the brand kit's initial display includes a harvested logo, an approximate color palette, and font families, improving the user experience by reducing the all-skeleton appearance during the first paint. Update the `startBrandExtraction` function to utilize this new module, allowing for a more seamless and visually appealing brand extraction process. Additionally, enhance the `BrandReferencePicker` component to reflect loading states and errors during brand extraction, ensuring users receive immediate feedback on their actions. Update related tests to verify the idempotency of the `finalizeBrand` function, ensuring that re-finalizing a brand correctly reuses the existing design system without duplication. * feat(brand-extract): enhance BrandReferencePicker and localization updates Updated the BrandReferencePicker component to reflect loading states and errors during brand extraction, improving user feedback. Added a new localization key for the brand extraction process and updated existing translations in English, Simplified Chinese, and Traditional Chinese to enhance clarity and user experience. Additionally, introduced new styles for better interaction with brand assets in the brand kit template. * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * i18n(web): add brandPicker.opening across remaining locales + picker test Completes the brand-reference picker i18n key that was committed only for en/zh-CN/zh-TW, so every locale satisfies the typed Dict, and lands the BrandReferencePicker extraction-feedback test left untracked by the concurrent worker. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(EntryShell): enhance AMR cloud card visibility post-detection Updated the EntryShell component to ensure the AMR cloud card remains visible after detection settles, even when the AMR runtime is unavailable. This change prevents the card from disappearing and allows it to degrade gracefully to fallback content and sign-in flow. Additionally, added tests to verify the new behavior, ensuring a better user experience during onboarding. * feat(library): OD Library asset registry + OD Clipper extension Add a global, cross-project asset registry (OD Library) and a Chrome MV3 capture extension (OD Clipper), wiring the full HTTP + CLI + Web UI three-track loop per specs/od-clipper.md. - contracts: LibraryAsset/Source/Kind, ingest, search, pairing, task DTOs - daemon: 6 additive SQLite tables, content-addressed owned storage, the idempotent registerLibraryAsset hook (hash dedup + append-source), programmatic enrichment (mime/size/image dims/domain/tags), pairing tokens with a persisted extension-origin allowlist, /api/library/* routes, and /api/tools/library/{search,apply} for in-task agent reuse - cli: `od library list|get|rm|search|import|pair` - web: Library tab (grid, source badges, filters, search, live SSE updates, extension pairing affordance) - clipper/: standalone MV3 extension (background SW, content toolbar, popup) - skills/library-curator: utility skill for agent-driven asset reuse Origin middleware now honors paired chrome-extension:// origins (seeded from SQLite on boot) and exempts the pairing-confirm handshake. Enrichment AI stages (caption/OCR/embedding) are recorded as skipped pending a configured model. * feat(brands): programmatic-first design system extraction + rename Make brand extraction two-phase so a usable design system is ready the moment the user enters a URL — the instant "aha" — instead of waiting on the AI agent: 1. PROGRAMMATIC-FIRST (synchronous): startBrandExtraction now harvests the site deterministically (logo, palette, typography, one-line description, cover imagery, source URL) via prefetchBrand, synthesizes a valid design system with brandFromMaterial (no LLM), and finalizes + registers it before returning. finalizeBrand is refactored into a reusable finalizeBrandCore shared by both the programmatic path and the agent path. 2. ASYNC AI ENRICHMENT: the seeded agent prompt is reframed to enrich the already-usable design system and re-finalize in place (same user:<id>), updating every artifact/template. Bounded + best-effort: a blocked/unreachable origin skips phase 1 and stays `extracting` for the agent to drive. Gated on userDesignSystemsRoot so the legacy agent-only path stays intact for tests. Also rename the user-facing "Brand Kit" surface to "Design System" across en + zh-CN strings, project names, and the enrichment prompt. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(library): enhance asset import and management features - Updated the `import` command to allow multiple local files and remote URLs, with restrictions on supported formats. - Added new commands: `apply` for copying assets into project design files, `edit-as-page` for converting HTML assets into editable projects, and `figma` for exporting Figma captures. - Introduced sidecar functionality for storing derived data alongside owned assets, including Figma capture IR and element HTML. - Enhanced server configuration to support larger ingest payloads for asset captures. - Improved error handling and user feedback during asset import and application processes. * feat(asset-management): enhance asset dropzone and introduce chat-to-design feature - Updated the DesignSystemAssetDropzone component to improve file preview handling with new functions for creating and revoking object URLs. - Adjusted CSS for better layout and spacing in the asset dropzone. - Added a new "Chat to design" button in the LibrarySection component, allowing users to send selected assets to the Home chat composer for project creation. - Updated localization strings across multiple languages to reflect changes in asset import terminology. - Enhanced the HomeView component to handle asset staging from the chat composer. * feat(library): enhance asset application with element markup support - Updated the `applyLibraryAsset` function to include an `includeElement` option, allowing the capture of element markup alongside assets. - Modified related components (e.g., `ChatComposer`, `LibrarySection`, `FileWorkspace`) to handle the new element markup feature, ensuring both asset paths and optional element paths are returned and processed. - Introduced a new function, `fetchLibraryAssetElementHtml`, to retrieve the captured HTML for element-pick assets. - Enhanced the UI to display element markup inline within the chat composer, improving user interaction with captured elements. - Updated API contracts to reflect changes in asset application responses, including optional element markup paths. * feat(library): enhance asset filtering and preview handling - Updated the LibraryPicker and LibrarySection components to implement a badge-aware kind filter, allowing for more precise asset filtering based on badge kind. - Introduced a new `matchesKindFilter` function to streamline the filtering logic across components. - Enhanced the DesignSystemAssetDropzone to ensure proper handling of image previews, addressing issues with broken thumbnails under React StrictMode. - Added CSS styles for kind badges to improve asset representation in the UI. - Implemented tests for the DesignSystemAssetDropzone to ensure correct preview lifecycle management. * feat(library): hydrate single asset on SSE ingest Add fetchLibraryAsset(id) so the Library grid can merge just the one asset an `ingest` SSE event references instead of refetching the whole list on every capture. Returns null on miss/error. * feat(clipper): richer in-page image picker Collect CSS background-image url()s in addition to <img> (so hero/section art painted as backgrounds is no longer silently missed), defer thumbnail decode to visible cells via IntersectionObserver, draw downscaled canvas thumbnails instead of second full-res decodes, and add locate-on-page highlighting so a picked image can be traced back to its DOM source. * feat(library): implement lazy loading for thumbnails and enhance asset filtering - Introduced a `LibraryThumb` component to lazily load heavy content (images, videos, iframes) only when they are near the viewport, improving performance. - Added a debounced search feature to optimize asset filtering, reducing unnecessary network requests during rapid input. - Enhanced the asset filtering logic to track active filters using a ref, ensuring efficient updates during live events. - Updated the `snapshotCardRects` and `cardIdsInBand` functions to support improved hit-testing for drag-and-drop interactions. * feat(library): lazy picker thumbnails + debounced search Extend the Library grid's lazy-thumbnail + 250ms debounced-search pattern to the composer LibraryPicker so opening it no longer fires one full-bytes request per asset, and tidy the clipper content-script image collection. * feat(clipper): compress and budget capture inlining Re-encode large raster images to downscaled WebP and inline smallest-first within a fixed budget, dropping only the secondary Figma IR past a safe body size, so an image-heavy page (e.g. a news front page) always saves as an editable HTML capture instead of 413-failing the ingest. * test(library): LibraryPicker debounce + lazy-thumbnail coverage Cover the composer picker's 250ms debounced search and its lazy <img> mount (deferred until the card is in view), matching the grid's perf test. * feat(design-system): enhance asset handling and UI for design systems - Updated the CLI to support additional asset kinds, including 'design-system'. - Enhanced the DesignSystemProvenance type to include source URLs, improving provenance tracking. - Modified the design system generation jobs to correctly summarize source links and GitHub repositories. - Updated UI components to reflect changes in asset handling, including new source link management in the DesignSystemFlow. - Improved tests to cover new functionality for adding source links and ensuring proper handling of design system assets. * refactor(library): rename 'design-system' to 'brand kit' and enhance thumbnail loading - Updated labels and filters in Library components to replace 'design-system' with 'brand kit'. - Introduced a shimmer skeleton for lazy-loaded thumbnails in the LibraryPicker to improve user experience during asset loading. - Enhanced the PickerCard component for better performance by memoizing individual asset cards. - Updated tests to ensure proper handling of brand kit assets and their visibility in the LibraryPicker. * feat(clipper): implement internationalization for toolbar and popup - Added i18n support to the clipper, enabling localization of UI elements and tooltips. - Introduced a new i18n.js file to manage translations for various languages. - Updated content.js and popup.js to utilize the i18n functions for dynamic text rendering. - Enhanced accessibility by ensuring aria-labels and tooltips are also localized. - Improved user experience by providing localized messages for actions and statuses. * feat(clipper): enhance brand kit extraction and localization support - Updated the brand kit extraction process to include improved handling of assets and localization for various UI elements. - Added internationalization support for the brand kit feature, allowing for dynamic text rendering based on user locale. - Enhanced the user experience by ensuring that all relevant messages and tooltips are localized. - Updated tests to cover new localization features and ensure proper functionality of the brand kit extraction process. * feat(clipper): enhance brand color derivation and update localization - Introduced new functions for color manipulation, including linear interpolation and clamping, to improve brand color derivation. - Updated the deriveBrandColors function to better map observed palettes to semantic roles, ensuring consistent brand representation. - Revised localization strings in i18n.js to reflect changes from 'brand kit' to 'design system', enhancing clarity and user experience. - Improved overall code organization and readability by refactoring existing functions and adding new utility methods. * refactor(clipper): update terminology from 'brand kit' to 'design system' - Replaced all instances of 'brand kit' with 'design system' across various components and localization files for consistency. - Updated UI elements, tooltips, and documentation to reflect the new terminology. - Enhanced user experience by ensuring clarity in the design system extraction process and related functionalities. - Adjusted localization strings in multiple languages to align with the updated terminology. * feat(clipper): enhance image fill handling and normalization - Introduced functions to normalize image fills by converting non-PNG/JPEG formats (SVG, WebP, GIF, AVIF) to PNG before import, ensuring all images are properly rendered in Figma. - Updated the UI to report the number of images converted and dropped during the import process, improving user feedback. - Enhanced the overall image processing workflow to prevent silent failures when unsupported formats are encountered. - Revised documentation to reflect the new image handling capabilities and supported formats. * feat(clipper): enhance UI kit and busy state feedback - Updated the UI kit to include new components such as inputs, selection, and overlays, improving the overall design system representation. - Enhanced the busy state feedback during capture processes with localized messages and a step-by-step progress indicator, providing users with clearer status updates. - Revised localization strings to support new UI elements and improve user experience across multiple languages. - Improved documentation to reflect changes in the UI kit and busy state handling. * fix(brands): restore design-systems nav entry + reconcile BrandsTab on re-activation Address review feedback on PR #4260: 1. EntryNavRail dropped the only control that reached view==='design-systems' when Brands replaced it in the rail, leaving the still-rendered/routed design-system manager deep-link only (the entry-nav-design-systems e2e specs assert this). Restore a reachable rail entry (blocks icon, existing navDesignSystems key) alongside Brands. 2. BrandsTab only fetched once on mount, but EntryShell keeps sub-views mounted and toggles visibility, so a brand finishing extraction in its backing project never reconciled until a full reload. Refresh whenever the Brands view becomes active again, and poll while any brand is extracting (torn down once settled / when hidden). Red spec: tests/components/BrandsTab.refresh.test.tsx (fails pre-fix: fetchBrands called once, not twice). * Update clipper/brand-capture.js * fix(clipper): improve busy state handling and UI feedback - Adjusted the spinner CSS to use flex properties for better layout control. - Enhanced the reclampIfMoved function to preserve user position during busy state transitions. - Added loading toast notifications for popup-launched captures to ensure progress visibility even when the on-page bar is hidden. * feat(daemon): add kiwi-schema dependency and enhance Figma API integration - Added kiwi-schema package to the daemon for improved schema handling. - Updated FigmaApiNode interface and related functions to support shared functionality with the offline decoder, ensuring consistency in node processing. - Refactored capture functions in clipper to maintain UI visibility during DOM/IR snapshots, enhancing user experience during capture operations. * fix(web): surface missing backing projects * fix(web): re-enable brand actions after use * fix(daemon): serve brand logos from data roots * fix(brands): reconcile failed extractions * feat(daemon): implement offline Figma import and decoding functionality - Added support for importing `.fig` files directly into the daemon, enabling offline processing without requiring a Figma account. - Introduced a new `fig-decode.ts` module for decoding `.fig` files, handling both ZIP-wrapped and raw formats. - Created `figma-import.ts` to orchestrate the import process, generating a canonical snapshot and associated metadata. - Enhanced the server to handle Figma file uploads and integrate with the new decoding logic. - Updated package dependencies to include `kiwi-schema`, `html2canvas`, and `jspdf` for improved functionality. - Added tests for the new Figma import features to ensure reliability and correctness. * feat(clipper): reload-proof capture progress badge on the extension icon The on-page progress strip dies if a page reloads itself mid-capture (aggressive paywall sites like economist.com do this), leaving no loading signal. Add a per-tab '•••' badge on the extension icon for the lifetime of any capture message — it lives on the action icon, so a page navigation can't take it down. Verified end-to-end via a real loaded extension. * feat(daemon): add export functionality for Figma and enhance PDF export process - Introduced `runFigma` command for importing Figma designs, supporting both local `.fig` files and Figma URLs. - Added detailed usage instructions for the `od figma import` command. - Implemented `runExport` command for programmatic export of HTML/deck artifacts to PDF, PPTX, or image formats. - Enhanced error handling and user feedback during export processes. - Removed obsolete `build-pptx-export-prompt` module and related tests to streamline the codebase. * feat(daemon): enhance library synchronization and export capabilities - Implemented `reconcileLibrary` to mirror design systems and agent-produced project deliverables into the Library as referenced assets. - Added support for programmatic export of artifacts via the `od export` command, including detailed usage instructions. - Introduced new functions for handling Figma imports and exports, improving integration with design workflows. - Enhanced error handling and user feedback during synchronization and export processes. - Added tests for new features to ensure reliability and correctness. * feat(web): PPTX export for any shareable artifact + Library toolbar tooltips * chore(nix): refresh pnpm deps hash * refactor(web): enhance onboarding view and file export progress indicators - Updated the onboarding view layout for improved accessibility and visual hierarchy, including adjustments to spacing, typography, and button styles. - Introduced a loading toast for file export progress, displaying elapsed time and estimated time remaining for slide exports. - Added new translation keys for export progress messages in multiple languages. - Refactored the export progress handling to provide real-time updates during the export process, improving user feedback and experience. * refactor(web): streamline export capture bridge and update connector styles - Removed unused loading logic for html2canvas in the export capture bridge, simplifying the code. - Updated CSS for the onboarding view connector to improve visual clarity and ensure it does not overlap with node numbers. * refactor(web): remove html2canvas dependency and enhance Figma URL handling - Removed the html2canvas package from the project, including its references in the lock file and related components. - Added functionality to manage Figma URLs within the Design System flow, allowing users to add, remove, and validate Figma file links. - Improved drag-and-drop handling to prevent unintended file navigation when dropping files outside designated areas. - Updated UI components to accommodate new Figma URL features, enhancing user experience and accessibility. * refactor(web): unify brand and design system flows - Merged the brand extraction process into the design system creation workflow, allowing users to start from a brand directly within the design system wizard. - Updated routing to redirect legacy brand links to the unified design systems tab. - Enhanced the onboarding experience by removing the separate Brand Kit tab and integrating brand selection into the design system creation process. - Improved UI components to reflect these changes, ensuring a seamless user experience across the application. * feat(web): introduce brand enrichment banner and picker modal - Added a new BrandEnrichmentBanner component to allow users to refine programmatically-extracted design systems with AI by selecting design-system skills. - Implemented a BrandPickerModal for selecting brands from a searchable gallery, enhancing the design system creation flow. - Updated ChatPane to conditionally display the enrichment banner for eligible brand projects, improving user engagement. - Enhanced the design system flow to support the new brand enrichment features, ensuring a seamless experience for users. * feat(web): enhance BrandPickerModal and DesignSystemAssetDropzone - Updated the BrandPickerModal to allow scrolling of the entire picker area, improving user experience by creating a unified scrolling surface. - Added new props to the BrandReferencePicker for action labels and scroll root reference, enhancing flexibility in brand selection. - Introduced a new DesignKitView component for rendering design kits consistently across different surfaces. - Enhanced the DesignSystemAssetDropzone to support a wider variety of file types with appropriate previews, improving asset management during design system creation. - Updated styles for better visual clarity and responsiveness across components. * feat(web): update Design Systems tab actions and enhance localization - Changed the button label in the DesignSystemsTab from "Edit" to "Open" for better clarity in user actions. - Added a new translation key for 'dsManager.openSystem' across multiple languages to support the updated button label. - Enhanced the FileWorkspace component to ensure the Design Files tab aligns correctly with the Design System tab, improving UI consistency. - Implemented a new design system editing feature that allows users to fetch and save design system content from DESIGN.md, enhancing the design workflow. * fix(merge): repair post-merge regressions after origin/main integration Follow-up fixes on top of the origin/main merge ( |
||
|
|
ed1df234ad |
fix(ci): needs-validation gate must resolve the queued PR from the merge_group ref (#4742)
* fix(ci): resolve merge_group PR from the queue ref, not commits/<sha>/pulls The needs-validation merge gate resolved PRs in the merge_group via commits/<sha>/pulls. The merge queue squashes, so the queued commits are NEW synthetic SHAs that GitHub does not associate back to the PR — the lookup returned no PRs, the gate logged "clear to merge", and a needs-validation PR (#4736) merged with no QA. (This was latent until the pull_request branch was removed and merge_group became the sole enforcement.) Resolve the PR number(s) from the queue ref name instead (refs/heads/gh-readonly-queue/<base>/pr-<N>-<sha>), which is stable regardless of squash. Fail closed if the ref yields no PR number. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): enumerate ALL PRs in a merge group (ref + commit subjects + commits/pulls) Addresses review: a batched merge group names only the tip entry in the queue ref, so parsing pr-<N> from the ref alone could miss earlier PRs in the batch (one could still carry needs-validation). Take the union of three sources — the ref pr-<N>, each commit subject's trailing (#N) (the queue's per-entry squash commit carries its own PR number, recovering non-tip batched PRs), and commits/<sha>/pulls (non-squash queues) — and fail closed if none resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): fail closed if the compare API (PR enumeration source) fails Addresses review: the compare call feeding msg_prs/assoc_prs used 2>/dev/null || true, so an API failure silently emptied those sources while ref_prs still held the tip — the -z check passed and only the tip PR was validated, letting a batched needs-validation PR merge. Capture compare once in a REQUIRED assignment (no || true) so failure trips set -e; derive subjects + shas from that parsed JSON; make commits/<sha>/pulls a required assignment too. Only grep no-match within a successful response is tolerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5360b5a4e9 |
fix(ci): enforce the needs-validation merge gate only in merge_group, keep the PR check green (#4714)
The needs-validation hard gate failed the validate job (Validate workspace) on pull_request, turning the PR's own required check red. A deliberately-red required check is indistinguishable from a real CI failure downstream: it flips mergeStateStatus to BLOCKED and reads as ci_status=failure to the review bot, which deadlocks the very QA/validation meant to clear the label — QA is never requested, the label is never removed, the PR is blocked forever. Move the gate to the merge_group (merge-queue) context only. main requires the merge queue, so every merge still passes through merge_group and a needs-validation entry is ejected there and can never land — but the merge_group run executes on the queue's transient ref, so its failure never appears in the PR head's status rollup. The PR's Validate workspace stays green, QA runs, removes the label, and only then does the queue let it through. No bot/charter change needed. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f4fe5ad757 |
feat(ci): implement the plugin-preview bake pipeline (spec 4548) (#4700)
* feat(ci): bake pipeline slice 1 — previews-diff guard + single rolling PR Per specs/change/20260618-plugin-preview-bake-pipeline/spec.md rollout step 1 (the smallest change that stops the bleeding on the stacked bake-PR backlog). - scripts/plugin-previews-diff.mjs: decide whether a manifest's `previews` subtree changed, ignoring the per-run `generatedAt` timestamp. node:test coverage in plugin-previews-diff.test.mjs (the #4261 timestamp-only noise case, entry change, add/remove, key-order stability). - bake-plugin-previews.yml: open a review PR only when `previews` actually changed (was: whole-file `git diff` that fired every run because of generatedAt), and reuse ONE rolling branch (chore/plugin-previews) / force-update the open PR in place instead of stacking chore/plugin-previews-<run_id> per run. - guard.ts: allowlist the two new CI-only .mjs scripts. Deferred to later slices (same spec): pre-merge same-repo coupling job, release-cut full bake, tag-union GC, directory-layered artifact keys. * feat(ci): bake pipeline slices 2-4 — pre-merge coupling, release full bake, GC, directory keys Completes the spec (specs/change/20260618-plugin-preview-bake-pipeline) on top of slice 1, all in one PR (GC ships dry-run so staged ENABLEMENT still holds): - .github/actions/bake-previews: composite action for the shared render + R2 upload core, so the three bake workflows stop duplicating it. - bake-plugin-previews.yml (post-merge): refactored onto the composite; role is now uploader + fork path + nightly backstop (rolling PR unchanged). - bake-plugin-previews-pr.yml (slice 2): pre-merge bake for SAME-REPO PRs — renders, uploads, and commits the manifest INTO the author's branch so it rides with the code change. Loop guard checks the head COMMIT author (git log -1 %ae of head.sha, fetched via full checkout), not head.user.login, plus a no-op previews-diff guard. Forks fall through to post-merge. - bake-plugin-previews-release.yml (slice 3): release-cut full bake committing the authoritative manifest onto release/**, with paths-ignore + bot-author loop guards. - scripts/bake-plugin-previews.mjs (slice 4a): directory-layered, content- addressed keys <id>/<fingerprint>/preview.mp4 (+ poster.jpg), prefix-relative in the manifest. Daemon consumer already resolves base+key / path.join(dir,key) so no consumer change; reused flat entries are left untouched (additive). - scripts/plugin-previews-gc.mjs (slice 4b) + .github/workflows/ bake-plugin-previews-gc.yml: weekly R2 GC. Protected set = keys referenced by every tag + live release/** HEAD + main; deletes orphans older than a 90d grace window. DRY-RUN by default (needs --delete AND GC_ENABLE_DELETE=1). Pure protected-set/orphan logic covered by node:test. - guard.ts: allowlist the new CI-only .mjs files. * fix(ci): capture diff-guard result on its own line so a helper error fails the step Per review: `if [ "$(node plugin-previews-diff.mjs ...)" != changed ]` swallows the helper's exit 2 (bad args / unreadable manifest) inside command substitution, so an error reads as empty string → 'unchanged' branch → the manifest PR/commit is silently skipped despite a successful bake. Capture into diff_result on its own line (so `set -e` aborts on a helper error) and `case` on the value, treating unexpected output as a workflow failure. Applied to all three bake workflows. * fix(ci): satisfy actionlint — quote gc description colon + route PR/dispatch context through env - bake-plugin-previews-gc.yml: quote the `delete` input description (the '(default: ...)' colon broke YAML parsing) and pass dispatch inputs via env (GC_DELETE/GC_GRACE_DAYS) instead of interpolating into the run body. - bake-plugin-previews-pr.yml: route head.sha/head.ref through HEAD_SHA/HEAD_REF env vars to avoid the script-injection lint on untrusted PR context. * fix(ci): hard-gate the release bake job to release/** branches workflow_dispatch can fire from any ref; the commit step pushes the authoritative manifest to the triggering ref with contents:write, so a dispatch on main would write straight to main and bypass the release back-merge. Add a job-level `if: startsWith(github.ref, 'refs/heads/release/')` guard. * fix(ci): GC fails closed on partial protected-ref data Per review (non-blocking but real once deletion is armed): the GC workflow's protected-ref fetches ended with '|| true', so a transient fetch failure could leave the tag/release/main protected set incomplete and, with GC_ENABLE_DELETE=1, prune clips a live release/main still references. Drop the '|| true' (fail the job if the protected refs can't be fetched), and add a script-side guard that refuses to delete when the protected set is empty or origin/main's manifest is unreadable. |
||
|
|
254fd7ddf0 |
fix(ci): only auto-merge a backport that is a pristine bot cherry-pick (#4705)
* fix(ci): only auto-merge a backport that is a pristine bot cherry-pick The auto-approve/merge premise is that a backport is the untouched cherry-pick of code already reviewed on main — so it can skip a fresh review. That breaks the moment a human pushes a commit (resolving a conflict, fixing a failing test): those commits were never reviewed, yet the existing gates don't catch them — the PR author stays the bot, a draft gets un-drafted, and head==CI-sha only guards post-green races, not bot-vs-human authorship. Gate auto-approve + auto-merge on pristine == 'true': every commit on the branch was committed by github-actions[bot] (what korthout's cherry-pick produces; the human author is preserved but the committer is the bot). Any human commit -> committer != github-actions[bot] -> pristine=false -> skip, and the PR falls back to human review (REVIEW_REQUIRED; release-gate blocks the release until it lands). Fail closed: any lookup miss / empty list -> not pristine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): paginate the pristine-commit check so a >100-commit backport can't pass on its first page per_page=100 without --paginate only validated the first 100 commits: a human commit at position 101+ would be missed and the PR would auto-merge unreviewed code (fail-open). Use --paginate to fold in every commit and fail if any committer is not github-actions[bot]; -F/-x match the literal bot login (the brackets are not a regex class). Fail closed on an empty list / lookup miss. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): use portable grep -c count for the pristine check, not grep -qv `! ... | grep -Fqvx` mis-evaluated to pristine=true on a human-touched branch under BSD grep (grep -q's early-exit status is unreliable across BSD/GNU) — the opposite of safe. Count the non-bot committer lines with grep -Fvxc, whose count semantics are portable, and require zero. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): fail closed when paginated commit lookup partially fails gh api --paginate can emit several pages and then fail on a later one; `|| true` kept that partial output, so an all-bot first page could read pristine=true without ever inspecting the commits on the failed page. Capture gh's exit status with `if !` and blank the list on any failure, so pristine requires a complete, successful enumeration of every commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(ci): assert the pristine check's actual shape (paginate + grep committer count) The topology assertion still referenced the old in-jq comparison '(.committer.login // "") == "github-actions[bot]"', which no longer exists after the check moved to extracting '.[].committer.login // "none"' and counting non-bot committers with grep -Fvxc across --paginate'd pages. Assert the shape that exists now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cbf7e881fa |
fix(ci): match the App author login gh actually returns (app/open-design-release-bot) (#4703)
The auto-approve/merge gates checked author == 'open-design-release-bot[bot]', but `gh pr view --json author --jq .author.login` returns 'app/open-design-release-bot' for an App-authored PR. The gate never matched, so every clean backport's approve + merge steps were skipped and the PR sat unmerged (e.g. #4697, #4698 — CI green, no approval). Match the actual value in all three gates + the topology test. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
fa852014df |
feat(ci): auto-approve the release bot's own clean backport to satisfy release/v* review rule (#4682)
release/v* requires one approving review, so backport-automerge could not merge the bot's own backport PRs (an app can't approve its own PR). Approve as github-actions[bot] — a separate identity — under the SAME gates as the merge (author == release bot, same-repo, non-draft, head == the CI SHA, CI green). The diff was already reviewed + approved on main; the backport is a mechanical, conflict-free cherry-pick, so this auto-approval is scoped strictly to the release bot's verified backports, then merged with the App token. Topology test updated. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c5ec410fd3 |
fix(ci): backport on a label added after merge (#4678)
* fix(ci): backport on a label added after merge, not only at merge time The backport workflow only triggered on pull_request_target closed, so a 'backport release/vX.Y.Z' label applied AFTER a PR was already merged never created a backport PR (a common slip). Add the labeled event; the existing 'merged == true' guard still skips still-open PRs, so labelling an open PR does nothing until it merges, while labelling a merged PR now backports it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): gate the labeled-trigger backport on the labeler's write+ permission Per review (PerishCode): the new labeled path let a triage-only user trigger the App-token backport on a merged PR, racing backport-label-guard.yml (which strips unauthorized labels in a separate workflow). Check the labeler's permission inside this workflow, before checkout/ backport-action: on a labeled event, require github.event.sender to have write+ (fail closed); the closed/merge path stays gated by the merger's write access. Also only run for backport-* labels, not every label on a merged PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |