Skip to content

refactor: collapse the duplicate stacks (PR 3/3)#169

Merged
EtienneLescot merged 6 commits into
release/1.8.0from
chore/ponytail-3-refactor
Jul 26, 2026
Merged

refactor: collapse the duplicate stacks (PR 3/3)#169
EtienneLescot merged 6 commits into
release/1.8.0from
chore/ponytail-3-refactor

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Last of three. #167 and #168 have merged, so this no longer stacks on anything — it has been rebased onto release/1.8.0 and stands alone. 55 files, −2,165 net.

This is the behaviour-changing set, so every claim here was re-verified against the code rather than taken from the review.

Verification

Gate Result
tsc --noEmit clean
biome check src electron clean (9 pre-existing warnings, untouched files)
vitest --run 96 files / 1108 tests pass

One provider stack, and two OAuth fixes

llm-call.ts opened with "no LangChain dependency" and hand-rolled a 736-line multi-provider fetch/SSE client — OpenAI-compat, Anthropic, Codex and Copilot dialects — while the live agent loop went through LangChain the entire time. Its only two remaining callers were one-shot prompt→text calls: chat compaction and caption translation. Both now use createOpenScreenChatModel(...).invoke(), which already knew every provider.

Removes llm-call.ts, codex-session.ts (a bespoke chatgpt.com Responses dialect reachable only from it) and agent-provider-capabilities.ts, whose reasoning-effort matrix was duplicated inside deep-agent/.

Copilot is fixed, not just preserved. I flagged this as the functional risk of deleting llm-call.ts. On inspection the risk ran the other way: chat-model.ts used the PAT directly with a comment saying the runtime-token refresh "can land as a follow-up" — so the main agent path was the broken one, and only the two side-features had it right. chat-model.ts now exchanges the PAT for the short-lived runtime token, honours the base URL that exchange returns, and sends the editor-identifying headers the endpoint requires.

Codex had the same bug, pointed the other way — caught in review. accountId was threaded from both new call sites into OpenScreenChatModelConfig and then read by nobody, while the deleted llm-call.ts:542 set chatgpt-account-id from it and called that header required by chatgpt.com/backend-api. So the reroute silently broke caption translation and compaction for Codex accounts. tsc cannot see this: an optional field that nothing reads still typechecks. The header is now wired at the same shared factory, so every caller benefits.

Two more review findings in the same area:

  • caption-translate built a model per batch. For Copilot that is a PAT → runtime-token exchange against api.github.com on every iteration; at batch size 40 a 400-segment transcript paid for ten of them. Built once, above the loop.
  • The surviving capability matrix was not the equivalent duplicate the deletion assumed. isOpenRouterReasoningModel strips the anthropic/ slug prefix but not openai/, and both vendor matchers are anchored at the start — so openrouter + openai/gpt-5 silently got no reasoning wiring at all. The deleted file stripped both and its deleted test asserted it. Fixed, and the tests for the module that survived are added back.

extractDelta became messageContentToText and moved to chat-model.ts: it was exported from deep-agent/service.ts, whose top level pulls langchain and the whole agent-tools graph — a lot to import for ten lines of content flattening. Provider aliases (claude → anthropic, gemini → google) are now normalized once at the createOpenScreenChatModel entry point, which also gives normalizeProviderId a caller again instead of leaving it dead.

Schema and store

Four document fields — agent, preview, export, history — were declared, defaulted, seeded in every test fixture, written on every edit, and read by nothing. preview.revision was bumped at eight sites and consumed only by tests asserting the bump happened. Gone, along with the ten schemas behind them.

timelineOperationSchema stays: the review called it dead, but AxcutTimelineOperation has 19 call sites. Only operationSchema, which extended it with suggestion ops nothing dispatches, goes.

Old projects are unaffected — documentSchema is not strict, so zod strips the now-unknown keys.

Seven useTimeline actions had no callers. editClip looked alive because it had its own test suite; the live Edit Clip modal actually calls tl.updateClipSourceRange + tl.updateClipCrop, so the action and its tests both went. useTimeline.ts drops 1192 → 970 lines.

