Skip to content

Release 2026.7.20#237

Open
flazouh wants to merge 46 commits into
mainfrom
codex/release-2026-7-20
Open

Release 2026.7.20#237
flazouh wants to merge 46 commits into
mainfrom
codex/release-2026-7-20

Conversation

@flazouh

@flazouh flazouh commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • ship canonical session, transcript, and replay improvements
  • improve review workspace, composer, voice, and streaming UI behavior
  • align desktop, website, shared UI, and Rust release gates
  • add the 2026.7.20 changelog

Verification

  • full pre-push affected CI mirror passed
  • desktop: 3,033 passed, 3 skipped
  • website: 44 passed
  • shared UI: 459 Bun tests and 239 DOM tests passed
  • Rust nextest: 2,819 passed, 9 skipped
  • real Tauri WebView QA: 2 panels, composer enabled, 0 visible errors

Summary by cubic

Ships a rebuilt streaming reveal, scoped task transcripts, and a hardened session event sequence, plus review/composer UX upgrades, updated build gates, and CI reliability fixes. Removes a retired release feature flag and improves performance, stability, and clarity across desktop and website.

  • New Features

    • Streaming reveal rebuilt with adaptive buffering and four modes (instant, buffer, buffer+fade, block-fade). Select in chat settings. Honors reduced motion.
    • Task transcript dialog shows subagent history with tool activity and task output.
    • Review workspace polish: flat/tree file lists, diff options, per-file reset confirm, clearer status. Composer can answer single-question prompts inline. Pin a default model in the agent model selector.
    • QA: add select-project command to acepe-qa.
    • Stability/perf: stop streaming markdown remount flicker, pace block reveals, reduce diff re-renders, clear strict Svelte diagnostics, prepare SvelteKit aliases before tests, allow perf-benchmark headroom in CI, stabilize cache benchmark in CI, and satisfy Rust 1.97 release lints.
  • Migration

    • Cargo features: defaults are now empty. Enable whisper-native (or whisper-metal) to build whisper-rs. Use the websocket feature when needed (adds tokio-tungstenite, url, http).
    • Runtime no longer auto-downloads the Claude CLI. Use the managed-agent install/repair flow. install-from-source.sh no longer sets the old auto-download flag. The release workflow also drops the retired auto-download feature flag.
    • Transcript paging supports scoped reads; root calls now pass scope: { kind: "root" }. Consumers that need subtask history should request an operation scope.
    • Session-open timing field rename: ledger_journal_cutoff_ms -> ledger_projection_frontier_ms. Update any consumers of the timing JSON.

Written for commit 6ccbe5f. Summary will update on new commits.

Review in cubic

flazouh and others added 30 commits July 16, 2026 15:33
Give user messages a borderless tool-like shell with a header timestamp and copy control, deepen shared input wash, equalize header corner insets, standardize header icon buttons to icon-sm, and use a robot glyph for subtasks.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove .agent-guides/ and .ori/ — stale local tooling state that doesn't
belong in the repo.
whisper-rs now sits behind a whisper-native feature (whisper-metal implies
it) instead of shipping in the default feature set, so a normal dev build
no longer compiles whisper.cpp. Voice integration tests are gated on
whisper-native accordingly.

Runtime session creation no longer auto-downloads the Claude CLI if it's
missing (auto_download_cli is now a deprecated no-op on the transport);
provisioning goes through the explicit managed-agent install/repair flow
instead. install-from-source.sh drops the now-unused auto-download build
flag.
…thority

Introduce the session event sequence as its own durable per-session
delivery/claim watermark, allocated only by the SQLite
session_event_sequence authority and kept separate from graph revision and
transcript revision (documented in CONTEXT.md). Session state lookups now
carry an explicit delivery_event_frontier instead of re-deriving it from
last_event_seq, and live-turn evidence is judged from actual
operation/interaction/transcript state rather than raw runtime presence.

Add TranscriptScope (root vs. per-operation) so transcript row paging,
projection, and the row ledger can be read scoped to a single subtask
instead of only the root transcript — this is the backend half of scoped
subtask transcript viewing; the frontend consumer lands separately.

Also carries: session-creation-attempt idempotency for repeated "consumed"
attempts, CLAUDE_HOME-relative source-path normalization for imported
session files, and pure rustfmt reformatting in model_catalog.rs.

This is a large, tightly-coupled cross-cutting change (60 files) — grouped
as one commit because splitting the Rust module graph further would leave
intermediate states that don't compile.
Add a dialog that pages through a subtask's own transcript, scoped via the
new backend TranscriptScope (kind: "operation"), instead of only showing
the root session transcript. TaskTranscriptDialogController tracks
open/loading state and revision-gated stale-page retries per (session,
panel, root row, operation) identity; TaskTranscriptDialogPresentation maps
that state to the AgentTaskDetailPresentation the row renderer consumes.

scene-content-viewport.svelte wires the controller in, syncing open dialogs
to graph revision changes and resolving a binding for each task row on
demand. transcript-viewport-row-renderer.svelte's props were reshaped to
take rowId/rowIndex/entry directly (plus taskDetailBindingFor) instead of a
single `rendered` blob, and transcript-rows-controller now requests root
row pages with an explicit `scope: { kind: "root" }`.
…reveal

