mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
codex/motion-hyperframes-retirement
977
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7bdf9978aa | fix(web): keep account menu open after avatar click (#7330) | ||
|
|
f92c2d957c | fix(viewer): stabilize Electron HTML previews (#7336) | ||
|
|
7320f9b8ab |
feat(daemon): make OD Next the default design strategy (#7016)
* refactor(observability): isolate legacy run exporter
* refactor(daemon): extract internal run creation service
Route /api/runs and compatibility /api/chat through one daemon-owned create, claim,
resume, and start seam. Preserve HTTP responses, SSE timing, session bookkeeping,
and durable Run formats.
Validated with daemon tests/build/typecheck, workspace guard, and workspace typecheck.
* test(plugin-runtime): freeze OD Next strategy fixture boundaries
Add exact pre-Build route and stage cases plus deterministic deletion-boundary mutations without shipping an evaluation harness.
Preserve the current official and community fallback pipeline and composed verification/critique prompt sections as golden witnesses.
Validated with plugin-runtime tests and types, focused daemon prompt composition, daemon types, guard, and workspace typecheck.
* fix(daemon): finalize terminal telemetry delivery
Persist a versioned delivery record so normal failures stop after bounded retries
and only an unfinished crash window resumes on restart.
Reuse the persisted identity across Vela and Relay transport, isolate stable
direct Langfuse event ids by delivery purpose, and log only redacted sink data.
Validated with focused daemon and contracts tests, workspace typechecks, and
guard. The full daemon suite had one timeout; its four-test file passed alone.
* feat(plugins): add OD Next strategy scenario and contracts
Ship the V2 strategy package as inactive bundled content and validate its contracts without changing ordinary plugin manifest compatibility.
Keep bundled strategy bytes out of the catalog and generic apply path until a later hash-gated activation owner binds them.
* feat(observability): add normalized agent observation contract
Define a pure versioned contract for identity, Prompt, usage, timing, and status evidence with explicit availability and per-value provenance.
Add a structured main-Run compatibility adapter that preserves unknown facts, separates clocks, and omits provenance-ambiguous derived totals without changing legacy export behavior.
* feat(plugins): bind OD Next strategy content identity
* feat(daemon): persist OD Next strategy task chains
Add the daemon-owned SQLite truth for immutable strategy task identity, ordered physical Run mappings, canonical Plan Contracts, revision-CAS transitions, and terminal reconciliation.
Pin task-owned Snapshots and exclude their durable references from pruning so mixed GC sweeps remain safe.
Validated with 103 focused daemon tests, contracts tests/typecheck, daemon and workspace typechecks, and guard.
* feat(daemon): gate OD Next runtime capabilities by fixtures
Add exact CLI/adapter/fixture tuple resolution, stable capability snapshots, and offline
replay graph invariants for the four initial runtime paths.
Keep production registry empty until sanitized real fixtures exist; synthetic evidence and
version drift remain unknown.
Validated with contracts tests, focused daemon and e2e boundary suites, guard, and
workspace typecheck.
* feat(daemon): add hash-gated OD Next prompt recipe
Compose the bundled strategy from validated content, one Task Skill, and the exact stage policy.
Fail closed on package, profile, pipeline, or atom drift while preserving ordinary quality paths.
Validated with focused tests, package typechecks, workspace typecheck, and guard.
* feat(observability): aggregate OD Next task observations
Build stable task, Run, Child, model, and tool hierarchies from durable StrategyTaskExecution mappings and normalized facts while keeping missing coverage explicit.
Map task aggregates to legacy ingestion behind the consent gate. Preserve owner/exclude hierarchy while accounting usage once, enforce kind-specific Prompt boundaries, and accept absolute time only from one Unix-epoch evidence record.
Validated with 228 focused daemon tests, 353 contracts tests, daemon/contracts and workspace typechecks, guard, and the task harness.
* feat(daemon): collect bounded Codex child evidence
Read declared Codex rollout sessions without scanning arbitrary homes.
Normalize verified child facts while redacting prompts and preserving Run semantics.
* feat(daemon): add OD Next planning coordinator
Add a daemon-owned planning reducer, deterministic Resolver and Preflight facts, and a stream-safe machine protocol boundary backed by the durable task store.
Lock one clarification and one serialization-only repair across restart, preserve the Plan hash, and keep ordinary Run text unchanged.
* feat(daemon): collect native OpenCode child evidence
* feat(observability): add Langfuse OTLP task exporter
Map normalized task observations to complete OTLP/JSON v4 spans with stable parent context, allowlisted metadata, usage accounting, and bounded terminal delivery.
Fail closed on drift across root and operation input, model, timing, type, hierarchy, status, and complete usage provenance while dual remains a local-only OTLP shadow.
Keep legacy rollback, consent gates, and X5 live self-host verification boundaries intact.
* feat(daemon): normalize Claude native child evidence
Collect matched Claude Task sidechain lifecycle facts without changing main stream turn, stdin, or error bookkeeping, and adapt them to provider-neutral observations with unavailable Prompt and usage evidence.
Bind session, Task id, and parent identity immutably; poison rebinding, session drift, and conflicting terminals so ambiguous evidence cannot reach L2. Keep production capability unknown until X1 supplies an exact-version sanitized real fixture.
* feat(daemon): consume versioned Vela child evidence
Negotiate schema v1 only on AMR and enforce run-scoped start-to-terminal graph, status/source coherence, and exact usage source/completeness bindings. Emit allowlisted Normalized child facts without Vela private I/O.
Pin the unpublished producer golden for offline compatibility while keeping production capability unknown until the X1/X3 evidence gates are satisfied.
Validated with 147 focused daemon tests, 353 contract tests, daemon/workspace typechecks, guard, and the task harness.
* feat(daemon): automate OD Next simple production
Connect parsed OD Next terminal output to the durable coordinator, claim clarification, repair, and production Runs atomically, and start continuations only after the source Run ends.
Converge task failure and cancellation before physical end, require daemon-owned execution facts and canonical delivery, and keep ordinary Runs compatible.
Validated with focused daemon route and coordinator tests, contracts tests, daemon and workspace typechecks, guard, and the task harness.
* feat(daemon): enforce verified complex production
* feat(web): follow OD Next task chains across Web and CLI
Persist daemon-issued task handles and visible predecessor-prefix boundaries, then follow projected active Runs through one terminal outcome in Web and od run.
Normalize reloads to daemon activeRunId before subscribing, transfer controller ownership atomically across successors, discard stale cursors, and preserve visible prefixes without replaying predecessor machine payloads.
Validated with focused Web and daemon tests, contracts tests, workspace typecheck, guard, diff checks, and the task harness.
* feat(daemon): roll out OD Next task observability
* feat(daemon): add controlled OD Next rollout
* fix(daemon): align OpenCode and Vela evidence tuples
Pass GPT-5.6 Sol's verified high variant to OpenCode, pin native evidence to 1.18.18, retain locally recorded success and failure seeds as explicitly unattested, and align the Vela consumer golden to the local versioned producer.
Validated with 5 focused daemon files (175 tests), daemon and workspace typechecks, guard, the real local Vela ACP Sol smoke, and a minimal test-relay telemetry write.
* fix(daemon): honor OpenCode model variant capabilities
Parse the live verbose OpenCode catalog per model and forward only variants advertised by the exact detected binary. Keep fallback and one-off OPENCODE_BIN connection tests on the base model when variant support is unknown.
Record machine-checkable OpenCode 1.18.18 root, tool, child, recovery, cancel, timeout, and resume evidence without promoting the unattested seed into the production capability registry.
Validated with focused runtime and connection tests, host process-group and watchdog tests, contracts tests, daemon/workspace typechecks, guard, and an isolated SIGKILL delivery-recovery exercise.
* feat(daemon): add OD Next analytics operations report
Reduce normalized task aggregates into source-aware operations metrics without
copying the Langfuse dashboard or exposing user trace links.
Preserve main/Child accounting, stage-scoped coverage, unavailable facts, and
external-signal-only quality inputs.
Validated with focused and contract tests, the full daemon suite, guard,
workspace typecheck, and exact-trace Langfuse readback.
* feat(observability): complete child prompt and runtime provenance
Export bounded redacted child-injected Prompt content and exact CLI, companion, and
adapter versions through task-level legacy and OTLP mappings.
Register the OpenCode 1.18.18 best-effort replay tuple while keeping unknown versions
and incomplete Codex evidence fail closed.
Validated with 353 contracts tests, 196 focused daemon tests, daemon/workspace
typechecks, and guard.
* fix(daemon): recognize Codex child failure terminals
Parse Codex 0.147.0 Child task_complete.error as the runtime-owned failed terminal, honor session_meta id over its legacy root session_id, and map interrupt_agent aborts to canceled.
Add a redacted seven-path best-effort replay and exact Codex capability tuple. Unknown versions remain fail-closed and ordinary Codex runs are unchanged.
Validated with 92 focused daemon tests, 353 contracts tests, daemon and workspace typechecks, guard, and the 25-unit task harness.
* fix(daemon): report verified child runtime versions
Use versions observed from resolved CLI probes or Vela's ACP handshake when annotating Child observations. Correct Native OpenCode's allowlist key, add Claude and Vela/OpenCode companion provenance, reject Vela probe/handshake disagreement, and ignore the legacy 0.0.0 placeholder.
Validated with 252 focused daemon tests, daemon and workspace typechecks, guard, the task harness, and a real local Vela --version/-v/ACP initialize build smoke.
* test(observability): record Vela child evidence matrix
Add a sanitized provider-backed Terra replay for main, tool, child success, child failure with parent recovery, cancel, timeout, and cross-process resume. Keep the unpublished Vela tuple outside the production registry while pinning its exact candidate and OpenCode companion evidence.
Validated with focused Vela consumer/capability tests, daemon typecheck, workspace typecheck, guard, and the task harness.
* feat(daemon): enable OD Next production profiles
Make prototype, presentation, marketing, and HyperFrames the exact global OD Next production set while preserving explicit off/observe, user plugin authority, and fail-closed runtime capability gates. Feed runtime-owned profile, route, output, input, and capability facts into the planning contract and keep all other artifact kinds on ordinary Open Design.
Use bounded executable-scoped CLI version probes so headless and restarted daemons can resolve exact tuples without a prior agent-list request or cross-binary cache contamination.
Validated with four provider-backed Terra CLI chains, focused daemon/contracts/plugin-runtime suites, daemon and workspace typechecks, guard, and the 25-unit task harness.
* feat(daemon): classify OD Next exact agent inputs
Route prompt contributors through one daemon-owned choke point and version their final-text, semantic-fact, transport, and exclusion ownership for the forthcoming canonical bundle. Send OD Next continuation stages directly through native sessions without rewrapping or re-seeding stable instructions.
Validated with focused daemon and contracts tests, daemon/workspace typechecks, and guard.
* fix(daemon): close OD Next input ownership gaps
Bind semantic request facts to their real daemon-system or request producers, cover the DSH profile exact-text transport, and reject lookalike prompt-bundle root names.
Validated with focused exact-input, production-server and DSH tests, daemon typecheck, task harness, and guard.
* feat(daemon): emit canonical OD Next prompt bundles
Add strict pure TypeScript Bundle and request-Turn serializers, then route OD Next request and continuation text through their versioned XML contracts.
Split Web prior transcript from the current turn without text subtraction, keep mutable Skill and attachment inputs explicitly pending, and preserve ordinary prompt behavior.
Validated with focused contracts, daemon and web tests, the real server fixture, package and workspace typechecks, and guard.
* fix(daemon): align OD Next bundle input ownership
Register the real canonical Bundle contributors and keep current-turn, prior-transcript, pending-fact, and research-query ownership explicit at the production seam.
Reject continuation task_run_index zero and cover Web framing, ordinary compatibility, fail-closed contributor drift, and real server prompt capture.
* feat(daemon): freeze OD Next user skills
Capture explicit Skill bodies and audited side files before task claim, persist verified task-owned bytes, and replay only the frozen package for OD Next runs.
Keep legacy single-Skill CLI requests and ordinary live staging compatible while adding Web/CLI multi-Skill coverage and fail-closed snapshot checks. Validated with focused server, contract, CLI, Web, typecheck, and guard suites.
* fix(daemon): harden OD Next skill snapshots
Require an explicit frozen package for every mapped OD Next task and fail corrupt task state before any live Skill staging.
Use bounded no-follow descriptor reads with race checks, preserve legacy CLI compatibility, and make materialized roots collision-resistant. Validated with focused, real-server, workspace typecheck, and guard suites.
* feat(daemon): freeze OD Next task input snapshots
Freeze OD Next attachments and canonical configuration before the atomic run/task claim, then route runtime access through durable task-scoped snapshots while preserving ordinary attachment behavior.
Validate immutable identities, transport references, continuation propagation, tamper failure, and the four production task types with focused contract and daemon coverage.
* fix(daemon): isolate OD Next runtime input projections
Keep canonical task snapshots daemon-only, rebuild a read-only per-Run projection, and deliver frozen document resources through ACP or OD_TASK_INPUT_DIR.
Harden source and snapshot reads with no-follow same-fd identity checks and bounded caps. Validated with focused runtime tests, contracts, daemon/workspace typechecks, guard, and the task harness.
* fix(daemon): harden OD Next snapshot transport boundaries
Reject intermediate symlinks and realpath escapes while loading canonical snapshot files, including identical-byte replacement targets.
Preserve ordinary ACP duplicate image ordering while suppressing only OD Next resource/image cross-duplicates. Validated with focused tests, daemon typecheck, guard, and the task harness.
* fix(daemon): preserve structured OD Next snapshot failures
Defer frozen Skill package errors from the Task 04 preclaim lookup to the existing post-claim failure boundary so mapped Runs remain durable JSON failures without orphan input snapshots.
Validated with the combined real-server repair, missing-package, and tampered-package regressions.
* fix(daemon): keep OD Next continuation cwd stable
Separate authoritative project cwd resolution from best-effort file inventory and tolerate entries deleted between directory enumeration and stat.
This keeps native-session identity stable while frozen task inputs replace live attachments during continuation.
* feat(daemon): persist exact OD Next prompt inputs
Persist the canonical first-turn Bundle and every deterministic continuation Turn with UTF-8 and SHA-256 identities, then send only those validated texts through runtime transports.
Fail closed on mapped task drift, tamper, legacy NULL identities, missing mappings, and cold continuation while leaving ordinary Runs on the existing live composer.
Validated with the 15-case automatic server acceptance twice, 223 focused daemon tests, daemon typecheck, guard, and the task harness.
* feat(daemon): observe exact OD Next prompt inputs
* feat(daemon): bind and observe Claude native subagents
Register the replay-verified Claude 2.1.233 tuple, bind complex Build Packages to daemon-issued native Agent handles, and derive completion only from durable child lifecycle evidence.
Export bounded parent-tool, Child Agent, and Child-tool spans through the existing legacy and OTLP task hierarchy without exposing raw tool inputs, outputs, provider ids, paths, or credentials.
Validated with focused contracts and daemon suites, the 17-case real-server chain, workspace typecheck, guard, and the 26-unit task harness.
* fix(daemon): repair OD Next rollout authority and controls
* fix(daemon): harden OD Next public canaries
* refactor(daemon): extract OD Next prompt preparation
* test(daemon): align plugin fixture with project schema
* test(e2e): assert daemon-owned prototype routing
* fix(daemon): restore OD Next task observability
Default eligible OD Next tasks to privacy-aware Langfuse hierarchy delivery through relay or direct sinks, with a versioned safe quality projection.
Persist lightweight representation ownership before suppressing mapped Runs, retain best-effort startup retry semantics, and preserve single-Run compatibility when Task delivery is not eligible.
Adopt the active payload relay for historical packaged launchers and cover the real sidecar environment handoff.
Validated with the full daemon suite, focused contracts/packaged/e2e/tools-pack suites, workspace guard, and workspace typecheck.
* fix(daemon): make OD Next capability fallback invisible
Admit capability-certified runtime adapters across CLI version drift while keeping recorded fixture versions as auditable evidence and reserving a narrow incompatibility denylist.
Fall back before Run claim on preparation failures, keep continuation bound to the frozen capability snapshot, and report truthful child coverage across Langfuse legacy and OTLP without replaying an active task.
Validated with focused contracts and daemon suites, the automatic server suite, pnpm guard, pnpm typecheck, daemon typecheck, LSP diagnostics, and git diff --check.
* fix(strategy): isolate OD Next automatic task routing
* fix(daemon): persist blocked strategy-task attribution
Every blocked strategy task now stores its gate reason codes and the
agent-visible turn text (blocked_reason_codes_json / blocked_visible_text)
so operators can diagnose a blocked task from the store alone. Covers the
coordinator blockTask gate, the automatic-continuation fail-closed path,
and startup run-terminal reconciliation.
* fix(daemon): accept form-only clarification turns by deterministic inference
A first Full-Plan request turn that renders exactly one question form and no
machine-protocol block has exactly one valid protocol meaning: outcome
clarification_required with an unlocked execution mode. The coordinator now
recovers that state deterministically instead of dead-ending the task as
blocked, stamps od_next_protocol_runtime_state_inferred into the accepted
state's reasonCodes for attribution, and keeps every ambiguous shape (extra
forms, recovered plan blocks, later stages, spent clarification budget,
any other protocol issue) fail-closed. This closes the observed field
failure where the agent's first reply produced a discovery form, the task
went blocked, and the user's form answer then failed with 409
STRATEGY_TASK_STATE_MISMATCH.
* feat(contracts,daemon): project blocked strategy-task attribution to clients
A task the OD Next protocol gate settles as blocked persists its
attribution (reasonCodes + agent-visible text) since
|
||
|
|
e34d823160 |
fix(web): gate PPTX export on a daemon-advertised renderer capability (#7224)
* fix(web): gate PPTX export on a daemon-advertised renderer capability
The export entry was offered whenever the artifact looked like a deck, so
in a deployment without an off-screen renderer (headless / container) the
button was always shown and always failed: `POST /export/pptx` answers 501
whenever `desktopSlideRenderer` is absent, and the check sits *before* the
editable/screenshot branch, so the mode picker in the modal was a choice
that could not affect the outcome either.
Ask the daemon instead of guessing. `/api/version` now carries an optional
`capabilities.slideRenderer`, computed from the very binding the export
routes guard on, so the advertisement cannot drift from the 501 they
return. It rides on the version payload because that is already the
"what runtime am I talking to" channel (packaged/channel/platform/arch),
already fetched once at boot, and — unlike `/api/app-config` — never
persisted: the same data dir can be opened by a desktop daemon today and a
headless one tomorrow, so a stored answer would be a stale claim.
The flag deliberately cannot be derived from `packaged`: a packaged binary
started as a plain daemon has no renderer.
Mixed versions are the delicate part. The field is optional and the client
treats absent as *unknown*, not false, so a newer web bundle keeps showing
PPTX against an older daemon that supports it but does not advertise it
yet. Only an explicit `false` hides the entry. A present-but-malformed
block is dropped rather than trusted, and an unresolved answer stays
retryable so one early fetch against a not-yet-ready daemon cannot pin
"unknown" for the session.
Client-side `isOpenDesignHostAvailable()` was not used for this even though
it is the existing desktop-only gate: it reports whether the web UI runs
inside the Electron shell, which is wrong in both directions here — a
browser pointed at a desktop-hosted daemon does have a renderer, and a
packaged daemon may not.
Also splits the two states that `unavailable: true` collapsed. A dead
daemon and a runtime with no renderer both produced "PPTX export is not
available here", which sends someone whose daemon is down to look at the
wrong problem. Callers keep the existing `'unavailable' in res` fallback
behaviour; only the message differs.
* test(web): pin the two unavailable reasons in the export specs
The three specs that asserted the old flat `{ ok: false, unavailable: true }`
now pin which reason they exercise: the two 501 cases as `no-renderer` and
the transport failure as `unreachable`. Without this the split is only
enforced by types, so a later edit could collapse both back onto one value
and every assertion would still pass.
* fix(web): let a late capability answer reach an already-mounted viewer
Review catch (thanks @nettee): the first shape probed once on mount and
ignored an unresolved result, so a probe that raced daemon startup or
blipped left `available` at null for the life of that mount. The gate then
stayed permissive and the entry could still be clicked into the very 501
this change exists to prevent. Worse, the module raced the app's own boot
fetch with a second request, so a failed probe here could keep the entry
showing even when the boot response had already reported `false`.
Three changes, all aimed at that gap:
- The boot fetch now feeds the cache. `fetchAppVersionInfo()` hands its
parsed payload to `recordAppVersionInfo()`, so the common path costs no
extra request and the two paths cannot disagree. The dependency runs
registry -> runtime-capabilities only, so there is no cycle; this module
no longer imports the registry and does its own minimal probe when it
still has to.
- Consumers subscribe. An answer arriving from boot, from a retry, or from
another mounted consumer now reaches every mounted viewer instead of
leaving each one pinned on its own failed probe.
- Unresolved probes retry with a bounded backoff (500ms / 1.5s / 4s) and
then stop. An unreachable daemon is its own visible problem and should
not be polled forever; a startup race is exactly what needs one more look.
Covered at the level the review asked for: a FileViewer spec where the
first probe cannot resolve, the entry is still offered (unknown means
assume available), and a later explicit `false` removes it. Verified red by
restoring the old single-shot behaviour — the late answer reaches nobody
and the entry stays.
* fix(web): recognise proxy-answered daemon outages as unreachable
Review catch (thanks @nettee): the daemon-down diagnostic did not actually
fire on the packaged / sidecar path, which is the common one. The proxies
in front of the daemon do not reject `fetch` when it is down — they answer.
The web sidecar ends a connection error as a plain-text 502, and Next's dev
rewrite as a plain-text 500 when OD_PORT points at a dead port. Both landed
in the generic `!resp.ok` branch and surfaced as "export request failed
(502)", so the new message this PR added never appeared where it was most
needed.
`state/projects.ts` already had exactly the right detector for this, kept
private. Extracted it to `runtime/daemon-proxy-failure.ts` and used it from
both places rather than growing a second copy.
Kept deliberately narrow: only a 500/502 that is `text/plain` AND carries a
connection errno counts. Widening to any 5xx would relabel genuine upstream
failures as an outage — the same misleading move in the opposite direction,
which is why the original detector was written this way. A JSON 502 stays a
semantic error, and there is a spec pinning that.
* fix(web): honour the unknown contract on both capability cache edges
Two compatibility edges from review (thanks @nettee), both of which broke
the same rule this PR is built on: absent or malformed means unknown, and
unknown must keep the entry visible.
- A resolved `false` outlived the daemon that gave it. If a page cached
`false` from a headless daemon and then talked to one that stops
advertising the field — a restart or a downgrade — the stale `false` kept
hiding the entry against a daemon we are supposed to know nothing about.
A fresh, valid response with no capability now drops the cache back to
unknown and tells mounted consumers, so listeners take `boolean | null`.
- The module's own probe trusted the capability without validating the
envelope it arrived in, so `{version:{capabilities:{slideRenderer:false}}}`
could publish `false` and hide the entry. That was self-inflicted: the
validation went missing when this module stopped importing the registry to
avoid a cycle, and nothing replaced it.
`isAppVersionInfo` now lives here and the registry imports it, so there is
one validator instead of two and the dependency still runs one way only.
Regression specs for both: `false` followed by an absent field returns to
unknown, and a capability in a malformed envelope is refused.
* fix(web): subscribe mounted viewers even when the cache is already resolved
Follow-on from the invalidation added in the previous commit, and the same
mistake one layer out: a viewer that mounted while the answer was already
`false` took the early-return path and never registered a listener, so when
a later valid response omitted the capability and invalidated the cache to
unknown, that viewer never heard it. Its entry stayed hidden against a
daemon the compatibility contract says we know nothing about — exactly the
bug the invalidation was added to prevent, just moved from the cache to the
consumer.
Subscribe unconditionally and only skip the probe when the cache is already
resolved. Spec covers the mounted case the loader-level tests could not:
render with `false` cached and the entry absent, invalidate, entry returns.
Verified red by restoring the early return.
* refactor(web): collapse the capability store into one atom with one writer
Four review rounds found six compatibility edges in this module, all the
same shape: `cached`, `inFlight` and the listener set were separate mutable
pieces, and each fix exposed another pair that could disagree — an
invalidation that forgot the in-flight promise, a validation path that
forgot the invalidation, a subscription that forgot to register. Patching
the seventh instance was not going to be different from the sixth.
So the state is now one snapshot behind one writer. `setSnapshot` is the
only thing that mutates it and always drops any in-flight probe, because a
promise created under a superseded answer can only report something this
state has already replaced. `loadSlideRendererAvailable` clears the promise
on settle rather than only on success, so an unanswered probe stays
retryable. Consumers read through `useSyncExternalStore`, so every mounted
viewer sees the same value including a later invalidation — there is no
longer a subscribe-or-not branch to get wrong.
That makes the two edges from this round unrepresentable rather than merely
fixed: a settled probe cannot be served after an invalidation, and a
malformed payload now routes through the same invalidation path as an
absent capability instead of early-returning past it.
One deliberate asymmetry: a transport failure does not invalidate. It is
not an answer, unlike a malformed body, so it must not erase what we know.
Covered by its own spec.
* fix(web): version probes and treat an unusable answer as unknown
Blocking catch (thanks @nettee): clearing `inFlight` stopped a stale promise
being *served*, but not a request already in the air from writing when it
lands. A probe issued before the boot fetch could resume afterwards and put
its `true` back over the boot answer's `false` — the entry returns on a
headless daemon and the next export hits the 501 this change exists to
gate. That was the half of "collapse the state" I had not done: the writes
were unified, but time was not.
Each probe now carries the generation it was issued under and drops its
result once a write has superseded it, so a request from the old world
cannot write into the new one.
Second, transport and parse failures are no longer the same branch. A 200
whose body will not parse IS an answer, just an unusable one, and the
contract says that means unknown — collapsing it with a dead socket let a
stale `false` survive it and keep hiding the entry. Both the boot fetch and
the direct probe now invalidate on an unparseable OK response while leaving
a known answer alone on a transport failure.
Specs: a deferred probe that resolves after a `false` is recorded, an
unparseable 200 through the boot path, and a transport failure that must
not erase what we know. The first is verified red by removing the guard.
Also replaced a spec that passed without exercising its path — a cached
answer short-circuits the direct probe, so the parse-failure case only
reaches the code through the boot fetch.
* refactor(web): read the capability from app state instead of a client store
Seven review rounds produced eleven findings, and ten of them were in the
client-side capability store rather than in what this PR is actually about.
The pattern was not bad luck: each fix added a variable to reconcile the
existing ones (`cached` -> `+inFlight` -> `+listeners` -> `+generation`),
which widened the interaction surface that produced the next finding. The
latest pair — a parsed JSON `null` throwing past the invalidation, and a
probe's `finally` clearing a newer probe's promise — are both interactions
between those variables, not defects in the gate.
The store existed to avoid prop-drilling a boolean that `App` already holds
in React state from its boot `/api/version` fetch. That trade bought a
tidier diff and paid for it with cache coherence, staleness, concurrency,
retry and subscription lifecycle — a distributed-state problem invented for
a value that was never distributed.
So the store is gone. `App` passes `appVersionInfo?.capabilities
?.slideRenderer ?? null` down the same path `metricsConsent` already takes,
and the gate reads a prop. No cache, no probe, no retry, no generations, no
subscription: every failure mode those rounds found is now absent rather
than fixed.
The three cases the contract cares about — `false` hides, `true` offers,
`null` (unknown) offers — are three renders in `FileViewer.test.tsx`. The
specs that exercised probe lifecycle went with the probe; they were testing
a mechanism, and the mechanism is what turned out to be wrong.
* fix(web): close the two paths that could still reach the 501
Both blocking notes from review, and both are consequences of how I threaded
the capability rather than of the gate itself.
- The design-system detail route mounts `FileWorkspace` directly, so it never
received the prop and `FileViewer` fell back to its `null` default. Since
unknown deliberately fails open, a headless daemon kept offering PPTX for
`system/artifacts/deck.html`. Threaded from the same App-level value the
project route uses; one source, two callers, rather than a second mechanism
for the second route.
- `appVersionInfo` came only from the one-shot boot fetch, and that helper
yields null on any failed or non-OK response. A daemon merely slow to come
up therefore left the capability unknown for the life of the page — and
unknown fails open, so the gate was defeated by a startup blip. It is now
re-asked once the daemon is reachable, which is the narrowest retry that
fixes it: keyed on the daemon becoming live rather than on a timer, and it
stops as soon as an answer arrives.
* fix(web): make the capability refetch schedule its own retry
Third incarnation of the same mistake, so worth naming: retrying on a
dependency change cannot recover from a failed probe, because a failure
changes nothing — `appVersionInfo` stays null, `daemonLive` was already
true, neither dependency moves, and no further attempt is ever issued. The
failure is sticky, `slideRendererAvailable` stays unknown, and unknown
deliberately fails open, so a headless daemon keeps offering an export it
can only answer with 501.
The retry now schedules its own next attempt instead of waiting to be
re-triggered: bounded with backoff (500ms / 1.5s / 4s / 10s), stopping the
moment an answer arrives. Bounded because an unreachable daemon is its own
visible problem and must not be polled for the life of the page.
* fix(web): cap the capability retry interval instead of the attempt count
Closing the residual case I flagged in my own audit rather than waiting for
it to be found: with a bounded number of attempts, once they were spent
`daemonLive` never flips again and nothing could ever ask, so the capability
stayed unknown for the life of the page — and unknown deliberately fails
open. That is the same hole the retry was added to close, just with a
smaller window (four consecutive failures against a daemon that had already
health-checked as live).
I argued for bounding attempts to avoid polling for the life of the page. On
reflection that traded a rare slow poll for a permanent fail-open hole,
which is the worse side of the trade. The backoff now settles into a steady
30s interval instead of stopping: it only runs while the answer is unknown,
it stops on the first answer, and it leaves no state the page cannot recover
from.
* fix: forbid caching /api/version and stop the two writers clobbering
Both blocking notes, and the first is a consequence of the change itself
that I should have caught: adding a capability turned `/api/version` from a
near-static fact about the build into an answer that differs by which daemon
is behind the URL. It was still served with no `Cache-Control`, so a browser
or proxy could reuse a pre-swap response and a stale `slideRenderer: true`
would be consumed as authoritative — reopening the export the gate exists to
hide. Now `no-store`, with a spec pinning it.
Second, `appVersionInfo` had two unsynchronised writers: the boot pass and
the capability retry. A boot response that resolved `null` after the retry
had obtained an explicit `false` would erase it and reopen the fail-open
entry; a slow but valid boot response could likewise overwrite a newer
answer. The boot pass now never writes `null` and never overwrites an answer
that is already present — a late boot response is by definition the older of
the two.
* refactor(web): ask for the capability at the point of use
Fourteen findings on this PR, and nearly all of them asserted one property:
the capability must not get stale or stuck, because unknown fails open. That
is a global property, and global properties cannot be established by local
patches — which is why each fix produced the next finding rather than
ending the series.
The property is global because the gate was reading a client-side replica of
remote state. A replica needs an initial fetch, a retry, invalidation, write
ordering, cache directives, multi-writer synchronisation, distribution to
consumers, and a refresh when liveness changes. Every one of those was a
finding here.
Deleting `runtime-capabilities.ts` last time did not end it because it moved
the replica into `App` rather than removing it. This removes it: the export
surface asks the daemon when it opens, uses the answer, and discards it with
the menu. If the daemon is swapped, the next open simply asks again.
Gone with it: the prop threaded through four components, the boot-fetch
coupling, the retry with its backoff, and the two-writer synchronisation.
Kept: the daemon-side capability, `Cache-Control: no-store`, the optional
field tolerance, and the export-reason split.
The pending window still fails open, and that is deliberate — but it is one
round trip inside a single menu opening and nothing carries it forward, so
unlike the replica it cannot get stuck.
* fix(web): start each export-menu opening from an unknown capability
The probe state outlives the menu — the component stays mounted — so the
previous opening's answer was still present on reopen. If the daemon was
replaced in between, the gate decided on an answer describing a daemon that
is gone: a stale `true` offers an export the new daemon can only 501.
My previous commit message claimed the answer was "discarded with the menu".
It was not; only the fetch was tied to the opening. Now the state is reset
when the menu opens, so the claim and the code agree.
The regression spec is not in this commit, deliberately. My first attempt
passed with and without the fix — the second response landed too quickly for
the stale window to be observable, so it asserted nothing. The second
attempt, holding that response open, fails in both directions, which means
my assumptions about the close/reopen path are wrong rather than the code
being wrong. Shipping either would be worse than shipping neither, so the
fix goes in now and the spec follows once I understand that path.
* fix(web): revalidate at the menu-to-modal handoff and state the contract
The blocking finding: clicking the PPTX row closes the menu and opens the
modal in one batched update, and the modal's confirm reads the same
capability value — so a `true` fetched at menu-open outlived its surface and
kept the confirm enabled against a daemon that may since only answer 501.
Because the two booleans flip in one batch, an OR of them never changes
across the handoff; the effect now depends on the booleans themselves, so
the handoff resets to unknown and asks again.
The capability contract is now stated once, at the probe:
1. An answer never outlives the surface opening that fetched it — every
opening, including the handoff, starts from unknown and re-asks.
2. Unknown fails open; only a resolved `false` hides the entry.
3. The daemon is the authority of last resort: a swap after the probe
resolves is unclosable client-side, and is caught by the export POST's
501, which now surfaces the no-renderer message.
Tests: the two vacuous positive cases now hold the response open and assert
against the resolved answer, and the reopen regression I owed exists. Its
three earlier failures had one cause worth recording: a stale `true` is
observationally identical to a pending probe — both fail open — so the
true-then-swap direction asserts nothing. The observable stale direction is
a lingering `false` wrongly hiding the entry on reopen, and that is what
the spec pins. Red-check matrix: removing the reset turns the reopen spec
red; reverting the deps to the OR turns the handoff spec red; mapping an
absent field to false turns the absent spec red.
* fix(web): recognise EPIPE as a proxy connection failure; drop a stray file
Both follow-ups from review.
EPIPE belongs in the errno set: the sidecar's own replay check classifies
exactly ECONNRESET and EPIPE as connection-level, and when a replay is not
applicable the EPIPE surfaces as a plain-text 502 containing `write EPIPE` —
which fell through to the generic "export request failed (502)" instead of
the daemon-down message. The classifier also had no direct tests since its
earlier ones were deleted along with the capability store, so it now has a
dedicated spec pinning both halves of its contract: every connection shape
the proxies actually emit is recognised, and JSON 5xx / errno-less text /
other statuses are not. Removing EPIPE turns exactly its case red.
`__diag.test.tsx` should never have existed in the repo: it was a local
scratch file for probing menu toggle mechanics, swept in by a `git add -A`
in the previous commit. It logged instead of asserting and leaked its fetch
stub into the rest of the file. Deleted.
* fix(web): key the capability probe to the export tab, not the popover shell
The popover shell can open on Share and sit there across a daemon swap;
switching to Export then presented the PPTX entry against the answer from
shell-open time. The export surface's opening is the tab becoming visible,
not the shell opening.
This is the terminating fix for the transition family, and the reason can
be stated as an equivalence rather than a hope: the probe's guard is now
the same predicate the consumers render under — the PPTX row mounts inside
`unifiedActionTab === 'export'` within the open popover, the modal confirm
under `pptxExportModalOpen` — and the effect's dependencies are exactly the
free variables of that predicate. A transition into visibility is by
definition a change of one of those inputs, so it necessarily re-runs the
probe. "A transition we forgot" is no longer a findable category: the
guard cannot be coarser than the render condition, because it is the
render condition.
Regression: false from the first daemon, switch to Share, swap, switch
back to Export with the new answer held open — the entry must show
(unknown fails open) and a second probe must have fired. Reverting the
guard to the popover shell turns exactly that spec red.
* fix(web): retry a failed probe within the opening; stop at a fieldless one
A probe that fails (daemon still starting, transient network) left the
whole opening at unknown, and unknown fails open — so a headless runtime
kept the entry clickable for the lifetime of that opening. Retried now with
a short bounded backoff that is cancelled by any surface transition:
nothing crosses surfaces, so the contract's first clause still holds for
the retry itself.
The distinction that keeps this from over-polling: a FAILED probe is worth
asking again, but a probe that SUCCEEDS without the capability field is an
old daemon — asking again cannot teach us more — so it stops at unknown on
the first valid answer. The two cases were previously collapsed into one
`null`, which is exactly why the earlier background-copy design kept
tripping over them; here they are separated at the only point that knows
the difference (did `fetchAppVersionInfo` return a payload or not).
Regression: first probe answers 500, the entry stays visible (unknown
fails open), the in-opening retry fires, and its held `false` then hides
the entry. Reverting to a single-shot probe turns exactly that spec red.
---------
Co-authored-by: Jason <>
|
||
|
|
1877bf607b |
fix(web): hide empty Skills source filters (#7298)
* fix(web): hide empty Skills source filters (#1374) Hide source filter options that have no matching skills and reset a stale source selection after refresh. Treat missing source metadata as built-in for consistent counts, filtering, and edit protection. Generated-By: looper 0.11.8 (runner=worker, agent=codex) * fix(web): scope Skills source reset to catalog Keep source selection stable when active filters temporarily hide a source, while resetting only after the catalog loses its last skill. Add regressions for filtered empty states and delete refreshes.\n\nGenerated-By: looper 0.11.8 (runner=fixer, agent=codex) |
||
|
|
2feaee4237 |
fix(web): validate onboarding runtime on Continue (#7164)
* fix(web): validate onboarding runtime on continue * chore: ignore local pi state * refactor(web): remove unused onboarding code * test(e2e): align onboarding Continue expectations * fix(web): discard onboarding validation results the user moved past Continue awaits a validation round trip before it persists the runtime configuration and finishes onboarding, but its closure is frozen at click time and nothing rechecked the intent afterwards. Two ways that surfaced: - Back stays enabled while the request is in flight. Clicking it returned the user to the model-source chooser, then the late success persisted the configuration they had just abandoned and completed onboarding under them. - The inputs being validated stay editable. A success for a replaced BYOK key still completed onboarding against the click-time configuration. `inputKey` already guarded what gets DISPLAYED — `visibleAgentTestState` and `visibleProviderTestState` fall back to idle once it drifts. Apply the same rule to what gets PERSISTED: `continueAttemptStillCurrent` requires the attempt to still be on the runtime setup step, on the same runtime, and against the inputs it validated, reading them from a ref the continuation can actually ask. |
||
|
|
6b9c6a09b1 |
fix(web): flatten message center list (#7231)
* fix(web): flatten message center list * fix(web): polish message center states --------- Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com> |
||
|
|
abcdc8a991 |
fix(web): separate updater from account capsule (#7237)
* fix(web): separate updater from account capsule * fix(web): anchor standalone updater prompt Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * fix(web): keep updater prompt visible in RTL Generated-By: looper 0.11.8 (runner=fixer, agent=codex) |
||
|
|
274bff46d2 |
fix(web): stop badging team plans as unlimited (#7202)
#7190 (mine) had `planUnlimitedTier` read a personal tier out of a team-namespaced id, so a Team Pro workspace saw 「无限使用」 on five models and its zero balance hidden from the credits pill. "Team plans are paid too" was the reasoning, and it is the wrong test — what matters is whether the plan funds usage without touching the wallet: • vela records in-plan usage through the `coding_plan` billing mode, which its schema constrains to personal tiers (`membership_tier_snapshot = ANY (ARRAY['go','plus','pro','max'])`), so a Team workspace never produces a zero-charge call; • `team_basic` is seats-only on top of that — `monthly_credits_per_seat` is 0 in the seeded team catalog. #7187's balance preflight already refuses to stand down for Team workspaces, so the badge was promising exactly what the preflight then blocked, and the pill was hiding the number that explains the block. Team ids now resolve to no tier: no badge, and the credits pill keeps showing their real zero. Personal go/plus/pro/max are untouched. |
||
|
|
c9eb23027f |
fix(viewer): preserve live HTML previews (#7156)
* fix(viewer): preserve live HTML previews * fix(viewer): sync saved text to retained preview * fix(viewer): prewarm retained file revisions * fix(viewer): expire saved preview adoption * fix(viewer): repaint retained previews on activation * fix(viewer): reconcile retained style history * fix(viewer): renew preview asset scopes without reloads * test(viewer): cover scope renewal in team previews * test(exports): expect managed preview bases |
||
|
|
ce791edbbb |
fix(web): hide a subscriber's zero balance, and badge unlimited models by plan tier (#7190)
* fix(web): hide the zero balance for subscribed plans
On Go / Plus / Pro / Max the popular models a user works with every day are
unlimited, so the wallet only meters flagship calls and legitimately sits at
$0.00. The workbench's top-right credits pill rendered that zero permanently
next to the avatar, which reads as "you are out of money" to a subscriber who
is not.
`shouldShowCreditsBalance` hides the pill for a subscribed tier whose balance
is exactly zero. Three cases deliberately keep it: a free plan (where zero is
the number that explains why hosted models are unavailable), an unresolved
plan (so the pill cannot flicker in and out as billing lands), and an
overdrawn wallet (the one case the user has to act on).
The tier table it asks lives in `state/plan-unlimited-models.ts` — the same
per-tier unlimited sets the model badge uses — so "which plans may hide their
zero" and "which models are unlimited" cannot drift apart.
* fix(web): badge unlimited models by plan tier, not just the campaign
The 「无限使用」 tag in the model chip and the compact model list was wired to
the DeepSeek V4 campaign alone, so it marked exactly two models for everyone.
What the product actually sells is an unlimited SET PER TIER — 3 models on Go,
4 on Plus, 5 on Pro, all 8 on Max — so a Pro subscriber saw no tag on Kimi
K2.7 Code (unlimited on their plan) while the metered MiniMax M2.7 sat right
next to it looking identical.
The badge now has two sources and the campaign stays first: inside the
campaign window DeepSeek V4 Pro / V4 Flash keep the campaign copy and tooltip
for everyone, free plans included. Only when the model is not a campaign model
does the badge ask the subscription, via `isUnlimitedModelForPlanTier`. BYOK
execution claims nothing — those calls are billed by the user's own provider.
Pricing had the Pro row wrong in the same place: it listed MiniMax M2.7 as
unlimited and GLM-5.2 as metered, the reverse of what Pro includes. Fixed
there too, and `e2e/tests/pricing-unlimited-models.test.ts` now pins the
marketing table and the runtime table together across the app boundary so
editing one side alone fails.
* fix(web): drop the invented tooltip from the plan unlimited badge
The plan branch shipped a tooltip line nobody wrote as product copy (and 19
translations of it). The campaign branch's tooltip is campaign copy with a real
source; the plan case has none, so it now renders the badge text alone rather
than an invented explanation. `od-tooltip` and the aria-label only attach when
there is real copy behind them.
* fix(e2e): type the pricing-parity guard under noUncheckedIndexedAccess
`Typecheck workspaces` failed on the new guard: the e2e package compiles with
noUncheckedIndexedAccess, where every regex capture is `string | undefined`, so
the `match![1]` shape from the first draft does not compile there even though
apps/web accepts it.
Capture access now goes through `captureOne` / `captureAll` / `tierEntry`,
which throw a message naming what could not be found instead of asserting
non-null — so a renamed constant fails as "unlimitedByTier not found — did it
get renamed?" rather than as a TypeError.
* fix(web): reuse the shipped Unlimited wording in ru/uk
The plan badge's Russian and Ukrainian values were written from scratch
("Без лимитов" / "Без обмежень") while the campaign badge beside them — the
same word, already shipped and translated — says "Безлимит" / "Безліміт".
Two different words for one state in the same popover is a translation bug,
so the badge now reuses the shipped wording. The other 17 locales already
matched.
* refactor(web): fold the badge's plan table into the one #7187 landed
#7187 shipped `runtime/amr-unlimited-models.ts` — the same per-tier unlimited
sets this branch had added under `state/plan-unlimited-models.ts` — while this
branch was open. Two copies of a product promise is exactly the drift the
cross-app guard exists to prevent, so the duplicate is gone and everything
reads the surviving table.
The badge's extra needs moved into it rather than staying in a second file:
`planUnlimitedTier` reads the tier out of a team-namespaced id (`team_pro`,
`team_max_yearly`) and `isUnlimitedModelForPlanTier` compares the model slug
past any provider prefix. `isUnlimitedAmrModelForPlan` is untouched — the
balance preflight matches the personal ladder exactly on purpose, since Team
workspaces spend their own balance (#7187), and folding the two questions
together would have changed that behaviour silently.
`e2e/tests/pricing-unlimited-models.test.ts` now parses the surviving table,
including the `...GO_UNLIMITED_MODELS` spreads it composes tiers with, so the
Pricing page and the runtime stay pinned to each other.
|
||
|
|
b032abed00 |
fix(web): allow unlimited plan models at zero balance (#7187)
* fix(web): skip zero-balance gate for unlimited models * test(web): cover balance gate model argument * fix(web): suppress low-balance warning for unlimited models |
||
|
|
5459ffbc0f |
fix(pricing): align Go plan launch experience (#7167)
* fix(pricing): align Go plan launch experience * test(e2e): dismiss Go launch campaign in UI specs * fix(go-plan): finish launch campaign polish * test(e2e): preserve Go campaign visual coverage --------- Co-authored-by: Amy <1184569493@qq.com> |
||
|
|
f6331fb33a |
feat(web): add Go campaign entries and route upgrades to Pricing (#7122)
* feat(web): add Go campaign entries and route upgrades to pricing * fix(web): localize Go campaign copy Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * feat(web): match Go campaign demo UI * test(web): align Go campaign source contract * fix(web): align Go modal model icons * fix(web): preserve Z.ai mark in Go modal list * fix(web): localize and fit Go campaign modal Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * fix(web): clamp Go campaign modal height * fix(web): use local Go campaign icons Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * test(e2e): stabilize Go campaign viewport assertion Generated-By: looper 0.11.8 (runner=fixer, agent=codex) |
||
|
|
13f776fb6c | fix artifact delivery reconciliation (#5750) | ||
|
|
479078b44c |
fix(web): keep first Home run output live (#7071)
* fix(web): keep first Home run output live * test(e2e): bound design system restoration probe * fix(web): preserve live turn through authority reload Generated-By: looper 0.11.8 (runner=fixer, agent=codex) |
||
|
|
a50a71b656 |
fix(viewer): stabilize retained HTML previews (#7128)
* fix(viewer): stabilize retained HTML previews
* fix(viewer): finish motion on direct slide seeks
* Revert "fix(viewer): finish motion on direct slide seeks"
This reverts commit
|
||
|
|
4eacda5751 |
fix(web): preserve deck thumbnail layout fidelity (#7095)
* fix(web): preserve deck thumbnail layout fidelity * fix(web): detect ranged viewport media queries |
||
|
|
b084ab075b |
fix(release): allow Chromium sandbox in prerelease E2E (#7090)
* fix(release): allow Chromium sandbox in prerelease E2E * fix(web): cancel stale preview recovery probes |
||
|
|
879236e614 |
fix(workspace): restore last selection after restart (#7067)
* fix(workspace): restore the last selection on restart * test(workspace): cover persisted restart default * fix(workspace): reuse directory authority for context reads * test(workspace): align unknown seat invite behavior * test(workspace): cover unknown seat invite fallback * fix(workspace): preserve unknown invite capacity * fix(workspace): commit selection after persistence * fix(workspace): preserve unknown seat analytics * fix(workspace): guard stale selection cleanup * fix(workspace): make stale recovery atomic |
||
|
|
aa5bb5d67a |
fix: persist public file shares across daemon restarts (#7002)
* fix: persist public file shares across restarts * fix(daemon): redact unpersisted public snapshots Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * fix(web): expose public share recovery action Generated-By: looper 0.11.8 (runner=fixer, agent=codex) |
||
|
|
42faa84d0c |
fix(web): recover aborted srcdoc previews (#7055)
* fix(web): recover aborted srcdoc previews * fix(web): parse only top-level stylesheet imports * fix(web): preserve font import conditions * fix(web): latch srcdoc parser completion * fix(web): defer recovery while srcdoc parses * fix(web): confirm blank previews before reporting * test(e2e): cover preview white-screen monitoring * test(e2e): select the requested preview file |
||
|
|
67576cb0b6 |
fix(collab): keep workspace projects usable during outages and first open (#7027)
* fix(daemon): decouple local data plane from workspace authority * test(e2e): align workspace authority contracts * fix(daemon): preserve read-only team mirrors offline * test(daemon): preserve retryable result literal * fix(web): keep projects usable during workspace outages * test(web): keep collaboration mounted during outages * test(daemon): keep viewer comments local during outages * fix(collab): open team projects during materialization * fix(collab): keep project placeholders fail-closed |
||
|
|
f86340f199 |
fix(web): stabilize home examples during startup (#7037)
* fix(web): stabilize home examples during startup Keep the Home examples rail at stable geometry while Workspace discovery resolves, and reveal only the settled Workspace-scoped plugin catalog. * fix(web): preserve Home example geometry while plugins load Keep loading and settled Home example cards on one responsive size contract so plugin discovery cannot move the centered startup layout. Add browser geometry coverage at desktop, tablet, and narrow viewports. --------- Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> |
||
|
|
afdc388b7b | fix(web): stabilize HTML card thumbnail viewport (#7007) | ||
|
|
c99f952d6f | fix(web): restore message center close button (#6992) | ||
|
|
c58229e530 | fix(web): reconcile produced file summary (#7006) | ||
|
|
50c15417a9 |
fix(web): enable completion feedback by default (#7038)
Enable completion sound and desktop notifications for fresh configurations while preserving saved opt-outs. Activate browser audio and permission flows only from the first real task submission gesture. Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> |
||
|
|
8af057224c |
test(e2e): freeze motion in visual captures (#7039)
* test(e2e): freeze motion in visual captures * fix(web): pin placeholder carousel in visual captures Generated-By: looper 0.11.9 (runner=fixer, agent=codex) |
||
|
|
82f2a9b730 |
restructure Home creation hierarchy (#6914)
* restructure Home creation hierarchy * test(home): align Prototype expectations |
||
|
|
894e265c01 |
fix project update-ready rocket (#7004)
* fix project update-ready rocket * test(web): cover project chrome without identity Generated-By: looper 0.11.8 (runner=fixer, agent=codex) |
||
|
|
f1a0b60c6c |
chore: unify product name display to OpenDesign repo-wide (#6998)
* chore: unify product name display to OpenDesign repo-wide Rename the user-facing product name from "Open Design" to "OpenDesign" across documentation, web/desktop UI strings, i18n locales, prompts, plugin/skill/design-system content, changelogs, and specs, matching the landing-page rename already in flight. Deliberately excluded so installed-app identity stays untouched: - packages/release PRODUCT_NAME and everything derived from it (app bundle names, installer/registry identity, updater feeds) - tools/pack, tools/release, apps/packaged, landing page (PR #6983) - mocks/ recorded traces and LICENSE - test fixtures and comments that encode real on-disk install paths (Open Design.app, Program Files\Open Design, AppData paths) Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host test runs over the renamed strings. * fix(web): keep two-letter author monogram for CamelCase brand names authorInitials split words on whitespace only, so the renamed single-token author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper CamelCase boundaries as word breaks so the avatar fallback stays "OD". * fix(release): restore spaced Windows source artifact names in win.ps1 tools/pack still derives builder outputs from the unchanged PRODUCT_NAME ("Open Design"), so the asset script must look for the spaced filenames. Matches the mac/mac-intel/linux asset scripts, which were already kept on the original names. --------- Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com> |
||
|
|
322ed407fa |
fix(web): clear removed skill mention picker state (#6920)
Co-authored-by: T-sanjay-ram <sanjayramctr07@gmail.com> |
||
|
|
38c1d11a6f | [verified] fix(web): coalesce full replay reattach events (#6926) | ||
|
|
d40d264f74 |
fix: derive workspace authority from project bindings (#6989)
* fix: derive workspace authority from project bindings * fix: preserve preview navigation without workspace params * fix: gate chat sends on authoritative history * fix: preserve derived auth and preview paths * fix: retain conversation during authority refresh * fix: clear transcripts across authority changes * fix: clear conversations after access revocation |
||
|
|
26a00dda71 | fix(web): tie artifact upsell to selected cloud model (#6995) | ||
|
|
262c1f8f00 |
fix: complete AMR environment switching (#6991)
* fix: keep AMR environment switching consistent * fix: package every AMR console environment * fix: discover daemon directly for profile switch * fix: apply selected AMR profile to billing * fix: gate personal runs by workspace balance * fix: complete feature-test account switching * fix: close AMR environment review gaps |
||
|
|
169dea1701 |
fix(web): default Home creation type to prototype (#6975)
* fix(web): default Home creation type to prototype * test(e2e): mock default prototype apply Generated-By: looper 0.11.8 (runner=fixer, agent=codex) |
||
|
|
9475b9817f |
fix(web): make deck thumbnails drive deck-stage previews (#6950)
* fix(web): sync deck-stage thumbnail navigation * fix(web): keep prototype annotations out of deck navigation * fix(web): distinguish legacy deck slides from annotations |
||
|
|
499eb402ba | fix(web): layer plugin modal above account chrome (#6912) | ||
|
|
cf094aad1c |
fix: batch streamed events and simplify project exports (#6915)
* fix: batch streamed message event persistence * fix: derive export scope from project id |
||
|
|
cc34d2d409 |
fix(web): restore message center CTA (#6894)
* fix(web): restore message center CTA * fix(web): unify expanded message card surface |
||
|
|
696f39263c | fix(web): show creator identity on recent projects (#6857) | ||
|
|
78baa52281 | fix(web): cover composer with move dialog (#6854) | ||
|
|
7b7214e52a |
reorder web home creation type priorities (#6892)
* fix web home media pill placement * swap UI Mockup and Slide deck positions |
||
|
|
34ad192de9 |
fix(web): recover aborted srcdoc preview navigation (#6882)
* fix(web): recover aborted srcdoc preview navigation * fix(web): close srcdoc recovery races * fix(web): version srcdoc base navigation |
||
|
|
7a37c1462d |
feat(export): add reliable standalone HTML export (#6855)
* feat(export): add standalone HTML export Bundle project-local HTML, CSS, JavaScript, images, fonts, workers, and iframe dependencies into one offline-safe file. Expose the same contract through the web download flow and od CLI, with structured failures and bounded output assembly. * fix(daemon): honor standalone source URL semantics Resolve document assets against local base href values, reject browser-invalid TypeScript and JSX sources with a structured 422, and count unquoted first-level resources before graph traversal. Validated with daemon tests, workspace typecheck, and guard. * fix(daemon): handle standalone data scripts and CSS URL tokens Preserve non-JavaScript script payloads as data blocks and scan CSS values without treating strings or comments as project dependencies. Validated with focused and full daemon tests, workspace typecheck, and guard. * fix(daemon): classify standalone export resources correctly Preserve non-HTML iframe MIME types, inline only resource-bearing link relations, and propagate unexpected Vite dist read failures instead of silently falling back. Validated with focused export tests, the full daemon suite, guard, and workspace typecheck. * fix(export): preserve version and stylesheet semantics Hide standalone HTML export for historical versions until dependency snapshots are available, avoiding an action that always fails. Keep disabled and alternate stylesheets as embedded link elements so standalone export preserves activation semantics. Validated with focused red/green coverage, the full web suite, pnpm guard, and pnpm typecheck. --------- Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> |
||
|
|
180bef2b7e | fix(web): remove deleted projects from workspace tabs (#6886) | ||
|
|
f9c07517b7 |
fix: make DeepSeek Harness cancellation authoritative (#6880)
* fix deepseek harness cancellation lifecycle * fix DeepSeek canceled status rendering * fix DeepSeek cancellation handshake race * feat support reasoning selection in composer * test DeepSeek pre-execute cancellation protocol * test reasoning selector visual surface |
||
|
|
eea8a8522d |
fix(collab): bound workspace refresh storms (#6871)
* fix(collab): bound workspace refresh storms * fix(collab): push workspace directory freshness * test(collab): follow identity-aware hub callbacks * fix(collab): reset authority on account changes * fix(collab): fence workspace identity transitions * test(collab): guard full identity reset |