A follow-up sweep caught what the first pass missed: migrate.ts still built the agent envelope on every migrated project (shipped code, not a fixture), and two fixtures still carried the removed fields. They survived for a reason worth knowing when reading the gate table above: tsconfig.json excludes **/*.test.ts(x) and its include is ["src", "electron"], so no test file and nothing under tests/ is typechecked — and vitest transpiles without checking.

showTrimWaveform

Removing TimelinePaneBody left this boolean declared, defaulted, persisted, migrated and history-tracked across five files with no reader and no UI able to set it. Removed, along with the settings.timeline.* keys in all 13 locales — the separate top-level timeline namespace (FloatingInspector, V4Timeline) is untouched.

Old projects keep working: migrate spreads input.editor wholesale into legacyEditor and legacyEditorSchema is z.object({}).passthrough(), so a stored showTrimWaveform rides along inertly rather than failing the parse. Pinned with a test rather than assumed.

Deferred, with reasons

Three items from the review are not here. Each needs something this PR cannot provide.

  • The native inspector channel (live.rs InspectorParams + 31 setNativeParam calls). My review said RightPanes' panes were unmounted; that was wrong — FloatingInspector.tsx:45 mounts them, so all 31 call sites are live. Removing the second write path means proving every one of those keys survives the scene push, which needs the app driven control-by-control. Not something to ship on a green typecheck.
  • 601 lines of "dead" CSS. LeftPanel.tsx:108 does styles[palette] — computed class access that static grep cannot see. No test would catch a wrong deletion; it would just silently break visuals. Pure styling is not worth that risk.
  • ModalShell → radix, LangButton → Popover. Carried over from PR 2 for the same reason: replacing hand-rolled focus/Escape/outside-click on eight live dialogs needs visual verification.

The remaining Rust cleanups (app.rs, the compositor.rs resize path, run_standalone, PreviewTransport) are mechanical and cargo check-verifiable, but belong with the inspector work since they touch the same files.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b54f5658-6731-4c5e-9299-f28d53a803ce

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/ponytail-3-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot
EtienneLescot force-pushed the chore/ponytail-2-stdlib branch from c5f501e to af79e27 Compare July 26, 2026 19:12
Base automatically changed from chore/ponytail-2-stdlib to release/1.8.0 July 26, 2026 19:22
document.agent, .preview, .export and .history were declared, defaulted,
seeded in every test fixture and written on every edit — and read by
nothing. preview.revision in particular was bumped at eight sites and
consumed only by tests asserting the bump happened.

Removes those fields, the ten schemas behind them (pendingQuestion,
preview, exportState, suggestion, agentState, operation, revision, plus
the three input schemas with no callers) and their inferred types.

timelineOperationSchema STAYS: the review called it dead, but
AxcutTimelineOperation has 19 call sites. operationSchema only extended it
with suggestion ops nothing dispatches, so that one does go.

Old saved projects are unaffected: documentSchema is not strict, so zod
strips the now-unknown keys on parse.

schema/index.ts 709 -> 604 lines. tsc clean; 95 files / 1114 tests pass.
llm-call.ts opened with "no LangChain dependency" and hand-rolled a
multi-provider fetch/SSE client — OpenAI-compat, Anthropic, Codex and
Copilot dialects, 736 lines — while the live agent loop went through
LangChain the whole time (chat-service.ts -> deep-agent/service.ts).
Its only two remaining callers were one-shot prompt->text calls: chat
compaction and caption translation. Both now use
createOpenScreenChatModel(...).invoke(), which already knew every
provider.

Removes llm-call.ts, codex-session.ts (a bespoke chatgpt.com Responses
dialect reachable only from it) and agent-provider-capabilities.ts, whose
per-provider reasoning-effort matrix was duplicated inside deep-agent/.

The Copilot runtime-token swap is preserved, and this fixes it rather
than porting it: chat-model.ts previously used the PAT directly with a
comment saying the refresh "can land as a follow-up", so the MAIN agent
path was the broken one. It now exchanges the PAT for the short-lived
runtime token, honours the base URL that exchange returns, and sends the
editor-identifying headers the endpoint requires. Every caller benefits,
not just the two being rerouted.

extractDelta (MessageContent -> text) is exported from deep-agent/service
rather than re-implemented.

tsc clean; 93 files / 1082 tests pass (the delta is the two deleted test
files, which covered the deleted code).
addClipBefore, addClipAfter, editClip, splitAndInsert, addTrimAt,
updateTrimRange and clearClipSelection were defined, memoised and
returned from useTimeline, and called by nothing. insertClipAt,
setTrimEntries and updateClipSourceRange already cover every UI path.

editClip had its own test suite, which is why it looked alive. The live
Edit Clip modal does not use it: NewEditorShell.tsx:1316 calls
tl.updateClipSourceRange + tl.updateClipCrop. The tests went with the
action.

Also removes RightPanes' TimelinePaneBody and the RightPaneId type, both
exported and unreferenced — FloatingInspector's Facet union replaced the
latter.

useTimeline.ts 1192 -> 970 lines. tsc clean; 93 files / 1079 tests pass.
Review follow-ups on the llm-call.ts removal. The reroute to
createOpenScreenChatModel was right, but three things did not survive it.

`accountId` was threaded from both new call sites into
OpenScreenChatModelConfig and then read by nobody. The deleted llm-call.ts
set `chatgpt-account-id` from it and called the header required by
chatgpt.com/backend-api, so caption translation and chat compaction were
newly broken for Codex accounts — the same bug class the Copilot fix
addressed, pointed the other way. tsc cannot see it: an optional field
that nothing reads still typechecks.

caption-translate built a model per batch. For Copilot that is a PAT ->
runtime-token exchange against api.github.com on every iteration; at
batch size 40 a 400-segment transcript paid for ten of them. Built once,
above the loop, with the construction failure kept in the same result
shape.

extractDelta is now messageContentToText in chat-model.ts. It was
exported from deep-agent/service.ts, whose top level pulls `langchain`
and the whole agent-tools graph — a lot to import for ten lines of
content flattening. chat-service.ts worked around it with a dynamic
import; caption-translate.ts did not. Both now import it statically with
nothing dragged along.

Also normalizes the provider once at the createOpenScreenChatModel
entry point. The deleted agent-provider-capabilities.ts resolved
`claude` -> anthropic and `gemini` -> google and had tests for it; the
surviving deep-agent copy compares raw strings, so those aliases stopped
resolving and normalizeProviderId was left with no callers at all.

And the surviving copy was not the equivalent duplicate the deletion
assumed: isOpenRouterReasoningModel strips the `anthropic/` slug prefix
but not `openai/`, and both vendor matchers are anchored at the start —
so openrouter + openai/gpt-5 silently got no reasoning wiring. The
deleted file stripped both and its deleted test asserted it.

Adds the tests back for the module that survived, plus runtime checks for
the account header and the alias routing.
migrate.ts still built `agent: {pendingQuestions, suggestions,
lastAppliedOperations}` on every migrated project. zod strips it, so it
was harmless — and it was in shipped code, not a fixture.

transcribe.test.ts still carried agent/preview/export, including a
`preview.playbackRate` that was never in the schema.

These survived because tsconfig.json excludes **/*.test.ts(x) and its
`include` is ["src", "electron"] — no test file and nothing under tests/
is typechecked, and vitest transpiles without checking. So the gate the
deletion leaned on could not see the fixtures it rewrote by hand.