Retire the standalone attention-queue sidebar card (queue store, queue
components, the "attention queue enabled" setting, and its persisted
workspace/expanded state) in favor of inline session-attention reveal rows
inside each project's session list, driven by the new
store/session-attention and components/session-attention modules and the
new panel-reveal in/out transition. Session-attention status icons use a
new Cursor-theme status palette (--cursor-status-*) added to
design-tokens.css/app.css.

Also drops three other overlay surfaces that had accumulated alongside the
queue: the legacy open-project-dialog (superseded by the unified
add-repository/project-discovery flow), the file-explorer-modal (superseded
by ProjectFileSystemDialog, already wired in main-app-view.svelte), and the
source-control dialog / git-panel legacy panel state (github-badge and
session-list no longer open a git dialog on click). main-app-view.svelte,
main-app-view-state.svelte.ts, app-sidebar.svelte and related tests are
touched by all of the above since they wired every one of these surfaces.

This is a large, tangled UI-simplification pass touched by one team; kept
as a single commit rather than force a false split between the queue
rename and the dialog removals, since both land in the same call sites
(main-app-view.svelte, panel-store.svelte.ts, workspace-store.svelte.ts).
Track hasLocalPendingSendIntent through the live-session presentation
pipeline (tab-bar, urgency tabs, session-status-mapper, local-placeholder-mode)
so a session that is still missing_canonical or lifecycle-"detached" right
after an optimistic send shows a connecting/planning placeholder instead of
an error state. Error surfacing now only fires on lifecycle.status ===
"failed", not on "detached" transiently.

Also fixes graph-patch reducing so activity and the active-streaming-tail
only reset to idle/null on an actual terminal turn state (Completed/Failed/
Cancelled) rather than whenever the command omits them, and makes composer
config-commit completion apply the accepted provisional mode/model/autonomy
values instead of re-reading committed state that may already have moved on.

Renames the "planning_after_tool" local placeholder mode to "planning" and
broadens when it shows (also triggers on a local pending send, not only a
trailing completed tool).
Add a per-agent, provider-scoped "default model" that's pinned from the
model selector (new AgentInputModelDefaultPin control, next to the
favorite-star toggle) and persisted alongside favorites in
agent-model-preferences-store. The default flows through toolbar model
resolution (resolveToolbarModelId now falls back to it after the live and
provisional model) and through session-connection-manager, which applies
the agent's default model when switching modes if the current model isn't
valid for the new mode.

Also raises the favorites-affordance threshold from >=5 to >12 models and
sorts model groups by catalog order instead of alphabetically.
Follow-up to the tool-call header polish pass: wrap AgentToolRead in the
shared AgentToolCard surface, hide AgentToolTask's live "current tool" row
once the task itself is no longer pending, and unify the chip shell's
badge/inline radius on a single rounded-md surface (was rounded-sm for
badges) via a new exported CHIP_SHELL_SURFACE constant.
Replace the fixed-line-clamp thinking viewport with a grid-template-rows
0fr<->1fr collapse animation (no JS measurement, spacing lives on the inner
element so the closed track never leaves a residual strip) and drop the now
-unused visibleLineCount/clampVisibleLineCount policy knobs.

Note: the duplicate desktop-side thinking-viewport-{policy,follow}.ts
modules (and their tests) were already deleted in the preceding
"drop local agent-tooling snapshots" commit — they were pre-staged in the
index before this cleanup pass started and got swept in there instead of
here.
The smooth token-reveal mode was missing its animation-name binding since
a prior migration, so [data-sd-animate] elements never actually ran the
reveal keyframes. Restore animation-name: var(--sd-animation,
sd-acepeTokenReveal) plus explicit opacity/timing-function/fill-mode so
the fade-in visibly plays, and rescope will-change to match. Also fixes a
native-markdown text-node keying collision (nextKey wasn't applied to the
word-index branch) and adds coverage for both.
Add a `select-project` CLI command that selects --project-path inside an
exact --panel-id after read-only path resolution and rejects ambiguous
project names, plus the supporting interact.ts driver
(selectPanelProject) and schema types. Extends test coverage across the
first-send and session-open probe summaries and the interact/cli tests.
WebKit can fire a contenteditable insertText with U+001D (group separator)
on ArrowRight; block it in beforeinput and scrub any that still land in
the DOM or the serialized message on input/focus/sync, so stray control
characters never make it into a sent message.
The fallback formatter (used when a model has no canonical display
metadata) was naively title-casing every word, turning "gpt-4" into
"Gpt-4". Recognize common acronyms (GPT, AI, API, CLI, MCP, SDK, UI, URL)
and keep version-like tokens (e.g. "5.6") glued to the preceding word, so
"gpt-5.6-sol" reads as "GPT-5.6 Sol" instead of "Gpt-5.6-Sol".
Preview-pane launch config for running the desktop and website dev
servers by name.
The per-word token-reveal pipeline was dead in production: its scene
derived never evaluated (gated behind a dev-only trace flag), no reveal
DOM was ever created, and streaming words painted instantly. Removed the
whole system end to end ahead of a client-side presentation-buffer
rebuild that needs no Rust-side timing.

