Commit Graph
563 Commits
Author SHA1 Message Date
Ray XiandGitHub 82f2a9b730 restructure Home creation hierarchy (#6914)
* restructure Home creation hierarchy

* test(home): align Prototype expectations
2026-08-18 09:22:16 +00:00
lefarcenandGitHub 3c183acf79 fix web clone browser startup without project installs (#7000)
* fix web clone browser startup without project installs

* test web clone real browser cli path

* fix web clone full page viewport width
2026-08-18 09:15:58 +00:00
f1a0b60c6c chore: unify product name display to OpenDesign repo-wide (#6998)
* chore: unify product name display to OpenDesign repo-wide

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

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

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

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

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

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

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

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-18 07:09:45 +00:00
lefarcenandGitHub 1a7a23d4cc fix(community): make deck remix previews reliable (#6996)
* fix(community): make deck remix previews reliable

* fix(community): support nested deck scrollers

* fix(web): isolate nested deck fallback scrolling

* fix(web): infer deck scroll axis from slide geometry
2026-08-18 05:17:00 +00:00
lefarcenandGitHub d40d264f74 fix: derive workspace authority from project bindings (#6989)
* fix: derive workspace authority from project bindings

* fix: preserve preview navigation without workspace params

* fix: gate chat sends on authoritative history

* fix: preserve derived auth and preview paths

* fix: retain conversation during authority refresh

* fix: clear transcripts across authority changes

* fix: clear conversations after access revocation
2026-08-18 03:16:58 +00:00
AmyandGitHub 262c1f8f00 fix: complete AMR environment switching (#6991)
* fix: keep AMR environment switching consistent

* fix: package every AMR console environment

* fix: discover daemon directly for profile switch

* fix: apply selected AMR profile to billing

* fix: gate personal runs by workspace balance

* fix: complete feature-test account switching

* fix: close AMR environment review gaps
2026-08-17 11:53:58 +00:00
Ray XiandGitHub bcedd985d7 fix web list project menu placement near viewport edge (#6971) 2026-08-17 09:30:57 +00:00
Ray XiandGitHub 169dea1701 fix(web): default Home creation type to prototype (#6975)
* fix(web): default Home creation type to prototype

* test(e2e): mock default prototype apply

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-17 08:45:06 +00:00
lefarcenandGitHub 9475b9817f fix(web): make deck thumbnails drive deck-stage previews (#6950)
* fix(web): sync deck-stage thumbnail navigation

* fix(web): keep prototype annotations out of deck navigation

* fix(web): distinguish legacy deck slides from annotations
2026-08-17 06:49:05 +00:00
lefarcenandGitHub b04ccd1d13 feat: add one-click DeepSeek Harness bootstrap installers (#6900)
* feat: add DeepSeek Harness bootstrap installers

* docs: add DeepSeek Harness one-click install guide

* docs: explain DeepSeek Harness API key setup

* feat: publish DSH installers through production workflow

* fix: make DSH CMD bootstrap self-contained

* fix: keep DSH CMD on the official installer URL
2026-08-14 10:18:38 +00:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
84c8f0ee0f chore(release): bump main to 0.19.2 ahead of stable 0.19.1 (#6902)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-08-14 09:48:02 +00:00
7a37c1462d feat(export): add reliable standalone HTML export (#6855)
* feat(export): add standalone HTML export

Bundle project-local HTML, CSS, JavaScript, images, fonts, workers,
and iframe dependencies into one offline-safe file.

Expose the same contract through the web download flow and od CLI,
with structured failures and bounded output assembly.

* fix(daemon): honor standalone source URL semantics

Resolve document assets against local base href values, reject browser-invalid TypeScript and JSX sources with a structured 422, and count unquoted first-level resources before graph traversal.

Validated with daemon tests, workspace typecheck, and guard.

* fix(daemon): handle standalone data scripts and CSS URL tokens

Preserve non-JavaScript script payloads as data blocks and scan CSS values without treating strings or comments as project dependencies.

Validated with focused and full daemon tests, workspace typecheck, and guard.

* fix(daemon): classify standalone export resources correctly

Preserve non-HTML iframe MIME types, inline only resource-bearing link relations, and propagate unexpected Vite dist read failures instead of silently falling back.

Validated with focused export tests, the full daemon suite, guard, and workspace typecheck.

* fix(export): preserve version and stylesheet semantics

Hide standalone HTML export for historical versions until dependency snapshots are available, avoiding an action that always fails.

Keep disabled and alternate stylesheets as embedded link elements so standalone export preserves activation semantics. Validated with focused red/green coverage, the full web suite, pnpm guard, and pnpm typecheck.

---------

Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
2026-08-14 07:21:21 +00:00
lefarcenandGitHub f9c07517b7 fix: make DeepSeek Harness cancellation authoritative (#6880)
* fix deepseek harness cancellation lifecycle

* fix DeepSeek canceled status rendering

* fix DeepSeek cancellation handshake race

* feat support reasoning selection in composer

* test DeepSeek pre-execute cancellation protocol

* test reasoning selector visual surface
2026-08-14 06:40:10 +00:00
lefarcenandGitHub eea8a8522d fix(collab): bound workspace refresh storms (#6871)
* fix(collab): bound workspace refresh storms

* fix(collab): push workspace directory freshness

* test(collab): follow identity-aware hub callbacks

* fix(collab): reset authority on account changes

* fix(collab): fence workspace identity transitions

* test(collab): guard full identity reset
2026-08-14 02:19:51 +00:00
lefarcenandGitHub 9a402b2e4c feat(campaign): extend the DeepSeek V4 campaign to Pro + Flash (#6861)
* feat(campaign): extend the DeepSeek V4 campaign to Pro + Flash

The V4 Flash free week ends 8/13 20:00; a two-week window offering BOTH
V4 Pro and V4 Flash opens at that same instant and runs to 8/27 20:00.

This grows the campaign already in production rather than landing a
second stack beside it. That implementation ran the free week for a
week: its audience resolution (wallet balance is not a subscription),
i18n wiring, localized countdown, frequency control and attribution are
all already exercised, and it carries a product decision this change
must not undo -- the URL review backdoors (campaign / audience / usage
overrides) were deliberately removed, and reviewing the campaign is done
by overriding `window.startAt`, never through query parameters.

Almost all of it is the campaign module growing a second model:
`isDeepSeekV4FlashCampaignModel` now matches either id, which is why the
model switcher badges both without its rendering changing at all, and
the modal's benefit line is an i18n key, so naming both models was copy.
`「立即使用」` now switches a paid user to Pro, the headline benefit.

Two things needed real edits. Model-benefit impressions fired once with
a hardcoded Flash id; product compares the two models' reach separately,
so they now fire once per campaign model actually on screen, each with
its own `model_id`. And `campaign_id` / `element` move to the spec's
`deepseek_v4_pro` naming, with `deepseek_v4_flash` kept declared so the
finished campaign's warehouse rows stay valid.

Copy is rewritten across all 19 locales -- dates, the two-week framing,
both model names, and the peak-hours queue caveat product added. Dates
follow each locale's existing format rather than one imposed format, so
only the day numbers move.

Frequency control is keyed on the campaign id, and the id is new, so a
user who dismissed the free week is still owed this campaign's single
showing. That was an invisible dependency on remembering to change the
id; it now has a test.

Module and i18n key names keep their `…V4Flash` spelling: renaming 28
keys across 19 locales mid-campaign is risk without user-visible
benefit, and belongs in a cleanup pass once the campaign is off.

* fix(web): read a rolling model window as a wait, not a failure

vela's hosted gateway rejects a request that exhausted its rolling
per-model window with `model_limit_exceeded`, phrased for API callers as
"You have reached the 5-hour usage limit for X. Try again after <instant>.
This request was not charged to Wallet Credits."

Two things went wrong with that sentence on the way to a user.

The daemon's `isHardQuotaText` matches its "usage limit" wording, so a
window that resets on its own was classified as `hard_quota` — an
exhausted quota, marked non-retryable. Nothing was charged and nothing
needs topping up, so that both misnames the cause and counts a self-
healing wait against reliability as a real failure.

The web runtime then had nowhere to put it. The AMR branch of
`resolveRunFailureUi` ends in a catch-all returning the generic "task
failed" title with a null message key, which renders the raw upstream
string — so a localized chat showed an English sentence under a wrong
heading. Home is worse: its send path fails before a run exists, never
reaches that mapping at all, and prints `err.message` verbatim.

Classify the window separately, ahead of the hard-quota reading, and give
both surfaces the same localized copy: high demand, when it reopens, and
the reassurance that nothing was charged. The instant is rendered in the
reader's locale — the gateway reports UTC. Both surfaces read the signal
through one shared contracts module so they cannot drift, and degrade to
copy that does not promise a time when no instant is readable.

Other rate limits are untouched: a genuine session-limit exhaustion keeps
its hard_quota reading, and every other business failure on Home still
prints verbatim.

* fix(analytics): keep a campaign entry for the whole campaign

The attribution record expires after seven days, but this campaign runs
fourteen. A visitor who clicks the banner on day 1 and subscribes on day
11 arrives at checkout with no entry left to attribute the payment to,
so the campaign under-reports against the metric it is judged on --
活动归因付费人数 / 金额, both defined as payments carrying its
`campaign_id`.

Extended only for entries that carry a `campaignId`. Raising the window
globally would move the attribution口径 for every other entry point and
the dashboards already built on it; the campaign is the thing that needs
a longer memory, so it is the thing that gets one. Past fourteen days a
campaign entry expires like any other -- the longer window is scoped to
the campaign, not an open-ended exemption.

* fix(campaign): carry the new campaign id through ingest and e2e fixtures

Two places still recognised only the finished free week's id, both found
in review.

The daemon's AMR entry ingest allowlist is fail-closed: an unrecognised
`campaignId` voids the WHOLE entry rather than just its campaign field.
With only `deepseek_v4_flash` listed, every entry this campaign produces
would have been dropped at ingest -- and its success metrics are defined
as payments carrying its `campaign_id`, so it would have reported zero
conversions while converting normally. Both ids are listed now, since
entries minted during the free week can still arrive inside their
attribution window.

The Playwright suite pre-seeds a dismissal so the campaign modal never
opens over a functional spec. That seeding was keyed on the old campaign
id, so the modal would have covered Home in every UI spec. `home-hero-
rail` also clears storage and re-seeded its own hardcoded copy of the
key; it now reapplies the shared map instead, so a future campaign
cannot be dismissed in one place and left interrupting specs in the
other.

* fix(campaign): land the workbench badge on the console, like the modal

The badge opened a hardcoded `https://open-design.ai/zh/pricing/` while
the modal's CTA opened the console's plan surface scoped to the caller's
workspace. Both are in-product entry points for a signed-in user, so one
funnel was being split across two destinations -- and only one of them
lets the user actually subscribe.

The hardcoded link also pinned `/zh/`, so every non-Chinese user landed
on a Chinese page from a badge that renders in all 19 locales.

Now shares the modal's destination, including the workspace scope, so
the console can resolve the right upgrade surface for that workspace.
Verified against a real browser: the badge opens
`/amr/dashboard?workspaceId=…&billing=plan` carrying entry id, source,
campaign id, conversion source and device id.
2026-08-13 16:37:16 +00:00
Ray XiandGitHub e279c457f8 fix(web): stop opening comments panel after save (#6862)
* fix(web): open comments panel only from view all

* fix(web): keep comments panel closed after save
2026-08-13 16:28:47 +00:00
698a17aac2 feat(web): refresh home and preview workspace controls (#6692)
* feat(web): home type-fan picker, floating account cluster, docked tab dropdown

Home hero: the 12 create-scenario types render as a fanned card carousel
above the composer (flat elliptical arc, apex card = selected, arrows /
card click switch along the arc; selected card scales about its own
center on an inner body so the outer transform stays a pure arc move).

Entry chrome: the account module leaves the nav-rail bottom and rides a
fixed top-right cluster next to the DeepSeek campaign badge — credits as
its own pill, avatar-only circular trigger, hover menu opens downward
(right-aligned, width-capped). The cluster is app-region no-drag so the
menu stays reachable in the desktop shell.

Project route: the docked workspace tab strip becomes one white dropdown
spanning the chat column (MRU order, most recent first), and the chat
collapse control lifts out of the chat card header to sit left of it.

Design files: the category tab bar drops its near-opaque sticky backdrop
strip and scrolls with content.

* feat(web): polish top-right capsule and dock controls, seed default deck chip

- Home composer seeds 幻灯片 (deck) as the default creation type on a
  fresh mount — same silent deferred-apply path as a user pick, skipped
  whenever a persisted chip draft owns the composer.
- Type fan: tighter card spacing (arc pivot 3700px → 3000px, ~128px
  between neighbours) so the hand-of-cards overlap reads again.
- Top-right chrome: credits + avatar merge into ONE capsule (pill
  material on the shared wrapper, chrome-free 24px segments inside,
  no hover fill on the credits segment).
- Project dock: the chat-collapse control gains a white seat matching
  the tab dropdown, and the dropdown drops its border (shadow only).

* test(web): align refreshed home chrome acceptance coverage

- Read account badges and member labels from the floating account menu.
- Match the default deck route, non-sticky Design Files tabs, and lifted chat control.
- Navigate projects through the visible docked Home control instead of hidden state nodes.
- Cover the affected Web suite and focused UI P0 restoration flows.

* fix(web): lock submit until default routing is ready

- Keep fresh-home sends disabled while the plugin catalog and default deck binding settle.
- Let explicit plugin handoffs supersede the default without leaving Send locked.
- Add a red-spec that holds the catalog response and verifies the routing barrier.

* test(e2e): await seeded type before picker actions

- Wait for the fresh-home deck binding before opening the creation-type menu.
- Switch to Prototype instead of clicking the already-active deck row.
- Preserve clear-selection coverage without racing menu reconciliation.

* fix(web): keep type menu open during internal scroll

- Listen for scroll only on the trigger ancestor chain and the viewport.
- Let the portaled creation-type list scroll without dismissing itself.
- Add coverage that distinguishes internal menu scroll from anchor-moving scroll.

* test(web): await routed submit readiness in legacy flows

- Wait for the Home submit control to become enabled before exercising the AMR workspace race path.
- Wait for refreshed workspace metadata and default routing to settle before submitting a community template.
- Keep the assertions aligned with the production guard that blocks submits until a deterministic default route exists.

* feat(web): replace the home type fan with a capsule pill row

The 12 create-scenario types render as ONE line of capsule pills exactly
as wide as the composer below: pills that fit render inline (8px gaps
throughout), 视频 stays pinned before the 全部 trigger, and the rest fold
into the 全部 popover. Fit is measured against an invisible probe row so
visibility never feeds back into its own measurement. 幻灯片 leads the
order (default-selected type); 网站复刻 trails so it folds first.

The fanned card carousel this replaces is fully removed (component +
styles) — selection still flows through the same onPick scenario path.

* feat(web): drop the clear-type control, cap the type menu at 6 rows

Clearing the creation type is gone (per product): the composer pill loses
its inline x (the chevron now stays in every state) and the menu loses its
leading Clear row, so a type is only ever swapped for another. TemplatePicker
no longer takes onClear.

The type menu shows exactly 6 rows and scrolls for the rest (6 x 44px row +
5 x 2px gap + 12px padding).

Specs that drove the removed control are rewritten around swapping instead:
the e2e clearHomeTemplate helper is deleted, pickHomeTemplate now observes
the pill's has-selection state, and the three clear-specific specs assert the
affordance is absent.

* feat(web): lift the GitHub star badge into the top-right cluster

The star count leaves the account menu's social row and becomes its own
chip between the campaign badge and the account capsule, sharing their
pill material. The menu's social row keeps Discord / X / mail.

* chore(web): clean up stacked EOF whitespace

- Remove the trailing blank line introduced in the home hero stylesheet.
- Keep the shared Playwright home helper free of the same EOF artifact.

* fix(web): polish preview workspace controls

* fix(web): stabilize floating comment interactions

- Treat a configured comment portal as an overlay from the first render and
  pre-mount its empty host, preventing the preview from briefly entering the
  320px local side-dock layout before the floating card appears.
- Route the floating card's close button and Escape key through one cleanup
  path, restore focus to the originating toolbar trigger, and clear board,
  create, and composer state so the card reopens with one click.
- Add red-spec coverage for the transient side-dock class, single-action
  close/reopen behavior, and focus restoration; the tests fail on the PR base
  and pass with this fix.
- Align the signed-out balance action with pill-radius controls, remove the
  redundant portal label, delete obsolete radial/reset/search/card picker CSS,
  and clean the two stacked EOF whitespace issues.
- Preserve the intended steady-state overlay behavior and validate the change
  with 389 focused tests, workspace typecheck, guard, diff checks, and Electron
  frame/focus sampling.

* test(e2e): align shared project title assertion with focus mode

- Wait for the file workspace to become visible after first-open materialization.
- Assert the canonical project-title state without requiring the intentionally hidden focus-mode dock to be visible.
- Preserve stale-title rejection and the visible catalog-title check after returning to All Projects.
- Validate with three focused repetitions, guard, workspace typecheck, and the entry-settings CI lane.

* fix(web): stabilize responsive home controls

- Recompute the Home type-pill split when localized labels or intrinsic pill widths change without a container resize.
- Observe the measured pill children and tail so late font layout cannot leave the overflow decision stale.
- Restore composer errors to the reviewed in-flow presentation instead of carrying the unrelated fixed top-chrome toast.
- Add focused coverage for locale-style rerenders and post-render width settling.

* test(e2e): await default home routing in attachment flow

- Wait for the visible deck binding before checking the Home attachment submit lifecycle.
- Use the shared long timeout so grouped CI does not race catalogue readiness.
- Preserve the production submit gate and the attachment add/remove assertions.

* feat(web): add a view-all-comments entry to the comment popover

* feat(web): cap the docked tab dropdown at six scrollable rows

* fix(web): preserve comment and tab menu interaction semantics

- Return focus to the still-open View all comments control when the floating comment panel closes.
- Include border-box borders in the six-row tab menu height so the last row is not clipped.
- Add focused regression coverage for comment focus restoration and menu sizing.

* test(web): await default routing in AMR home flows

- Wait for the cold Home composer to finish binding its default deck route before clicking Send.
- Preserve the existing AMR balance retry and auth-expiry assertions after the new readiness gate.

* fix(web): preserve explicit Home submit routes

* fix(web): defer restored Home plugin apply

* fix(web): close workspace chrome review gaps

- Clamp the Home template menu to six rows and the visible viewport, flipping it above the trigger when needed.
- Reset docked tab-menu state when its route-owned portal disappears and preserve editor-owned keyboard shortcuts.
- Scope the project header account identity, balance, and billing destination to the project workspace while retaining the ambient fallback for unbound local projects.
- Cover tall and short viewports, dock remounts, composer focus, and ambient/project workspace divergence.

---------

Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
Co-authored-by: Amy <1184569493@qq.com>
2026-08-13 11:16:44 +00:00
open-design-crew[bot]GitHubxiaoche-hubmrcfpsopen-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>
4908baa4f4 feat(design-systems): ship structured runtime workflow (#6805)
* feat(design-systems): add structured runtime contract (#6456)

* feat(design-systems): add structured runtime contract

* chore: retrigger CI for PR 6456

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
Co-authored-by: mrcfps <mrc@powerformer.com>

* feat(design-systems): resolve structured runtime intents

* fix(design-systems): preserve workspace runtime scope

* test(design-systems): cover scoped prompt runtime

* fix(design-systems): use one component authority

* fix(design-systems): preserve run workspace authority

* fix(design-systems): honor runtime rollback gate

* fix(design-systems): pin run binding authority

* fix(runs): reject client-supplied scopes

* fix(design-systems): keep user package assets scoped

* feat(design-systems): validate generated UI adherence

* test(e2e): assert durable reload authority state

* fix(design-systems): reject non-executable adherence evidence

* fix: harden design system adherence validation

* fix(design-systems): validate JSX selectors structurally

* fix(design-systems): close adherence input gaps

* chore(nix): refresh pnpm dependency hashes

* chore(ci): retrigger design system validation

* fix(daemon): bound design system artifact reads

* fix(design-systems): bridge Codex runtime tools

* docs(codex): clarify shell environment policy

---------

Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>
Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
Co-authored-by: mrcfps <mrc@powerformer.com>
2026-08-13 09:02:20 +00:00
lefarcenandGitHub a2e71eb8a1 test(e2e): cover shared design system logo assets (#6825)
* test(e2e): cover shared design system logo assets

* test(e2e): reuse team design system collaboration cluster

* test(e2e): select team workspace before owner logo check

* test(e2e): activate owner team workspace before logo checks

* test(e2e): wait through workspace tab restoration
2026-08-13 08:57:40 +00:00
CaprikaandGitHub e654592ba6 fix(daemon): point users at the Settings section that actually exists (#6831)
* fix(daemon): point users at the Settings section that actually exists

When image generation failed for a missing credential, the reply told
the user to configure it in "Settings → Media". No such nav item exists —
the section is "Media providers" ("媒体生成提供商" in zh-CN) — so users
following the instruction found nothing.

The cause is that Settings destinations were spelled independently
wherever a producer happened to need one: 29 provider errors in
apps/daemon/src/media/index.ts said a bare unnamed "Settings" (one said
"Settings -> Media Providers"), and the od-media-generation skill said
"Settings → Media". None of them were tied to the Settings dialog, so a
rename could not reach them.

packages/contracts/src/settings-nav.ts is now the single place a Settings
destination is spelled, and every producer reads it: the media provider
errors, the ElevenLabs voice-options prompt (daemon + contracts), and the
external-MCP auth guidance. e2e/tests/settings-nav-copy.test.ts holds the
constants against the labels apps/web actually renders and fails the
build if a producer re-inlines its own guess.

Swept the same drift class while here: both hatch-pet SKILL.md copies
pointed at "Settings → Pets", but #5517 folded the pet picker into
General, so the real path is "Settings → General → Pets".

Verified the gate red on the pre-fix tree (3 failing assertions) and
green after. Adjacent, not fixed: these strings are English and the
agent translates them ad hoc, so a localized client can still render a
section name that does not match its own UI — see the report.

Record: recvre8FrTE2Oa (V0.19.1 acceptance)

* fix(contracts): gate destinations on rendered nav, not the i18n dictionary

Review on #6831 caught that SETTINGS_EXTERNAL_MCP named a place users
cannot navigate to. `mcpClient` kept its Settings render block but lost
its sidebar nav item (SettingsDialog.tsx says so in its own comment), and
reconnecting an external MCP server now lives in the top-level
Integrations view. So the destination is not a Settings section at all.

The stale string predates this branch — it was hardcoded in
prompts/system.ts on main. But promoting it into a named constant made it
worse twice over: the docblock asserted "The Settings nav item where
external MCP servers are added/reconnected" in a canonical location, and
the new gate whitelisted it by construction, so the check built to catch
dead paths became the thing defending one.

The deeper defect was the gate's invariant. Comparing a constant to its
i18n key's value proves only that some dictionary entry matches — not
that anything renders it. `settings.externalMcpTitle` still resolves to
"External MCP" with no nav item behind it, which is exactly how the dead
path passed. The same hole covered SETTINGS_MEDIA_PROVIDERS.

Now the gate parses what the UI actually renders — `settings-nav-item`
buttons and INTEGRATION_TABS — and resolves those keys through the
dictionary, using it as a lookup rather than as the source of truth.
Verified by reintroducing "Settings → External MCP": the old gate passed
it, the new one fails with the rendered destinations listed.

Replaces SETTINGS_EXTERNAL_MCP with INTEGRATIONS_MCP_PATH
(`Integrations → MCP`) and adds a regression test pinning that a
dictionary-only label is rejected.
2026-08-13 08:42:27 +00:00
AmyandGitHub 34c796a4d0 test(e2e): assert Cloud sign-in after logout (#6828) 2026-08-13 07:51:26 +00:00
lefarcenandGitHub b55d443b44 fix(web): streamline Home submit preflight (#6756)
* fix(web): retry transient home balance preflight

* refactor(web): streamline home submit preflight

* fix(web): keep local submit independent from cloud identity

* fix(web): preserve local and legacy submit lanes

* fix(web): keep in-memory workspace witness valid

* fix(web): retain workspace preflight compatibility

* fix(web): require workspace witness for team plugin apply

* fix(plugins): apply exact local plugin source

* fix(web): invalidate legacy witness on identity change

* refactor: keep Home submit local-first

* test: align local-first creation coverage

* fix: preserve exact local plugin context

* fix: fence exact local plugin use

* fix: disable inexact legacy plugin fallback

* test: mock exact plugin apply in visuals

* fix: reject unsupported exact project source

* Revert "fix: reject unsupported exact project source"

This reverts commit 99e0cf23c1.

* Revert "test: mock exact plugin apply in visuals"

This reverts commit 33b36688c7.

* Revert "fix: disable inexact legacy plugin fallback"

This reverts commit d1b4d9bcaa.

* Revert "fix: fence exact local plugin use"

This reverts commit aebaf6a20b.

* Revert "fix: preserve exact local plugin context"

This reverts commit aa6ab67631.

* Revert "fix(plugins): apply exact local plugin source"

This reverts commit e776a7d079.

* fix: reconcile local submit selections

* fix: preserve cold cloud submit handoff

* test: seed active team design-system binding

* fix(plugins): preserve exact local source semantics

* test(web): align Home failure copy after main merge

* fix(home): preserve staged resource catalog scope
2026-08-13 06:33:29 +00:00
a1d279649f test(e2e): make beta recovery fixture version-independent (#6817)
* test(e2e): make beta recovery fixture version-independent

* test(daemon): await restart analytics recovery

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-08-13 04:11:46 +00:00
AmyandGitHub 8db28b4a2f fix(release): gate prerelease on P0 and skip current blockers (#6808)
* fix(release): gate prerelease UI on P0

* test(release): skip current prerelease blockers
2026-08-13 03:20:21 +00:00
lefarcenandGitHub 85d2e4893c fix(amr): recover expired cloud sessions (#6786)
* fix(amr): recover expired cloud sessions

* fix(diagnostics): redact dynamic API routes

* fix(daemon): honor settings in AMR diagnostics

* fix(daemon): align workspace auth revisions

* fix(web): gate expired cloud sessions on sign-in

* fix(web): scope cloud sign-in gate to cloud execution

* test(web): align cloud auth fixture

* fix(web): recover transient run authority failures

* test(daemon): expect retryable authority detail

* test(e2e): align BYOK auth boundary

* test(e2e): align expired cloud recovery flows
2026-08-13 02:22:39 +00:00
AmyandGitHub 028bde50ad fix(release): harden prerelease E2E validation (#6755)
* fix(release): harden prerelease E2E validation

* fix(daemon): refresh active run tool tokens

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

* chore(release): keep Feishu notification unchanged

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

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

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

* test(e2e): defer system theme coverage

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

* fix(e2e): close prerelease coverage gaps

* fix(release): remove unrelated UI changes

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

* fix(e2e): restore remaining prerelease coverage

* docs(e2e): record remaining scoped gaps

* test(e2e): isolate workflow command fixtures

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

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

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

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

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

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

This reverts commit 201f3f3ee3.

* fix(plan): reliably open generated artifacts

* fix(plan): reject unconfirmed artifact paths
2026-08-12 11:29:11 +00:00
lefarcenandGitHub 98d5460a2a perf(collab): batch team resource pulls (#6782)
* perf(collab): batch team resource pulls

* fix(nix): refresh pnpm dependency hashes

* fix(nix): refresh merged pnpm dependency hashes

* test(e2e): cover Vela batch pull compatibility
2026-08-12 10:21:57 +00:00
lefarcenandGitHub e3ac55baf7 fix(ci): pin Blacksmith apt mirror before Playwright deps install (#6790)
* fix(ci): pin Blacksmith apt mirror before Playwright deps install

* ci: retrigger on blacksmith runner mode
2026-08-12 09:56:25 +00:00
lefarcenandGitHub 044324b9a6 fix(web): scope artifact upgrade gate to AMR (#6760) 2026-08-12 03:45:04 +00:00
PerishFireandGitHub 3b27ad15de ci: gate configured contributors on maintainer check (#6747) 2026-08-11 13:26:28 +00:00
AmyandGitHub a338a13aab test(ci): harden Functional E2E and daemon release gates (#6708)
* test(e2e): harden coverage and runtime isolation

* test(e2e): gate prerelease on full functional suite

* test(daemon): enforce prerelease coverage

* test(daemon): stabilize full-suite fixtures

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

* test(e2e): preserve interaction regression coverage

Keep deck navigation subject to Playwright actionability checks and restore the current rail updater/composer stacking witness as an explicit expected failure.

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

* fix(ci): close conformance validation gaps

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

* test(e2e): restore MCP snippet style guard

Restore the browser-computed style witness that was over-deleted when the OAuth flow moved to Integrations, and navigate through the current Settings MCP server entry.\n\nGenerated-By: looper 0.11.8 (runner=fixer, agent=codex)

* fix(ci): close directive and job-boundary gaps

Compare normalized MUST_FIX directives as sets so identical multi-item rounds remain weak debates, and bound workflow topology assertions to their immediate sibling jobs.\n\nGenerated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-11 08:31:14 +00:00
Ray XiandGitHub 7ecd35a746 fix(release): guard automated release trains (#6729)
* fix(release): guard automated release trains

* fix(release): resolve beta recovery before checkout

* style(release): group workflow outputs

* fix(release): publish daily beta snapshots without promotion
2026-08-11 06:06:53 +00:00
lefarcenandGitHub bf71452807 fix workspace deletion and extension installation failures (#6701)
* fix(workspace): reduce project and extension failures

* fix(workspace): avoid remote checks for local project deletes

* fix(workspace): address extension install review gaps

* fix(skills): retry manifestless ref prefixes

* fix(plugins): close remaining install diagnostics gaps

* fix(plugins): preserve user-input error buckets

* test(plugins): enforce bounded cleanup diagnostics

* fix(plugins): classify manifest validation failures
2026-08-10 17:19:17 +00:00
lefarcenandGitHub bc15c73b65 fix(web): auto-open generated media previews (#6719) 2026-08-10 15:21:54 +00:00
lefarcenandGitHub 0460157e1e feat(analytics): observe conversation fork outcomes (#6707)
* feat(analytics): observe conversation fork outcomes

* docs(daemon): note native session fork follow-up
2026-08-10 13:29:01 +00:00
Ray XiandGitHub ca894ab862 fix release smoke gating and notifications (#6709) 2026-08-10 12:37:29 +00:00
Ray XiandGitHub 5862adb866 fix(ci): gate release cuts on main Windows smoke (#6693) 2026-08-10 11:12:00 +00:00
cee05a57b5 feat(viewer): split Share / Export / Handoff back into three header entries (#6654)
* feat(viewer): split Share / Export / Handoff back into three header entries

0.18.0 merged sharing, exporting, and editor handoff into one Share
popover with three tabs. Export reach halved (per-capita export success
fell from ~34% to ~17% among 0.18.x users) and the handoff panel's reach
fell from 41% to 7% of artifact creators — the tabs buried both flows one
level deep behind a default Share tab.

- Share and Export are separate header buttons again; they keep sharing
  one popover shell so switching intents keeps the menu anchored, but the
  tab strip inside the popover is gone.
- Handoff returns as the standalone split button (trigger + caret) next
  to them, restoring the pre-0.18 control and its trigger/caret analytics.
- Outside-click dismissal is scoped so the popover and the handoff menu
  are mutually exclusive.
- HandoffButton now mounts on every artifact view, so a malformed
  /api/editors response degrades to an empty editor list instead of
  crashing the viewer.
- The caret picks up the 28px Design-Files-row height override that the
  trigger already had.

Both viewer chromes (HtmlViewer and ReactComponentViewer) get the same
structure. Header analytics elements share_dropdown / download_dropdown /
handoff trigger reconnect with their pre-0.18 history.

* feat(viewer): Share panel owns publish-online, social share, and save-as-template

Product feedback on the three-entry split (孙庆雨, 8/9): everything that
produces a link or a reusable asset is sharing, so 发布到线上 (Deploy to
Vercel / Cloudflare Pages, plus the copy/open share-link rows once
deployed), 社媒分享, and 保存为模板 move from the Export panel into the
Share panel. Export becomes a pure file-format menu (PDF / image / .zip /
standalone HTML / deck- and markdown-gated formats).

The 'publish online first to enable share' guide row is deleted rather
than moved: on the Share panel the publish button directly above IS that
step, so the row only restated the obvious.

* fix(viewer): keep the 6px gap between the Share and Export header buttons

The unified wrapper's inline-flex + gap rule was silently losing to
viewer/tools.css's later-imported `.share-menu { display: inline-block }`
(equal specificity, later cascade), so the two buttons rendered flush.
Doubling the class selector wins regardless of import order.

* feat(viewer): reorganize the Share panel by intent — hero publish, inline social, secondary deploy

Product alignment (孙庆雨, 8/9): the panel read as four unrelated piles
(publish card / publish-online / social / save). Reorganized around the
one dominant intent — get a link:

- The publish card is the hero, retitled 发布并获取链接 / 'Publish & get
  a link' across all 19 locales.
- Social icons render inline (SocialShareGrid) only once a CLEAN link
  exists — published file or a deployment whose share URL is live. The
  'deploy first, then share to social…' teaser row and its whole section
  are gone, along with the now-unused openSocialShareFlow entry and
  socialShareMenuLabel. A published-file URL now unlocks social sharing
  exactly like a ready deployment (socialShareDisplayUrl falls back to
  publishedFileUrl); protected/preparing deployments offer no icons.
- Vercel / Cloudflare demote to a 更多发布方式 / 'MORE WAYS TO PUBLISH'
  secondary tier (shareMenuPublishOnline retitled in all 19 locales).
- Save-as-template keeps its spot at the bottom.

* feat(viewer): de-shell the publish hero — the button is the headline

Product feedback (孙庆雨, 8/9): the grey publish card spoke a different
visual language from the menu rows below it, and its copy talked past
itself (title said 'publish & get a link', button said 'publish file',
and the failure line said 'try again' when retrying wasn't the fix).

- The card container and separate title row are gone: the panel opens
  with one solid primary button carrying the headline (发布并获取链接),
  a muted one-liner under it (任何拿到链接的人都可在线查看 — description
  shortened in all 19 locales), and the error line last.
- Failure copy now offers a way out: 发布失败,请稍后重试,或改用下方的
  部署方式 (all 19 locales). The signed-out failure branch is untouched
  in code but per product no longer reachable — everyone signs in now.
- Published state keeps the link block + copy + unpublish, unchanged.
- Both viewer copies (HtmlViewer, ReactComponentViewer) share the
  structure; new .chrome-publish-plain / .chrome-publish-hint styles.

* feat(viewer): publish hero gets a section title and a help tooltip

Product feedback (孙庆雨, 8/9): the hero should align with the tiers
below it — a section label like theirs, not a floating button.

- New section label 通过 Open Design 发布 / PUBLISH VIA OPEN DESIGN
  (new i18n key in types.ts + all 19 locales) sits above the button,
  matching 更多发布方式 and 保存.
- The always-visible hint line is replaced by a small "?" beside the
  button whose od-tooltip explains reach AND the current limitation:
  only a single file can be published — embedded local assets such as
  images aren't supported yet (description re-written in 19 locales).
- Both viewer copies share the structure; new .chrome-publish-cta /
  .chrome-publish-help styles, .chrome-publish-hint removed.

* fix(viewer): align the publish hero column with the menu rows

The section rows inset their content 10px; the publish stack only 2px,
so the button jutted 8px left of the text line and the help ? touched
the popover edge. The hero now shares the rows' horizontal inset.

* feat(viewer): publish becomes a menu row like every other share action

The hero still read as a foreign body: a solid button + floating help
circle next to plain icon+label rows. Publishing now uses the exact same
row as 部署到 Vercel / 保存为模板 — same section label, same
icon + label, same 36px row box — with the help '?' pushed to the row
end as a tooltip span inside the row.

Measured parity after the change: all four rows share x/width/height
(346x36) and 8px 10px padding; all three section labels share the same
left edge.

The published state keeps its link block (content, not an action) and
the failure line moves outside it so it carries the rows' own inset.
Dead .chrome-publish-primary / -cta / -help styles removed.

* feat(viewer): the workspace-access control joins the same row language

With publishing converted to a menu row, the team card was the last grey
box in the panel. It now uses the section-label + control layout of every
other tier: label 在工作空间中分享项目 with a trailing '?' carrying the
state-dependent explanation (private vs workspace), and the access
dropdown inset to the rows' 10px so its left edge lines up with the row
icons below.

Verified with the team gate forced on: all four section labels share one
left edge and the dropdown aligns with the rows' content column.
Dead .chrome-share-card* styles removed.

* feat(viewer): export leads the header pair and takes the dark treatment

Export is the far more used of the two intents (30 days: ~14k users
exported successfully, ~13.9k; only ~620 attempted a deploy and ~1.2k
saved a template), so it takes the lead slot and the solid dark button
while share steps back to the light secondary.

Applied in both chromes (workspace header and the React component
viewer toolbar). The dark modifier is renamed .chrome-action-share-dark
-> .chrome-action-dark since it now marks the lead action, not sharing.

* fix(viewer): the share/export popover hugs its content

The popover was pinned to 360px while its rows are short labels, so both
menus rendered with a wide empty gutter on the right. It now sizes to
max-content between a 248px floor (keeps the four-row Export menu from
looking cramped) and the previous 360px ceiling (long deploy labels and
a published URL still fit; the URL block already ellipsises).

Measured after the change: both menus render at 248px with no row
overflow, down from 360px.

* feat(viewer): the share panel speaks share, not publish

The button says 分享 / Share but every line inside said 发布 / publish,
so users had to translate mechanism words back into their intent. The
panel now describes outcomes in link terms and lets the mechanism serve
the goal (孙庆雨, 8/9):

- 通过 Open Design 发布 -> 快速分享(Open Design 托管) / QUICK SHARE · OPEN DESIGN
- 发布并获取链接 -> 生成分享链接 / Get a share link
- 更多发布方式 -> 分享到自有平台 / SHARE ON YOUR OWN HOSTING
- 发布中… -> 生成中… ; 取消发布 -> 停止分享
- failure, tooltip and signed-out copy re-worded to match

Deploy rows keep 部署到 Vercel / Cloudflare Pages: deploy is those
platforms' own verb, and the section header now frames them as a way of
sharing. All eight keys updated across 19 locales (152 values); no
analytics names change (publish_file / copy_publish_link /
artifact_publish_result stay as-is).

* fix(viewer): harden the /api/editors response against off-contract hosts

HandoffButton now mounts in the artifact viewer chrome on every artifact,
which puts it on the viewer's crash path. `fetchHostEditors` casts the raw
/api/editors JSON to HostEditorsResponse without validating it, so the
previous `resp.editors ?? []` guard only covered null/undefined: a body of
`{"editors": {}}` or `{"editors": [null]}` still reached `editors.filter`
and took the whole viewer down with a TypeError.

Normalize at the boundary instead. `normalizeHostEditors` requires an
array and keeps only entries carrying a non-empty string id and label,
coerces `available` to a real boolean so a truthy-but-wrong value reads as
not-detected rather than a launch target, and de-dupes ids that would
otherwise collide as React keys. `normalizeHostPlatform` degrades an
unrecognized platform to `unknown` instead of leaking the raw string into
the open-in call. A non-object body is null-guarded before either runs.

The new spec is red on the parent commit: 3 of its 5 cases fail, including
both payloads from review, which throw at the `editors.filter` call.

* fix(viewer): reading the publish help must not publish

The publish row's trailing "?" carries the reach + single-file-limitation
copy — exactly what a user wants to read BEFORE committing — but it was
nested inside the same role="menuitem" button whose onClick calls
publishCurrentFilePublic() unconditionally. Activating the help therefore
created a public link. Hover hides this on desktop; on touch there is no
hover path at all, so pressing was the only way to read it. A gesture
whose intent is "explain this" was silently making an externally visible
URL.

Move the "?" onto the shareMenuPublishViaOd section label, outside the
actionable row — the same structure the workspace-access help above
already uses, so the two help affordances now behave identically. Both
viewer chromes are updated. .share-menu-help keeps its margin-inline-start
auto, so it still sits at the end of its label.

Red spec in FileViewer.test.tsx covers both viewer copies, locating the
help by the explanation it carries rather than by a testid the fix added,
so it fails on the pre-fix nesting rather than on a missing hook.

Verified on a tools-dev runtime: clicking the "?" issues no publish-public
POST and leaves the panel open; light and dark both render the "?" at the
label end with the section labels still sharing one left edge.

* test(e2e): follow the header split in the Playwright suites

The header split removed the share popover's tab strip, but the Playwright
suites still drove the old shape — they clicked Share and then reached for
a role=tab named Export/Share/Send to…. Those tabs no longer exist, so the
UI P0 shards project-workspace and project-workspace-editor both failed on
this branch's only CI run (85a1cb14, 8/9); every later push has failed to
trigger ci.yml at all, so the breakage stayed invisible.

Two different corrections, because the split also moved rows between the
two panels:

- project-management-flows: the share-link / share-page / deploy-target
  rows used to sit under the popover's Export tab. Share owns them now, so
  the helper takes the Share door (openShareMenu). Reaching them through
  Export opens the right popover and then finds nothing.
- app-manual-edit: the file-format rows (PDF, PPTX, standalone HTML) are
  Export's, and Export is its own header button, so openShareExportMenu
  clicks it directly instead of Share-then-tab. Two call sites that opened
  Share and then looked for "Export as …" are routed through the helper.

The toolbar smoke that asserted the tab strip now asserts the split it
replaced: all three header controls present, and Share not offering file
formats.

Verified per-case against a tools-dev runtime (workspace prebuild first —
without it the seeding API fails and every case dies in setup):
  - @critical preview toolbar keeps share/download/comment/zoom — passed
  - share menu copies the current share link — passed
  - share menu opens the current share page — passed
  - @critical share menu publish opens the deploy flow — passed
  - first-loop onboarding completes after an artifact export — passed
The PPTX call site is not covered by a green run: its case dies earlier in
seedDeckArtifact, a daemon-side upload failure this branch cannot cause —
the diff against main touches only apps/web and its unit tests.

* fix(viewer): make the panel help reachable, and level the split button

Three review findings, all verified against the source before acting.

**The help "?" was unreachable without a pointer.** The tooltip layer opens on
`focusin` (TooltipLayer.tsx), but the affordance was a `<span>`, which never
receives focus — and touch has no hover path at all. So the reach + single-file
limitation copy, and the workspace-access explanation, were pointer-only. Both
are now real `<button type="button">`, which also means shedding the global
button box (36px, bordered, filled) to stay a 14px inline glyph. They keep the
"outside the actionable row" invariant the publish-help specs already pin.

**The split button was uneven wherever nothing sized it.** `.handoff-caret` had
no height, so it inherited `button { height: 36px }` while `.handoff-trigger` is
30px, and `.handoff-split`'s `align-items: stretch` rendered the mismatch. The
`.ws-tabs-actions` override sized both to 28px, which hid this on the Html path
while the viewer-toolbar mount stayed uneven. The caret now declares 30px to
match its trigger, so the pair is correct before any host override.

**Share wore Export's primary treatment.** One hard-coded class string served
both tabs in ReactComponentViewer, so Share got the accent fill too and the
intended hierarchy — Export leads, Share is the quieter neighbour, as the Html
chrome already does — was flat. The class is now branched by tab.

Specs assert the tag, the button type, and that focus actually lands, for both
viewer chromes plus the workspace-access copy; they fail on the parent commit
with `expected 'SPAN' to be 'BUTTON'`.

Verified on a tools-dev runtime: one Tab from the Share button lands on the "?"
and opens the tooltip carrying the limitation copy, with no publish request;
trigger and caret both measure 30px in the viewer toolbar; Export renders dark
filled against Share's transparent ground.

Web suite 609 files / 6311 passed. An earlier run showed 4 failures — favicon
URL, BYOK array length, iframe download timeout, annotation File instance —
none reachable from this diff; a stashed baseline and a second run were both
green, so they are concurrency flake, not regressions.

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-08-10 09:24:45 +00:00
Ray XiandGitHub f7a3fe3662 fix(release): repair Windows prerelease auth smoke (#6682) 2026-08-10 08:43:56 +00:00
lefarcenandGitHub 2fb33fb282 fix(web): make project conversation forks reliable (#6673)
* fix(web): make conversation forks reliable

* fix: preserve fallback fork boundary
2026-08-10 05:19:47 +00:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
d1f5e1f2fb chore(release): bump main to 0.18.2 ahead of stable 0.18.1 (#6588)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-08-10 04:53:24 +00:00
Chanhyo JungandGitHub d55970a9df fix(web): cancel stale invite dialog close timer (#6580) 2026-08-07 09:10:21 +00:00
AmyandGitHub e01efc78f6 test(e2e): align UI P0 coverage with the current product (#6405)
* test(e2e): preserve useful UI P0 hardening

* test(e2e): remove retired Automations coverage

* test(e2e): retain Automations smoke coverage

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-07 08:21:48 +00:00
Marc ChanandGitHub a1995a186a test(e2e): harden UI flake waits with real completion signals (#6539)
* test(e2e): harden UI flake waits with real completion signals

Replace synthetic /projects routing, blind boot sleeps, and call-site
create retries with observable readiness: real navigation, suite daemon
health warmup, expect.poll, clock fast-forward for coalesce TTL, and
quiescence for AMR refresh traffic.

* test(e2e): raise Playwright fixture timeout for daemon warmup

The suite fixture now polls daemon /api/health for up to 60s, which
consumed the prior 390s budget's bookkeeping margin. Raise the worker
fixture timeout to 450s so start + web/daemon warmup + stop still leave
a full minute for teardown bookkeeping on slow CI workers.

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

* test(e2e): fail closed on health body drain and narrow billing mock

Require warmPlaywrightDaemonRuntime to fully consume ok health bodies
before declaring readiness, drain non-ok probes, and fulfill only the
exact /api/workspace/billing path so nested billing routes fall through.

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

* test(e2e): reopen projects by id in cross-project file-tab restore

Avoid relying on chrome workspace tabs surviving leave+create hard
navigation; assert per-project file-tab restore via project routes.
2026-08-07 10:37:59 +08:00
Marc ChanandGitHub 4e47119edb ci: right-size UI P0 runners to medium (#6502)
Measured UI P0 shards fit nexu-runners-medium; drop the blanket xlarge
assignment and keep only project-collab on ui_p0_heavy (xlarge) because
multi-client collab has hit large/xlarge memory ceilings.
2026-08-06 21:17:25 +08:00
AmyandGitHub 5580736c1a fix(e2e): align packaged onboarding smoke selector (#6538)
* fix(e2e): align packaged onboarding smoke selector

* test(e2e): guard packaged onboarding selector drift

* fix(e2e): align platform onboarding probes

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

* fix(e2e): align packaged smoke with identity gate

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-06 20:00:13 +08:00
8b3ec59cfe feat(campaign): DeepSeek V4 Flash free-week surfaces (landing + web + attribution) (#6507)
* feat(campaign): port DeepSeek V4 Flash free-week surfaces from #6450

1:1 port of the demo net diff (base feat/workspace-team) onto main.
Landing home banner, landing pricing benefits, cloud-team-demo review
page, in-app home modal, workbench badge, model-switcher benefit badge,
and the campaign attribution/analytics pipeline. No behavior changes in
this commit; agreed behavior fixes land separately.

* fix(campaign): show the DeepSeek V4 Flash modal only on the active home view

The dialog portals to document.body while EntryShell hides inactive entry
views with display:none, so the campaign escaped the home view and
interrupted projects/tasks/plugins routes. Thread EntryShell's existing
isActive signal through HomeView into a new `active` prop: when inactive
the modal neither opens nor renders (and closes without being marked seen,
so returning to home while still unseen re-opens it).

Requirement: 弹窗只在 #/home 展示,不在编辑器或其他工作流中打断用户。

* fix(campaign): fail closed when frequency-control storage is unreadable

hasSeenCampaign() answered "unseen" when localStorage threw (private
mode / storage disabled), so the modal re-opened on every Home mount even
though markCampaignSeen() could never persist the dismissal. The campaign
promise is one appearance per activity window — an unreadable store now
counts as seen.

* feat(campaign): make the paid 立即使用 CTA switch to DeepSeek V4 Flash for real

产品拍板 D5 + Demo 表格「点击立即使用模型切换为DSV4」: the paid modal CTA
previously only clicked the composer chip open and highlighted it. Now it
performs the actual switch — agent to Cloud (amr), model to
deepseek-v4-flash — through EntryShell's onAgentChange/onAgentModelChange
persistence pair (the same sink InlineModelSwitcher writes through),
threaded HomeView → DeepSeekV4FlashCampaign as onUseCampaignModel. The
chip keeps the data-campaign-highlight pulse as feedback but no longer
opens the picker; the use_now analytics element is unchanged.

* fix(campaign): carry metricsConsent + device id on the unpaid modal upgrade

The unpaid modal's recordAmrEntry was the only campaign touchpoint that
dropped metricsConsent — the workbench badge and the model-switcher
upgrade already forward it — so the consent-gated AMR analytics mirror
never fired and the plans URL missed od_device_id. Thread
config.telemetry.metrics and config.installationId from EntryShell through
HomeView into the modal, and stamp the URL via
amrHandoffDeviceId + attributedAmrUrl(plansUrl, attribution, deviceId).

* fix(campaign): make the campaign/campaignAudience/campaignUsage review params production-inert

产品拍板 D7 + 需求文档「该入口仅用于本地 Demo,不作为线上产品规则」: the
review URL fixtures forced campaign surfaces for real users on released
builds.

Web: new isCampaignReviewAllowed() gate (NODE_ENV !== 'production',
inlined at build time) now fronts isDeepSeekV4FlashCampaignReview, the
campaignAudience override, the modal's shouldForceCampaignReview, and the
switcher's campaignUsage read.

Landing (Astro): inline scripts cannot see build-time constants, so the
home banner, pricing page, and cloud-team-demo inject the verdict as
data-campaign-review-allowed (__OD_LANDING_NOINDEX__: staging/PR previews
true, production false) and preview activation now requires it. The
[locale] wrappers re-render the same components, so they inherit the gate.

* fix(landing): stamp campaign attribution only inside the activity window

Pricing subscribe CTAs and the cloud-team-demo upgrade/pricing clicks
minted __odRecordCampaignEntry with campaignId and stamped
campaign_id on analytics props unconditionally, so clicks outside the
fixed Aug 6-13 window were counted toward the campaign. Both surfaces now
pass the campaign id and the campaign_id prop only while campaignActive
is true; od_entry_* attribution itself is still recorded outside the
window, just without the campaign stamp. Inbound first-touch
od_campaign_id preservation in posthog-analytics.ts is untouched.

* test(campaign): use undefined-safe access for window.open mock calls

Strict TS (tsc -b) flags open.mock.calls[0][0] as possibly undefined in
the modal spec added for the unpaid-upgrade consent behavior; go through
String(...[0]?.[0]) instead. No behavioral change.

* chore(campaign): drop demo-only artifacts before shipping

Remove the cloud-team-demo static review page (fake team cards and
prices, noindex but publicly reachable — review-only material that must
not ship), its locale wrapper and test, and five unreferenced campaign
poster PNGs left over from demo iterations. The team-modal campaign
surfaces the page previewed live in the real Cloud plan-selection
component (powerformer/vela#1379).

* fix(campaign): make review URLs a pure UI projection with zero config writes

Product boundary ruling: the client-side campaign change is a badge next
to DeepSeek V4 Flash (auto-hides after the window) plus the modal CTA
reusing the existing model-selection callbacks — selection behavior must
not change. The demo's review fixtures violated that: an effect force-
switched the persisted agent to AMR under ?campaignAudience=, and the
model write-back effect persisted the review projection into the user's
config. Both writes are removed; review URLs now project AMR + DeepSeek
purely in the UI and leave persisted state untouched.

This also roots out the CI heap OOM in
InlineModelSwitcher.compact-model-click.test.tsx (run 31076791463):
the stateful test wrapper re-rendered on every persisted write, looping
the unconditional write-back forever. With review write-back gone the
file completes in ~10s. The review-URL test now asserts the projection
and that onAgentChange/onAgentModelChange are never called.

* refactor(campaign): remove DeepSeek V4 Flash URL review backdoors

Product decision: the ?campaign= / ?campaignAudience= / ?campaignUsage=
review parameter mechanisms are deleted entirely. Campaign visibility is
now decided only by the real activity window plus the real subscription
audience, and pre-launch acceptance switches to a temporary startAt
constant override instead of URL fixtures.

web:
- campaigns/deepseek-v4-flash.ts: drop review param constants, review
  gate, review detection, and the audience URL override; visibility and
  audience resolution take only `now` (plus plan/loggedIn); restricted
  presentation kept as a reserved branch for the backend usage-limit
  signal (no trigger wired yet)
- DeepSeekV4FlashCampaign.tsx: drop the forced review open and the
  synthetic openedAt+7d countdown; the countdown always runs against the
  real endAtExclusive boundary
- InlineModelSwitcher.tsx: drop campaignUsage URL read, audience
  override, review agent/model fixtures and the currentAgent/model
  review projection (incl. data-campaign-review); 已暂停 and 升级可用
  badge branches stay as reserved presentation flags with no trigger
- EntryShell.tsx / use-deepseek-v4-flash-campaign.ts: no more
  location.search plumbing; home-hero.css review-width rule removed

landing-page:
- index.astro / pricing/index.astro: preview branch removed (review
  params, review-allowed attribute, synthetic preview countdown);
  campaignActive follows the fixed window only
- _lib/deepseek-v4-flash-campaign.ts: reviewParam field removed

tests: review-only specs deleted; convertible specs rewritten as pure
time-window assertions (clock pinned inside/outside the real window);
new negative specs pin that no campaign surface reads URL parameters.

* fix(campaign): device id on badge handoff + impressions on window-open transition

Two review findings from PerishCode, both verified real:

- EntryShell workbench-badge handoff called attributedAmrUrl without the
  device id, so opted-in clicks omitted od_device_id (confirmed in live
  browser acceptance: the modal URL carried it, the badge URL did not).
  Now derives it via amrHandoffDeviceId like the other two touchpoints;
  the source-contract assertion pins the three-arg call.
- The pricing page emitted campaign impressions only during initial
  script execution, so a tab opened before startAt showed the surfaces
  at launch without any surface_view. Impressions now share the reveal
  loop's cadence with a one-time flag, covering both load-time and
  mid-session window-open transitions.

* temp!: open the campaign window early for pre-launch acceptance — REVERT BEFORE MERGE

startAt 2026-08-06T20:00 → 00:00 (+08:00) in both front-end constants so
reviewers can see the real campaign state (true countdown, no review
backdoors) on local runs and the PR preview today. endAtExclusive is
untouched. This commit MUST be reverted before this PR enters the merge
queue — merging it would launch the landing surfaces ahead of 20:00.

* test(campaign): derive the start-instant countdown from the window constants

Pins the assertion to endAtExclusive - startAt instead of a literal
'7天 00:00:00', so the temporary pre-launch start-time override (and any
future window change) cannot silently break the suite.

* Revert "temp!: open the campaign window early for pre-launch acceptance — REVERT BEFORE MERGE"

This reverts commit f123289c0f.

* Reapply "temp!: open the campaign window early for pre-launch acceptance — REVERT BEFORE MERGE"

This reverts commit e472cc81d2.

* test(e2e): seed campaign-modal dismissal into every Playwright context

The DeepSeek V4 Flash home modal fires once per fresh browser profile the
moment the campaign window is open — which is every CI run from launch
onward. It steals focus and covers the elements functional specs are
about to drive, surfacing as 14-minute waitForResponse timeouts in the
UI P0 groups. Seed the one-shot dismissal (a legitimate user state the
product already persists) through a shared context fixture on both test
and clusterTest, so specs stay deterministic inside and outside the
window. One storage entry per campaign; keys stay in sync with the
component's SEEN_KEY.

* test(e2e): seed campaign dismissals into collab cluster contexts

createCollabCluster allocates its own BrowserContext via browser.newContext(),
so the suite-level context fixture never runs for multi-client specs. That left
the DeepSeek V4 Flash home modal free to open and block ensureRailOpen clicks
(UI P0 project-collab / project-workspace-editor). Share the dismissal seeder
and apply it before each cluster client's first page.

* test(e2e): move project collab scenarios to P1

* test(ci): remove project collab from P0 matrix

* test(e2e): mock campaign init scripts in collab cluster

* fix(e2e): restore project-collab P0 gate and collab mock seeding

PerishCode correctly blocked the follow-up that retagged the multi-client
convergence specs as P1 and dropped project-collab from the merge-gated UI P0
matrix: those two cases are the only browser coverage for live two-client
content/presence/unshare and authority-revocation convergence.

- Restore [P0] tags, the project-collab group/matrix/coverage entries, and the
  six-domain / four-domain shadow contracts (matches main).
- Teach collab-cluster unit mocks about addInitScript so the campaign-
  dismissal seed on independently created BrowserContexts does not break E2E
  Vitest.

* fix(campaign): close paid-CTA mode switch and attribution gaps

Address Siri-Ray's blocking review on head 61de050:

1. Paid 立即使用 now flips execution mode to daemon before writing amr +
   deepseek-v4-flash, so a BYOK (mode=api) user does not keep the BYOK
   provider after the modal CTA.
2. Landing campaign helpers preserve od_device_id across Pricing → Cloud,
   treat campaign membership as explicit (no stale inbound od_campaign_id
   inheritance), and Pricing only re-stamps campaign id while active.
3. Daemon AmrEntryAnalyticsPayload/parser validate and forward campaignId +
   conversionSource so the consented AMR mirror no longer strips them.

---------

Co-authored-by: mrcfps <mrc@powerformer.com>
Co-authored-by: Amy <1184569493@qq.com>
2026-08-06 19:51:33 +08:00
lefarcenandGitHub 1aad2f5ef6 fix(release): treat an empty windows smoke mode as core (#6524)
* test(e2e): pin that an empty windows smoke mode means core

Two independent holes let an empty value read as a different profile:

The workflow-level one — a workflow_call `default:` applies only when an
input is OMITTED, so forwarding '' defeats the declared `core` default.

The spec-level one — `??` falls back only on null/undefined, so an empty
environment variable survives it and `smokeProfile === 'core'` is false.

Together they made release/v0.18.1's first prerelease take the `full` path,
demand the updater fixture only a genuine `full` request configures, and die
before the smoke started — on the branch-cut commit, before anything else had
landed there. release/v0.18.0 never showed it because its branch name matched
a special case that produced `skip`, so its smoke never ran at all.

Red on both layers: the resolver mirrors main's `?? 'core'` verbatim.

* fix(release): treat an empty windows smoke mode as core

Close both layers that let an empty value read as a different profile.

notify-release-feishu now names `core` as its final arm instead of ''. A
workflow_call `default:` applies only when an input is omitted, so forwarding
an empty string defeated the callee's declared default.

win.spec.ts resolves the profile through `resolvePackagedSmokeProfile`, which
treats empty, whitespace and unset alike as `core` and rejects anything it does
not recognise — a typo must not select the updater path the way '' did.

Either fix alone would unblock release/v0.18.1, but they guard different holes:
one stops an empty value being produced, the other stops one being misread.
2026-08-06 18:49:12 +08:00
886bcc4db0 feat(web): streamline onboarding model source flow (#6475)
* feat(web): streamline onboarding model source flow

* test(e2e): align onboarding coverage with cloud-first entry

* test(e2e): isolate cloud status in non-auth specs

* test(e2e): keep restoration focused on tab state

* test(e2e): close cloud-first onboarding gaps

* test(e2e): align runtime checks with cloud-first gate

* ci(e2e): retire automations from P0 gate

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-08-06 18:48:33 +08:00
99fc8a45bc Revert "feat(design-systems): add structured runtime contract (#6456)" (#6520)
This reverts commit 2b8aa8c457.

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-08-06 18:48:11 +08:00