Also drops the comments still describing `preview.revision`, a field that
no longer exists.

(The roadmap-coverage.spec.ts fixture this originally also fixed is gone:
release/1.8.0 deleted that spec in the v4-shell e2e retarget.)
The boolean was declared, defaulted, persisted, migrated and
history-tracked across five files and read by nothing. Its only UI was
RightPanes' TimelinePaneBody, which was already unreferenced before PR 3
deleted it — so the toggle had no way to be set and no effect if it was.

Removes it from EditorSettingsSnapshot, DEFAULT_EDITOR_SETTINGS,
LegacyShape, getEditorSettings, EditorSettingsPatch,
DEFAULT_EDITOR_APPEARANCE_SETTINGS, ProjectEditorState, the
projectPersistence parse branch, EditorState + INITIAL_EDITOR_STATE, and
the v2 migration's editor envelope.

The i18n keys go with it: settings.timeline.{title,waveform,help} in all
13 locales, not the 10 the note listed — vi, zh-CN and zh-TW were
missed. Those three keys were the whole `settings.timeline` namespace and
TimelinePaneBody was its only consumer; the separate top-level `timeline`
namespace (FloatingInspector, V4Timeline) is untouched.

Old projects keep working: migrate spreads input.editor wholesale into
legacyEditor, and legacyEditorSchema is z.object({}).passthrough(), so a
stored showTrimWaveform rides along inertly instead of failing the parse
— and getEditorSettings reads legacyEditor through a loose shape check
that ignores unknown keys. Pinned with a test rather than assumed, since
tsconfig excludes test files and would not have caught a regression here.

tsc clean; biome clean; 95 files / 1101 tests pass.
@EtienneLescot
EtienneLescot force-pushed the chore/ponytail-3-refactor branch from 8a40c81 to b5a60d6 Compare July 26, 2026 20:04
@EtienneLescot
EtienneLescot merged commit 36e3966 into release/1.8.0 Jul 26, 2026
7 checks passed
@EtienneLescot
EtienneLescot deleted the chore/ponytail-3-refactor branch July 26, 2026 20:08
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