Deleted the AgentPanelScenePipelineController and its read models, the
sceneEntries prop chain, buildTokenRevealCss / token-reveal-motion /
token-reveal-scene-read-model, the markdown-prose sd-animate CSS, the
native-markdown reveal props, and the Rust AssistantTextDelta pipeline
(payload, envelope kind, live emission, runtime coalescing, RowTokenStream
and clockAnchor projection state). Also removed reveal-text-projection and
its scene-patch/stability helpers, which were collateral of the dead
controller; their text-holding-across-canonical-blanks behavior is
documented in docs/plans/2026-07-17-reveal-text-projection-spec-before-removal.md
for the rebuild to reconsider.

Kept the streaming-repro-lab shell (reveal calls neutralized) for the
rebuild to re-point. activeStreamingTail, reduceTranscriptDelta, and the
turnState-derived isStreaming are untouched — live text rendering is
unaffected.

Tree is knowingly red: pre-existing TranscriptScope-refactor Rust test
failures (18 on the prior HEAD, 17 here) and the pre-existing
model-selector display-name test remain; no new failures introduced.
Pure-TS engine that smooths bursty model output into a steady character
drip. Accepts target-text updates and releases characters at a
rate-adaptive cadence (msPerChar = min(cap, drainMs/remaining)) with
sub-frame time-banking, a backlog snap cap, and drain-on-end / flush-on-
boundary lifecycle. DOM-free with an injectable clock and frame scheduler;
13 deterministic pacing tests. Four modes: instant and block-fade pass
text through whole, buffer and buffer-fade drip. Shared core for the
website showcase and, later, the desktop transcript.
Design-review page at /design-system/streaming-reveal. Streams one
simulated ~260-word assistant reply into four panels at once in identical
~15-word bursts so the reveal strategies can be compared in real time:
instant, buffer (Zed-style adaptive drip), buffer+fade (drip + per-word
opacity fade), and block-fade (whole blocks fade as they complete).
Controls for drain-ms, burst interval, and reduced motion (defaults to the
OS setting). Consumes @acepe/ui/streaming-reveal; no engine changes.
Adds a reveal="word"|"block" prop (default "none", so the transcript is
unchanged). "word" fades each inline word as it first mounts; "block"
fades each top-level block. Pure CSS, opacity-only, mount-driven — an
element animates once when inserted, so with stable keys only newly
revealed nodes fade, never the re-rendered tail. Word parts are now keyed
by document position (word:index) rather than position+text, so a
character-dripping tail word updates in place instead of re-mounting and
re-firing its fade every keystroke. Rebuilds the streaming reveal the
correct way: local, presentation-only, no clock sync or projection state.
All four panels now feed the engine's smoothed visibleText into the
production NativeMarkdown component with a per-mode reveal prop, so the
page shows each strategy against real markdown (code blocks, lists,
emphasis) exactly as the app will render it. Replaces the three throwaway
plain-text renderers and their block/word split helpers.
The text-inline key was minted from the global nextKey counter, which
increments for every space and mark parsed before it — so a paragraph's
key shifted each time it grew a word while streaming. That re-keyed the
inline subtree and remounted every word span on every delta, restarting
each word's reveal fade from zero (buffer-fade rendered as a perpetually
washed-out, flickering block). Anchor the key to the first word's document
position (firstWordIndex), which is stable as the paragraph streams in, so
the subtree reconciles in place: only genuinely new words mount and fade.

Fixes the long-failing "preserves existing word DOM nodes when streaming
appends a new tail word" test, which was asserting exactly this. Verified
live: tagged word nodes survive across frames and their fades complete and
hold instead of restarting.
Top-level blocks were keyed by block.key, which derives from the global
nextKey counter and shifts as an earlier or in-progress block gains
content — so the streaming tail block remounted on later bursts and
re-ran its block-fade animation (flicker). Key blocks by position instead:
a streaming document only appends or grows its last block, so index keys
keep each block's identity stable and only genuinely new blocks fade.

Verified live: the in-progress list block now survives across bursts
(faded once, holds opacity 1) where it previously remounted each burst.
block-fade was passthrough — it painted each burst just like instant, so
its only distinction was a 250ms fade that read as noise. Now it drips
like the buffer modes (paced, not burst) and its consumer reveals only the
completed-block prefix (up to the last blank-line boundary), so a
half-typed block stays hidden until it finishes, then surfaces and fades
in as one unit. Result: blocks appear one at a time at the drip's pace —
distinct from instant's burst and from buffer's character stream.
Interpose the presentation-buffer engine on the streaming assistant text at
the markdown-text leaf: a per-tail controller mirrors the growing canonical
text (setTarget, display-only — visibleText never flows back), and renders
the smoothed prefix through NativeMarkdown with the matching reveal prop.
Four modes selectable via a new "Streaming reveal" chat setting (persisted
UserSettingKey chat_streaming_reveal_mode, default buffer): instant, buffer,
buffer-fade (word), block-fade (whole blocks, completed-prefix only).

