Commit Graph
2467 Commits
Author SHA1 Message Date
mehmet turacandGitHub a588cefd2b test(daemon): add regression tests for headerless /raw/ reads of workspace-bound projects (#7206)
* test(daemon): add regression tests for headerless raw reads of workspace-bound projects

Closes #7072.

The /raw/ and /preview/ routes call authorizeProjectRequest with
{ mode: 'read', allowNavigationQuery: true }. Before the
enforceLocalProjectDataPlaneRequest refactor, these routes delegated to
enforceVerifiedWorkspaceResourceRead, which required an explicit workspace
context for any workspace-bound project and returned
WORKSPACE_CONTEXT_REQUIRED (400) when headers were absent — breaking
local/desktop preview iframes and MCP preview URLs for projects with a
personal workspace_projects binding.

The two new test cases pin the corrected behaviour: a headerless request
to authorizeProjectRequest with allowNavigationQuery: true must succeed
(return true, not call any remote verifier) for both personal and team
workspace-bound projects, matching exactly what an iframe src or a bare
loopback fetch sends.

* chore: retrigger CI after flaky workspace-restoration P0

* chore: retrigger CI after repeated workspace-restoration flake
2026-08-23 03:48:14 +00:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
05f5b33ef5 chore(release): bump main to 0.20.3 ahead of stable 0.20.2 (#7253)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-08-21 20:57:23 +00:00
01169bcd99 feat(pricing): add DeepSeek vision unlimited access (#7250)
* docs: specify pricing vision model update

* docs: plan pricing vision model update

* docs: record pricing vision usage estimate

* test(pricing): cover vision model entitlement

* feat(pricing): add DeepSeek vision unlimited model

* fix(pricing): fit long vision model name in plan cards

* fix(pricing): widen usage chart label spacing

* feat(pricing): explain model entitlement activation

---------

Co-authored-by: zhanghuihua <zhanghuihua@zhanghuihuadeMacBook-Pro.local>
2026-08-21 15:25:26 +00:00
CaprikaandGitHub 2feaee4237 fix(web): validate onboarding runtime on Continue (#7164)
* fix(web): validate onboarding runtime on continue

* chore: ignore local pi state

* refactor(web): remove unused onboarding code

* test(e2e): align onboarding Continue expectations

* fix(web): discard onboarding validation results the user moved past

Continue awaits a validation round trip before it persists the runtime
configuration and finishes onboarding, but its closure is frozen at click
time and nothing rechecked the intent afterwards. Two ways that surfaced:

- Back stays enabled while the request is in flight. Clicking it returned
  the user to the model-source chooser, then the late success persisted the
  configuration they had just abandoned and completed onboarding under them.
- The inputs being validated stay editable. A success for a replaced BYOK
  key still completed onboarding against the click-time configuration.

`inputKey` already guarded what gets DISPLAYED — `visibleAgentTestState` and
`visibleProviderTestState` fall back to idle once it drifts. Apply the same
rule to what gets PERSISTED: `continueAttemptStillCurrent` requires the
attempt to still be on the runtime setup step, on the same runtime, and
against the inputs it validated, reading them from a ref the continuation
can actually ask.
2026-08-21 10:55:36 +00:00
6b9c6a09b1 fix(web): flatten message center list (#7231)
* fix(web): flatten message center list

* fix(web): polish message center states

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-08-21 10:19:52 +00:00
Ray XiandGitHub abcdc8a991 fix(web): separate updater from account capsule (#7237)
* fix(web): separate updater from account capsule

* fix(web): anchor standalone updater prompt

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

* fix(web): keep updater prompt visible in RTL

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-21 10:08:12 +00:00
Ray XiandGitHub 8bc9b9ffc8 fix(pricing): align landing actions with subscription state (#7239)
* fix(pricing): align subscription actions with billing state

* fix(pricing): avoid duplicate cancellation banner

* fix(pricing): block yearly to monthly changes
2026-08-21 09:47:27 +00:00
Alex LuceroandGitHub 9caec8056d Normalize agent resume fallback policy (#5269) 2026-08-21 09:07:02 +00:00
Ray XiandGitHub b9b7bb86d6 fix(i18n): preserve locale in Pricing handoff (#7197)
* fix(i18n): preserve locale in Pricing handoff

* fix(i18n): consume Pricing locale handoff

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-21 08:37:43 +00:00
CaprikaandGitHub 5a79ad8d74 feat(web): move social links to the nav rail footer (#7232)
* feat(web): move social links to the nav rail footer

The Feishu/Discord, X and mail entries used to live inside the
top-right account menu, one popup away from anyone looking for the
community. They now sit permanently in the home nav rail's bottom-left
footer, which previously rendered as a dead white strip.

The community slot follows the client language: Chinese UIs
(zh-CN / zh-TW) link to the OpenDesign Feishu group, every other locale
keeps Discord. X and mail are locale-independent.

* fix(web): keep the signed-out sign-in card off the social row

`.entry-local-mode-tip` was pinned to the rail footer's bottom edge
(position: absolute; bottom: 3px; z-index: 2). That was harmless while
the footer had nothing else in it, but the social row now occupies that
strip, so the opaque card painted straight over the Discord/Feishu, X
and mail links for exactly the signed-out users it is shown to.

Put the card in flow instead (order 2, above the social row's 3) and let
the footer size itself around both.
2026-08-21 08:08:09 +00:00
4b48f94b7c refactor(ci): align tools-pack sources with scope boundaries (#7230)
* refactor(ci): align tools-pack sources with scope boundaries

* refactor(tools-pack): unify test source imports

* fix(tools-pack): resolve workspace root from package

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

* test(tools-pack): exercise bundled CLI directly

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-08-21 07:56:48 +00:00
lefarcenandGitHub d00492ca2a fix(desktop,web): stop 10-minute export hangs and make export failures attributable (#7182)
* fix(desktop,web): stop 10-minute export hangs and make export failures attributable

Three findings from PostHog on `artifact_export_result` (14d, image
format): 13.5% of image exports fail, 22.8% on 0.19.2, and 20 users
failed 3-11 times each with zero successes. All failures are packaged
desktop (od://).

1. waitForPrintableContent had no timeout. It awaits document.fonts.ready,
   every <img> load/error, and a new Image() per CSS url() background —
   under od:// any of those can settle neither way, leaving the promise
   pending until the daemon's 600s desktop-IPC ceiling. 122 of 142
   DESKTOP_RENDERER_UNAVAILABLE failures sat at ~10 minutes before the
   user saw an error. Its sibling waitForPrintReadyHandshake already
   guards itself with a 30s race for this exact reason; this one was
   missed. Bound it in-page per resource (10s) with an outer backstop
   (15s), resolving rather than rejecting: capturing without a late
   resource beats failing ten minutes later, and the script already
   treats a failed resource as fine.

2. error_code reported the literal "Error" for 148 failures / 82 users —
   48% of them, and undiagnosable. The daemon classifies its own errors,
   but the web export path kept only .error.message and dropped .code, so
   exportErrorCode fell through message-regex matching to err.name. Carry
   code + status through, prefer a meaningful err.name (TypeError etc),
   fall back to the daemon envelope code, and report UNCLASSIFIED instead
   of "Error".

3. CAPTURE_FAILED (90 events / 37 users) collapsed five distinct give-up
   points into one code. Tag each stage so the bucket says which step of
   the fallback chain gave up.

* fix(desktop): bound the artifact document load and release the renderer

End-to-end verification against a real Electron main process found the
first fix incomplete: the export never even reached
waitForPrintableContent. Probing the actual hang showed

  [OD-PROBE] loadURL start     <- and nothing after it

renderDeckSlides awaited window.loadURL() directly, and Electron
resolves that on did-finish-load, which Chromium fires only once EVERY
subresource has settled. One image or font URL that answers neither way
leaves it pending forever, so the export hung one step earlier than the
function I had bounded.

Wait on dom-ready instead (the capture pipeline only needs a parsed,
scriptable document; subresources are waitForPrintableContent's job and
it bounds itself), with a 15s backstop and the loadURL rejection kept
handled.

Bounding the waits still was not enough. The requests we stop waiting
for stay in flight and every later executeJavaScript queues behind a
renderer still busy with them, so the steps after the wait still cost
~38s and the total stayed unstable. Cancelling the outstanding loads on
timeout hands the renderer back.

Measured on an artifact whose <img> and CSS url() both point at a socket
that accepts and never answers, against a live daemon+desktop runtime:

  before          hung past the daemon's 600s IPC ceiling
  bound loadURL   169s -> 53s, but unstable (a later run hung again)
  + stop()        25.7s / 25.8s / 25.7s, identical output every run

  stage breakdown   without stop()   with stop()
    loadMs                      89        70-160
    assetsMs                 15001         15001
    prepareMs                15340             6
    renderMs                 22459         10566
    totalMs                  52889         25700

Both Electron mocks that stub webContents grew the once() the real
EventEmitter always had; without it the new dom-ready wait threw and
base-href-precedence went red.

* fix(desktop): address review — propagate load failures, cancel on either bound

Two blocking findings from review, both real and both verified by
reverting each fix and watching the matching [P0] go red.

1. A did-fail-load was being swallowed. `.catch(() => undefined)` turned
   a genuine main-document failure into an immediately-resolved race
   winner, so the pipeline went on to capture Chromium's error page and
   could report a successful-but-wrong export. That is worse than the
   hang this function exists to prevent: the previous bare
   `await window.loadURL(...)` rejected and renderDeckSlides reported
   RENDER_FAILED. Race tagged outcomes instead — a failure that beats
   dom-ready rethrows, while one arriving after dom-ready has already
   won stays handled (onRejected is attached up front) rather than
   escaping as an unhandled rejection.

2. Cancellation keyed off the outer timer only. The in-page per-resource
   deadline is 10s and the outer bound is 15s, so whenever the renderer
   is healthy the script returns on time, `timedOut` stays false, and
   the abandoned requests were left in flight — exactly the state that
   makes every later executeJavaScript crawl. My own measurement hid
   this: assetsMs 15001 means the outer timer won there. The injected
   script now counts the deadlines it fired and reports
   `{ stalled }`, and either layer giving up triggers stop().

Re-verified end-to-end against a live daemon + desktop runtime on the
same artifact (an <img> and a CSS url() both pointing at a socket that
accepts and never answers): 25.9s / 25.7s / 25.7s, http=200,
byte-identical PNG, stage split loadMs 69 / assetsMs 15002 /
prepareMs 8 / renderMs 10549.
2026-08-21 07:54:41 +00:00
Ray XiandGitHub 105dcf3764 fix(pricing): align follow-up acceptance details (#7228)
* fix(pricing): unify model display order

* fix(pricing): scope banner colors and footnote size

* fix(pricing): render all access checks consistently

* fix(pricing): center model access icons
2026-08-21 07:21:47 +00:00
netteeandGitHub d343775fa7 fix(pricing): return checkout to source Cloud Console (#7219)
* fix(pricing): return checkout to source Cloud Console

* fix(pricing): keep console link in source environment

* fix(pricing): allow trusted Cloud domains
2026-08-21 06:31:56 +00:00
c031dc1ed5 feat(landing): localize event recap pages (#7119)
* feat(landing): localize event recap pages

* fix(landing): preserve canonical event tutorial links

---------

Co-authored-by: 徐燕来 <xuyanlai@refly.ai>
2026-08-21 06:08:09 +00:00
1bf50f4d00 fix(web): align low-balance dialog action styles (#7138)
Co-authored-by: 黄玉婷 <huangyuting@refly.ai>
2026-08-21 06:06:53 +00:00
Ray XiandGitHub 6def520ee2 fix(pricing): align P1 acceptance details (#7222) 2026-08-21 06:04:27 +00:00
lefarcenandGitHub 274bff46d2 fix(web): stop badging team plans as unlimited (#7202)
#7190 (mine) had `planUnlimitedTier` read a personal tier out of a
team-namespaced id, so a Team Pro workspace saw 「无限使用」 on five models and
its zero balance hidden from the credits pill. "Team plans are paid too" was
the reasoning, and it is the wrong test — what matters is whether the plan
funds usage without touching the wallet:

  • vela records in-plan usage through the `coding_plan` billing mode, which
    its schema constrains to personal tiers
    (`membership_tier_snapshot = ANY (ARRAY['go','plus','pro','max'])`), so a
    Team workspace never produces a zero-charge call;
  • `team_basic` is seats-only on top of that — `monthly_credits_per_seat` is
    0 in the seeded team catalog.

#7187's balance preflight already refuses to stand down for Team workspaces,
so the badge was promising exactly what the preflight then blocked, and the
pill was hiding the number that explains the block.

Team ids now resolve to no tier: no badge, and the credits pill keeps showing
their real zero. Personal go/plus/pro/max are untouched.
2026-08-21 05:11:17 +00:00
lefarcenandGitHub c9eb23027f fix(viewer): preserve live HTML previews (#7156)
* fix(viewer): preserve live HTML previews

* fix(viewer): sync saved text to retained preview

* fix(viewer): prewarm retained file revisions

* fix(viewer): expire saved preview adoption

* fix(viewer): repaint retained previews on activation

* fix(viewer): reconcile retained style history

* fix(viewer): renew preview asset scopes without reloads

* test(viewer): cover scope renewal in team previews

* test(exports): expect managed preview bases
2026-08-21 11:41:27 +08:00
Ray XiandGitHub 393af2f991 fix(landing): align Pricing model badges and seats (#7196)
* fix(landing): refine pricing plan details

* fix(landing): align Pro comparison matrix
2026-08-21 10:54:41 +08:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
8b18210103 chore(release): bump main to 0.20.2 ahead of stable 0.20.1 (#7201)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-08-21 01:32:59 +08:00
lefarcenandGitHub ce791edbbb fix(web): hide a subscriber's zero balance, and badge unlimited models by plan tier (#7190)
* fix(web): hide the zero balance for subscribed plans

On Go / Plus / Pro / Max the popular models a user works with every day are
unlimited, so the wallet only meters flagship calls and legitimately sits at
$0.00. The workbench's top-right credits pill rendered that zero permanently
next to the avatar, which reads as "you are out of money" to a subscriber who
is not.

`shouldShowCreditsBalance` hides the pill for a subscribed tier whose balance
is exactly zero. Three cases deliberately keep it: a free plan (where zero is
the number that explains why hosted models are unavailable), an unresolved
plan (so the pill cannot flicker in and out as billing lands), and an
overdrawn wallet (the one case the user has to act on).

The tier table it asks lives in `state/plan-unlimited-models.ts` — the same
per-tier unlimited sets the model badge uses — so "which plans may hide their
zero" and "which models are unlimited" cannot drift apart.

* fix(web): badge unlimited models by plan tier, not just the campaign

The 「无限使用」 tag in the model chip and the compact model list was wired to
the DeepSeek V4 campaign alone, so it marked exactly two models for everyone.
What the product actually sells is an unlimited SET PER TIER — 3 models on Go,
4 on Plus, 5 on Pro, all 8 on Max — so a Pro subscriber saw no tag on Kimi
K2.7 Code (unlimited on their plan) while the metered MiniMax M2.7 sat right
next to it looking identical.

The badge now has two sources and the campaign stays first: inside the
campaign window DeepSeek V4 Pro / V4 Flash keep the campaign copy and tooltip
for everyone, free plans included. Only when the model is not a campaign model
does the badge ask the subscription, via `isUnlimitedModelForPlanTier`. BYOK
execution claims nothing — those calls are billed by the user's own provider.

Pricing had the Pro row wrong in the same place: it listed MiniMax M2.7 as
unlimited and GLM-5.2 as metered, the reverse of what Pro includes. Fixed
there too, and `e2e/tests/pricing-unlimited-models.test.ts` now pins the
marketing table and the runtime table together across the app boundary so
editing one side alone fails.

* fix(web): drop the invented tooltip from the plan unlimited badge

The plan branch shipped a tooltip line nobody wrote as product copy (and 19
translations of it). The campaign branch's tooltip is campaign copy with a real
source; the plan case has none, so it now renders the badge text alone rather
than an invented explanation. `od-tooltip` and the aria-label only attach when
there is real copy behind them.

* fix(e2e): type the pricing-parity guard under noUncheckedIndexedAccess

`Typecheck workspaces` failed on the new guard: the e2e package compiles with
noUncheckedIndexedAccess, where every regex capture is `string | undefined`, so
the `match![1]` shape from the first draft does not compile there even though
apps/web accepts it.

Capture access now goes through `captureOne` / `captureAll` / `tierEntry`,
which throw a message naming what could not be found instead of asserting
non-null — so a renamed constant fails as "unlimitedByTier not found — did it
get renamed?" rather than as a TypeError.

* fix(web): reuse the shipped Unlimited wording in ru/uk

The plan badge's Russian and Ukrainian values were written from scratch
("Без лимитов" / "Без обмежень") while the campaign badge beside them — the
same word, already shipped and translated — says "Безлимит" / "Безліміт".
Two different words for one state in the same popover is a translation bug,
so the badge now reuses the shipped wording. The other 17 locales already
matched.

* refactor(web): fold the badge's plan table into the one #7187 landed

#7187 shipped `runtime/amr-unlimited-models.ts` — the same per-tier unlimited
sets this branch had added under `state/plan-unlimited-models.ts` — while this
branch was open. Two copies of a product promise is exactly the drift the
cross-app guard exists to prevent, so the duplicate is gone and everything
reads the surviving table.

The badge's extra needs moved into it rather than staying in a second file:
`planUnlimitedTier` reads the tier out of a team-namespaced id (`team_pro`,
`team_max_yearly`) and `isUnlimitedModelForPlanTier` compares the model slug
past any provider prefix. `isUnlimitedAmrModelForPlan` is untouched — the
balance preflight matches the personal ladder exactly on purpose, since Team
workspaces spend their own balance (#7187), and folding the two questions
together would have changed that behaviour silently.

`e2e/tests/pricing-unlimited-models.test.ts` now parses the surviving table,
including the `...GO_UNLIMITED_MODELS` spreads it composes tiers with, so the
Pricing page and the runtime stay pinned to each other.
2026-08-21 01:30:35 +08:00
Ray XiandGitHub 4fccbdb31b fix(pricing): disable current and lower plan actions (#7191) 2026-08-20 23:03:52 +08:00
Ray XiandGitHub b032abed00 fix(web): allow unlimited plan models at zero balance (#7187)
* fix(web): skip zero-balance gate for unlimited models

* test(web): cover balance gate model argument

* fix(web): suppress low-balance warning for unlimited models
2026-08-20 21:51:08 +08:00
3df6db478a feat(landing): hero and header polish — capsule header back, smaller CTAs, green chips, no avatar (#7186)
* feat(landing): restore the floating capsule header, trim the hero community button, drop the download badge dot

- Condensed header goes back to the centred Liquid Glass capsule (14px top
  gap, 1280px max, pill radius) while keeping the icon-only action cluster
- Hero community button shrinks to 52px (46px on phones) with a smaller
  Feishu mark and credits tag
- The red attention dot on the download CTAs (.hm-dl::after) is removed

* feat(landing): hero polish — selection frame back, green trait chips, smaller CTAs; header drops the account avatar, monochrome brand icons

- Hero: blueprint selection frame restored around the headline; trait chips
  lose the dot and take a light coral fill; download + community CTAs one
  notch smaller with more air above them; the block sits higher
- Header: signed-in avatar module removed from the marketing header; the
  community hover card becomes title + quiet perk line; Feishu / Discord
  marks are monochrome at rest and coloured on hover / focus

* feat(landing): more air between the hero chips and the CTA row

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-20 12:57:21 +00:00
544aa34c44 feat(landing): calmer first screen, full-bleed header, credits-led community entries, hesitant-only download prompt (#7178)
* feat(landing): simplify header community entry, move language switcher to footer, video-first hero

- Header: community entry becomes a plain word (飞书群 / 飛書群 / Discord);
  zh / zh-tw hover reveals the Feishu group QR; benefit list and framed
  pill removed; Discord / Feishu rows dropped from the Community dropdown
- Language switcher moves from the fixed header into the footer bottom bar
  (homepage + shared site-footer, opens upward); Header keeps the prop for
  call-site compatibility but no longer renders it
- Hero: drop the Claude-Design eyebrow (SEO layer keeps it), move the
  value-promise paragraph and the product shot into the About section,
  replace the first-screen shot with a click-to-play YouTube walkthrough

* feat(landing): merge about statement with hero value promise, product shot replaces desktop-native panel art

* feat(landing): move community entry from header to hero, beside the download CTA

* feat(landing): full-bleed flush-top header with icon-only community + X links

* feat(landing): drop X from the Community dropdown (lives in the header icon cluster)

* feat(landing): restore hero community button; keep Download in the bar at every width

* feat(landing): Feishu mark for the community entry, credits nudge on header + hero entries, core-trait chips in hero

* feat(landing): reword the coding-agent chip

* feat(landing): reframe the local-run chip around data safety

* feat(landing): reword the brand chip to brand consistency

* feat(landing): shorten the data-safety chip

* feat(landing): merge BYOK and coding-agent chips

* feat(landing): right-align the footer language switcher next to the social icons

* feat(landing): tighten the about statement across all locales

* feat(landing): lead the hero chips with brand consistency

* feat(landing): icon-only language switcher back in the header action cluster

* feat(landing): hero headline from the redesign, scenario line, no selection frame; hesitant-only download prompt; drop Launch Week float

- Hero: "The Vibe Design Workspace for your brand." as the single-line
  headline (all 17 locales), scenario line reordered to prototype / slides /
  marketing image / video / dashboard, blueprint selection frame removed
- Download prompt: any download click suppresses it for 30 days; triggers are
  now exit intent or scroll-past-hero + 20s dwell (the old 35s / 3-page rules
  fired on cold traffic with an 84% close rate); tests updated
- Launch Week float removed from the homepage (754 closes vs 61 opens)

* chore(landing): drop a stray local screenshot script

* fix(landing): keep the brand intact on phones; thread canonical-only into the footer switcher

- <=640px hides the secondary icon cluster from the bar and pins the brand
  so it no longer collapses to 0px at 320/375px (ja/zh/en); Download stays.
  Layout test now asserts 320/375/420 alongside 1081/1367.
- The three canonical-only community pages and the canonical PPT tutorial
  now pass localeCanonicalOnly to SiteFooter, matching their header; a
  source-level test fails if a canonical-only page forgets the flag.

* fix(landing): make the hero video play button the single focus target

The facade wrapper was a focusable generic div while the labelled button was
pulled out of the tab order. The button is now the only focusable control and
native Enter / Space activation bubbles to the frame's click handler; the
custom keydown handler on the div is removed. Adds a source-level regression
test for the facade's accessibility contract.

* fix(landing): visible focus ring on the hero video play button

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-20 11:41:17 +00:00
5459ffbc0f fix(pricing): align Go plan launch experience (#7167)
* fix(pricing): align Go plan launch experience

* test(e2e): dismiss Go launch campaign in UI specs

* fix(go-plan): finish launch campaign polish

* test(e2e): preserve Go campaign visual coverage

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-08-20 10:57:59 +00:00
lefarcenandGitHub 90741ab238 fix(web): restore About update row spacing and stop the project frame flashing twice (#7174)
* fix(web): restore Settings About update action row spacing

The About page's update row holds two sibling buttons — the primary
update action and "View release notes". #6142 dropped the
.settings-about-update-actions rule group while SettingsDialog.tsx kept
emitting the class, leaving the container at display: block. JSX puts no
whitespace text node between adjacent elements, so the two inline-flex
buttons rendered with their borders touching at exactly 0px.

Give the container back its flex row and 8px gap. Adds a Playwright UI
regression guard that stubs the packaged-desktop host bridge with a
downloaded update so both buttons render, then asserts the declared gap
and the distance the buttons actually land at.

* fix(web): stop the project frame flashing twice on entry from Home

Submitting from Home hands off through ProjectCreationPendingView while
POST /api/projects settles, then swaps in ProjectView. Both components
owned their own `div.app`, so React mounted a second element on the
hand-off and .app's 180ms od-fade-in entrance restarted from zero. The
pending surface only lives ~150ms — shorter than the animation it starts
— so the frame never finished its first fade before the second one
began, which reads as the project view flashing twice.

Move the .app shell up to App.tsx and render it around both branches.
React now reconciles one element across the hand-off: the entrance plays
once, continuously, while the contents swap underneath.

Measured in a real browser on the Home -> project transition:
  before  t=260 .app#1 od-fade-in@0 -> t=408 .app#2 od-fade-in@0 (restart)
  after   t=200 .app#1 od-fade-in@0 -> t=317 .app#1 od-fade-in@117 (continuous)
2026-08-20 10:39:22 +00:00
lefarcenandGitHub a81d114b1d fix(daemon): anchor run phases on first model event (#7155)
* fix(daemon): anchor run phases on first model event

Phase boundaries anchored on `firstTokenAt`, the first *text* token. A run
that opens with a tool call and stays silent until the end therefore had its
entire tool loop billed to `runtime_init`, leaving the generation phase
holding only the closing sentence. Tool-first is the dominant opening shape
for the OpenCode/OpenAI families, so `bottleneck_phase` reported startup for
runs that were working the whole time.

The decomposition was also not a partition: on the new fixture's 24.2s run the
old boundaries sum to 37.8s, because the tool loop is counted inside both
`runtime_init` and `tool_execution`.

Anchor phases on `telemetry.firstModelEventAt ?? telemetry.firstTokenAt` --
tracer marks only. Deliberately not the nearby `firstModelEventAt` composite,
whose `firstToolUseAt` fallback is scanned off event records on the daemon's
clock and is never reset per retry attempt.

Every published metric keeps its current value; a spec pins the four
first-token metrics so a future edit cannot silently redefine them. Adds
`runtime_init_to_first_model_event_ms`, `model_active_duration_ms`, and
`phase_schema_version` so old and new rows can be filtered apart. The
`stream_output` phase now subtracts tool time to stay mutually exclusive with
`tool_execution`.

Trace side gains a `model-active` phase-diagnostics entry rather than a second
span, to avoid an extra observation row per run.

* fix(daemon): measure the tool phase as wall-clock occupancy

Review found two ways the tool phase could exceed the window it sits inside,
both because it was built from `tool_duration_ms` -- the sum of paired
tool_use -> tool_result spans. That sum is the right published answer to "how
much tool work happened" but it is not elapsed time, so it cannot partition a
window:

- Parallel tool calls sum to more than the clock they occupy. Two overlapping
  4s and 5s tools inside a 6s active window reported 9s of tool execution.
- `run.events` is never cleared between retry attempts, while the phase anchor
  comes from the current attempt's lifecycle marks. A retried run charged the
  previous attempt's tool spans to this attempt's window.
- ACP producers supply their own `startedAt`, which can predate the anchor when
  their clock differs from ours.

In each case the inflated phase won `bottleneck_phase` with a duration longer
than the run had been active, and the previous `Math.max(0, ...)` floor on the
residual hid the symptom rather than fixing the partition.

Derive both `stream_output` and `tool_execution` from the union of tool
intervals clipped to [phaseAnchorAt, runEndAt]. The two are then halves of the
model-active window by construction. `tool_duration_ms` keeps summing paired
spans and is unchanged.

Three fixtures cover the parallel, retry, and producer-clock cases; all three
reported `tool_execution` as the bottleneck before this change.

* fix(daemon): keep the phase anchor on real model output

Second review round found three more ways the new boundaries could publish
wrong attribution.

A daemon-persisted artifact was being marked as the first model event. Agent
`artifact` events are emitted from exactly one place -- the daemon's close-time
persistence of plain-stream stdout -- and never by a runtime relaying model
output. On a plain-stream run that stamps first-token from its first buffered
stdout chunk, that late mark won the anchor and reported a 1s active window and
a 16s runtime init for a run that had been streaming since second 5. That is
the opposite of this PR's intent, and it falsified the claim that the
plain/BYOK family keeps its current behaviour through the first-token fallback:
the fallback was never reached, because the field was set -- just set by the
daemon, at the end of the run.

Fixed at the source (the tracer no longer treats an artifact event as a model
event, and neither does the scan-based fallback that feeds
`first_model_event_type`), and again as an invariant: the anchor is the
EARLIEST of `firstModelEventAt` and `firstTokenAt`, because the model cannot
have started responding after it emitted its first token. A late producer mark
can no longer drag the boundaries to the end of the run.

`phase_timing_status` still required `firstTokenAt`, so a fully instrumented
tool-only turn -- every boundary the phases are measured from present, no text
ever emitted -- was stamped `partial` and dropped by dashboards filtering on
complete timings. It now follows the anchor the phases actually use. The
first-token fields are unchanged.

Tool occupancy ignored a tool still outstanding at run end, handing the whole
active window to `stream_output` while `last_observed_phase` said
`tool_execution`. Outstanding spans are now closed at run end for phase
purposes; `tool_duration_ms` still sums completed pairs only.

Five fixtures, red before the change, including one in the tracer suite
pinning that an artifact event is not a model event.

* fix(daemon): scope outstanding tool spans to the current attempt

Closing every still-open tool span at run end interacted badly with the retry
path. `run.events` survives a retry while lifecycle telemetry does not, so an
attempt whose child was killed mid-tool leaves an unpaired tool_use behind.
Closing that at run end stretched it across the retry boundary: a tool_use at
3s, an attempt-2 anchor at 20s, and a run ending at 25s clipped to a 5s
`tool_execution` phase for an attempt that never called a tool.

Gate the close on the daemon-clock timestamp the tool_use was OBSERVED at,
which shares a clock with our own marks, rather than on its start. A
producer-supplied `startedAt` can legitimately predate the anchor on a tool
that is genuinely running, and clipping already handles that case -- gating on
the start would discard real occupancy instead.

Two fixtures: the killed-prior-attempt case (red before this change), and a
guard that an outstanding tool with a skewed producer start is still counted,
so the gate cannot be tightened into discarding live work.

* fix(daemon): give the summarizer a real attempt boundary

Both remaining review findings came from the same gap: the outstanding-tool
gate used `phaseAnchorAt` as a stand-in for "when this attempt started", and
those are different instants.

`phaseAnchorAt` falls back to `firstTokenAt` when no model-event mark exists.
On a legacy or recovery tool-first run -- tool_use at 4s, first text at 10s,
run ending at 30s -- the gate then discarded a tool this attempt had issued and
never finished, reported zero tool occupancy, and handed the whole active
window to `stream_output`. That is the exact shape this change exists to
measure. The gate now uses the attempt boundary; `phaseAnchorAt` is only used
for clipping.

For that boundary to exist, two producers had to stop destroying it:

- The retry path called `resetForAttempt`, which stamps `attemptStartedAt` and
  `attemptIndex`, and then replaced `run.analyticsTelemetry` wholesale, so both
  were dropped every time. It now spreads, keeping the `run.createdAt` fallback
  for the logical run start. This also means `attempt_index` and
  `attempt_duration_ms` -- already-published fields -- start reporting across
  retries instead of silently vanishing.
- `prepareRestart` reused the run for a recharge resume and cleared its
  terminal state but left `analyticsTelemetry` untouched, so a resumed attempt
  inherited attempt 1's `firstModelEventAt`/`firstTokenAt`. Every boundary then
  measured from before the recharge pause, putting the wait inside the new
  attempt's model-active window. It now resets the marks, preserving only
  `startRequestedAt` so queue time is still measured from the user's request.

Two fixtures, red before the change: a no-model-event outstanding tool, and a
recharge resume asserting the stale marks are gone while the logical run start
survives.

* fix(daemon): anchor ACP tool-first turns at the tool's start

Three findings from the fifth review round.

ACP accumulates `tool_call` frames and emits the canonical `tool_use` only once
the call reaches terminal status, carrying `startedAt` set to the first frame's
arrival. The universal `send()` path stamped the anchor with `Date.now()` when
that delayed event arrived, so a tool-only ACP turn put its anchor at tool
COMPLETION -- measuring the whole tool loop as runtime init. That is precisely
the case this PR exists to fix, still fully present for the ACP family. The
stream-event markers now carry the producer's `startedAt` (daemon clock, same
domain as our own marks) and the tracer stamps that instead of arrival time.

Tool ids are reused across a retry: sequential ids like `call_0` restart in the
retry's fresh session, and `openTools` is keyed by id alone. A killed
attempt's still-open `call_0` therefore merged with the retry's genuinely new
`call_0`, pairing attempt one's opening with attempt two's close and reporting
a 19s tool that never ran -- inflating `tool_duration_ms` as well as phase
occupancy. A still-open entry observed before the current attempt began is now
replaced rather than merged.

The Langfuse `model-active` diagnostic used different boundaries from the
metric it exists to be compared against: it preferred `firstModelEventAt` over
the earliest mark, and ended at `finalizeStartAt` rather than run end. On a
text-first stream those differ, so the same run reported two windows. It now
mirrors the canonical rules; the shared "earliest finite mark" logic is a named
helper.

* fix(daemon): stop reporting phase attribution we cannot support

Two findings from the sixth review round.

The first-model-event mark was first-write-wins, which is wrong for ACP. It
holds each toolCallId until terminal status, so two parallel calls can complete
in the opposite order they started: a call that began at 200ms can be the first
one we hear about, and the call that began at 100ms is then discarded. Every
phase boundary shifted later by the difference. The mark now keeps the earliest
finite timestamp with its type. Marks without a producer timestamp default to
arrival, which is monotonic, so this only ever moves the anchor earlier.

Phase occupancy is reconstructed from tool frames in `run.events`, which is a
bounded ring buffer. A run that opens with a 20s tool and then emits enough
output to evict it reconstructs zero occupancy, hands the whole active window
to `stream_output`, and -- because lifecycle marks live on the run and survive
eviction -- still stamped the bundle `complete`. The summarizer now reads the
same truncation signal the execution diagnostics in `runtimes/runs.ts` already
use (first surviving event id is no longer 1), downgrades a `complete` phase
bundle to `partial`, and withholds `bottleneck_phase` entirely rather than
naming whichever phase the buffer happened to keep. Timings that come from
lifecycle marks are unaffected and keep reporting.

Five fixtures. Two of them were green before their change and exist to pin the
boundary from the other side: a later event must not move the anchor forward,
and a truncated run must still report its mark-derived timings.

* fix(daemon): gate every tool interval on the opening attempt

The pairing path never checked which attempt opened the tool. A `tool_result`
that arrives after a retry has started -- a buffered stdout flush racing the
abort, which the ACP session code already documents as a real sequence -- was
paired with its attempt-1 opener into a single interval spanning the retry
boundary. Clipping then charged the new attempt 8.9s of tool execution inside a
9s window for an attempt that issued no tool at all.

Earlier rounds gated the two other ways a stale entry reaches an interval (id
reuse on open, close at run end) but did so at each site, which is why this
third path was missed. The rule is now stated once as `openedInCurrentAttempt`
and every append to `toolIntervals` goes through it, so a fourth path cannot
quietly skip the check.

`tool_duration_ms` deliberately does not pass through the gate and keeps its
whole-run paired-sum definition; the fixture asserts that value explicitly so
the distinction stays deliberate.

* fix(daemon): stop moving a published metric, and abstain when tools are unattributable

Two findings from the seventh review round.

Passing the producer's start into `markFirstModelEvent` moved
`time_to_first_model_event_ms`, which this PR promised not to touch. That field
means "when we first saw a model event"; for an ACP tool starting at 4s and
emitted at terminal 20s it began reporting 4s instead of 20s. My own invariance
fixture did not catch it because it only pins the four first-token metrics.

The mark is now split. `firstModelEventAt` goes back to first-write-wins on
arrival and keeps feeding the published metric unchanged. A new
`firstModelResponseAt` holds the earliest producer start (clamped to arrival, so
a fast producer clock cannot claim the model responded in the future) and is
what phase boundaries anchor on. The new phase field is renamed to
`runtime_init_to_first_model_response_ms` to match what it measures; it is
introduced by this PR and has never shipped.

Second, a tool id shared by two attempts cannot be attributed. When a retry
reuses an id whose previous opener is still outstanding, a later `tool_result`
could belong to either, and the two readings differ by seconds of occupancy.
Earlier rounds tried to pick a winner; nothing in the event log supports one.
The summarizer now records that an opener was displaced, marks the ledger
ambiguous when a result arrives for such an id, and withholds
`bottleneck_phase` and a `complete` phase status -- the same treatment
truncation already gets, and for the same reason. Timings derived from
lifecycle marks keep reporting.

Fixing this properly rather than abstaining needs an attempt or generation id
carried on the tool events themselves, which is a producer and event-schema
change; noted in the review thread rather than smuggled in here.

* fix(daemon): share one phase-anchor rule between both sinks

The Langfuse `model-active` diagnostic listed its own marks, so adding
`firstModelResponseAt` to the analytics anchor last commit left the mirror
behind again. On an ACP tool-first run with no text token it reported 10s
against the analytics value of 26s for the same run -- on precisely the runtime
shape this change targets, and in the one entry whose whole purpose is to be
compared across sinks.

This is the second time the two drifted, so the fix is structural rather than
another manual sync: the rule is exported as `phaseAnchorFromMarks` and both
call sites use it. Re-listing marks per sink is what allowed the drift, and a
third mark would have caused it again.

Validity rules are now identical by construction too, which resolves the
separate half of the finding.
2026-08-20 08:45:45 +00:00
645aace758 feat(landing): add locale-aware community entry (#7120)
* feat(landing): surface localized community benefits

* feat(landing): localize community benefits

* fix(landing): keep community entry inside mobile nav

* fix(landing): cover compact header transition band

---------

Co-authored-by: 徐燕来 <xuyanlai@refly.ai>
2026-08-20 07:38:35 +00:00
Ray XiandGitHub f6331fb33a feat(web): add Go campaign entries and route upgrades to Pricing (#7122)
* feat(web): add Go campaign entries and route upgrades to pricing

* fix(web): localize Go campaign copy

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

* feat(web): match Go campaign demo UI

* test(web): align Go campaign source contract

* fix(web): align Go modal model icons

* fix(web): preserve Z.ai mark in Go modal list

* fix(web): localize and fit Go campaign modal

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

* fix(web): clamp Go campaign modal height

* fix(web): use local Go campaign icons

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

* test(e2e): stabilize Go campaign viewport assertion

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-20 07:26:15 +00:00
Ray XiandGitHub 228113ff99 feat(landing): launch Go pricing and homepage banner (#7121)
* feat(landing): launch Go pricing and homepage banner

* fix(landing): keep Go pricing and Max branding readable

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

* fix(landing): preserve desktop hero spacing with Go banner

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

* fix(landing): align Go banner with allowance copy

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

* feat(landing): match Go pricing demo UI

* fix(landing): align live pricing entitlements

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

* fix(landing): align Go pricing visuals with demo

* fix(landing): collapse pricing benefits by default

* fix(landing): correct Go banner and collapsed benefits

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

* fix(landing): localize Go pricing and banner handoff

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

* fix(landing): coordinate campaign and pricing help

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-20 04:27:06 +00:00
CaprikaandGitHub 3662637d38 fix(media): surface concrete image-generation failure reasons (#7118)
* fix(media): surface concrete generation failures

* fix(media): preserve error code underscores in chat

* test(contracts): expect concrete media failures

* fix(media): flush structured CLI failures
2026-08-20 04:01:11 +00:00
Albert UnchiasuandGitHub 13f776fb6c fix artifact delivery reconciliation (#5750) 2026-08-20 03:34:25 +00:00
36d2ea3e53 fix(landing): fill the Codex Plugin mega-menu column and adopt the dsh one-line installer (#7139)
* fix(landing): fill the Codex Plugin mega-menu column and adopt the dsh one-line installer

The product mega menu's Codex Plugin column rendered as a blank panel
next to the populated Solution and Agent columns because it carried a
head link only. Fill it with the plugin ecosystem's real destinations:
the install anchor (reusing the plugin page's translated phase label,
no new i18n keys) plus the Codex Design and DeepSeek Harness
collection pages.

The DeepSeek Harness agent guide still taught the manual
npm-install path that requires a preinstalled Node.js toolchain. The
published installer flow replaces it: per-OS one-line commands from
open-design.ai/install-dsh.*, an isolated user-directory toolchain
with no sudo and no global Node.js changes, pinned to the release
OpenDesign has verified, finishing in dsh web for API-key setup.
Setup section, credential note, quick-summary steps, and FAQ updated
in both the English and Chinese guides; installer-specific FAQ
entries added.

* fix(landing): shape the Feature column per design and promote DeepSeek Harness

Rework the first mega-panel column to the reviewed design: a localized
Feature category head (new header i18n key across all 18 locales) with
Codex Plugin as its entry, replacing the interim link list. In the
Agent column, move DeepSeek Harness to the top with a red new-dot to
mark the freshly launched integration.

* fix(landing): roll the one-line installer into every active locale of the Harness guide

The landing test suite guards the DeepSeek Harness guide content per
active locale, so the installer switch has to land everywhere at once:
ja/ko/de/fr/es/ru/pt-br/it/tr setup sections now carry the three
per-OS one-line commands, the installer behavior list, the updated
Web UI step and credential note, plus the reworked install FAQ and two
new installer FAQs (translated, not machine-padded). Retired locales
(vi/pl/id/nl/ar/uk/zh-tw) keep their legacy dead copy untouched.

The content guard flips accordingly: it now requires all three
installer URLs and rejects any resurfacing of the manual
npm-install path; EN/ZH phrase anchors track the new intro copy.

147/147 landing tests pass; astro check clean.

* fix(landing): refocus the Harness guide on design and add HowTo schema

The guide led with a 'does DeepSeek Harness have a GUI' angle that is
off the page's actual message. Refocus every active locale on the real
narrative — designing with DeepSeek Harness through OpenDesign:
design-first title and description, the GUI section, its TOC entry and
FAQ removed (en/zh plus the nine localized copies; retired locales
keep their dead copy).

SEO on top of the accurate tutorial: add HowTo structured data built
from the real five-step walkthrough (install → download → detect →
connect → first task) with per-step section anchors, joining the
existing Breadcrumb/WebPage/ItemList/FAQPage blocks. The content
guard's title anchor tracks the new intent.

* fix(landing): keep a #gui compatibility anchor on the Harness setup section

The GUI section was removed in the design-first refocus, which broke
saved and search-indexed /agents/deepseek-harness-design/#gui deep
links. Alias the anchor onto the setup section so those links keep
landing on content, without reintroducing the GUI copy.

* fix(landing): give each HowToStep its instructional text

The HowTo steps carried only headings and anchors; schema consumers
that require an instructional field saw five names with no directions.
Each step now carries its section's lead paragraph as plain text
(markdown links and code markers stripped), falling back to the
heading when a section opens without a paragraph.

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-20 03:32:27 +00:00
mehmet turacandGitHub 8d62fda2b5 fix(daemon): preserve scripts during preview URL rewriting (#6938)
* fix(daemon): preserve scripts during preview URL rewriting

* fix(daemon): preserve inline script contents during rewrites

* fix(daemon): preserve executable HTML attributes

* fix(daemon): preserve scoped link rewriting

* fix(daemon): harden protected HTML rewriting

* fix(daemon): normalize executable URL schemes

* fix(daemon): preserve namespaced executable URLs
2026-08-20 02:51:50 +00:00
Ray XiandGitHub 479078b44c fix(web): keep first Home run output live (#7071)
* fix(web): keep first Home run output live

* test(e2e): bound design system restoration probe

* fix(web): preserve live turn through authority reload

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-20 02:44:44 +00:00
lefarcenandGitHub 057b0f4e10 fix(viewer): defer preview font stylesheets (#7134) 2026-08-19 14:11:36 +00:00
lefarcenandGitHub a50a71b656 fix(viewer): stabilize retained HTML previews (#7128)
* fix(viewer): stabilize retained HTML previews

* fix(viewer): finish motion on direct slide seeks

* Revert "fix(viewer): finish motion on direct slide seeks"

This reverts commit 856eff1849.

* fix(viewer): keep direct slide seeks same-document

* test(viewer): keep remount cache assertion scoped

* fix(viewer): never mask rendered srcdoc previews

* fix(viewer): recover aborted blob previews immediately

* fix(viewer): retry aborted previews after reactivation

* fix(viewer): preserve keyboard-only deck state
2026-08-19 13:13:04 +00:00
open-design-crew[bot]GitHubClaude Opus 5elifive555555Eliopen-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
713caf0110 feat(web): ask the experience survey after a delivered artifact, and add an "other" answer (#7117)
* feat(web): add an "other" answer with a free-text field to the survey

The eight choices cover what users already tell us most often, but they cannot
cover what we have not heard yet — and the people whose problem is not on the
list are exactly the ones worth hearing from. Picking "Something else" opens a
text field instead of finishing the survey.

The cost is paid only by the people who need it: everyone whose answer is on
the list still finishes in one tap. This is the open-ended question we cut
earlier, put back behind a door rather than in everyone way.

Wire shape follows PostHog open-choice convention — the response recorded is
the text they typed. Picking "Something else" and typing nothing still reports
the choice itself, because "none of these fit" is an answer and dropping it
would turn those people into non-responders.

Skip disappears once the choice is made; it would only have been a second way
to submit.

* style(web): use a neutral focus border on the survey text field

The green ring competed with the brand fill on the selected chip directly
above it — two green things one under the other, neither reading as primary.
A neutral border marks focus without joining that fight, and the inset fill
still separates the field from the card.

* fix(web): keep the survey submit button on the footer right edge

Submit replaces Skip in the footer, but only Skip carried the margin-left:auto
that pushes it right — so the moment the text field opened, the primary action
collapsed back against the step counter and jumped from the right edge to the
left. Measured from the card edge, both now sit at the same 17px gap.

* feat(web): ask the experience survey after a delivered artifact, not an export

The post-export survey could only reach people who exported. Over 30 days
~13k users exported while ~37k produced an artifact, so roughly two thirds
of the people who got real work out of the product were never asked — and
the ones who tried it, got something, and left without exporting are the
ones worth hearing from most.

The card is now armed by a delivered design run (resolveDesignDeliveryOutcome
=== 'delivered'), from the second such delivery on. The first delivery
measures a first impression rather than an experience, and lands at the one
moment the user most wants to look at what they just got; waiting for the
second costs almost no reach because deliveries repeat (~13 per user per 30
days).

Because a delivery is usually mid-session rather than end-of-session, the
3s delay now bails out if the user starts writing their next prompt.
'beforeinput' rather than 'keydown' so IME composition counts as typing.
A dropped chance is taken by the next delivery, so the card lands after the
turn the user actually stopped on.

Export no longer arms anything; the three FileViewer call sites are gone.
Every survey event carries trigger: 'post_generation' so the two regimes
stay distinguishable in the events table.

* feat(web): arm the experience survey on the first delivered artifact

Drops the two-delivery threshold to one. Waiting for a second run bought a
more considered opinion at the cost of everyone who produces one artifact
and leaves — the users we understand least, and the ones this survey was
widened to reach in the first place.

Asking on the first delivery is affordable because the card is not an
interruption: the component still waits out the delay and still drops the
chance entirely if the user starts typing, so an early ask that lands badly
costs a dropped chance rather than a derailed session.

The delivery counter stays. It is the seam the policy is made of, and it is
what makes an unwritable store read as "not yet qualified" — a store that
cannot write cannot record a dismissal either, so without it the card would
come back after every run with no way to stop it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(web): retire the experience survey when it is shown, not when it is answered

One ask per user was the intent, but `retireSurvey()` only ran from `finish`
and `close`. Ignoring the card — the most common way to decline a prompt —
left the flag unset, so the card returned after the user's next artifact, and
the one after that. `exposedRef` did not cover this: it is a ref, so it resets
on the next page load.

Retiring on exposure makes the rule what it claims to be: a user sees this
card at most once, whatever they do with it. A chance dropped by the typing
bail-out is still retried, because a card the user never saw did not spend
their one ask.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(web): drop the typing bail-out from the experience survey

The card no longer cancels itself when the user starts typing during the
three-second delay. With one ask per user, a dropped chance is the whole ask
for anyone whose habit is to type straight into the next prompt — and that
habit belongs to the engaged users this survey most wants to hear from.

Removes the `beforeinput` listener and the IME reasoning that went with it.
The delay stays: it keeps the card from animating in on top of the artifact,
which was always its other job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(web): give the survey's free-text field an accessible name

Review finding from #7074, folded in with that branch. The textarea is
auto-focused the instant "Something else" is picked, so its accessible name
is the only thing telling a screen-reader user what the field is for — and it
had none. A placeholder is not a label and disappears on the first keystroke.

Reuses the placeholder string rather than adding a twentieth translation of
the same sentence, so the visible and announced names cannot drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: ensure PR CI

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
Co-authored-by: Eli <qiongyu1999@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
2026-08-19 10:35:34 +00:00
CaprikaandGitHub 13c3be557f fix(daemon): hide media models from chat picker (#7123) 2026-08-19 09:54:51 +00:00
lefarcenandGitHub 16fad1c4d8 revert(design-systems): remove structured runtime workflow (#7110)
* revert: remove structured design system runtime

* fix: preserve authority boundaries during revert
2026-08-19 05:37:48 +00:00
Ray XiandGitHub 12de3ed69d fix(desktop): preserve layered PPTX backgrounds (#6845)
* fix(desktop): preserve layered PPTX backgrounds

* fix(desktop): constrain layered PPTX rasterization

Keep unsupported gradients and static nested panels on their authored elements, and avoid duplicating the slide background shim when Chromium capture owns the layer.\n\nGenerated-By: looper 0.11.8 (runner=fixer, agent=codex)

* fix(desktop): preserve layered panel edge cases

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

* fix(desktop): preserve pseudo and masked PPTX layers

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

* fix(desktop): preserve composed PPTX background layers

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

* test(desktop): batch layered PPTX export probe

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

* test(desktop): consolidate layered PPTX probe slide

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

* test(desktop): terminate layered PPTX probe cleanly

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

* fix(desktop): preserve layered PPTX capture visibility and order

Filter Chromium capture targets to rendered slide intersections, and keep rasterized ::before backgrounds below native pseudo content. Extend the Electron export fixture and give its Linux subprocess enough concurrent-run budget.

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

* test(desktop): expose layered PPTX probe stage

Emit the active Electron renderer stage so CI-side subprocess timeouts identify the blocked boundary instead of returning an empty error.

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

* test(desktop): paint layered PPTX probe window

Mirror the production transparent showInactive lifecycle so requestAnimationFrame advances under Linux/Xvfb. Restore the original probe budgets and remove temporary stage diagnostics.

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

* fix(desktop): layer slide backgrounds below pseudos

Reserve explicit and captured slide-background sort slots below rasterized ::before layers so opaque slide fills cannot hide authored gradients. Cover the slide-root pattern with decoded PNG and PPTX media-order assertions.

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

* fix(desktop): keep layered PPTX captures background-only

Order positive-z ::after background captures below native pseudo content, and suppress replaced foreground objects only while Chromium captures their layered backgrounds.

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

* fix(desktop): flatten blended PPTX backgrounds

Preserve the authored backdrop while Chromium captures non-normal blend layers, including materialized pseudo-element backgrounds, and verify the resulting multiply-composited PPTX pixel.

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

* fix(desktop): preserve blended PPTX backdrops

Keep preceding sibling and ancestor pseudo backgrounds visible while Chromium flattens non-normal blend layers, without capturing their foreground content. Extend the real Electron export fixture with a layered pseudo backdrop pixel assertion.

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

* fix(desktop): preserve composited PPTX capture fidelity

Normalize the export DOM before Chromium measures layered backgrounds, flatten backdrop-filter dependencies, and preserve per-layer background blending for pseudo captures. Extend the Electron export probe with geometry and decoded-pixel regressions.

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

* test(desktop): tolerate Chromium color rounding

Allow a one-level RGB tolerance for backdrop-filter output so platform-specific Chromium quantization does not fail an otherwise faithful export.

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

* fix(desktop): preserve nested blend backdrops

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

* fix(desktop): honor paint order and pseudo masks

Use Chromium paint-stack hit testing for backdrop selection and preserve complete computed mask geometry on layered pseudos.

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

* fix(desktop): preserve layered PPTX compositing boundaries

Flatten nested layered backgrounds through a shared ancestor compositor, and materialize masked pseudo content and borders in the same Chromium capture while suppressing native duplicates.

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

* fix(desktop): preserve native PPTX foreground layers

Keep compositor foreground descendants editable while rasterizing only layered backgrounds, suppress captured pseudo helpers, and fully capture masked elements whose foreground cannot be serialized natively.

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

* fix(desktop): preserve PPTX backdrop and pseudo fills

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

* fix(desktop): preserve PPTX compositor root paint

Capture solid compositor-root backgrounds and ancestor backdrop effects with layered descendants, and suppress duplicate native fills for materialized pseudos.

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

* fix(desktop): capture composited pseudo foregrounds

Capture generated pseudo content and borders with layered mix-blend or backdrop-filter backgrounds so Chromium preserves their complete compositing before native pseudo suppression.

Add an Electron/PPTX pixel comparison against the authored Chromium rendering and verify no duplicate native pseudo text remains.

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

* test(desktop): normalize composited pseudo capture scale

Sample the Chromium reference at two CSS pixels regardless of host device pixel ratio so Linux/Xvfb and Retina runners compare equal-sized images.

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

* fix(desktop): preserve nested PPTX compositor paint

Capture intermediate compositor paint and real mix-blend foregrounds in Chromium while suppressing their duplicate native output.

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

* fix(desktop): preserve whole-paint PPTX captures

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

* fix(desktop): capture unsupported PPTX foreground effects

Capture text-clipped layers, unsupported ancestor-effect subtrees, and self-composited pseudos as whole Chromium paint so the editable exporter cannot emit mismatched native foregrounds.

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

* fix(desktop): preserve layered PPTX capture fidelity

Normalize Chromium capture scale by renderer DPR, capture text-clipped generated pseudos as complete paint, and retain pseudo box shadows in raster helpers.

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

* fix(desktop): preserve PPTX compositor roots

Capture slide-root effects with their layered foregrounds and use paint-aware full-overlap sampling for clipped blend backdrops.

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

* fix(desktop): bound layered PPTX paint groups

Preserve intermediate wrapper fills inside compositor captures so raster replacements keep authored paint order. Bound clipped-backdrop fallback work with stratified sampling instead of area-scaled hit testing.

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

* fix(desktop): preserve clipped PPTX blend backdrops

Replace the fixed paint-membership grid with a clip-aware Chromium paint-order probe so narrow masked or clipped backdrops cannot fall between sample centers. Cover the 1920x1080 DPR-2 case with a focused 1px stripe pixel comparison.

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

* fix(desktop): capture clipped PPTX foreground groups

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

* fix(desktop): recheck clipped PPTX backdrop order

Let clip and mask candidates reach the clip-free paint-order probe, and clear the first probe state before evaluating the second pass.

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

* test(desktop): map PPTX captures to exported media

Resolve each Chromium capture through its slide picture geometry and relationship, using pixel similarity only to disambiguate overlapping images.

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

* test(desktop): make PPTX probe scale DPR-aware

Match the Electron media probe capture scale to the production 2-physical-pixels-per-CSS-pixel formula across DPR 1 and DPR 2 renderers.

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

* fix(desktop): retain clipped PPTX backdrop order

Use full-coverage clip and mask probe values so Chromium paint-order sampling preserves the candidates' stacking contexts before authored styles are restored.\n\nGenerated-By: looper 0.11.8 (runner=fixer, agent=codex)

* fix(desktop): retain ancestor-clipped PPTX backdrops

Expand paint-order probes across standard and prefixed clip or mask ancestors, then keep the focused Chromium-to-PPTX stripe regression on that path.

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

* fix(desktop): preserve nested PPTX blend backdrops

Probe non-member compositor descendants against the actual backdrop-dependent capture members and keep selected backdrop paint visible during isolation.

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-19 03:27:51 +00:00
Ray XiandGitHub 0d53c96beb fix daemon HyperFrames scaffold and runtime (#7030)
* fix daemon HyperFrames scaffold and runtime

* fix CI snapshots and Nix hashes

* fix(pack): preserve HyperFrames sharp runtime

Copy the target-native sharp closure into final standalone payloads and execute the packaged HyperFrames CLI during afterPack.

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

* test(pack): avoid chmod on linked Node fixture

Only chmod the copied fallback so a successful hard link does not try to change the CI-owned Node inode.

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-19 03:16:46 +00:00
e711d112eb prioritize prototypes in Community filters (#6980)
* prioritize prototypes in Community filters

* test(web): select slides in modal mapping coverage

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

* fix(web): fall back to an available Community facet

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

* test(web): select slides before deck preview assertion

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-08-19 02:54:23 +00:00
lefarcenandGitHub 4eacda5751 fix(web): preserve deck thumbnail layout fidelity (#7095)
* fix(web): preserve deck thumbnail layout fidelity

* fix(web): detect ranged viewport media queries
2026-08-19 02:51:53 +00:00
lefarcenandGitHub b084ab075b fix(release): allow Chromium sandbox in prerelease E2E (#7090)
* fix(release): allow Chromium sandbox in prerelease E2E

* fix(web): cancel stale preview recovery probes
2026-08-18 18:05:52 +00:00
b800b91e39 feat(landing): add image generation showcase (#6904)
* feat(landing): add image generation showcase

* fix(landing): refine image prompt dialog controls

* fix(landing): simplify image generation hero actions

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-08-18 16:25:49 +00:00
lefarcenandGitHub 879236e614 fix(workspace): restore last selection after restart (#7067)
* fix(workspace): restore the last selection on restart

* test(workspace): cover persisted restart default

* fix(workspace): reuse directory authority for context reads

* test(workspace): align unknown seat invite behavior

* test(workspace): cover unknown seat invite fallback

* fix(workspace): preserve unknown invite capacity

* fix(workspace): commit selection after persistence

* fix(workspace): preserve unknown seat analytics

* fix(workspace): guard stale selection cleanup

* fix(workspace): make stale recovery atomic
2026-08-18 16:12:14 +00:00
Ray XiandGitHub aa5bb5d67a fix: persist public file shares across daemon restarts (#7002)
* fix: persist public file shares across restarts

* fix(daemon): redact unpersisted public snapshots

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

* fix(web): expose public share recovery action

Generated-By: looper 0.11.8 (runner=fixer, agent=codex)
2026-08-18 15:49:39 +00:00