Commit Graph
1314 Commits
Author SHA1 Message Date
lefarcenandGitHub cb5fd3baab fix(web): auto-fit wide html previews (#5751)
* fix(web): auto-fit wide html previews

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

* test(e2e): allow auto-fit preview zoom

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

* fix(web): gate preview auto-fit on content width

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

* fix: measure preview width through iframe bridge

* fix: bridge powered preview sizing
2026-07-17 06:16:04 +00:00
1b2eace97f fix(updater): harden update handoff flow (#5766)
Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
2026-07-17 03:04:19 +00:00
Nicholas-XiongandGitHub ba92a075b9 fix(web): portal deploy success toast above modal backdrop (#5326) (#5455)
The deploySavedToast was rendered inline inside the component tree, so it
ended up underneath the deploy modal backdrop (z-index 1700). Portal it to
document.body like the adjacent deployActionToast, and raise the placement-top
toast z-index from 1200 to 1800 so toasts always render above active modals.
2026-07-16 07:15:02 +00:00
mehmet turacandGitHub 8a1eb3deb8 Fix large HTML preview fallback (#5710)
* Fix large HTML preview fallback

* fix: refresh promoted large HTML previews
2026-07-16 07:14:33 +00:00
265aa51630 feat(web): make Studio discovery visual and inline (#5496)
* feat(web): make Studio discovery visual and inline

Move clarification forms into the chat flow, add artifact-aware visual style choices, and keep follow-up actions hidden until discovery is complete.

* feat(web): keep clarification entirely in chat

Remove the legacy Questions workspace tab, preserve discovery analytics in the inline form, and keep Skip, Submit, and answered summaries in one conversation flow.

* docs: split Studio Chat input and response work

Frame visual discovery as the input-side initiative and record response aggregation as a separately reviewed follow-up so the current PR stays bounded.

* docs: reorganize Studio Chat experience plans

Separate the overall journey, visual discovery, and response architecture so each scope can be reviewed and delivered independently.

* docs: define first-artifact satisfaction prompt

Limit the feedback prompt to the first successful previewable artifact so it gathers a useful signal without interrupting every iteration.

* docs: keep Studio Chat plans independent

Preserve the existing discovery proposal as one document and keep response architecture as a separate review surface without an extra master layer.

* fix(web): preserve inline discovery behavior

Keep visual direction single-select while restoring custom input, optional auto-continue, analytics, and current end-to-end coverage for the inline flow.

* fix(web): preserve files when skipping discovery step

* fix(web): exclude skipped file answers

* fix(web): align inline discovery with slim prompt

Preserve the latest discovery contract while keeping inline forms, uploads, drafts, and schema-driven visual selections consistent after the rebase.

* feat(web): add style catalog preview images

* fix(web): clarify discovery form completion

* fix(web): preserve question form context

* fix(web): time out unanswered question forms

* test(web): cover inline question form next steps

* fix(web): roll back partial inline uploads

* ci: trigger validation after rebase

* fix(web): unlock blocked inline question submits

* fix(web): retain previewless visual answers

* fix(web): load visual style previews from R2

* fix(web): clean blocked inline uploads

* test(web): expect R2 style preview assets

* feat(web): show full visual style catalog

* fix(web): keep inline question answers consistent

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-07-16 06:57:43 +00:00
e70f0eeefc fix(web): contain absolute tool paths to the project root when resolving touched files (#5352) (#5577)
attribution, restricted to agent-touched files resolved through
findTouchedProjectFile. Two residual gaps in that matching:

- A ROOT-level project file (candidate contains no '/') never
  suffix-matches an absolute event path like /workspace/index.html, so a
  mixed turn that rewrites index.html plus any resolvable file drops the
  rewritten HTML from the touched set and nothing auto-opens.
- An out-of-root absolute path sharing a tail with a project file
  (/tmp/site/index.html, or a '..' escape resolving outside the root)
  CAN suffix-match and mis-attribute an external write.

findTouchedProjectFile now takes the project's resolvedDir: absolute
paths are lexically normalized ('.'/'..') and must sit under the root,
in which case they match by their project-relative form; out-of-root
paths (including '..' escapes) are rejected before any suffix matching.
Paths whose '..' climbs above their own anchor are always rejected.
Callers without a usable root keep the legacy behavior.

Red on main / green here:
apps/web/tests/components/ProjectView.touched-path-containment.test.ts
(6 of 10 cases fail on main: both root-file misses and out-of-root
false positives).


Claude-Session: https://claude.ai/code/session_017peXvs7Q3sA5dhZ6vsYYwN

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 06:47:50 +00:00
lefarcenandGitHub c8bad6bcb4 fix(web): keep report-only Design runs off the ARTIFACT_NOT_FOUND path (#5724)
A successful Design turn with zero produced files is only a missing
deliverable when the run actually attempted to mutate project files.
Image-analysis and report-only audit turns answer in prose without ever
calling a write tool; classify them as report_only and let them finish
as normal text results instead of appending ARTIFACT_NOT_FOUND.
2026-07-16 06:40:05 +00:00
6e38058dcf feat(web): add agent-assisted home headline (#5699)
* feat(web): add time-aware home greeting

* feat(web): add contextual greeting emoji

* feat(web): use Agent-focused home headline

* fix(web): refine agent-focused home copy

* fix(web): keep German home copy informal

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-07-16 03:40:39 +00:00
Nicholas-XiongandGitHub a219a933eb fix(memory): forward BYOK chat model through /api/memory/extract (#5177)
* fix: forward BYOK chat model to memory extraction (#5162)

Root cause: Two issues preventing non-OpenAI BYOK providers (e.g.
MiniMax) from working with memory-llm extraction:

1. Web side (ProjectView.tsx): The BYOK chatProvider snapshot sent
   to /api/memory/extract was missing the `model` field. Without it,
   pickProvider() fell through to PROVIDER_DEFAULTS.openai.model
   ('gpt-4o-mini'), which doesn't exist on non-OpenAI endpoints.

2. Daemon route (memory.ts): The /api/memory/extract POST handler
   did not extract body.chatModel, unlike every other memory route.
   Even when the web side passes a model, it was silently dropped.

Changes:
- ProjectView.tsx: Add `model: byokOpenCodeProvider.model` to the
  BYOK chatProvider snapshot so pickProvider() can use the user's
  actual chat model (e.g. 'MiniMax-M3').
- routes/memory.ts: Read `body.chatModel` and forward it to
  extractWithLLM options, matching the pattern used by all other
  memory routes.

Testing: typecheck passes for apps/web (affected packages). The
daemon typecheck requires pre-built @open-design/release which is
a separate toolchain concern.

* fix(contracts): add model field to ByokChatProviderConfig for BYOK memory extraction (#5177)

The web-side memory snapshot was reading byokOpenCodeProvider.model but
the BYOK config contract had no model property, causing a compile-time
type error. Add an optional model field so the daemon can forward the
user's chat-model picker choice to memory extraction.

* fix(byok): copy config.model into byokOpenCodeProvider snapshot

Address review comment on PR #5177: byokOpenCodeProviderFromConfig()
did not copy config.model into the returned ByokChatProviderConfig,
so byokChatProvider.model serialized as undefined and /api/memory/extract
fell back to the hardcoded default (gpt-4o-mini) for OpenAI-compatible
BYOK endpoints like MiniMax. Threading config.model through ensures the
chat-selected model reaches memory extraction.

Reviewed-by: mrcfps (review comments on ProjectView.tsx:5049)

* test: add model field to BYOK payload expectations in web workspace tests

* fix(daemon): forward BYOK chatProvider to post-turn memory extraction

The daemon close handler creates memoryOptions for pickProvider() but
never forwarded the per-call BYOK provider snapshot ('byokProvider'
from the chat request body). This meant post-turn LLM memory extraction
always fell through to environment/default provider detection instead
of using the user's selected BYOK provider and endpoint.

Added 'chatProvider: byokProvider ?? null' to the memoryOptions object
so pickProvider() can run 'Same as chat' extraction against the user's
actual provider/config, matching the behavior of the pre-turn
/api/memory/extract path.

Addresses reviewer feedback on PR #5177 (fix #5162).

* fix(daemon): normalize byokProvider shape for post-turn memory extraction (#5177)

The close handler forwarded byokProvider ({ protocol, ... }) directly as
chatProvider, but memory-llm.pickProvider expects { provider, ... }
with provider being a PROVIDER_DEFAULTS key. Now we map protocol → provider
the same way the web pre-turn path does (ProjectView.tsx L5894).

* fix(daemon): support keyless BYOK endpoints in post-turn memory extraction (#5177)

Three-part fix for keyless BYOK (local vLLM / Ollama / openai-compatible servers with requiresApiKey=false):

1. server.ts normalization: thread requiresApiKey through to the memoryChatProvider snapshot so pickProvider() can distinguish keyless endpoints from missing-provider cases.

2. memory-llm pickProvider() BYOK gate: relax the apiKey guard so endpoints explicitly marked with requiresApiKey=false enter the BYOK branch instead of falling through to env/media-config fallbacks (which would route to unrelated OpenAI credentials / gpt-4o-mini).

3. memory-llm callOpenAI(): conditionally emit Authorization header only when apiKey is non-empty, preventing empty Bearer from being sent to keyless local servers that would reject it.
2026-07-15 12:20:24 +00:00
Ethan GuoandGitHub 05cb03c8ab fix(web): sandbox the speaker-notes presenter deck iframes (#5571) 2026-07-15 12:19:46 +00:00
c67048516c fix(web): polish preview status action (#5696)
* fix(web): polish preview status action

* fix(web): keep preview failure hint passive

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-07-15 12:09:48 +00:00
Chanhyo JungandGitHub de4ca325d3 fix(web): keep model lock affordances visible (#5533) 2026-07-15 12:08:39 +00:00
zhanghuiyingandGitHub a795322b63 feat: add SiliconFlow Global BYOK preset (#5108) 2026-07-15 11:15:06 +00:00
lefarcenandGitHub 24c7876b37 fix(web): preserve delivery for in-place HTML edits (#5689) 2026-07-15 10:48:26 +00:00
167db9de20 feat(web): add preview delivery status feedback (#5557)
* feat(web): add preview delivery status feedback

* fix(web): keep delivery retry in chat

* feat(web): stage preview delivery progress

* refine(web): clarify preview delivery stages

* fix(web): preserve preview delivery feedback

* fix(web): address preview delivery review

* refine(web): keep preview delivery status passive

* Revert "refine(web): keep preview delivery status passive"

This reverts commit df5579da48.

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-07-15 09:47:02 +00:00
Chanhyo JungandGitHub 27f8421851 fix(web): reset entry scroll on route changes (#5607) 2026-07-15 09:32:05 +00:00
lefarcenandGitHub e38f5d1c24 fix(web): stamp chunk_id on exception frames so PostHog can symbolicate (#5679)
* fix(web): stamp chunk_id on exception frames so PostHog can symbolicate

Every packaged front-end exception ships with minified frames (`?:?`,
mangled function names) even though tools-pack faithfully injects chunk
ids and uploads the browser sourcemaps to PostHog on every release. In
production, 0 frames ever carried a chunk id and 5,140 frames in 7 days
failed with "had no source url or chunk id".

Root cause: exception capture is owned by our own error-tracking.ts (it
runs before consent + before posthog-js loads, via a direct ingest
fetch), and client.ts sets `capture_exceptions: false`. The chunk id
that @posthog/cli bakes into each chunk is stamped onto frames by
posthog-js's native capture — which we bypass. Packaged chunks load over
the `od://` scheme, so PostHog cannot fetch a `.map` by URL either; the
chunk id is the ONLY correlation key, and we never attached it. Result:
the uploaded maps had nothing to match against.

Fix: error-tracking.ts now builds the same filename->chunkId map from
`globalThis._posthogChunkIds` that @posthog/core does, and stamps
`chunk_id` on each frame whose chunk registered one. Scrubbing preserves
it (spread + field override), and it is set before scrub, so PostHog can
correlate regardless of filename redaction.

* fix(web): key chunk id on the newest registration frame, not the outermost

PerishCode review: parseStack keeps Error.stack newest-first while
@posthog/core reverses to oldest-first, so copying its length-1..0 loop
selected the wrong end. For a multi-frame registration stack that keyed
the chunk id onto a webpack-runtime filename and left the real chunk
unmatched. Iterate from the newest frame (frames[0]) instead; regression
test now uses a multi-frame registration stack.
2026-07-15 08:10:16 +00:00
Nicholas-XiongandGitHub 21f25cde52 fix: hide native caret while composer placeholder carousel runs (#5522)
An empty, focused composer runs a placeholder typewriter carousel that
draws its own decorative caret trailing the animated text. The native
browser caret also blinks at offset 0, so two carets show at once
(#5373). Suppress the native caret while the carousel is mounted; it
reverts automatically the moment the field has text.

Keyed off the carousel being a sibling of the editor (:has(~ ...)), so
it covers both the chat composer (.composer-input-wrap) and the home
hero (.home-hero__prompt-editor) without depending on the wrapper class.
2026-07-15 07:52:50 +00:00
lefarcenandGitHub b5d9a12f48 fix(web): break redirect-loop scripts that freeze the HTML preview (#5469)
* fix(web): break redirect-loop scripts that freeze the HTML preview

A generated or hand-edited artifact carrying a self-redirecting directive
— most reliably a `<meta http-equiv="refresh">` that reloads the same
document, or a cycle of meta refreshes (A -> B -> A -> ...) — reloads the
preview iframe forever and pegs the main thread until the whole design
workspace freezes. There was no loop detection or hop limit, so the
preview stayed unusable.

Add an in-iframe circuit breaker (`injectPreviewRedirectGuard`, always
injected by `buildSrcdoc`) that counts meta-refresh hops in `window.name`
— the one store that survives an iframe navigating itself — resets the
count once a full window elapses with no further refresh (timeout
safeguard so a slow legitimate auto-refresh never accumulates), and once
the hop budget is exceeded, or immediately for a near-instant
self-refresh, strips the offending `<meta>`, stops the frame, and posts
`od:redirect-loop-blocked` to the host. The host parks the srcDoc iframe
on a static "loop detected" placeholder, the reliable stop the browser's
unforgeable `window.location` otherwise denies.

Route any self-redirecting source onto the srcDoc path
(`htmlNeedsRedirectGuard`) so the guard is always present instead of the
URL-load path serving the document raw and unguarded.

Covered by a runtime spec that runs the real injected guard through a VM
modelling a sandboxed iframe reloading itself (hop budget, window reset,
immediate self-refresh kill, chain-break, host message) plus render-mode
detection specs.

Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/94709b20-f403-4ed7-a960-1e26572fa394

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

* fix(web): cover srcdoc redirect guard gaps

* fix(web): avoid redirect guard false positives
2026-07-15 07:07:34 +00:00
PerishFireandGitHub be98224b88 fix(web): clarify silent-update copy, footer layout, and preference seed (#5631)
* fix(web): clarify silent-update copy and seed preference on prompt show

Rewrite the silent-update strings so they mean "install later on startup,
not this session", fix the updater footer so long en labels wrap without
pushing actions to a second row, and on prompt open idempotently seed the
default preference when unset (undefined → true; existing booleans are
only read). Checkbox toggles now persist immediately.

* fix(web): address silent-update PR review (hydrate, persist fail, copy)

Gate default preference seeding on daemonConfigLoaded so a saved false is
not overwritten while app-config is still hydrating. Await checkbox
persistence and revert with a visible error when the daemon write fails.
Rewrite silent-update strings to say "future in-app updates" so the
installer prompt no longer promises silent behavior for installer builds.

* test(web): wait for community default category selection under load

The community-filter decoupling test only waited for the deck pill to
mount, then asserted aria-selected immediately. Under full-suite CI
load the pill can paint before resolveDefaultSelection applies, which
flaked Web workspace tests and failed the Validate workspace gate.

* fix(web): harden silent-update seed and non-optimistic daemon write

- Clear seed in-flight via refs so parent setConfig mid-flight no longer
  cancels finally and leaves the checkbox permanently disabled.
- Write allowSilentUpdates only after /api/app-config succeeds, then
  update app config — failed saves cannot leave global state dirty.
- Gate seed on a successful daemon GET (daemonAppConfigReady), not merely
  bootstrap completion, so a failed fetch cannot overwrite an unread opt-out.

* fix(web): non-optimistic silent-update save from Settings About

Route Settings → About through handleSilentUpdatePreferenceChange and
keep allowSilentUpdates out of optimistic handleConfigPersist so a failed
daemon write cannot leave Settings (or global config) on the rejected
value. Autosave is suppressed for the dedicated toggle path.

* fix(web): align Settings silent-update callback type for typecheck

SettingsDialog requires Promise<void>; the test helper allowed void | Promise
and broke Preflight typecheck. Narrow the helper to Promise-only.

* fix(web): serialize silent-update preference writes against races

Queue concurrent allowSilentUpdates writes so an earlier request cannot
commit after a later toggle (Settings About on→off). Disable the Settings
checkbox while a save is in flight, and unit-test the writer with
out-of-order completion scenarios.

* fix(web): only advance silent-update field in Settings autosave baseline

On silent-update save success, patch allowSilentUpdates onto the previous
autosaveLastSavedRef instead of spreading the whole latest draft. Concurrent
edits (e.g. accent color) that land while the request is in flight must still
reach onPersist via the generic autosave loop.
2026-07-15 06:54:19 +00:00
lefarcenandGitHub 1278816a8f fix(web): route cross-project chat file links in-app instead of a home-page window (#5611)
* fix(web): route cross-project chat file links in-app instead of a home-page window

Chat file links pointing at @-referenced projects arrive as absolute
managed-projects disk paths or app file routes for another project.
asInProjectFilePath refused both, so the click fell through to the
default target="_blank" behavior and Electron's window-open handler
produced a detached child window whose unroutable path rendered the
home screen instead of the file.

resolveChatFileLink now classifies those hrefs and navigates to the
owning project's file route in the same window; any remaining
schemeless path-like href is swallowed instead of falling back to the
useless home window. Thinking-block markdown gets the same handler.

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

* worker: 当有文件链接在chatpane中出现时,点击文件链接唤起的是home页的预览

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

* fix(web): classify owning-project chat links before the basename fallback and keep the handler on opener-less surfaces

Review round on #5611:

- resolveChatFileLink now classifies hrefs that name their owning project
  (app file routes, managed-projects disk paths) BEFORE delegating to
  asInProjectFilePath, so a cross-project /…/projects/other/index.html can
  no longer be captured by a same-named file in the current project and
  silently open the wrong workspace tab. Legacy name-keyed directories
  (projects/<name>/ from 0.10.x previews) consequently navigate to that
  name instead of basename-matching — an in-window bounce at worst.

- chatFileLinkClickHandler is always installed; only the workspace-file
  open action is gated on onRequestOpenFile. Surfaces that mount the chat
  without a workspace opener (design-system chat in DesignSystemFlow)
  now still navigate cross-project targets and swallow unresolvable
  path-like hrefs instead of falling through to the detached home window.

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

* chore: drop the looper task attachment accidentally committed to the branch

.looper-attachments/ is runner-local reference material and must stay out
of git; a stray worker commit (d17c3cc) had checked the bug screenshot in.

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

* fix(web): restore the current-project fallback for ambiguous disk paths and narrow href swallowing to file-like links

Review round 2 on #5611:

- resolveChatFileLink resolves the current project (including the
  known-file basename fallback for absolute paths) BEFORE managed-projects
  disk-route navigation. A /projects/<seg>/ boundary cannot positively
  prove another project's ownership client-side: legacy 0.10.x preview
  dirs are keyed by project NAME and imported folders may contain their
  own projects/ directory — both reference current-project files, per the
  maintained e2e contract (e2e/ui/project-file-link-routing.test.ts,
  verified locally: passes again). Only paths no current-project file
  matches navigate to the owning project (the actual 0.14.1 scenario).

- isPathLikeChatHref only swallows confirmed file-like hrefs (final
  segment carries an extension) and never protocol-relative URLs, so SPA
  routes like /automations or /projects/<id> and //host/… links keep
  their default behavior instead of becoming dead links.
  extractManagedProjectsDiskRoute also refuses //host/… hrefs.

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

* fix(web): prove disk-link ownership from the project's resolvedDir instead of inferring from /projects/ boundaries

Review round 3 on #5611: an imported-folder workspace whose baseDir
itself contains a projects/ segment (e.g. /Users/x/workspace/projects/acme)
made resolveChatFileLink misroute just-written current-project files to a
phantom 'acme' project, because ownership was inferred from any
/projects/<seg>/ boundary once the known-file match failed.

Ownership is now decided only from positive proof against the daemon-
computed resolvedDir (GET /api/projects/:id), plumbed from ProjectView
through ChatPane into AssistantMessage's link handler:

- a href under resolvedDir is a current-project file even when absent
  from the (stale) file list — imported folders included;
- for managed projects resolvedDir reveals the projects root, so sibling
  paths provably belong to that other managed project and navigate there
  (the 0.14.1 @-reference scenario; proof also outranks the basename
  fallback on collisions);
- a /projects/<seg>/ boundary anywhere else proves nothing and never
  yields project-file — the generic extractManagedProjectsDiskRoute
  inference is removed; unproven absolute paths only resolve through the
  current-project known-file fallback (e2e contract re-verified locally).

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

* fix(web): thread projectResolvedDir into the side-chat and design-system ChatPane hosts

Review round 4 on #5611: the resolvedDir proof anchor only reached the
primary ProjectView ChatPane. The side-chat tab (FileWorkspace ->
SideChatTab) mounted ChatPane without projectResolvedDir OR
projectFileNames, and DesignSystemFlow without projectResolvedDir, so
absolute managed disk links stayed unclassifiable (inert) on those
production chat surfaces.

- SideChatTab accepts and forwards projectResolvedDir; FileWorkspace
  passes its existing resolvedDir prop plus a known-file set derived
  from the same files list ProjectView feeds its primary ChatPane.
- DesignSystemFlow fetches the workspace project's resolvedDir via
  getProjectDetail and threads it into its ChatPane.
- Host-level regression added above AssistantMessage: FileWorkspace ->
  SideChatTab -> real ChatPane -> real AssistantMessage, asserting a
  managed cross-project disk link navigates in-window and a stale
  current-project disk link opens as a workspace tab. SideChatTab
  prop-forwarding spec added alongside.

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

* fix(web): swallow unroutable chat hrefs by asking the router instead of an extension heuristic

Review round 5 on #5611: narrowing isPathLikeChatHref to extensioned
last segments reopened the detached home window for extensionless file
links (Dockerfile, /tmp/README, ../Makefile, bare binaries) — their
clicks fell back to target=_blank again.

Routability is now decided by parseRoute itself: its catch-all home
fallback IS the detached-home-window symptom, so exactly the schemeless
hrefs that would trigger it are swallowed. Recognized SPA routes
(/automations, /projects/<id>, /design-systems/<id>, ...) keep their
default behavior — including gated routes, since the router's own flag
handling decides. Relative hrefs reaching the check are always
unresolvable (traversals, malformed encodings) and stay inert.

Regressions added for Dockerfile/README/Makefile shapes at the helper
and click-handler level; the round-4 spec that locked in false for
/usr/local/bin/node is flipped to the corrected expectation.

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

* fix(web): harden the routability check against throwing parseRoute and the root-path fallback shape

Review round 6 on #5611: parseRoute() decodes route segments with a bare
decodeURIComponent, so a malformed same-origin href like /projects/%E0
made isPathLikeChatHref throw out of the click handler; and the root
path (/, /?q=1) shares the {kind:'home', view:'home'} shape with the
unknown-route catch-all, so a legitimate link to the app root was
swallowed.

isPathLikeChatHref now special-cases the root path as routable before
consulting the router and wraps parseRoute in try/catch — an href the
router would crash on is not a route and stays inert. Regression specs
cover malformed app-route shapes (helper + click level, asserting no
uncaught error) and the root path.

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

* fix(web): classify Windows drive-letter hrefs as disk paths instead of URI schemes

Review round 7 on #5611: C:\... and C:/... hrefs match the RFC 3986
single-letter-scheme grammar, so isPathLikeChatHref treated them as
external URLs and Windows disk links skipped the inert-link swallow
entirely, reopening the detached home window on that platform; the
resolvedDir proof also only compared POSIX shapes.

- isWindowsDrivePath classifies drive-letter paths BEFORE the generic
  scheme check: an unresolved one is always inert (the SPA router can
  never serve it).
- resolveAgainstResolvedDir now compares over normalizeDiskPath output
  (backslashes unified to '/', drive letter case-folded), so the
  current-project prefix proof and managed-root sibling navigation work
  against a Windows daemon's resolvedDir, including mixed-separator
  hrefs; the traversal guard runs on the normalized form.

Regressions cover Dockerfile-style drive-letter hrefs at the helper and
click level plus resolver proof cases for backslash and mixed-separator
shapes.

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

* fix(web): case-fold Windows disk-path proofs while preserving returned casing

Review round 8 on #5611: normalizeDiskPath only case-folded the drive
letter, so a valid mixed-case pair like C:\users\ME\...\project-1 vs a
resolvedDir of C:\Users\me\...\project-1 — the same location on a
case-insensitive filesystem — failed both prefix proofs and the click
was swallowed instead of opening the file or navigating.

resolveAgainstResolvedDir now runs its prefix/suffix proofs over
case-folded comparison keys for Windows drive-letter paths (POSIX stays
exact — Linux filesystems distinguish case), while projectId/filePath
are still sliced from the original normalized strings so their casing
survives into the returned target. Folding falls back to exact
comparison when lower-casing would change string length, keeping slice
offsets aligned for rare locale characters.

Coverage: mixed-case current-project and sibling-project proofs (red on
the previous head), original-casing preservation for the navigated
projectId, and a POSIX case-sensitivity guard.

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

* fix(web): open design-system chat file links via the Files tab and prove UNC-share ownership

Review round 9 on #5611:

- The design-system chat threaded projectResolvedDir but neither
  projectFileNames nor an opener, so its own workspace file links were
  classified as workspace-file and then swallowed (opener undefined).
  DesignSystemDetailView now feeds ChatPane a memoized known-file set
  and an opener that switches to the Files tab before handing
  requestWorkspaceFileOpen the file — the workspace consuming the open
  request only mounts on that tab. Host-level regression added in
  DesignSystemFlow.test.tsx.

- normalizeDiskPath only recognized drive-letter absolutes, so UNC
  share workspaces (\\server\share\...) never matched a UNC
  resolvedDir and their links stayed inert. isUncPath classifies the
  backslash form (forward-slash //host/... remains excluded as
  protocol-relative), normalization keeps the //server/... shape, and
  isWindowsDiskPath folds case for both Windows shapes in the proof
  keys. isPathLikeChatHref treats unresolvable UNC hrefs as inert
  explicitly. Resolver tests cover current-project and sibling-project
  UNC cases.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-15 04:59:37 +00:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
a1cbe0ec57 chore(release): bump main to 0.15.1 ahead of stable 0.15.0 (#5661)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-07-15 03:30:28 +00:00
098c5951ed fix(web): keep browser assist guidance in context (#5423)
* fix(web): keep browser assist guidance in context

Replace the global long-form toast with concise, light guidance beside the Browser download action so the workflow no longer obscures unrelated surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): contain snapshot toast in workspace

Anchor page-snapshot feedback to the file workspace so it cannot overlap chat, and quiet the close affordance until interaction.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): distinguish error and dismiss icons

Use an alert triangle for toast status so error feedback cannot be mistaken for the close control.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): quiet toast dismiss control

Vertically center the dismiss affordance and remove its bright surface so error feedback remains the visual focus.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): pin snapshot dismiss control to corner

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): remove snapshot success glyph

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: retrigger CI

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: retrigger CI

* test(web): align browser assist cleanup coverage

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(web): align snapshot error toast expectation

* test(web): wait for default community facet selection

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Amy <1184569493@qq.com>
2026-07-15 03:14:48 +00:00
Ray XiandGitHub 88c238ec78 fix(web): reveal rendered deck thumbnails (#5626) 2026-07-14 12:31:42 +00:00
lefarcenandGitHub d7463072a8 fix(web): Plan 模式生成/重写 HTML 后自动打开生成的文件 (#5602)
* fix(web): re-focus HTML rewritten in place when a turn ends

Plan mode's core loop is plan -> generate -> edit the plan -> regenerate.
On the second generation the deliverable (index.html) already exists, so
the turn-end auto-open — a pure pre/post file-NAME diff — saw no new file
and left the viewer stuck on the markdown plan. Protocols that emit no
Write tool events (codex, gemini, opencode, ACP agents) have no per-write
fallback, so nothing opened at all.

Turn-end selection now also considers pre-existing project files whose
mtime lands inside the turn window (same attribution as AssistantMessage's
inferProducedFilesFromTurn), ranked by the existing HTML > markdown
preview priority. Turns without a start stamp keep the produced-only
behavior.

Red spec: e2e/ui/real-daemon-run.test.ts "Plan mode regeneration re-opens
the existing generated HTML file" (red on main, green here), plus a
first-generation companion spec and unit coverage for the new selector.

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

* worker: Plan模式下,不会自动打开生成的HTML文件

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

* fix(web): scope the turn-window auto-open to agent-touched files and bound it by turn end

Review follow-ups on the Plan-mode auto-open selector: the mtime
attribution window [turnStartedAt - 1s, +inf) could attribute a file the
USER was editing to the turn - in Plan mode plan.md autosaves from the
split editor during a run, so a text-only turn could yank focus back to
it - and the missing upper bound let post-turn edits fall in-window on
the reload/reattach recovery call sites.

selectAutoOpenTurnArtifact now accepts:
- agentTouchedFileNames: when the protocol emits Write/Edit tool events
  (claude stream-json), mtime-window candidates are restricted to files
  the agent actually wrote; protocols with no write events (codex,
  gemini, opencode, ACP) pass an empty set and keep the pure time
  window - the case the window exists for.
- turnEndedAt: bounds the window at endedAt + 60s, matching
  AssistantMessage's inferProducedFilesFromTurn on both sides; callers
  without an end stamp keep the open-ended window.

All three call sites resolve the agent's raw tool paths to project file
names through the existing findTouchedProjectFile matching (new
resolveAgentTouchedFileNames helper) and pass their terminal timestamp.

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

* chore: untrack the looper task-attachment directory

The runner's fallback lifecycle commit (a838f8a) accidentally captured
.looper-attachments/bug15_1.png (1.2 MB task-reference screenshot).
Those files are task-local reference material and must stay out of git;
remove them from the tree without rewriting pushed history.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 11:58:53 +00:00
TuTouPowerandGitHub 3e5725e543 fix(web): unwrap false-positive question-form open tag in prose (#5493)
* fix(web): unwrap false-positive question-form open tag in prose

When the model mentions `<question-form>` inside backtick-quoted prose
before emitting the real form, splitOnQuestionForms matched the first
open tag against the real close tag. The body between them failed JSON
parse, the real form was swallowed as plain text, and findFirstQuestionForm
returned null. Reopening the banner then fell through to the manual/historical
path, which locked all inputs (interactive=false).

Fix: when a body fails to parse, check for another open tag inside it.
If found, unwind to that position so the real form gets a clean parse
on the next iteration.

* fix(web): avoid prose duplication when unwinding false-positive question-form tag

Slicing from cursor re-emitted prose that was already pushed before the
false-positive tag was parsed. Slice from openStart so only the text
between the false-positive open tag and the real one is emitted.

Also assert segment reconstruction equals the raw input.

* fix(web): unwind false-positive question-form tags in both parser branches

Two changes:

1. closeIdx === -1 branch (tag-name mismatch): prose mentioning
   <ask-question> when the real form uses <question-form> now unwinds
   to an inner open tag instead of swallowing the entire remainder.

2. Malformed-body branch: rename inner→nestedOpen, drop always-true
   resumeAt guard, and replace line-number comment with durable prose.

Test: tag-name mismatch fixture + reconstruction assertion.
2026-07-14 11:13:39 +00:00
lefarcenandGitHub 4988021896 fix: use baked previews for slide presets (#5630) 2026-07-14 11:05:06 +00:00
open-design-crew[bot]GitHubNJUHuaMasonopen-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>
4b660237c0 feat(prompts): land the slim system-prompt line as the default charter (#5603)
* test(daemon): freeze scenario x section gating matrix for composeSystemPrompt

composeSystemPrompt splices ~30 conditional sections whose gates form an
implicit matrix (session mode x design system x skill surface x memory x
execution profile). Before any prompt-slimming refactor, this golden
snapshot freezes which sections each representative scenario receives,
so a re-gating regression surfaces as a snapshot diff instead of a
silent production behavior change.

Detection is line-anchored heading markers: discovery quotes headings
like "## Active plugin" mid-sentence, so bare substring matching would
false-positive on sections that were never spliced in. A companion
assertion fails when a marker goes stale (no scenario produces it), and
position invariants pin injection-resistance first / role-marker guard
last across every scenario.

* perf(prompts): stop shipping rare-path media/deck payloads on every run

Langfuse telemetry (24h, 2k-trace sample) puts the composed charter at
p50 96KB (~24K tokens) with a 21% stable-cache hit rate — most turns
resend it whole. Two always-on blocks serve paths most runs never take:

- MEDIA_DISPATCH_HINT (2.8KB into every non-media project) embedded a
  40-line worked bash polling loop. The loop moves to 'od media help'
  (the CLI already prints the exact next wait command on handoff), and
  the hint keeps only the dispatch rules agents historically fumbled:
  no provider REST calls, no API-key asks, POSIX not PowerShell,
  python3 not jq, fal-ai/* passthrough. 2,771 -> 1,547 chars.

- The freeform maybe-deck branch injected the full 20.7KB deck
  framework into every kind=other / no-metadata run 'just in case'.
  New detectDeckIntentSignal() scans the same request text the agent
  will see (transcript included, so a deck mention anywhere in the
  conversation stays sticky) for generous EN/CJK deck vocabulary and
  skips the framework when nothing matches: 78.5K -> 57.7K chars on
  non-deck freeform runs. False positives fall back to today's
  behavior; deck-kind projects stay unconditional. An absent signal
  (contracts/BYOK composer, tests) preserves legacy always-inject.

Also relieves the POSIX argv budget: DS-active runs sat within 4KB of
the 100KB hard cap that fails argv-bound adapters (aider/deepseek).

Baseline check: tests/media/policy-routes.test.ts (7) and
tests/connection-test.test.ts (1, local Kimi CLI version) fail
identically on the unmodified tree — no new failures from this change.

* test(daemon): refresh golden matrix snapshot for upstream memory prompt hardening

Rebase onto main picked up #4913, which reworded the memory
rule-proposal block (+230 chars in memory-bearing scenarios). Section
gating is unchanged — size-only refresh.

* feat(prompts): rewritten slim core charter behind OD_PROMPT_CORE=slim

The always-on doctrine layer (DISCOVERY_AND_PHILOSOPHY ~28K chars +
designer charter ~14K + three tail overrides that re-assert rules the
layered stack states in softer or contradictory forms) is rewritten as
ONE ~15.5K-char document: core-slim.ts. Same composed inputs drop from
65.7K to 36.0K chars (-45%); freeform non-deck runs from 57.7K to 28.9K.

Design rules of the rewrite:
- Every rule stated exactly once, under an explicit precedence ladder
  (user request > active skill > design system > memory > charter).
- Contradictions resolved instead of stacked: one verification budget
  (static pass + P0 checklist + craft scan + at most ONE render; the
  'two passes is normal' re-score loop is gone), one turn-1 form
  decision path (four skip conditions listed in one place, including
  the memory task-brief replacement), Tweaks reconciled (iteration
  surface on request, never demo controls in product files).
- Protocol markers are frozen API and survive verbatim: question-form
  shapes/ids, pick_direction/brand_spec/reference_match branch values,
  task-type routing options, data-od-id, EDITMODE markers, pinned
  React/Babel/Motion tags, artifact handoff shapes.
- Emphasis quota: no IMPORTANT/CRITICAL inflation; imperatives carry
  the weight.

Rollout: ComposeInput.promptCoreVariant ('classic' default | 'slim'),
daemon flips via OD_PROMPT_CORE=slim — zero behavior change until set,
A/B-able per daemon. Ask mode keeps the clarifying-questions tail (it
composes no core charter). Guards in tests/prompts/core-slim.test.ts:
a 16KB byte budget that fails any silent regrowth, frozen-marker
assertions, handoff profile switch, and classic-parity checks; matrix
snapshot gains slim scenarios.

Not yet covered (follow-ups): the packages/contracts BYOK composer
still ships only the classic stack; instruction-following A/B against
classic is the sign-off gate before changing the default.

* feat(prompts): shrink the slim core to an 8KB budget by evicting what a system prompt shouldn't carry

The 15.5K v1 slim charter still carried content that has no claim to an
always-on seat. v2 evicts it by category:

- Already owned elsewhere: the od-default task-type router form ships in
  plugins/_official/scenarios/od-default/SKILL.md and reaches the prompt
  as '## Active skill'; the charter now just defers to skill-owned
  turn-1 forms. An ownership test pins the form to the skill file.
- Conditional-path content: per-platform delivery contracts (breakpoint
  matrix, per-target files, native chrome) move to
  PLATFORM_CONTRACTS_BLOCK, injected only when the project declares a
  platform or multiple targets. Deck rules were already duplicated by
  the deck-gated directive - dropped from the charter entirely.
- Model-competence content: PDF extraction how-tos, attached-image
  explanations, JSON syntax reminders - deleted.
- Recital: the default discovery form example keeps only the brand
  question verbatim (its pick_direction/brand_spec/reference_match
  values are the one protocol-matched part); the other six questions
  become a one-line recipe. Option labels were always model-localized
  per language, so exact English menus were never stable anyway.
- React pins keep versions (the actual bug fix: 18.3.1 pairing +
  framer-motion React build) but drop the SRI hashes and script-tag
  boilerplate - reviewers should weigh the integrity-attribute loss.

Charter now renders at 8,187 bytes (budget test tightened 16KB -> 8KB).
Composed prompt for the common design path: 65.7K classic -> 29.6K slim
(-55%); freeform non-deck: 57.7K -> 21.5K (-63%).

* perf(prompts): evict the remaining always-on payloads from the slim stack

Follow-up to the 8KB charter: the composed slim prompt still carried
~13K of non-charter payload. Four evictions, all slim-only (classic
stays byte-stable for the A/B):

- Project metadata becomes a fact sheet ('facts' style). The classic
  block had grown seven doctrine rules plus a responsive contract and
  cross-platform rule - prose owned by the slim charter's Craft section
  and PLATFORM_CONTRACTS_BLOCK. facts keeps key-value fields, media
  workflow data, and two compact delivery lines (screen files, product
  depth). kind=other: 3.6K -> ~1.1K.
- Memory two-loop scaffolding compressed ~5.5K -> ~2.9K: identical
  headings and od-card shapes (web parses the card types; the daemon
  checks the verify-scorecard), repeated rationale prose cut.
- zh-CN locale override drops the worked quick-brief sample copy under
  slim - it mirrored the classic form verbatim, which slim recipes
  instead; the form contract already mandates localization. 2K -> 0.85K.
- MEDIA_DISPATCH_HINT gates on detectMediaIntentSignal (same
  transcript-sticky pattern as the deck signal): most conversations
  never mention media, and the hint returns for the rest of the session
  the moment one does.
- Direction library becomes a pull layer: slim composes a ~1.3K
  id+label index and the agent fetches the chosen direction's full
  palette/posture spec via the new 'od tools directions --id <id>'
  CLI (also --label / --json / bare list). One tool call on the
  direction-pick path, which runs at most once per project, replaces
  6.7K of always-on palette data. Classic keeps the inline library.

Composed slim sizes: freeform non-deck 21.5K -> 12.6K; design full
stack 29.6K -> 22.2K (fixture DS; media hint drops another 1.4K on
non-media conversations). Baseline classic: 57.7K / 65.7K.

* refactor(prompts): restructure the slim prompt as static-first layers for cache and hierarchy

Two structural defects in the slim composition, both flagged against
prompt best-practice guidance:

1. Heading hierarchy was inverted: the document opened with an H2
   ('## Security: prompt injection resistance') and the H1 charter
   appeared six sections later. The security section now lives INSIDE
   the charter as an H2 right after Precedence (single source moved to
   core-slim.ts; the classic stack imports it for its unchanged
   standalone-first position), so the composed document reads
   identity-first with a coherent H1 > H2 tree.

2. Ordering was cache-hostile: conditional blocks (mode override,
   locale, direction index, platform contracts) preceded the static
   charter — the exact 'conditional prefix sections' anti-pattern from
   the prompt-caching guidance, where every flag combination becomes a
   distinct prefix and nothing is shared. Slim now composes in
   variability tiers:

     static charter (identical across all runs — shared cacheable
     prefix) -> conversation-stable (mode, locale) -> project-stable
     (direction index, frames, platform, memory, instructions, DS,
     skill, metadata, MCP) -> turn-variable (maybe-deck and media-hint
     signal blocks, deferred to after MCP so a mid-conversation signal
     flip only invalidates the suffix) -> recency-pinned role guard.

   The charter's Precedence section now states that a session-mode
   directive appearing after it takes precedence, replacing the
   positional 'read first' authority the old ordering relied on.

Classic composition is byte-untouched (A/B baseline); ask mode keeps
the classic head. Charter budget 8KB -> 9.25KB (absorbed the ~1KB
security section; composed totals unchanged: freeform 12.7K,
full-stack 22.4K). New layered-ordering test pins the tier sequence
with line-anchored markers; the matrix position invariant now asserts
the per-variant head.

* fix(prompts): resolve five structure/expression defects from the charter review

Findings from the senior-review pass, each with a concrete failure mode:

1. Turn-ordinal section names misfire mid-session. 'Turn 1/2/3+' made
   the model treat 'now is not turn 1' as license to skip the fresh-
   brief flow when a NEW artifact is requested at turn 8 — and,
   conversely, an event-based fix alone would have re-asked questions
   the session already answered. Sections are now event-named ('On a
   fresh brief' / 'When the brand answer arrives' / 'Once direction is
   locked') AND a session-inheritance rule makes the mid-session case
   explicit: a fresh brief inherits the conversation's locked
   direction/brand/audience, asks only genuinely new unknowns, and
   builds directly when there are none — matching current production
   behavior with zero extra rounds or cache cost.

2. The handoff rule — the single highest-cost-if-violated output
   protocol (never emit <artifact> in filesystem runs) — was the only
   rule block without a heading. Promoted to '## Handoff'.

3. The form example read backwards: it showed the LAST question
   (brand) and described the earlier six in reverse ('preceded
   in-form by'), the only place in the prompt requiring the model to
   assemble structure in reverse. Now forward-order: output question
   shown in full, brand question shown in full (its values are the
   protocol-matched part), the middle questions listed as
   insert-between recipes.

4. Dangling reference: Craft said 'single family only for
   tech/utility' — a direction name defined only in the Direction
   index, which is absent whenever a design system is active.
   Reworded direction-independent.

5. direction-cards was dead vocabulary: listed as a legal control but
   every mention forbade it. Its one legitimate path is now named —
   only when the user explicitly asks to see direction options.

Charter budget 9.25KB -> 10KB: fix #3 cost ~0.7KB (restoring the full
output-question JSON), accepted deliberately — it also restores
few-shot density for weaker fleet models, and the review's own
conclusion was that the last byte-squeezing round had negative ROI.

* fix(prompts): identity-first head for slim ask mode

The last remaining hierarchy inversion: slim ask still opened with the
H2 security block before the H1 ask override. Ask now matches design
mode's structure — the ask override (the turn's whole charter) opens
the document and the security section reads as its first subsection.
Both blocks are static, so the swap is cache-neutral. Classic remains
byte-untouched.

* fix(prompts): repair five slim-vs-classic regressions from the second audit

Second senior review against the shipping classic stack surfaced two real
bugs and four wrongly-compressed classic behaviors in the slim variant:

- text_artifact/BYOK runs got the direction INDEX plus an instruction to
  run the `od tools directions` CLI they do not have; classic inlined the
  palettes. Slim now profile-switches: filesystem keeps the 1.3K pull
  index, text_artifact gets the full inline library back (+1.35K tokens
  on that path only; still ~68% below classic, and now under deepseek's
  30KB Windows argv cap, which classic never was).
- API_MODE_OVERRIDE's literal scope ('every LATER instruction … is
  overridden') inverted when slim moved the charter ahead of it: the
  charter's TodoWrite/render-check instructions escaped the override.
  Plain-stream slim runs now compose the override first again. Pure
  reorder, zero bytes.
- PLATFORM_CONTRACTS_BLOCK gated only on project metadata, missing
  freeform briefs that name a platform in text ('做个 iOS app 原型').
  New detectPlatformIntentSignal (same generous EN/CJK shape as the
  deck/media signals) ORs into the gate; server wires it.
- media-contract's 'explicit layer overrides' paragraph cross-referenced
  the classic-only layer names; reworded layer-agnostic (classic +19B).
- Restored four classic rules the compression pass misfiled as
  model-competence filler: never hot-link user-attached images by URL
  (product constraint), skill/DS per-domain precedence clause, expressive
  form-control guidance, and the modern-CSS encouragement line. Charter
  budget 10,240 -> 10,752 with rationale in the test comment.

Classic bytes untouched except the shared media-contract rewording.
Golden matrix: gating unchanged; sizes only (classic +19, slim +537).

Pre-existing failures unrelated to this change and failing on the
unmodified baseline: tests/media/policy-routes.test.ts (7), Kimi
connection test (local CLI version mismatch).

* fix(prompts): defer signal-triggered platform contracts to the turn-variable suffix

Two follow-ups from the third structure audit:

- PLATFORM_CONTRACTS_BLOCK sat in the project-stable zone while its gate
  now includes the turn-variable conversation-text signal. A mid-session
  'make it an iOS app' would insert the block early and break the cached
  prefix for every section after it (DS, skill, memory, metadata, MCP).
  The gate is now split by trigger stability: metadata triggers keep the
  early slot (fixed at project creation), signal-only triggers defer to
  slimTurnVariableParts like the deck/media signal blocks. The
  slimTurnVariableParts declaration hoists above the design section so
  the gate can reach it; no byte or ordering change for any existing
  gating combination.

- Slim ask mode on a plain stream composed CHAT -> injection -> API
  override, inverting classic's authority order (API before CHAT, so
  'overrides every rule below' covers the chat charter). The ask head
  now leads with the API override on plain streams; the later push is
  gated on !isSlimCore. Zero-byte reorder; the two overrides do not
  conflict in content, this restores parity with the shipping order.

* fix(prompts): restore form-tailoring mandate and first-message form scope

The slim-vs-classic blind A/B eval (2026-07-04, 17 pairs, double-order
opus judging) surfaced two form regressions with converging judge
rationales:

- Form quality lost 12:1:4 to classic. Mechanism: classic's discovery
  layer carries extensive per-brief form-tailoring guidance; slim
  compressed it to the single word 'tailor'. Models shipped the default
  recipe verbatim — app-platform checkboxes on landing-page briefs, the
  fundraising ask/traction buried in one textarea placeholder. Restored
  as an explicit mandate with concrete per-brief examples, a
  plausible-targets-only rule for the platform options, and a
  translate-the-labels note on the tone recipe.
- DS + detailed-brief projects skipped the turn-1 form 3/6 vs classic
  1/6: the mid-session inheritance clause generalized to first
  messages. Added the explicit scope limit: inheritance never applies
  to a project's first message.

Charter budget 10,752 -> 11,264 with the eval citation in the test
comment. Golden matrix: sizes only.

* fix(prompts): template tokens in the seed must be replaced before handoff

The 40-task x 5-turn A/B eval (2026-07-05) found slim's remaining T2
quality gap is driven by catastrophic failures, not taste: 3 of 39
pairs shipped the live-artifact skill seed with unrendered {{mustache}}
tokens (classic did this once). Mechanism: slim compressed classic's
seed-handling guidance to 'copy the seed, paste layouts', losing the
explicit fill-with-real-content step.

- Read-before-write bullet now ends with: replace EVERY template token
  with real content; a {{placeholder}} or empty section reaching the
  user is a failed delivery. (Also covers the eval's 4 thin-content
  failures.)
- Self-check static pass adds 'leftover template tokens' to its scan.

Charter 11,155B, within the existing 11,264 budget — no bump needed.

* fix(prompts): first build ships complete, no stub sections

Clean-scope T2 analysis of the 40-task eval (excluding the 10 tasks
confounded by the live-artifact skill protocol) still shows a first
build completeness gap: main 7.34 vs slim 6.10, driven by thin-content
deliveries (empty canvases, placeholder-looking blocks) on 4 of 29
pairs. The charter's 'show something visible early' line, uncushioned,
reads as license to hand off the early skeleton.

- Qualify it: '…but the turn ships the COMPLETE artifact: no stub
  sections.'
- Self-check static pass adds 'empty or stub sections' to its scan.

11,215B, within the 11,264 budget.

* refactor(prompts): split the overloaded turn-1 form section by concern

The slim charter's '## On a fresh brief' H2 bundled six distinct
concerns under one heading — trigger, default action, mid-session
inheritance, skip conditions, form-shape tailoring, and the form JSON
contract — so a maintainer could not scan the outline to find any one
rule, and the cross-cutting form contract (valid on ANY turn) was buried
under a heading scoped to fresh briefs.

Behavior-equivalent restructure (every directive's wording preserved;
only headings added and the form contract's 'any turn' note moved from a
parenthetical to the section lead):

- ## Turn 1 — the discovery form        (trigger + default action)
  - ### When to skip or inherit the form (inheritance + skip conditions)
- ## Writing a `<question-form>`         (cross-cutting: shape + tailoring + example + ordering)
  - ### Form contract                     (JSON validity + type enum + localization)

The charter outline now reads as a per-concern document. Frozen protocol
markers (form JSON, brand values, type enum, allowCustom) unchanged.
Charter 11,242 -> 11,322B; budget raised to 12,288 for readability
headroom at the maintainer's direction (budget explicitly deprioritized
for this change). Golden snapshot regenerated; 113 prompt tests green.

* refactor(prompts): readability second pass + multi-turn DS/constraint adherence

Two changes, from a critical re-read plus production feedback.

A) Readability / logic second pass (behavior-equivalent, wording preserved):
- Heading style: the two remaining imperative-tail H2s became event
  labels with the directive moved to the body lead ('## When the brand
  answer arrives', '## Once direction is locked'), so the outline reads in
  one consistent grammar. Dropping '…hand off' also fixes a heading that
  promised handoff while the rules live in the next section.
- The self-check bullet's inline (1)(2)(3)(4) run-on became a four-item
  sub-list — the least-scannable line is now the most scannable.
- The 70-word 'Read before you write' sentence split into shorter units;
  'show early / ship complete' separated into its own bullet so the two
  ideas stop reading as contradictory.
- Precedence: skill and DS collapsed into one rank split by domain (was
  numbered 2 vs 3, contradicting the 'each highest in its own domain'
  clause) — removes the only real logical seam in the ladder.

B) Multi-turn edit adherence (new '## On an edit or tweak' section):
  Production feedback reports the design system and explicit user
  constraints frequently drift during multi-turn edits. The charter was
  turn-1/first-build heavy and had no standing edit-mode invariant. Added,
  in the lifecycle between build and handoff:
  - an edit touches ONLY what the user named; untouched sections/values
    stay as they were; re-read the file and edit in place.
  - the design system binds on EVERY turn, not just first build — no
    drift, no raw hex, no palette re-pick because this turn was about
    something else.
  - every hard constraint stated this session persists across later turns
    until the user relaxes it; a new request adds to constraints, never
    silently resets them.

Charter 11,322 -> 12,029B, within the 12,288 budget (no raise). New
frozen assertion pins the adherence invariants against future
compression. 114 prompt tests + guard + typecheck green; golden snapshot
regenerated.

* refactor(prompts): two-tier charter — lifecycle spine, grouped pillars, precedence precision

From a senior word-by-word re-read. The charter was 13 flat H2s spanning
three conceptual tiers (foundations / lifecycle / reference) with no
visible spine, and the precedence wording could invert authority.

Structure (behavior-equivalent, rule wording preserved):
- 13 flat H2 -> 5 H2: two foundations (Precedence, Security) + three
  pillars, each grouping its H3s —
    ## Discovery — ask before you build   (Turn 1, skip/inherit, writing, contract)
    ## Delivery — brand → build → iterate  (brand answer, build, edit, handoff)
    ## Craft & contracts                    (craft, technical, conduct)
  The delivery lifecycle is now one connected, ordered spine — 'Editing
  an existing artifact' reads as a named phase of it, which should help
  the multi-turn drift the edit-adherence rules target.

Precision fixes:
- Precedence: 'On conflict, higher wins: 1…2…3…4' was ambiguous (which
  number is higher?) — reworded to an explicit list, 'the one higher on
  this list wins — the user's request is the highest authority, this
  charter the lowest', and the trailing pronoun tightened to 'overrides
  it wherever the two conflict'.
- 'no tool calls or file reads first' -> 'before it'; 'Resolve the
  source' -> 'Resolve the brand source'; 'restyle the whole thing' ->
  'restyle beyond the request'.
- Trimmed one of three localization restatements (kept the authoritative
  one in Form contract, now naming labels/options/placeholders) and the
  'however complete the brief reads' duplicate.

Handoff/Craft/Technical/Conduct headings demote to H3 under their
pillars (local constants, not shared with classic). Charter 12,029 ->
12,294B; budget 12,288 -> 13,312. Ordering/adherence test anchors and
golden snapshot updated. 114 tests + guard + typecheck green.

* fix(prompts): make edit-adherence forceful — do exactly what was asked, verify it landed

Production feedback and maintainer review: the edit section addressed
only scope (don't over-change) and persistence (DS/constraints), but not
the core instruction-following failure — 'the user asked for A and the
result isn't A'. The whole section was defensive; the positive imperative
that the requested change must actually happen, completely, was missing.

Strengthened '### Editing an existing artifact':
- Leads with the imperative: the delivered file must actually be A — do
  exactly what was asked, in full, in every place it applies, with
  complete-vs-partial examples ('dark green' = every element, not one).
- Forbids reinterpreting, 'improving on', partially applying, or
  substituting own taste for the user's literal words — ties to
  Precedence #1 (user's request is highest authority).
- New 'Verify before you hand off' bullet: open the file, confirm the
  change is actually present and complete and no in-force constraint
  broke; never report a change you did not make (the claim-vs-reality
  failure).
- Kept the scope / DS-binding / constraint-persistence bullets.

Charter 12,294 -> 13,122B, within the 13,312 budget. Frozen assertion
updated to pin the positive imperative and the verify bullet.

* fix(prompts): a later explicit request overrides a conflicting earlier constraint

Edge case raised in review: turn 2 'fix the color to blue, don't change
it', turn 4 'change everything to yellow'. Correct behavior is turn 4
wins (the user is changing their own mind; yellow becomes the new
standing constraint), which Precedence #1 + 'persist until the user
relaxes it' already imply — but the old bullet also said 'a new request
ADDS to those constraints', which a literal model could read as
additive-only and stall on the turn-2 'don't touch it'.

Rewrote the constraint-persistence bullet to make the override explicit:
constraints carry forward, only the user can lift or change one, and a
later explicit request overrides a conflicting earlier constraint — with
the blue->yellow turns as the in-prompt example. The anti-silent-drop
guard (model must never drop a standing constraint on its own) is kept.

Charter 13,122 -> 13,412B; budget 13,312 -> 14,336.

* fix(prompts): make the plan step agent-agnostic — TodoWrite is Claude-only

Open Design drives many local code agents; the real session mix is ~65%
non-Claude (codex, opencode, gemini, ACP family, …). The slim charter's
plan step hardcoded 'first tool call is TodoWrite' + in_progress/completed
— a Claude Code tool those agents don't have. For ~2/3 of traffic that
instruction is dead: the agent either skips planning (losing the exact
lever that helps multi-turn adherence) or hallucinates a tool it lacks.

Everything else in the slim charter was already agent-agnostic (render
checks and direction pulls go through the shell OD CLI wrapper; protocol
markup is assistant text), so TodoWrite was the one hardcoding.

- Charter plan step → generic: 'lay out a short, updatable plan; if your
  runtime has a structured plan/todo/task tool, use it, else a numbered
  list; never call a tool you don't have.'
- Composer injects a concrete note ('Your plan tool is TodoWrite; the
  host renders it as a live Todos card; mark in_progress/completed') only
  for Claude-family runs (streamFormat claude-stream-json: claude,
  codebuddy, amp) — the only agents with TodoWrite and the host's live
  Todos card. Agent-specifics live in the composer, portable doctrine in
  the charter.
- API_MODE_OVERRIDE reframed from 'TodoWrite/Read/… are unavailable' to
  'any tool call — TodoWrite/…/or whatever your runtime exposes — will
  not execute'. Keeps the specific names (issue #313 anti-pseudo-tool,
  test-pinned) while covering non-Claude runtimes.

Two frozen tests pin: charter has no TodoWrite + generic wording;
Claude-family gets the note, json-event-stream doesn't. 116 tests +
guard + typecheck green. Charter 13412 to 13543B (budget 14336).

* feat(prompts,live-artifacts): render one-level repeats, tighten slim charter craft guards

- live-artifacts/render: implement the documented `data-od-repeat="item in data.path"`
  one-level repeat directive (specified in the schema but never rendered), with
  loop-scoped `{{item.*}}` bindings, single-pass expansion so data values are never
  re-interpolated, and explicit rejection of nested repeats / non-array sources.
- live-artifact SKILL.md: state the html_template_v1 binding contract in the workflow
  (flat data.* scalars + data-od-repeat lists; no hand-loops, no bare loop vars) so
  templates stay within the renderer's capability.
- core-slim charter: "replace every template token" now yields to a skill's
  template-plus-data protocol; add an objective Layout-integrity craft rule (no
  overlap, no clipping, oversized display type must fit its column, charts use fills
  not outlines) and name those failure modes in the self-check.

* feat(prompts): add agent-agnostic production-value craft rule to slim charter

Visual-first pages regressed on slim: hero images were hand-drawn CSS boxes
instead of generated art (P1), and controls read flat/greyscale (P5). The slim
charter had dropped all of main's imagery + visual-system guidance.

Add one "Production value — feel shipped, not greyscale" craft rule: generate a
real image via the Open Design media tool (referenced through the injected
`"$OD_NODE_BIN" "$OD_BIN"` wrapper, so it is runtime-agnostic — not tied to any
one code agent) when a hero/product shot would carry the page; ship a real
product palette, colored interaction states, and elevated primary controls.

Bump SLIM_CORE_BYTE_BUDGET 14_336 -> 15_360 to seat this load-bearing guidance.

* feat(prompts): generalize the production-value imagery rule beyond hero shots

40-task eval + user review (C3 tea gift-box, P1) showed slim under-generates real
imagery on visual-first work, defaulting to hand-drawn schematic graphics. Reframe
the rule from a narrow "hero/product shot" trigger to a general default: whenever a
real picture would lift the artifact (product, place, food, person, hero, texture),
generate it via the media tool; drop to a diagram/UI mock only when that genuinely
serves the content better. Still agent-agnostic (media tool via the injected
"$OD_NODE_BIN" "$OD_BIN" wrapper).

* fix(prompts): let the production-value rule use the run's own image generation

Hard-pointing imagery at `od media generate` only works when a daemon spawned the
agent (it injects OD_PROJECT_ID); a standalone agent (eval harness, external
runtime) can't reach it, so slim silently fell back to hand-drawn placeholders
while codex-run peers used their native imagegen. Broaden the rule: generate through
whatever image generation the run offers — the Open Design media tool when wired up,
otherwise the runtime's own native image generation. Verified: C3 slim now emits a
real generated product photo instead of a schematic box.

* test(daemon): drop connected-external-mcp from the golden matrix

Upstream moved the connected-external-MCP directive out of
composeSystemPrompt into the per-turn slice (live OAuth token state must
stay out of the cached stable prefix), so it is no longer a compose
input or a gateable section.

* chore(prompts): default to the slim charter on the validation branch

VALIDATION-ONLY commit for feat/system-prompt: packaged beta builds must
exercise the rewritten slim charter, and the packaged sidecar env
allowlist does not forward OD_PROMPT_CORE, so the default flips to slim
here. OD_PROMPT_CORE=classic restores the classic stack. Do not carry
this default into main.

* perf(prompts): batch slim tool work, with quality guards restored (#5555)

* perf(prompts): batch slim tool work

* fix(prompts): restore seed-copy rule and unconditional own-browser ban

The tool-economy pass (02923153a) dropped two quality instructions as
collateral while compressing the read/preview bullets:

- 'Copy the seed and paste its layouts — don't write CSS from scratch'
  guards the visual floor when a skill ships a seed template.
- The hard 'never your own browser' ban was softened to 'no browser
  probes first', which let the candidate arm reach for Playwright after
  an export failure (2026-07-13 slim-tool-economy eval, v1_001 turn 3).

Restore both inside the batched tool-economy wording, raise the charter
byte budget 15_360 -> 15_616 following the existing restore-precedent,
and freeze both lines as regression markers.

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>

* feat(prompts): prefill question forms with a recommended default per question (#5556)

Every <question-form> the agent emits now ships a brief-inferred
recommended `default` on each question — an option value for
radio/select, an array of values for checkbox, concrete text for
free-text fields — so the user can review and submit the form unchanged
instead of answering from scratch. Applied across the slim charter form
contract, the full discovery rules, and the od-default task-type router
(whose options stay verbatim; prefill is the one permitted tailoring).
Both prompt mirrors (daemon + contracts) updated in sync, with the
example forms anchoring the pattern via "default": "pick_direction".

The web renderer already honoured defaults, with one gap: the `default`
alias only accepted scalars, so a checkbox array default was silently
dropped (parseDefaultValue). Fixed alongside, with parser coverage for
the radio/checkbox/text shapes the prompt now instructs.

Charter byte budget raised 15_616 -> 15_872 for the new bullet +
example anchors; prefill markers frozen in core-slim and
discovery-form tests.

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>

* fix(prompts): make form prefill survive streaming key order; add web search/fetch imagery fallback (#5558)

* fix(web): adopt streamed question-form defaults that arrive after the question renders

The #5556 prefill contract asks the model to ship a recommended `default`
on every <question-form> question, but whether the prefill actually showed
up depended on JSON key order: the partial-JSON parser reveals a question
as soon as its label lands, and a `default` emitted after `options`
(observed in production run fca86faa) arrived only after the reveal pass
had already parked the question on its auto-assigned empty value — which
the backfill effect then treated as "answered" and never touched again.

Fix on two sides:

- Web: QuestionFormView now tracks which questions the user actually
  touched (seeded from submitted/draft snapshots) and adopts a
  late-arriving streamed default via shouldAdoptStreamedDefault — only
  while the question is untouched AND still holds its auto-assigned empty
  value, so a late default can never clobber a real answer or an
  intentional clear (checking then unchecking a box stays cleared).
- Prompts (daemon + contracts mirrors, slim charter + full discovery
  rules): instruct models to write `default` before `options`, as the
  example forms already anchor, so the recommendation streams in with the
  question instead of trailing a long options array.

Red spec: "adopts a default that streams in after the question was
revealed" went red on the pre-fix tree and green with this change; a
companion guard pins that "untouched" means never-interacted, not
currently-empty.

* feat(prompts): fall back to web search/fetch imagery when image generation is unavailable

The production-value craft rule tells runs to reach for real imagery
through the OD media tool or the runtime's native image generation — but
a run with neither (BYOK agents without media config, runtimes without
imagegen) had no sanctioned next step and shipped the exact fallbacks the
rule bans: empty slots, schematic boxes, flat icons.

The slim charter's imagery guidance is now an explicit resolution chain:
OD media tool → native image generation → web search / web fetch. The new
final step has the run find a fitting real photo, pull it into the
project (e.g. curl), and reference it by relative path — never hot-link
the remote URL, matching the existing no-hot-link file rule so artifacts
don't rot with dead image links.

Charter byte budget raised 15_872 -> 16_128 for the fallback sentence,
documented in the budget history; a new frozen-marker test pins the three
chain steps and the no-hot-link wording so a future tool-economy pass
can't silently drop them.

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>

* chore(prompts): land #5573 form refinements on the system-prompt validation line (#5575)

* fix(web): adopt streamed question-form defaults that arrive after the question renders

The #5556 prefill contract asks the model to ship a recommended `default`
on every <question-form> question, but whether the prefill actually showed
up depended on JSON key order: the partial-JSON parser reveals a question
as soon as its label lands, and a `default` emitted after `options`
(observed in production run fca86faa) arrived only after the reveal pass
had already parked the question on its auto-assigned empty value — which
the backfill effect then treated as "answered" and never touched again.

Fix on two sides:

- Web: QuestionFormView now tracks which questions the user actually
  touched (seeded from submitted/draft snapshots) and adopts a
  late-arriving streamed default via shouldAdoptStreamedDefault — only
  while the question is untouched AND still holds its auto-assigned empty
  value, so a late default can never clobber a real answer or an
  intentional clear (checking then unchecking a box stays cleared).
- Prompts (daemon + contracts mirrors, slim charter + full discovery
  rules): instruct models to write `default` before `options`, as the
  example forms already anchor, so the recommendation streams in with the
  question instead of trailing a long options array.

Red spec: "adopts a default that streams in after the question was
revealed" went red on the pre-fix tree and green with this change; a
companion guard pins that "untouched" means never-interacted, not
currently-empty.

* feat(prompts): fall back to web search/fetch imagery when image generation is unavailable

The production-value craft rule tells runs to reach for real imagery
through the OD media tool or the runtime's native image generation — but
a run with neither (BYOK agents without media config, runtimes without
imagegen) had no sanctioned next step and shipped the exact fallbacks the
rule bans: empty slots, schematic boxes, flat icons.

The slim charter's imagery guidance is now an explicit resolution chain:
OD media tool → native image generation → web search / web fetch. The new
final step has the run find a fitting real photo, pull it into the
project (e.g. curl), and reference it by relative path — never hot-link
the remote URL, matching the existing no-hot-link file rule so artifacts
don't rot with dead image links.

Charter byte budget raised 15_872 -> 16_128 for the fallback sentence,
documented in the budget history; a new frozen-marker test pins the three
chain steps and the no-hot-link wording so a future tool-economy pass
can't silently drop them.

* feat(forms): Other-chip escape hatch, 5-question cap, form-language copy, and streamed-default truncation fix (#5573)

* feat(forms): host-owned Other chip, 5-question hard cap, and form-language host strings

Three form-UX upgrades that shipped together on the validation branch:

1. Host-owned "Other" escape hatch. Finite-choice questions no longer show
   an always-on "Custom answer" input; the host injects a localized,
   dash-bordered "Other" chip (an "Other…" entry for selects) that expands
   into the type-in field via the shared accordion motion. Opening it on a
   single-choice question deselects the fixed options; picking a fixed
   option collapses a still-empty field; checkbox custom entries coexist.
   Prompts (slim charter + discovery mirrors) now ban model-authored
   catch-all "Other …" options — they would duplicate the host's — and the
   od-default router's taskType question pins allowCustom: false in all
   three copies since its own "Other" option IS the route.

2. Hard cap: 5 questions per form. The old "keep ≤7" guidance let the
   model pad forms to the example's length. The rule is now an explicit
   count-then-cut step ("count your draft; delete the least build-critical
   until 5 remain"), and the discovery example itself was trimmed from 7
   to 5 questions so the anchor matches the cap.

3. Copy + language. Form copy anchors lead with the benefit ("Prefilled
   for you — send as is, or tweak anything first"); the localization rule
   demands native phrasing with a concrete wrong-vs-right anchor (快速确认,
   not the literal 快速简报); and forms now declare a top-level "lang"
   BCP-47 tag that the web card uses to render its own strings (Other
   chip, custom-answer copy, submit CTA) in the form's language instead of
   the UI locale — no more English "Other" chips inside a Chinese form.
   New i18n key qf.otherOption ships in all 19 locales.

Charter byte budget 15_872 → 16_896 across the two documented bumps, with
frozen-marker tests pinning the escape-hatch delegation, the hard cap, the
native-localization rule, and the lang anchor.

* fix(web): never surface a mid-stream question-form default

The streaming shaper reveals a question the moment its label parses, and
runs every question object — including the trailing, still-open one —
through mapRawQuestion. That let a MID-STREAM `default` reach the card:
the partial-JSON repair terminates it early, so "单位教育" surfaces as
"单位教" and ["历史背景与经过", "抗战精神与当代意义"] as ["历史背景与经过",
"抗战精"]. The card adopts a streamed default only while the answer is
still empty, so a truncated adoption freezes garbage that the completed
value can never overwrite — checkbox defaults (long arrays, near-certain
to be cut across delta chunks) effectively never prefilled, while short
radio scalars usually survived inside one chunk. Observed in production
run beaf2da0 (抗日主题汇报PPT): both maxSelections checkboxes rendered
with nothing selected despite the model emitting complete defaults.

Fix at the source: the trailing in-flight question exposes NO
defaultValue until its braces close. A closed object's braces are
balanced, so its default is complete; the card's empty-and-untouched
adoption rule then fills it exactly once, correctly.

Red spec reproduces the production body cut mid-default (went red on the
pre-fix tree) and pins that a closed question's default surfaces while
the next in-flight question's stays hidden.

* fix(prompts): align task-type form contract across all three copies; fit the slot menu inside the 5-question cap

Review follow-ups from PR #5573 (nettee):

- The od-default skill's task-type form now carries the same top-level
  "lang" tag as the daemon/contracts prompt copies, so host-rendered
  controls on that path key off the form language instead of falling back
  to the app locale. A new parity check reads all three copies and locks
  both contract keys ("lang" + taskType allowCustom: false) so the skill
  copy cannot drift again.

- The slim charter's default-shape recipe contradicted the new hard cap:
  "Between output and brand, in this order: platform, audience, tone.
  After brand: scale, then constraints" prescribed 7 questions in the
  same breath as "Hard cap: 5". The recipe is now 2 fixed slots plus a
  pick-AT-MOST-3 menu (same option vocabulary, same order), and the
  frozen-marker test asserts the menu wording is present while the old
  prescriptive sequence is gone — the charter can no longer carry both.

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>

* chore(prompts): land #5586 photo-overlay discipline on the system-prompt validation line (#5600)

* fix(web): adopt streamed question-form defaults that arrive after the question renders

The #5556 prefill contract asks the model to ship a recommended `default`
on every <question-form> question, but whether the prefill actually showed
up depended on JSON key order: the partial-JSON parser reveals a question
as soon as its label lands, and a `default` emitted after `options`
(observed in production run fca86faa) arrived only after the reveal pass
had already parked the question on its auto-assigned empty value — which
the backfill effect then treated as "answered" and never touched again.

Fix on two sides:

- Web: QuestionFormView now tracks which questions the user actually
  touched (seeded from submitted/draft snapshots) and adopts a
  late-arriving streamed default via shouldAdoptStreamedDefault — only
  while the question is untouched AND still holds its auto-assigned empty
  value, so a late default can never clobber a real answer or an
  intentional clear (checking then unchecking a box stays cleared).
- Prompts (daemon + contracts mirrors, slim charter + full discovery
  rules): instruct models to write `default` before `options`, as the
  example forms already anchor, so the recommendation streams in with the
  question instead of trailing a long options array.

Red spec: "adopts a default that streams in after the question was
revealed" went red on the pre-fix tree and green with this change; a
companion guard pins that "untouched" means never-interacted, not
currently-empty.

* feat(prompts): fall back to web search/fetch imagery when image generation is unavailable

The production-value craft rule tells runs to reach for real imagery
through the OD media tool or the runtime's native image generation — but
a run with neither (BYOK agents without media config, runtimes without
imagegen) had no sanctioned next step and shipped the exact fallbacks the
rule bans: empty slots, schematic boxes, flat icons.

The slim charter's imagery guidance is now an explicit resolution chain:
OD media tool → native image generation → web search / web fetch. The new
final step has the run find a fitting real photo, pull it into the
project (e.g. curl), and reference it by relative path — never hot-link
the remote URL, matching the existing no-hot-link file rule so artifacts
don't rot with dead image links.

Charter byte budget raised 15_872 -> 16_128 for the fallback sentence,
documented in the budget history; a new frozen-marker test pins the three
chain steps and the no-hot-link wording so a future tool-economy pass
can't silently drop them.

* feat(forms): Other-chip escape hatch, 5-question cap, form-language copy, and streamed-default truncation fix (#5573)

* feat(forms): host-owned Other chip, 5-question hard cap, and form-language host strings

Three form-UX upgrades that shipped together on the validation branch:

1. Host-owned "Other" escape hatch. Finite-choice questions no longer show
   an always-on "Custom answer" input; the host injects a localized,
   dash-bordered "Other" chip (an "Other…" entry for selects) that expands
   into the type-in field via the shared accordion motion. Opening it on a
   single-choice question deselects the fixed options; picking a fixed
   option collapses a still-empty field; checkbox custom entries coexist.
   Prompts (slim charter + discovery mirrors) now ban model-authored
   catch-all "Other …" options — they would duplicate the host's — and the
   od-default router's taskType question pins allowCustom: false in all
   three copies since its own "Other" option IS the route.

2. Hard cap: 5 questions per form. The old "keep ≤7" guidance let the
   model pad forms to the example's length. The rule is now an explicit
   count-then-cut step ("count your draft; delete the least build-critical
   until 5 remain"), and the discovery example itself was trimmed from 7
   to 5 questions so the anchor matches the cap.

3. Copy + language. Form copy anchors lead with the benefit ("Prefilled
   for you — send as is, or tweak anything first"); the localization rule
   demands native phrasing with a concrete wrong-vs-right anchor (快速确认,
   not the literal 快速简报); and forms now declare a top-level "lang"
   BCP-47 tag that the web card uses to render its own strings (Other
   chip, custom-answer copy, submit CTA) in the form's language instead of
   the UI locale — no more English "Other" chips inside a Chinese form.
   New i18n key qf.otherOption ships in all 19 locales.

Charter byte budget 15_872 → 16_896 across the two documented bumps, with
frozen-marker tests pinning the escape-hatch delegation, the hard cap, the
native-localization rule, and the lang anchor.

* fix(web): never surface a mid-stream question-form default

The streaming shaper reveals a question the moment its label parses, and
runs every question object — including the trailing, still-open one —
through mapRawQuestion. That let a MID-STREAM `default` reach the card:
the partial-JSON repair terminates it early, so "单位教育" surfaces as
"单位教" and ["历史背景与经过", "抗战精神与当代意义"] as ["历史背景与经过",
"抗战精"]. The card adopts a streamed default only while the answer is
still empty, so a truncated adoption freezes garbage that the completed
value can never overwrite — checkbox defaults (long arrays, near-certain
to be cut across delta chunks) effectively never prefilled, while short
radio scalars usually survived inside one chunk. Observed in production
run beaf2da0 (抗日主题汇报PPT): both maxSelections checkboxes rendered
with nothing selected despite the model emitting complete defaults.

Fix at the source: the trailing in-flight question exposes NO
defaultValue until its braces close. A closed object's braces are
balanced, so its default is complete; the card's empty-and-untouched
adoption rule then fills it exactly once, correctly.

Red spec reproduces the production body cut mid-default (went red on the
pre-fix tree) and pins that a closed question's default surfaces while
the next in-flight question's stays hidden.

* fix(prompts): align task-type form contract across all three copies; fit the slot menu inside the 5-question cap

Review follow-ups from PR #5573 (nettee):

- The od-default skill's task-type form now carries the same top-level
  "lang" tag as the daemon/contracts prompt copies, so host-rendered
  controls on that path key off the form language instead of falling back
  to the app locale. A new parity check reads all three copies and locks
  both contract keys ("lang" + taskType allowCustom: false) so the skill
  copy cannot drift again.

- The slim charter's default-shape recipe contradicted the new hard cap:
  "Between output and brand, in this order: platform, audience, tone.
  After brand: scale, then constraints" prescribed 7 questions in the
  same breath as "Hard cap: 5". The recipe is now 2 fixed slots plus a
  pick-AT-MOST-3 menu (same option vocabulary, same order), and the
  frozen-marker test asserts the menu wording is present while the old
  prescriptive sequence is gone — the charter can no longer carry both.

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>

* feat(prompts): pin photo-overlay placement discipline in the slim charter

Real-imagery production value (the strongest-texture-lever rule) kept
shipping badges and caption cards that straddle the hero image's edge or
sit on the photo's focal subject — 2026-07-14 beta feedback on a
campus-open-day landing hero showed a session-type chip half-off the
photo, covering the graduates.

New Craft bullet: an overlay on a photo pins to ONE corner with a
consistent inset, sits fully inside the image bounds, stays clear of
faces/subjects, and reads on a legible surface with separating shadow;
with no safe corner it moves beside the image. Byte budget
16_896 -> 17_408 per the in-file bump trail; markers frozen.

* chore: retrigger ci

UI P0 (entry-settings) failed on 'Use with query carries the hydrated
starter prompt' — orthogonal to this PR's prompt-text diff, green on the
base branch's identical run, and passes locally on this branch (35s).
Empty commit to re-run the shard.

* chore: drop local-build churn in next-env.d.ts

A local 'next build' rewrote the generated routes.d.ts reference path and
the merge commit swept it in via add -A; restore the mainline version —
this PR's intent touches prompts/tests only.

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>

* fix: address review findings — repeat-directive false positives, directions flag validation, router-form cap exemption

1. live-artifacts/render.ts: a data-od-repeat match now counts as a
   directive only inside an element's open tag; literal mentions in prose
   or comments render as inert content per html_template_v1 (red spec in
   tests/live-artifacts/render.test.ts).
2. cli.ts tools directions: --id/--label now fail fast on a missing
   value, a flag posing as the value, duplicates, or both flags at once
   (red spec in tests/directions-cli.test.ts) — agents call this straight
   from the prompt contract.
3. prompts/discovery.ts (+contracts mirror): the 5-question hard cap now
   names the verbatim six-field task-type router form as its one
   sanctioned exception, resolving the contradiction between two locked
   instructions; the cap itself is unchanged for authored/tailored forms.
   Marker frozen in discovery-form.test.ts; matrix snapshot regenerated.

* fix: second-round review — nested literal repeat false positive, keyboard-accessible Other chip

1. live-artifacts/render.ts: the nested-repeat guard now uses the same
   inside-open-tag detection as the top-level scan (extracted
   findRepeatDirective helper), so a literal data-od-repeat mention inside
   a repeated element's body renders inert instead of throwing; a REAL
   nested directive still throws. Red spec: reviewer's exact repro added,
   red on the previous head.
2. web QuestionForm Other chip: replaced the display:none checkbox-in-label
   with a real <button aria-pressed aria-expanded> plus focus-visible ring
   and UA-chrome reset, restoring Tab reachability for keyboard and
   screen-reader users. Existing chip tests migrated to the button role;
   new focus/toggle spec added.

* chore: retrigger ci

Web workspace tests failed on HomeView.community-filter-decouple
(aria-selected timing) — a main-side test untouched by this PR; the full
web suite passes locally on this exact head (4351/4351). Empty commit to
re-run the shard.

* fix: third-round review — media runs skip the slim design charter; token-aware repeat-directive scan

1. prompts/system.ts: resolve the media surface (image/video/audio via
   skillMode or metadata.kind) BEFORE building the prompt head, and gate
   the slim design charter on it — the media-generation contract is again
   the sole workflow authority on media runs, matching classic. The
   mid-conversation clarifying-questions block now keys off the charter's
   actual presence so media runs keep classic's clarify guidance. New spec
   proves image/video/audio slim prompts contain no discovery-form or
   HTML-handoff rules while design runs keep the charter.
2. live-artifacts/render.ts: findRepeatDirective is now quote- and
   comment-aware — a directive-looking string inside another attribute's
   quoted value or inside an HTML comment is authored text. Reviewer
   repros added as regressions (attribute-quoted literal, tag-like
   comment text); nested detection shares the same scanner.
Red-checked: 3 new specs fail with the two source files reverted.

* fix: fourth-round review — slim media head drops the Ask charter; comment-aware element-boundary scan

1. prompts/system.ts: the non-charter slim fallback head applied
   CHAT_MODE_OVERRIDE to every slim run that was not the design charter —
   including media runs, whose Ask charter ('do not create images/video/
   audio') contradicted the media contract. The head now branches
   explicitly: ask -> Ask charter, slim media (non-ask) -> classic skeleton
   (plain-stream API override + injection resistance) with the media
   contract as sole authority. Media spec extended to assert '# Ask mode'
   is absent.
2. live-artifacts/render.ts: extracted insideComment() and applied it to
   findElementEnd(), so same-tag tokens inside comments no longer move the
   depth counter (<!-- <div> --> threw 'unbalanced'; <!-- </div> --> closed
   the repeat early). findRepeatDirective shares the helper. Reviewer
   repros frozen as regressions.
Red-checked: both new specs fail with the two source files reverted.

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: Mason <jinmeihong0201@gmail.com>
Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>
2026-07-14 10:21:32 +00:00
lefarcenandGitHub 61ee3914d3 fix(web): paint the update-ready prompt above the raised home composer (#5608)
The updater prompt lives inside .entry-main__topbar, an isolated sticky
stacking context at z-index 10, so its own z-index 80 could not escape.
When the composer's agent picker (or another composer popover) is open,
.home-hero__input-card:has(...) raises the card to z-index 1700, painting
the composer and its popover over the update prompt on scrolled home
windows. Mirror the settings-menu precedent and lift the topbar to
z-index 1900 while .updater-popup is open.

Red spec: e2e/ui/updater-popup-stacking.test.ts reproduces the overlap
with elementFromPoint sampling (red on main, green here).

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 09:39:12 +00:00
lefarcenandGitHub 8f7c4bb80a fix(web): hold the next-step card while a question form awaits answers (#5616)
A clarification turn terminates its run while the emitted <question-form>
is still waiting for the user in the Questions tab, so the terminal-turn
next-step card (Continue working / Generate artifact / More) popped up
during the question phase. Gate showNextStepActions on the message's
question forms: hold the card until the immediate next user message
submits the form answers (skip-all submits through the same path), and
ignore suppressed direction forms since they render as a locked pill the
user cannot answer.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 09:29:39 +00:00
a250ac662e fix(web): stop silent-updates settings toggle from crashing the page (#5609)
* fix(web): stop silent-updates settings toggle from crashing the page

Settings → About read event.currentTarget.checked inside a functional
setCfg updater. React clears currentTarget after the handler returns, so
when SettingsDialog already has pending lanes (about-updater status,
autosave), the deferred updater throws TypeError and lands on the
reload error page. Capture the checked value before setState.

* test(web): fix TS2352 in silent-updates race test null cast

The bad-pattern assertion cast `event.currentTarget` (typed
`HTMLInputElement | null`) directly to `HTMLInputElement`, which
`tsc --noEmit` rejects with TS2352 because the types don't overlap.
Route the cast through `unknown` (matching the fixed-capture assertion
below it) so Preflight typecheck passes.

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

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-07-14 09:08:24 +00:00
lefarcenandGitHub ad584ff901 fix(web): keep deck page counters in sync during keyboard paging (#5594)
* fix(web): keep deck page counters in sync during keyboard paging

Host-driven deck navigation (keyboard shortcuts and prev/next buttons)
used to mutate slide classes/transforms directly in the srcdoc bridge,
bypassing the artifact's own navigation script. Decks that render their
own page counter from an internal index never saw the move, so the
bottom page number stayed frozen on the first slide while the canvas
advanced. The bridge now prefers dispatching the navigation key to the
artifact's own keyboard handler and only falls back to direct DOM
mutation when no handler moves the deck.

The mirror gap: iframe-focused keyboard navigation on transform-track
decks mutates only the shared track element, which the bridge's
MutationObserver (slide attributes only) never saw, leaving the HOST
counter stale. The observer now watches the track's style too.

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

* worker: 使用键盘快捷键翻页的时候,下面的页码不会变动

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

* fix(web): wait out deferred deck key handlers before the bridge's direct-DOM fallback

Review follow-up for #5594: the artifact-key probe treated only a
synchronous index change as proof the artifact handled the key. Decks
that defer their keydown state update (requestAnimationFrame or
setTimeout) fell through to direct DOM mutation immediately AND received
the key twice — the window dispatch and the document re-dispatch both
reach a deferred window listener — so one host "next" advanced two
slides and desynced the artifact's internal index.

The probe is now asynchronous: dispatch to one target, then wait one
bounded mutation window (MutationObserver on class/style/hidden plus a
48ms cap) for the active index or track transform to change before
trying the next dispatch target or the direct-DOM fallback. Synchronous
handlers still resolve on a zero-latency fast path, and a responsive
dispatch target is cached so later keypresses skip re-probing.

Decks with no keyboard handling must not pay the probe wait: the key
path is gated on the artifact actually shipping keyboard navigation,
seeded from a source scan of the artifact html before any od bridge is
injected plus registration-time tracking through the existing
addEventListener patch — the same two-pronged pattern the od:slide
message-listener flag already uses. This preserves the pinned invariant
that the bridge-owned fallback stays synchronous for handler-less decks.

Regression fixtures: a deferred window-listening deck (red on the
synchronous probe with a visible double advance to slide 3) and
seed-scan assertions proving od-injected bridge scripts do not
contaminate the keyboard-navigation flag.

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

* fix(web): catch external-script deck keyboard runtimes with a head-start registry hook

Review follow-up for #5594: detectArtifactKeyboardNavigation() only sees
inline HTML bytes, so decks that load their keyboard runtime via
<script src> (design-templates/html-ppt wires ../assets/runtime.js,
which registers an ArrowRight/ArrowLeft document keydown handler) were
classified handler-less. Their listeners also register before the deck
bridge executes at the end of <body>, so the bridge's own
addEventListener patches installed too late — the flag stayed false,
host-driven od:slide fell back to direct DOM mutation, and
artifact-owned counters / internal indices went stale again.

A tiny registry hook is now injected at the start of <head> — before any
artifact script — wrapping window/document addEventListener and flagging
keydown listeners that pass the shared navigation-key screen into
window.__odArtifactKeydownNavigation. The deck bridge reads that flag
lazily alongside its inline-source seed and its own late patches, so
registrations are observed no matter when they happen relative to the
bridge. The bridge marks its own top-of-file keydown listeners via
__odDeckBridgeOwnListenerInstall so the framework branch's key-token
source does not classify every framework deck as artifact-navigable.

The navigation-key screen is now a single shared snippet between the
hook and the bridge. Regression: a fixture that registers its keydown
handler before the bridge evaluates with no keydown token in the html
bytes (the script-src shape) — red on the previous commit (counter stays
1 while the canvas moves), green with the hook.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 08:59:13 +00:00
d7151b2b60 feat(analytics): link downloads and first-party browser opens (#5442)
* feat(analytics): attribute desktop downloads

* fix(analytics): initialize attribution routes after HTTP deps

* feat(analytics): bridge first-party browser opens

* feat(analytics): bridge first-party anchor opens

* fix(analytics): make attribution claims idempotent

* fix(analytics): record pending attribution results

* chore(analytics): bind landing attribution KV

* fix(ci): remove landing preview wrangler config override

* fix(landing): resolve download attribution function

* fix(analytics): harden attribution token consumption

* fix(analytics): preserve same-installation claim retries

* fix(analytics): merge recovered attribution retries

* fix(analytics): preserve attributed browser links

* fix(analytics): clear attribution on privacy reset

* fix(analytics): bridge stopped AMR console links

* fix(ci): deploy attribution schema before Pages

* fix(ci): run attribution migrations with Wrangler 4

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
Co-authored-by: PerishCode <perishcode@gmail.com>
2026-07-14 08:46:04 +00:00
lefarcenandGitHub da7c4ae051 fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps (#5597)
* fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps

The Community gallery Newest toggle sorted by the installed-record
updatedAt/installedAt. Those are stamped when a machine seeds its own
database, so a fresh install (or an upgrade that re-stamps the catalog)
writes the whole bundled catalog milliseconds apart in folder-walk
order — Newest showed reverse walk order (roughly reverse-alphabetical),
unrelated to publication recency.

Root-cause fix: bundled templates now ship a real publication date.

- packages/contracts: add optional manifest field publishedAt (ISO 8601).
- apps/web sortByNewest: freshness = publishedAt when parseable, else
  updatedAt (right signal for user-installed plugins); tie-breaks and
  stability unchanged.
- plugins/_official: backfill publishedAt into all 460 bundled manifests
  from each folder's first-appearance commit date in upstream git
  history.
- e2e: repository-resource guard requiring every bundled manifest to
  declare a parseable publishedAt, so the signal cannot silently erode.
- docs: field reference entries in plugins-spec (en + zh-CN).

Issue: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/ca0778af-97f5-4fc0-bc63-739280904979

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

* worker: 模板用最新筛选,筛选出来的不是最新的

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

* fix(web): keep shipped appeal order inside same-publishedAt batches

Review follow-up (#5597, nettee): after the freshness tie on an equal
publishedAt, the comparator still consulted local updatedAt/installedAt,
which a fresh install stamps in folder-walk order — so every
same-publication batch (largest: 322 manifests on one catalog date)
re-sorted per machine instead of keeping the shipped visual-appeal
ranking. Same-publishedAt records now preserve their incoming position;
local timestamps break ties only when at least one side has no usable
publishedAt. Adds the requested regression spec pinning
same-publishedAt / different-updatedAt records.

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

* fix(web): gate publishedAt freshness to bundled records; untrack oversized looper attachment

Review follow-up (#5597, nettee round 2): publishedAt was overriding
updatedAt for every visible plugin, so a user-installed github/local/
marketplace plugin carrying an old manifest publishedAt would be buried
behind bundled templates despite a newer local install. publishedAtMs
now returns null for non-bundled records — "Newest" means install
recency for user installs, catalog publication for bundled ones. Adds
the requested regression (non-bundled, old publishedAt, newer
updatedAt) and clarifies the field docs (en + zh-CN).

CI repair: commit 578f7a9 tracked .looper-attachments/bug13_1.png
(3.8MB bug screenshot), tripping the Static gate 1MiB blob guard and
the Validate workspace aggregate. Untrack it and gitignore
.looper-attachments/ so harness attachments stay out of the repo.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 08:43:57 +00:00
9d055a0fa6 feat(amr): add artifact-based upgrade guidance for Free users (#5591)
* feat(amr): add artifact-based upgrade guidance

* fix(amr): fail open when billing plan is unavailable

---------

Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
2026-07-14 08:11:07 +00:00
lefarcenandGitHub db4613797e fix(web): replace retired Home/nav-rail logo with the current brand glyph (#5599)
The web app still shipped the pre-rebrand mark (444x444 dark tile with a
white cursor ring) in /logo.svg, /logo.png, and /brand-icon.svg, so the
Home hero, the entry nav rail, and the cloud onboarding page all showed
the old logo. The landing page moved to the ink superellipse glyph in
PR #3444; copy that glyph in as the web app's brand assets.

Because the new glyph is a solid ink mark (not self-contrasting like the
old dark tile), render it through a shared .od-brand-glyph primitive that
masks /brand-icon.svg with --text-strong — the same pattern the cloud
onboarding logo already used — so it reads as ink in light mode and cream
in dark mode. Hero/nav-rail img tags become masked spans, and the hero's
panel disc behind the mark is dropped since the glyph is self-shaped.

home-logo-assets.test.ts now pins the asset contents (current glyph path
present, retired markers absent) instead of only the img src strings.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 07:55:20 +00:00
kiranandGitHub bd67d8cc7b fix(web): align fullscreen settings controls (#5550) 2026-07-14 07:55:00 +00:00
Xinmin ZengandGitHub e2502d0e5d feat(web,daemon): keep annotation position anchors when the screenshot is missing (#4105)
Re-applied onto current main (the original branch predated the
server.ts -> runtimes/chat-prompt-inputs.ts extraction and 400+
upstream commits; a mechanical rebase was not meaningful).

- normalizeCommentAttachments keeps screenshot-less visual marks that
  carry a usable anchor (positive-size pagePosition or selector), and
  drops anchor-less ones so the agent is never hard-scoped onto fields
  that identify no region
- renderCommentAttachmentHint / web context render the structured
  location instead of a screenshot line when none exists, with a
  screenshot-less intent copy on both sides
- ChatComposer builds the visual attachment whenever the mark has a
  location, with or without the screenshot upload (#4080 capture
  failures no longer reduce the send to bare prose)

Fixes #4084
2026-07-14 07:05:44 +00:00
lefarcenandGitHub 125c7141ad fix(web): anchor browser-assist guide and workspace snapshot toasts to their panes (#5590)
The Download Page guide toast emitted when Continue extraction cannot read
any snapshot (and the other brand continue-extraction flow toasts) rendered
through the viewport-fixed fallback, floating bottom-center across both
panes and covering the composer send area. Scope them to the chat pane so
they anchor between the transcript and the composer, matching the
browser-assist confirm guide fixed in #5403.

The workspace-owned Download Page snapshot progress/result toast (and tab
launcher errors) had the same defect: a bare fixed .od-toast centers on the
whole window. Wrap them in a .workspace-toast-anchor pinned to the
workspace pane's bottom-center, with .workspace as the containing block.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 06:47:21 +00:00
lefarcenandGitHub 906d6ff1f9 fix(web): keep the sticky home topbar above the popover-elevated composer card (#5596)
Opening any popover inside the home composer card (agent/model switcher,
session-mode toggle, plugin picker, template menu) lifted the whole card
to z-index 1700, which out-stacked the sticky entry topbar (z-index 10)
and the fixed modal family (automation 80, plus-menu 90, plugins-import
100, use-everywhere 920). With the home view scrolled, the card body
painted over the topbar chips (GitHub star, Teams, Discord, settings).

The lift only needs to beat the static home content below the hero,
whose stacking contexts top out at 8, so 9 restores the intended
layering: content < elevated card < sticky topbar < fixed modals.
Popover z-indexes inside the card are local to its stacking context and
keep working unchanged.

Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/928fae75-d660-426f-be8e-f98ed4eb88e1

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 06:44:53 +00:00
53eff935e1 feat(amr): enable Parallel Search MCP (#5552)
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-07-14 04:39:49 +00:00
lefarcenandGitHub f23c96edff fix(web): classify deploy failures instead of the generic "Error" code (#5554)
* fix(web): classify deploy failures instead of the generic "Error" code

`artifact_deploy_result` reported `err.name` on the deploy catch path,
which collapses to the generic "Error" for a plain thrown Error — ~half
of failed deploys (~40/day) land in that opaque bucket with no way to
tell an auth failure from a network drop from a provider 5xx, so the
deploy failure rate can't be root-caused from telemetry.

Add `deployErrorCode(err)` (mirrors export-error-code.ts / #5220):
structured `.code` wins, then map the common transport / provider-API
signatures (NETWORK, TIMEOUT, RATE_LIMITED, FORBIDDEN, NOT_FOUND,
HTTP_<status>, desktop sidecar skew) to fixed codes, else fall back to
err.name. Only fixed codes are emitted — never free-form message text —
so no deploy token, account id, zone, or URL can leak into analytics.

* fix(web): preserve deploy HTTP status/code so classification isn't lost upstream

Review follow-up (#5554): deployErrorCode can only emit HTTP_<status> or a
structured code if that survives into the deploy catch, but deployProjectFile
threw a bare `new Error(payload.error.message || ... )`, dropping the status
whenever the provider/daemon returned a human message. So the daemon-backed
deploy failures the classifier was meant to bucket still collapsed to "Error".

deployProjectFile now attaches a `.code` to the thrown error: a structured
provider/daemon `error.code` when present, else `HTTP_<status>`. deployErrorCode
reads `.code` first, so those failures now bucket by status instead of "Error".
Transport failures (fetch throws before a response) still classify via message.

* fix(web): ignore the daemon's generic deploy envelope code so status bucketing runs

Follow-up to @nettee's review: the daemon deploy route
(apps/daemon/src/routes/deploy.ts) collapses every non-404 provider failure's
code to `BAD_REQUEST` (and 404 to `FILE_NOT_FOUND`) while keeping the REAL
provider HTTP status on the response and the real message in the body. Since the
prior change preferred `payload.error.code`, a live 403/429/5xx was rethrown as
`{ code: 'BAD_REQUEST' }` and deployErrorCode returned that generic code, never
reaching HTTP_403 / RATE_LIMITED / etc.

Ignore the generic envelope codes (shared `GENERIC_DEPLOY_ENVELOPE_CODES`) at
both the throw site (fall back to the real `HTTP_${resp.status}`) and inside
deployErrorCode as a safety net, so status/message-based bucketing runs. Added
registry tests that exercise the actual daemon envelope (BAD_REQUEST+403 →
HTTP_403, FILE_NOT_FOUND+404 → HTTP_404) and a deployErrorCode generic-code test.
2026-07-13 13:35:56 +00:00
ae27ad2541 Add Atlas Cloud BYOK provider preset (#5406)
* Add Atlas Cloud BYOK provider preset

* Add UTM to Atlas Cloud provider link

---------

Co-authored-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com>
2026-07-13 10:28:31 +00:00
6426847fba fix(web): separate run success from result delivery (#5525)
* fix(web): distinguish run success from result delivery

Persist delivery outcomes separately so successful design runs cannot report completion before a usable artifact is available.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): recover missing design delivery after reload

* chore(ci): rerun pull request checks

* fix(web): avoid reattaching local delivery finalization

* fix(web): retry delivery failures

* chore(ci): rerun pull request checks

* chore(ci): retrigger managed checks

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 10:16:04 +00:00
PerishFireandGitHub 35e8ded24d fix(web): restore silent update checkbox geometry (#5547) 2026-07-13 09:21:54 +00:00
d55c3ff216 fix(web): align skills filter counts (#5450)
* fix(web): align skills filter counts

Generated-By: looper 0.10.1 (runner=worker, agent=codex)

* fix(web): type skills search locale helper

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

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-07-13 09:20:51 +00:00
lefarcenandGitHub e6bef56e19 fix(web): keep the split resize handle hitbox off the chat scrollbar gutter (#5526)
The chat/workspace split handle extends its pointer hitbox with a
::before overhang of left: -2px / right: -10px. The 2px chat-side
overhang sits exactly on the outer edge of the chat-log's 8px scrollbar
gutter, so clicks aimed at the (~4px visible) scrollbar thumb start a
panel resize instead of scrolling, and hovering the gutter flips the
cursor to col-resize with the divider highlight. In RTL the physical
right: -10px lands on the chat side and covers the entire gutter.

Switch the overhang to logical properties with a hard invariant: the
hitbox never crosses the handle's inline-start edge (where the scrollbar
lives in both directions) and extends 8px on the workspace side only
(inset-inline-start: 0; inset-inline-end: -8px). Effective grab width
stays 16px; resize JS is untouched.

Red spec: e2e/ui/split-resize-scrollbar-hitbox.test.ts probes
elementFromPoint on the gutter edge (LTR + RTL), asserts hover does not
enter the resize posture, and keeps a drag-the-handle regression guard.

Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/5c505d35-42e9-40e7-ab07-ba6e3bbfb2ee

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-13 09:13:00 +00:00
Chanhyo JungandGitHub 7e302031d3 fix(web): use current logo on home surfaces (#5396)
Fixes #5365

Verified with focused RED/GREEN regression, web typecheck, pnpm guard, root typecheck, visual DOM readback, and independent Codex diff review.
2026-07-13 07:59:27 +00:00
Dion KobiandGitHub 4b4c7f4028 Improve empty agent output guidance (#4241)
* Improve empty agent output guidance

* Preserve updated empty-output guidance
2026-07-13 07:59:24 +00:00
Nicholas-XiongandGitHub 5127ad188b fix: add Kiro to agentLabels.ts (AGENT_LABELS + AGENT_ALIASES) (#5085)
* fix: add Kiro to agentLabels.ts (AGENT_LABELS + AGENT_ALIASES)

- Add kiro to AGENT_LABELS so exactAgentDisplayName('kiro') returns 'Kiro'
- Add 'kiro cli' and 'kiro-cli' aliases to AGENT_ALIASES
- Kiro icon (kiro.svg) already exists, this was purely a missing label mapping

Closes #5081

* test: pin Kiro label and alias resolution in agentLabels utility

PerishCode's review on PR #5085: mapping change is merge-safe but the
new behavior should be pinned in the existing utility test matrix.

Added 4 tests covering:
- agentDisplayName resolves 'kiro cli' → 'Kiro' (alias)
- agentDisplayName resolves 'kiro-cli' → 'Kiro' (alias)
- exactAgentDisplayName returns 'Kiro' for exact id 'kiro' (canonical label)
- exactAgentDisplayName returns null for 'kiro cli'/'kiro-cli' (aliases are
  not themselves agents — substring aliases stay routed via agentDisplayName
  and do not pollute the exact-id table)

Locks the fix from db025410 and prevents regression.

* test: update kiro alias assertions to match exactAgentDisplayName behavior
2026-07-13 07:59:21 +00:00