Only live-streamed text animates — completed/replayed messages and the
non-transcript MarkdownText uses (plan dialog, file panel) render raw.
Honours reduced motion (engine + CSS), drains on stream end, flushes on
tab-hidden, tears the controller down on unmount. Pure UI presentation; no
canonical/GOD state touched.
Streaming the reveal fed a new, whole markdown string to NativeMarkdown
every drip frame, which re-parsed AND re-rendered the entire document —
O(message size) per frame. Measured live at ~26fps (avg 38ms/frame, p95
68ms), degrading as the reply grew (31ms → 44ms/frame at 7000 chars).

createNativeMarkdownParser() reuses each block object whose source
(token.raw) is unchanged since the previous parse, so a growing document
yields referentially-stable blocks for its completed prefix; the
position-keyed each then skips them and only the tail block re-renders.
Also removes the vestigial `wordCount` prop (threaded everywhere, read
nowhere) — it changed every frame and would have forced every block to
re-render regardless. Per-frame work is now O(tail), not O(document).
The controller-creation effect seeded the buffer with `created.setTarget(text)`,
which made the effect depend on `text` — so every streaming delta re-ran it,
destroying and recreating the controller and restarting the drip from zero.
The whole reply visibly reset and re-revealed ~hundreds of times per stream
(measured: 223 text-shrink events in one focused stream). Seed via untrack()
so the creation effect depends only on mode/reduced-motion; the feed effect
keeps the controller current as text grows.
parseIncompleteMarkdown was a dead prop on NativeMarkdown — streaming text
rendered partial syntax literally (`**bold` showed asterisks) then dropped
the markers when the token closed, flashing on every bold/code/link as the
buffer dripped through it. completeIncompleteMarkdown() now auto-closes the
trailing dangling token (**, *, _, ~~, inline code, links, open code fence)
before parsing, so partial syntax renders formatted immediately.

Conservative by design: ambiguous cases (list markers, intraword _,
space-flanked *, markers inside complete code spans/fences) are left as-is —
a false auto-close that mangles text is worse than an occasional flash.
39 unit tests incl. anti-corruption + idempotency guards. Wired behind the
prop markdown-text already passes as isStreaming.
The first auto-close pass blindly appended `**` on an odd count, which the
character drip turned into flashes: an opener with no content yet (`x **`)
became `x ****` (four literal asterisks), and a half-revealed close
(`**bold*`) became `**bold***`. Net effect made streaming WORSE (marker
shrinks 18 -> 25). completeTrailingRunMarker now has three cases: strip an
opener that's immediately followed by whitespace/end (can't open emphasis
per CommonMark left-flanking, avoids ****); finish a partial closing run by
appending only the missing char; otherwise close normally. Adds 7 drip-state
regression tests incl. a "no run of 3+ asterisks" guard.
A char drip through a multi-word bold (**every single**) hits "**every "
mid-token; case 3 appended the closer at the very end, producing "**every **"
whose space-before-`**` is an invalid close that renders literal asterisks.
Insert the closer after the last non-whitespace char instead, so the bold
region simply grows word by word ("**every** " then "**every single**") with
no marker flash. Adds multi-word drip regression cases.
The final drip artifact: the first `*` of a `**` opener is revealed alone
("reads *") for one frame before the second arrives, flashing a literal
asterisk. A marker followed by end-of-string can't open emphasis, so strip
a lone trailing `*`/`_` when it's preceded by whitespace (clearly an opener
attempt). Ambiguous closers ("word*"), space-flanked ("5 * 3"), list markers
and intraword underscores are left untouched. Live marker-shrinks now ~0.
flazouh added 10 commits July 18, 2026 01:06
…s enum

The streaming-reveal-mode key was added to the TypeScript UserSettingKey
union but never to the Rust UserSettingKey enum (the authority for
get_user_setting/save_user_setting). Both load and save were failing
silently with "unknown variant", so the picker never persisted and the mode
was stuck on the in-memory default "buffer" (a drip with no fade) — selecting
"Buffer + fade" appeared to do nothing and reset on reload. Add the variant,
its as_str mapping, and its all-keys entry so the setting round-trips.
Scrolling the transcript micro-flickered near edit diffs. Instrumented live:
no layout shift and no row position jumps, but <diffs-container> elements
were added+removed ~2x/frame during scroll. The render effect tore down and
rebuilt the pierre FileDiff (cleanUp + new FileDiff().render(), recreating
<diffs-container>) whenever ANY tracked dep changed — and a fresh themeNames
object reference threaded down through row reconciliation on each scroll frame
re-triggered it. Guard the effect with a value-based signature (cacheKey +
theme + theme names) plus container identity, so the diff rebuilds only on a
real content/theme change or a genuinely new container, never as a side effect
of scrolling. Predates the reveal work.
@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown

Too many files changed for review. (864 files found, 100 file limit)

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

19 issues found across 852 files

Not reviewed (too large): packages/desktop/src/lib/acp/session-state/__tests__/agent-panel-graph-materializer.test.ts (~9,831 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".claude/launch.json">

<violation number="1" location=".claude/launch.json:7">
P1: The `desktop` launch profile starts `vite dev` instead of the Tauri app, so native commands and the Rust backend are unavailable during a launch. Using the package's `tauri` script here keeps this profile aligned with the documented desktop workflow.</violation>

<violation number="2" location=".claude/launch.json:13">
P2: The launch profiles are not portable because their working directories point at a developer-specific absolute checkout. Repository-relative working directories (or the launcher's workspace-root variable) would let other contributors use these profiles.</violation>
</file>

<file name="packages/desktop/src-tauri/src/db/repository/session_event_sequence.rs">

<violation number="1" location="packages/desktop/src-tauri/src/db/repository/session_event_sequence.rs:13">
P2: A valid zero frontier in `session_event_sequence` now makes every caller of `last_assigned_event_seq` fail instead of treating it as the initial frontier. The repository validation should match the database invariant (accept zero, or tighten/migrate the schema to guarantee that rows can never contain zero).</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/transcript_viewport/ledger.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/transcript_viewport/ledger.rs:28">
P3: The projection constant now advertises v22, but the migration note for this change is labeled v20. Keeping those identifiers inconsistent makes future ledger invalidation history ambiguous; the note should use the version actually introduced here or document the intervening versions.</violation>
</file>

<file name="packages/desktop/src-tauri/src/cc_sdk/types/options.rs">

<violation number="1" location="packages/desktop/src-tauri/src/cc_sdk/types/options.rs:165">
P3: Rust callers can continue using `auto_download_cli` without any compiler deprecation warning, even though both public APIs are documented as deprecated. Adding `#[deprecated(...)]` to the field and builder method, or removing the deprecated wording, would keep the public API signal consistent.</violation>
</file>

<file name="packages/desktop/scripts/acepe-qa/first-send-probe-summary.ts">

<violation number="1" location="packages/desktop/scripts/acepe-qa/first-send-probe-summary.ts:71">
P2: The new fail check can report success while the planning row is hidden: any hidden row whose text remains in the target panel makes `bodyText.includes("Planning next moves")` true, so `!sample.planningVisible` is false. The sampler should derive this signal only from an actually visible planning row before this gate relies on it.</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/transcript_viewport/projection.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/transcript_viewport/projection.rs:77">
P1: Nested transcript rows under non-Task operations are now projected into canonical operation scopes, but the ledger scope discovery still skips their provider-alias entries and does not add the rewritten scope. As a result, those rows are omitted from persisted/pageable transcript views; scope discovery should normalize the entry scope (or add the mapped operation scope) before applying the alias filter.</violation>
</file>

<file name="packages/desktop/src-tauri/src/lib.rs">

<violation number="1" location="packages/desktop/src-tauri/src/lib.rs:5">
P3: Every function in this crate now bypasses `clippy::too_many_arguments`, allowing unrelated or future command and persistence APIs to grow parameter lists without review feedback. Keeping the exemption on only the specific stable APIs preserves the lint elsewhere.</violation>

<violation number="2" location="packages/desktop/src-tauri/src/lib.rs:7">
P2: All enums in the crate now bypass `clippy::large_enum_variant`, so newly added serialized variants can introduce unnecessarily large enum layouts without a diagnostic. The exemption should remain on only the specific enums whose construction API must stay unboxed.</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/ui_event_dispatcher/dispatcher.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/ui_event_dispatcher/dispatcher.rs:336">
P2: Interaction responses can now publish a non-advancing graph revision when the session projection exists but its runtime lifecycle checkpoint is missing, because the registry's default graph revision (0) replaces the event-sequence fallback. Preserving the `last_event_seq` fallback for this recovery path would keep the interaction delta ordered and consumable.</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/session_restore/tool_link_audit.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/session_restore/tool_link_audit.rs:56">
P2: The stored-entry audit now reports `scoped_entry_count` as zero for sessions containing nested task operations, because `from_stored_entries` materializes only root-scoped entries and drops child scopes. This makes the new diagnostic inconsistent with the materialized audit; deriving the count from the scoped fold/projection or recursively materializing child entries would preserve the intended signal.</violation>
</file>

<file name="packages/desktop/src-tauri/Cargo.toml">

<violation number="1" location="packages/desktop/src-tauri/Cargo.toml:109">
P1: Rust CI and the desktop release build now fail before compilation because both workflows still pass `--features auto-download`, but this feature was removed from the manifest. Updating those invocations to the intended feature set (or retaining a compatibility alias) would keep the release gate buildable.</violation>
</file>

<file name="packages/desktop/src-tauri/src/db/repository/session_journal.rs">

<violation number="1" location="packages/desktop/src-tauri/src/db/repository/session_journal.rs:85">
P3: Session journal writes now have two independent transaction implementations, so future changes to sequence allocation, rollback, or insertion behavior can silently apply to only one event type. A shared helper that builds the payload after allocation would keep these paths consistent.</violation>

<violation number="2" location="packages/desktop/src-tauri/src/db/repository/session_journal.rs:88">
P3: Replay coverage does not verify the new `responded_at_event_seq` persistence. An assertion that the replayed question has `Some(3)` would guard the behavior this assignment is intended to provide.</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/commands/session_commands/state_lookup.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/commands/session_commands/state_lookup.rs:100">
P1: Active assistant streaming turns are treated as stale when no tool or interaction is pending. The trailing-entry check should also recognize `TranscriptEntryRole::Assistant`, matching `select_active_streaming_tail`, so `acp_get_session_state` preserves the running turn and partial assistant output.</violation>
</file>

<file name="packages/desktop/src-tauri/src/voice/commands.rs">

<violation number="1" location="packages/desktop/src-tauri/src/voice/commands.rs:73">
P2: Multilingual voice users can no longer select languages other than English because `voice_list_languages` now returns only Auto and English. Restoring the backend-supported language enumeration, while retaining Auto, keeps the language picker consistent with multilingual model support.</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/projections/operations.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/projections/operations.rs:169">
P2: An older durable response can overwrite a newer response in the live projection when concurrent reply tasks finish out of order: this unconditional insert replaces the current interaction regardless of `responded_at_event_seq`. Retaining the current snapshot when its response sequence is greater than or equal to `event_seq` would keep the projection consistent with journal order.</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/client_updates/mod.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/client_updates/mod.rs:48">
P2: These terminal mappings are not applied by the production canonical transcript fold: terminal updates return before the fold path consumes `ingress_fold_event`. The added test therefore validates only helper output, not production folding; either route terminal events through the canonical fold or remove this pre-normalized event plumbing.</violation>
</file>

<file name="packages/desktop/src-tauri/src/acp/client/cc_sdk_client/mod.rs">

<violation number="1" location="packages/desktop/src-tauri/src/acp/client/cc_sdk_client/mod.rs:432">
P2: Stopping or reconnecting while the initial stream is waiting for provider identity can leave the creation attempt permanently pending. Because this line removes the ID from the client and the bridge's failure cleanup is skipped when its task is aborted, the abort path should retain and explicitly fail/hand off the attempt.</violation>
</file>

Note: This PR contains a large number of files. cubic only reviews up to 200 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.

Re-trigger cubic

Comment thread .claude/launch.json
{
"name": "desktop",
"runtimeExecutable": "bun",
"runtimeArgs": ["run", "--cwd", "/Users/alex/Documents/acepe/packages/desktop", "dev"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The desktop launch profile starts vite dev instead of the Tauri app, so native commands and the Rust backend are unavailable during a launch. Using the package's tauri script here keeps this profile aligned with the documented desktop workflow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/launch.json, line 7:

<comment>The `desktop` launch profile starts `vite dev` instead of the Tauri app, so native commands and the Rust backend are unavailable during a launch. Using the package's `tauri` script here keeps this profile aligned with the documented desktop workflow.</comment>

<file context>
@@ -0,0 +1,17 @@
+		{
+			"name": "desktop",
+			"runtimeExecutable": "bun",
+			"runtimeArgs": ["run", "--cwd", "/Users/alex/Documents/acepe/packages/desktop", "dev"],
+			"port": 1420
+		},
</file context>

scope: &TranscriptScope,
) -> Vec<TranscriptViewportRow> {
let operation_links_by_entry = operation_links_by_entry_id(entries, operations);
let normalized_entries = normalize_entry_scopes(entries, operations);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Nested transcript rows under non-Task operations are now projected into canonical operation scopes, but the ledger scope discovery still skips their provider-alias entries and does not add the rewritten scope. As a result, those rows are omitted from persisted/pageable transcript views; scope discovery should normalize the entry scope (or add the mapped operation scope) before applying the alias filter.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/src/acp/transcript_viewport/projection.rs, line 77:

<comment>Nested transcript rows under non-Task operations are now projected into canonical operation scopes, but the ledger scope discovery still skips their provider-alias entries and does not add the rewritten scope. As a result, those rows are omitted from persisted/pageable transcript views; scope discovery should normalize the entry scope (or add the mapped operation scope) before applying the alias filter.</comment>

<file context>
@@ -74,10 +74,11 @@ fn project_transcript_viewport_entry_rows_for_scope(
     scope: &TranscriptScope,
 ) -> Vec<TranscriptViewportRow> {
-    let operation_links_by_entry = operation_links_by_entry_id(entries, operations);
+    let normalized_entries = normalize_entry_scopes(entries, operations);
+    let operation_links_by_entry = operation_links_by_entry_id(&normalized_entries, operations);
     let interaction_links_by_operation = interaction_links_by_operation_id(interactions);
</file context>

[features]
default = ["whisper-metal", "auto-download"]
auto-download = []
default = []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rust CI and the desktop release build now fail before compilation because both workflows still pass --features auto-download, but this feature was removed from the manifest. Updating those invocations to the intended feature set (or retaining a compatibility alias) would keep the release gate buildable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/Cargo.toml, line 109:

<comment>Rust CI and the desktop release build now fail before compilation because both workflows still pass `--features auto-download`, but this feature was removed from the manifest. Updating those invocations to the intended feature set (or retaining a compatibility alias) would keep the release gate buildable.</comment>

<file context>
@@ -103,14 +106,13 @@ libc = "0.2"
 [features]
-default = ["whisper-metal", "auto-download"]
-auto-download = []
+default = []
 manual-test-targets = []
-websocket = []
</file context>

|| transcript_snapshot
.entries
.last()
.is_some_and(|entry| entry.role == TranscriptEntryRole::User)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Active assistant streaming turns are treated as stale when no tool or interaction is pending. The trailing-entry check should also recognize TranscriptEntryRole::Assistant, matching select_active_streaming_tail, so acp_get_session_state preserves the running turn and partial assistant output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/src/acp/commands/session_commands/state_lookup.rs, line 100:

<comment>Active assistant streaming turns are treated as stale when no tool or interaction is pending. The trailing-entry check should also recognize `TranscriptEntryRole::Assistant`, matching `select_active_streaming_tail`, so `acp_get_session_state` preserves the running turn and partial assistant output.</comment>

<file context>
@@ -77,6 +77,29 @@ pub(super) fn resolve_state_lookup_authority(
+        || transcript_snapshot
+            .entries
+            .last()
+            .is_some_and(|entry| entry.role == TranscriptEntryRole::User)
+}
+
</file context>
Suggested change
.is_some_and(|entry| entry.role == TranscriptEntryRole::User)
.is_some_and(|entry| {
matches!(
&entry.role,
TranscriptEntryRole::User | TranscriptEntryRole::Assistant
)
})

Comment thread .claude/launch.json
{
"name": "website",
"runtimeExecutable": "bun",
"runtimeArgs": ["run", "--cwd", "/Users/alex/Documents/acepe/packages/website", "dev"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The launch profiles are not portable because their working directories point at a developer-specific absolute checkout. Repository-relative working directories (or the launcher's workspace-root variable) would let other contributors use these profiles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/launch.json, line 13:

<comment>The launch profiles are not portable because their working directories point at a developer-specific absolute checkout. Repository-relative working directories (or the launcher's workspace-root variable) would let other contributors use these profiles.</comment>

<file context>
@@ -0,0 +1,17 @@
+		{
+			"name": "website",
+			"runtimeExecutable": "bun",
+			"runtimeArgs": ["run", "--cwd", "/Users/alex/Documents/acepe/packages/website", "dev"],
+			"port": 5173
+		}
</file context>

// v17 persists independently pageable canonical transcript scopes and widens
// parent Task rows with graph-derived child scope/action facts.
pub const TRANSCRIPT_ROW_LEDGER_PROJECTION_VERSION: &str = "transcript_viewport_row:v17";
// v20 invalidates rows that were built before restored raw Claude sidechain

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The projection constant now advertises v22, but the migration note for this change is labeled v20. Keeping those identifiers inconsistent makes future ledger invalidation history ambiguous; the note should use the version actually introduced here or document the intervening versions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/src/acp/transcript_viewport/ledger.rs, line 28:

<comment>The projection constant now advertises v22, but the migration note for this change is labeled v20. Keeping those identifiers inconsistent makes future ledger invalidation history ambiguous; the note should use the version actually introduced here or document the intervening versions.</comment>

<file context>
@@ -25,7 +25,9 @@ use serde::{Deserialize, Serialize};
 // v17 persists independently pageable canonical transcript scopes and widens
 // parent Task rows with graph-derived child scope/action facts.
-pub const TRANSCRIPT_ROW_LEDGER_PROJECTION_VERSION: &str = "transcript_viewport_row:v17";
+// v20 invalidates rows that were built before restored raw Claude sidechain
+// files were discovered from Claude's per-session subagents directory.
+pub const TRANSCRIPT_ROW_LEDGER_PROJECTION_VERSION: &str = "transcript_viewport_row:v22";
</file context>
Suggested change
// v20 invalidates rows that were built before restored raw Claude sidechain
// v22 invalidates rows that were built before restored raw Claude sidechain

#[allow(clippy::type_complexity)]
pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
/// Automatically download Claude Code CLI if not found
/// Deprecated compatibility flag.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Rust callers can continue using auto_download_cli without any compiler deprecation warning, even though both public APIs are documented as deprecated. Adding #[deprecated(...)] to the field and builder method, or removing the deprecated wording, would keep the public API signal consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/src/cc_sdk/types/options.rs, line 165:

<comment>Rust callers can continue using `auto_download_cli` without any compiler deprecation warning, even though both public APIs are documented as deprecated. Adding `#[deprecated(...)]` to the field and builder method, or removing the deprecated wording, would keep the public API signal consistent.</comment>

<file context>
@@ -162,24 +162,10 @@ pub struct ClaudeCodeOptions {
     #[allow(clippy::type_complexity)]
     pub stderr_callback: Option<Arc<dyn Fn(&str) + Send + Sync>>,
-    /// Automatically download Claude Code CLI if not found
+    /// Deprecated compatibility flag.
     ///
-    /// When enabled, the SDK will automatically download and cache the Claude Code
</file context>

// consumers are target- or feature-specific. Keep these cohesive APIs available.
#![allow(dead_code)]
// Tauri commands and canonical persistence functions have stable, explicit argument lists.
#![allow(clippy::too_many_arguments)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Every function in this crate now bypasses clippy::too_many_arguments, allowing unrelated or future command and persistence APIs to grow parameter lists without review feedback. Keeping the exemption on only the specific stable APIs preserves the lint elsewhere.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/src/lib.rs, line 5:

<comment>Every function in this crate now bypasses `clippy::too_many_arguments`, allowing unrelated or future command and persistence APIs to grow parameter lists without review feedback. Keeping the exemption on only the specific stable APIs preserves the lint elsewhere.</comment>

<file context>
@@ -1,3 +1,11 @@
+// consumers are target- or feature-specific. Keep these cohesive APIs available.
+#![allow(dead_code)]
+// Tauri commands and canonical persistence functions have stable, explicit argument lists.
+#![allow(clippy::too_many_arguments)]
+// Boxing these serialized domain variants would change their public construction API.
+#![allow(clippy::large_enum_variant)]
</file context>

let tx = db.begin().await?;
let event_seq =
SessionEventSequenceRepository::allocate_in_transaction(&tx, session_id).await?;
interaction.responded_at_event_seq = Some(event_seq.get());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Replay coverage does not verify the new responded_at_event_seq persistence. An assertion that the replayed question has Some(3) would guard the behavior this assignment is intended to provide.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/src/db/repository/session_journal.rs, line 88:

<comment>Replay coverage does not verify the new `responded_at_event_seq` persistence. An assertion that the replayed question has `Some(3)` would guard the behavior this assignment is intended to provide.</comment>

<file context>
@@ -78,14 +78,23 @@ impl SessionJournalEventRepository {
+        let tx = db.begin().await?;
+        let event_seq =
+            SessionEventSequenceRepository::allocate_in_transaction(&tx, session_id).await?;
+        interaction.responded_at_event_seq = Some(event_seq.get());
+        let event = SessionJournalRecord::new(
             session_id,
</file context>

db,
tracing::debug!(session_id = %session_id, "Appending interaction snapshot journal event");

let tx = db.begin().await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Session journal writes now have two independent transaction implementations, so future changes to sequence allocation, rollback, or insertion behavior can silently apply to only one event type. A shared helper that builds the payload after allocation would keep these paths consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src-tauri/src/db/repository/session_journal.rs, line 85:

<comment>Session journal writes now have two independent transaction implementations, so future changes to sequence allocation, rollback, or insertion behavior can silently apply to only one event type. A shared helper that builds the payload after allocation would keep these paths consistent.</comment>

<file context>
@@ -78,14 +78,23 @@ impl SessionJournalEventRepository {
-            db,
+        tracing::debug!(session_id = %session_id, "Appending interaction snapshot journal event");
+
+        let tx = db.begin().await?;
+        let event_seq =
+            SessionEventSequenceRepository::allocate_in_transaction(&tx, session_id).await?;
</file context>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/desktop/src/lib/acp/components/agent-panel/components/agent-panel-resize-edge.svelte">

<violation number="1" location="packages/desktop/src/lib/acp/components/agent-panel/components/agent-panel-resize-edge.svelte:9">
P2: Keyboard and screen-reader users still cannot resize the agent panel: this exposes a static separator while the only resize path remains pointer dragging. If this is intended to be an accessible resizer, implement a focusable splitter with keyboard resizing and updated `aria-valuenow`/min/max (plus an accessible name); otherwise keep the pointer hit target from claiming an interactive separator role.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


<!-- Wider invisible hit target, inset to avoid overlapping header/footer controls. -->
<div
role="separator"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Keyboard and screen-reader users still cannot resize the agent panel: this exposes a static separator while the only resize path remains pointer dragging. If this is intended to be an accessible resizer, implement a focusable splitter with keyboard resizing and updated aria-valuenow/min/max (plus an accessible name); otherwise keep the pointer hit target from claiming an interactive separator role.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop/src/lib/acp/components/agent-panel/components/agent-panel-resize-edge.svelte, line 9:

<comment>Keyboard and screen-reader users still cannot resize the agent panel: this exposes a static separator while the only resize path remains pointer dragging. If this is intended to be an accessible resizer, implement a focusable splitter with keyboard resizing and updated `aria-valuenow`/min/max (plus an accessible name); otherwise keep the pointer hit target from claiming an interactive separator role.</comment>

<file context>
@@ -6,6 +6,8 @@ let { isDragging, onPointerDown, onPointerMove, onPointerUp }: AgentPanelResizeE
 
 <!-- Wider invisible hit target, inset to avoid overlapping header/footer controls. -->
 <div
+	role="separator"
+	aria-orientation="vertical"
 	class="absolute top-7 bottom-7 right-0 z-10 flex w-3 cursor-col-resize justify-end touch-none"
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant