* fix(ui): correct diff line numbers by padding hunks to their real file positions
When reconstructing before/after content from patches, hunks starting
at lines other than 1 were rendered starting at line 1. Pad blank lines
before each hunk so content aligns with actual file line numbers. Also
add hunkSeparators="simple" prop across all diff viewer instances and
guard against undefined patch parse results.
* refactor(diff): replace content reconstruction with Pierre's native FileDiffMetadata rendering
Migrate all diff viewers from manually reconstructing before/after strings
to passing FileDiffMetadata directly to Pierre's renderer. This ensures
hunk headers are the source of truth for line numbers without requiring
blank-line padding.
Key changes:
- Rename `contents()` to `normalize()` and return FileDiffMetadata
alongside before/after text for legacy consumers
- Introduce discriminated union DiffProps (DiffPair | DiffPatch) so
components accept either before/after or a pre-parsed fileDiff
- Thread `patch` field through openDiff payloads and DiffVirtualProvider
- Update DiffVirtualApp, PermissionDiff, and message-part to use the
new `normalize()` API and render via fileDiff prop
- Add test coverage verifying hunk start lines are preserved without
padding
BREAKING CHANGE: `contents()` export from session-diff is removed; consumers must migrate to `normalize()`
* fix(diff): normalize trailing newline in text() helper output
* refactor(ui): remove before/after fields from diff payload interfaces
The diff pipeline now relies exclusively on patch strings for rendering
and opening diffs. This eliminates the redundant before/after content
fields from DiffVirtualFile, PermissionFileDiff, OpenDiffFn, and
ApplyPatchFile interfaces across all packages.
- Drop before/after from all diff-related type definitions
- Simplify normalize() call sites to require only patch presence
- Make DiffText type private (no longer exported from session-diff)
- Update edit tool to render from tool input during pending state
- Fix DiffVirtualApp to nest markdown view inside patch guard
- Adjust contract tests to match new fallback behavior
* fix(kilo-ui): Move session-diff normalization logic into kilo-ui package
* refactor(ui): decouple DiffProps type hierarchy between shared and kilo-ui packages
Simplify the shared ui package's DiffProps back to a single concrete
type requiring before/after contents, and relocate the discriminated
union (DiffPair | DiffPatch with optional FileDiffMetadata) into the
kilo-ui package where patch-based rendering is actually needed.
- Revert shared DiffProps to a flat type without fileDiff variant
- Define extended DiffProps union in kilo-ui/src/pierre/index.ts
- Update kilo-ui diff components to import from local pierre module
- Expand OpenDiffFn signature with optional before/after fields
Extend the "Open in Diff Viewer" icon button to appear on Write tool
calls, not just Edit. Refactor shared CSS selectors to use generic
data-slot names (tool-trigger-actions) so both triggers share styling.
* fix(vscode): restore inline tool diffs
* fix(vscode): render tool patches in kilo ui
* style: format long regex assignments and add change marker comment
Reformat multi-line regex match assignments in kilo-ui-contract test
to satisfy line length limits, and annotate the `contents(diff)` call
in session-diff with a kilocode_change tracking comment.
* fix(kilo-vscode): append trailing newlines to expected diff content assertions
Update test expectations in diff-session-source to include trailing
newlines in before/after content, matching actual file content behavior.
* fix(vscode): guard empty-patch diffs in session turn accordion
Match diff-session-source.ts:99 behavior by short-circuiting contents()
when the patch is empty (binary or summarized files), so the accordion
content stays empty instead of rendering a confusing whitespace-only
diff.
---------
Co-authored-by: Imanol Maiztegui <imanol.mzd@gmail.com>
The kilo-ui Icon was capturing the registry lookup into a non-reactive
const at component setup, so callers that flip the icon prop (e.g.
toggling between thumbs-up and thumbs-up-filled when a feedback rating
changes) saw the SVG content stay frozen on the original icon.
Fix by reading local.name directly inside the JSX expressions so Solid
re-evaluates viewBox and innerHTML when the prop updates. Matches the
upstream Icon pattern.
Also rename the local 'def' temp to 'icon' as part of the inline
refactor — clearer alongside the 'icons' registry.
Addresses review feedback: Kilo-specific additions belong in Kilo-owned
packages so the diff against upstream OpenCode stays minimal.
- Icons: move thumbs-up/-down/-up-filled/-down-filled paths from
packages/ui/src/components/icon.tsx into packages/kilo-ui's icon
registry. Extend the kilo-ui Icon registry to carry per-icon viewBox
(Heroicons thumbs are 20x20; existing Kilo icons are 16x16). Replace
packages/kilo-ui/src/components/icon-button.tsx (previously a re-export
of @opencode-ai/ui/icon-button) with a local implementation that
uses the kilo-ui Icon so the new names resolve.
- i18n: move the three feedback strings (helpful, notHelpful,
clearRating) from each of 19 packages/ui/src/i18n/<locale>.ts files
into the corresponding packages/kilo-i18n/src/<locale>.ts. The
webview's language.tsx already merges kilo-i18n on top of upstream,
so the runtime keys remain available.
- TUI: extract submitFeedback from
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx into a
new Kilo-owned helper at
packages/opencode/src/kilocode/cli/cmd/tui/feedback.ts. The session
route now imports and invokes it, passing { toast, session, messages }.
Also tightens the Kilo Gateway gate from startsWith("kilo") to
=== "kilo" to match the equivalent fix in the webview.
- Replace the fractional hand-drawn thumbs paths with Heroicons 20
outline (default / rated=false) and solid (filled / rated=true).
- Add a CSS rule under assistant-copy-wrapper so hovering the outline
button fills it with currentColor, giving a preview of the rated
state before the user commits.
Adds thumbs up/down buttons next to the copy button on every assistant
message in the VS Code sidebar, and <leader>+/<leader>- keybinds in the
TUI. UI state is in-memory only — ratings reset on reload / session
switch. Persistence can be added later without changing the telemetry
contract.
Events are sent to PostHog via the existing telemetry pipeline. For
Kilo Gateway turns the payload includes session and message IDs so
feedback can be correlated against gateway logs; for direct providers
those IDs are omitted since we cannot correlate them to upstream data.
* feat(vscode): add foundational types and abstractions for unified diff panel
Introduce the core type definitions and abstractions that support the
upcoming session diff viewer:
- PanelSurface: testable abstraction over vscode.WebviewPanel
- DiffSource/DiffSourceDescriptor: contracts for swappable diff providers
- DiffSourceCatalog: stub registry for enumerating and constructing sources
- PanelContext/DiffFile: cross-cutting types shared by manager and webview
* feat(diff): implement worktree diff source with polling and file revert
Add WorktreeDiffSource, a DiffSource implementation that computes diffs
between the local working tree and the base branch. Key behaviors:
- Resolves the diff target from the workspace root via GitOps
- Performs an initial fetch then polls every 2.5s with hash-based dedup
- Supports single-file revert through WorktreeDiffClient
- Posts structured messages (loading, diffs) to the panel via DiffSourcePost
* add(vscode): session diff source and unified patch parser
Introduce two new modules under the diff sources layer:
- patch-to-before-after.ts: reconstructs before/after file content
from full-context unified diff patches by filtering line prefixes
- session.ts: SessionDiffSource fetches accumulated diffs for a given
session, converts patches to before/after pairs, and posts results
to the diff panel without polling or SSE
* feat(diff): wire up diff source catalog with session and worktree builders
Replace stub implementations in DiffSourceCatalog with real logic:
- Inject KiloConnectionService and use it to fetch session diffs
- Populate listAvailable based on PanelContext (workspace root, session)
- Route build() to WorktreeDiffSource or SessionDiffSource by id prefix
- Extract sessionDescriptor() and SESSION_PREFIX from SessionDiffSource
to decouple descriptor creation from class instantiation
- Export WORKSPACE_DESCRIPTOR from worktree module for catalog reuse
* refactor(vscode): introduce DiffPanelManager and centralize diff panel lifecycle
Extract panel ownership out of DiffViewerProvider into a new
DiffPanelManager class that manages a single global "Changes" webview.
Key changes:
- Add DiffPanelManager with source-swapping, loading pulse, webview
serialization, and disposable lifecycle management
- Add testable Scheduler abstraction to decouple setTimeout usage
- Extend DiffSourceCatalog with defaultSourceId() resolution logic
and replace magic "workspace" string with WORKSPACE_SOURCE_ID const
- Extract sessionSourceId() helper in session source module
- Thread sessionId through openChanges command and sidebar context so
the panel can open directly to the relevant session diff
- Re-register webview panel serializer under DiffPanelManager.viewType
* feat(diff): add source descriptor and capability message types for webview
Introduce new extension-to-webview and webview-to-extension message
interfaces to support multi-source diff panel switching:
- DiffSourceCapabilities, DiffSourceDescriptor for describing available
diff sources with revert/comments flags and grouping metadata
- SetAvailableSourcesMessage and DiffViewerCapabilitiesMessage for
pushing source lists and active capabilities to the webview
- SelectSourceRequest for the webview to request a source switch
* refactor(webview): integrate diff source picker into viewer and relocate shared types
Move DiffSourceCapabilities and DiffSourceDescriptor interfaces out of
extension-messages.ts into the shared diff/sources/types module, and
re-export them via import. Add DiffPickerHeader component with a
grouped Select dropdown for switching between multiple diff sources.
Wire source selection state and capability tracking into DiffViewerApp,
handling setAvailableSources and diffViewer.capabilities messages and
conditionally rendering the picker header above the diff view.
* feat(diff-viewer): add canRevert and canComment capability flags to full-screen diff
Introduce two optional boolean props (`canRevert`, `canComment`) on
FullScreenDiffView that allow callers to disable revert actions and
comment creation based on the active diff source's capabilities.
Guard gutter click handlers, keyboard shortcuts, the "Send all" button,
revert buttons, and sidebar revert callbacks behind these flags. In
DiffViewerApp, forward the capability values from the selected source
and reset transient UI state (comments, diff style, reverting set) on
source switches via a reactive effect.
* test(vscode): add unit tests for diff panel manager, source catalog, and patch utilities
Cover DiffPanelManager lifecycle (open, reveal, dispose, deserialized
panel disposal, source switching, comment forwarding), DiffSourceCatalog
listing/defaulting/building logic, SessionDiffSource fetch-and-convert
flow, and patchToBeforeAfter edge cases.
Also refactor DiffPanelManager to decouple panel creation from surface
adoption: the createSurface factory no longer receives a panel argument,
panel construction moves into a private defaultCreateSurface method, and
deserializePanel now disposes stale panels instead of rewiring them.
Extend the vscode mock with a createOutputChannel stub to support the
new test harness.
* refactor(diff): extract command-context assembly into DiffPanelManager.openFromCommand
Move PanelContext construction (workspace root resolution, session ID
lookup, initial source selection) out of the extension command handler
and into a dedicated openFromCommand method on DiffPanelManager. This
eliminates the extension's direct dependency on getWorkspaceRoot and
provider.getCurrentSessionId by accepting a sessionIdProvider callback
through DiffPanelManagerOptions.
Update tests to cover both provider-based and arg-based session ID
resolution paths in openFromCommand.
* feat(vscode): unify changes panel with multi-source diff architecture
Delete the legacy DiffViewerProvider in favor of the new
DiffPanelManager-based architecture. Decompose review-utils into
focused modules under diff/shared/ (client, hash, target) and update
all consumers to import from the new locations.
- Remove DiffViewerProvider and its registration in extension.ts
- Extract WorktreeDiffClient, DiffTarget into diff/shared/client
- Extract hashFileDiffs into diff/shared/hash
- Extract resolveLocalDiffTarget into diff/shared/target
- Slim down review-utils to only VS Code UI helpers
- Add changeset for the unified Changes panel feature
BREAKING CHANGE: DiffViewerProvider is removed; all diff viewing now routes through DiffPanelManager
* refactor(diff): add polling with hash dedup to SessionDiffSource
Upgrade SessionDiffSource from one-shot fetch to periodic polling
(2.5s interval) with hash-based deduplication, matching the pattern
already used by WorktreeDiffSource. Also clean up legacy references
in WorktreeDiffSource doc comments.
- Introduce POLL_INTERVAL_MS constant and start/stop polling lifecycle
- Track lastHash to skip redundant diff posts when content unchanged
- Extract fetchDiffs helper to share between initialFetch and poll
- Guard all post calls against disposed state
- Add comprehensive polling unit tests with scripted fetch helper
* fix(ui): rename worktreeStats() calls to session.summary() in ChatView
Update ChatView component to use the renamed `session.summary()`
accessor instead of the deprecated `session.worktreeStats()` method,
aligning with the unified multi-source diff architecture.
* feat(diff): add snapshots-disabled notice to session diff source
Introduce a `notice` message type in the diff source protocol so that
`SessionDiffSource` can warn users when snapshot tracking is turned off
for their repository. The check queries the workspace config before
fetching diffs; when disabled, polling is skipped entirely and a
warning banner is rendered in the diff viewer webview.
- Add `SnapshotEnabledCheck` callback wired through `DiffSourceCatalog`
- Extend `DiffSourceMessage` and `ExtensionMessage` with `notice` type
- Forward notice messages from `DiffPanelManager` to the webview
- Render warning banner with icon in `DiffViewerApp`
- Add CSS for `.diff-viewer-notice` component
- Cover new behavior with unit tests
* refactor(i18n): replace hardcoded notice strings with typed keys and localized messages
Convert the diff viewer notice system from passing raw English strings
through the message protocol to using well-known typed identifiers that
the webview resolves to translated text at render time.
- Define `DiffSourceNotice` union type in source protocol layer
- Define `DiffViewerNotice` union type in webview message contract
- Change `notice` field from `message: string` to `notice: T | undefined`
- Map notice identifiers to i18n keys in `DiffViewerApp` via lookup table
- Localize `DiffPickerHeader` labels and group names through `useLanguage`
- Add `diffViewer.*` translation keys across all 19 locale files
- Update unit tests to assert typed notice identifiers
* docs(changeset): update unified diff panel description
Clarify feature summary to mention sidebar badge counts and
snapshots-disabled warning alongside the source dropdown.
* feat(vscode): port markdown diff render ahead of main merge
Cherry-picks the user-facing bits of main's #9846 (render markdown diffs) into
this branch so merging main later is trivial for the 5 shared files. Wires the
markdown toggle through the new DiffPanelManager architecture as global state,
persisted via the kilo-code.new.diff.renderMarkdown setting.
* refactor(vscode): replace if-chain message dispatch with handler map
Extract DiffPanelManager.onMessage logic into a declarative
messageHandlers record and a dedicated onWebviewReady method.
Also fix import paths for DiffSourceDescriptor/DiffSourceCapabilities
to reference canonical source types, add explicit generic to Set<string>,
and extend webview tsconfig include to cover diff-viewer and diff-virtual.
* style(ui): add padding to select section headers
* fix: formatting
* fix(vscode): prevent stale source activation after panel teardown
Introduce an epoch counter to guard against race conditions where
activateSource completes its async initialFetch after the panel has
been disposed or the source has been swapped. The epoch is incremented
on every source teardown and checked before starting polling.
* refactor(vscode): scope source post guard to lifecycle epoch
Pass the epoch counter into createSourcePost so that messages emitted
by a source after its lifecycle has ended are silently dropped. This
closes a gap where polling callbacks could still push updates to the
surface after the source was swapped out.
* feat(diff-viewer): rename workspace source to "Local Changes" and add tooltip
Remove the "Workspace" source group, moving the worktree source under
"Git". Rename its label from "Workspace local" to "Local Changes" across
all locales and introduce a tooltip explaining that it covers all branch
changes vs the base (uncommitted files and local commits). The picker
header now renders option tooltips on hover with a delayed open.
* docs(i18n): translate workspace tooltip into all supported locales
Replace the English fallback string for
`diffViewer.source.workspace.tooltip` with native translations in ar,
bs, ja, ko, no, pl, ru, th, tr, uk, zh, and zht locale files.
* style(diff-viewer): rename parameter `d` to `desc` in DiffPickerHeader
Improve readability by expanding the terse single-letter parameter name
to a more descriptive `desc` across helper functions and the render
callback in DiffPickerHeader.
* refactor(diff-sources): replace hardcoded label with type-driven i18n key lookup
Replace the `label` string field in DiffSourceDescriptor with a
`DiffSourceType` discriminant (`"workspace" | "session"`). The webview
now composes i18n keys dynamically from the type, eliminating ad-hoc
id-matching logic in DiffPickerHeader and ensuring every source gets
both a label and a tooltip via the translation dictionaries.
Also adds `diffViewer.source.session.tooltip` translations for all
supported locales and introduces descriptor-type stability tests.
* fix(i18n): shorten session source label by removing "current" qualifier across all locales
* refactor(diff): extract shared polling interval constant into dedicated module
Consolidate the duplicated `POLL_INTERVAL_MS` definitions from
`session.ts`, `worktree.ts`, and the hardcoded literal in
`worktree-diff-controller.ts` into a single `DIFF_POLL_INTERVAL_MS`
export in `diff/polling.ts`. Update all consumers and tests to
reference the new canonical constant.
* feat(diff): replace DiffPanelManager with DiffViewerProvider and SourceController
Remove the `manager/` directory (DiffPanelManager, panel-surface,
scheduler) and introduce two focused replacements:
- `DiffViewerProvider`: owns the webview panel lifecycle, HTML rendering,
and message routing directly against `vscode.WebviewPanel`
- `SourceController`: manages the active DiffSource with epoch-based
stale-message filtering, decoupled from any webview abstraction
The PanelSurface and Scheduler indirection layers are eliminated in
favor of direct VS Code API usage in the provider and a simpler
epoch-guarded post function in the controller. Tests are rewritten
to exercise SourceController in isolation without fake surfaces.
BREAKING CHANGE: DiffPanelManager, PanelSurface, and Scheduler exports are removed
* refactor(diff): decouple SourceController from DiffSourceCatalog via dependency injection
Replace the direct DiffSourceCatalog dependency in SourceController with
two injected function callbacks (`build` and `listAvailable`), removing
the tight coupling to the catalog class. DiffViewerProvider now passes
thin wrappers that delegate to the catalog instance.
Update tests to use lightweight inline fakes instead of the FakeCatalog
subclass, and remove redundant polling/lifecycle tests that tested
implementation details rather than behavior.
* feat(diff): add lazy per-file content loading for worktree diffs
Introduce on-demand fetching of full file content (before/after) for
summarized diff entries. The worktree source now polls only a lightweight
summary and resolves detail via `requestFile` when the webview expands a
file.
- Add `requestFile` to `DiffSource` interface and implement in
`WorktreeDiffSource` using local git operations
- Wire `diffViewer.requestFile` message through DiffViewerProvider to
SourceController
- Add `diffViewer.diffFile` extension message to deliver single-file
detail back to the webview
- Webview merges incoming summaries with cached detail, tracks
per-file loading state, and auto-refreshes stale entries
* refactor(session): replace custom patch parser with shared kilo-ui session-diff utilities
Remove the hand-rolled `patchToBeforeAfter` function and its unit tests
in favor of the `normalize` and `text` helpers exported from
`@kilocode/kilo-ui/session-diff`. This eliminates duplicated parsing
logic and aligns the extension with the canonical diff reconstruction
used across the UI layer.
* test(diff): add hash utility tests and remove obsolete polling specs
Introduce a dedicated test suite for `hashFileDiffs` covering stability,
field sensitivity, and summarized-patch exclusion semantics. Remove the
now-unused polling tests and their helper functions (`scripted`, `wait`,
`modifiedPatchV2`) from the session source spec, reducing test surface
to match the current implementation boundaries.
* style(webview): add spacing for diff picker header component
* fix(diff): prefer workspace over session as default diff source
Change priority order in defaultSourceId so workspace source takes
precedence over session source when both workspaceRoot and sessionId
are present. Update ChatView to use renamed worktreeStats() accessor.
* fix(vscode): correct DiffViewerProvider path, bump max-lines cap, and apply font-size token
Update font-size arch test to reflect DiffViewerProvider's move into
src/diff/, raise KiloProvider max-lines eslint cap to 3500, and
replace hardcoded 12px in banners.css with --kilo-font-size-12.
Remove the `.kilo-diff-theme` utility class and its dedicated stylesheet,
relocating all Pierre diff engine custom properties into the centralized
`vscode-bridge.css` theme definition. This allows diff components to
inherit theming from the document root without requiring an explicit
class annotation on each container element.
- Delete `webview-ui/src/styles/diff.css`
- Drop `kilo-diff-theme` class from DiffPanel, FullScreenDiffView,
DiffVirtualApp, and PermissionDiff components
- Remove corresponding `@import` statements from chat.css and
agent-manager.css
Introduce `WithEditToolOpenDiffAction` story and supporting
`editCompletedPart` fixture with full filediff metadata to exercise
the `canOpenDiff()` code path. Force-reveal the hover-only action
slot via a scoped style override so visual regression snapshots
capture the icon-button layout.
- Add completed edit tool part with before/after file content
- Wire `onOpenDiff` prop through `AllProviders` into `DataProvider`
- Create dedicated `mockDataEdit` dataset for the new story
Introduce an icon button that appears on hover within the edit-tool
trigger, allowing users to open the diff viewer directly from the
tool header without clicking the file path.
- Add "Open in Diff Viewer" IconButton with tooltip inside edit trigger
- Extract openDiff logic into reusable handler with dedicated click handler
- Style the action button with opacity transition on parent hover/focus
- Add i18n string for "Open in Diff Viewer" label
* perf(vscode): stop O(N) reactive cascade on every streaming token
The webview DataBridge wrapped the whole session Data shape in a
`createMemo`, whose body walked `store.parts[msg.id]` for every message
in the session family. Any single part mutation — i.e. every token
delta — invalidated the memo, produced a fresh POJO, and invalidated
every downstream consumer (including O(N) scans inside each mounted
SessionTurn). On a 200-message session a Chrome CPU profile showed
three back-to-back 440ms main-thread blocks per SSE batch, ~46% of
the time in Solid reactive runtime alone.
Expose `data` as a plain object with reactive getters over
`session.allMessages`/`allParts`/`allStatusMap` so consumers reading
`data.store.part[Y]` subscribe to only that key. Removes the now-unused
`familyData` helper and its interface/mock entries.
* perf(ui): drop TextShimmer JS timer — CSS-only animation
A createEffect inside TextShimmer ran clearTimeout + setTimeout on every
`active` prop change to gate the sweep animation via a `data-run`
attribute. During LLM token streaming in long sessions, `active` props
(bound to `pending()` / `running()` accessors) thrashed as tools
started/finished across many shimmer instances. CPU profile of a 7s
streaming window showed ~2,500 timer operations — 16% of the blocked
main-thread time.
Remove the effect and drive the animation purely from the `data-active`
attribute. The opacity transition on the shimmer char (220ms) already
handles the fade, so visual behavior is unchanged. Adds one static
regression guard and one runtime perf assertion (with happy-dom) that
toggling the prop 1000 times results in zero timer calls.
* perf(kilo-ui): skip layout reads in GrowBox watch-mode ResizeObserver
The GrowBox component wraps each assistant part and, when watch=true
(which is set on the currently-streaming text part), runs a
ResizeObserver that called body.getBoundingClientRect() via
targetHeight() on every body-size change. During streaming this fires
at ~60Hz and each call forces a synchronous layout. CPU profile of a
7s streaming window showed 1,362 gBCR samples (~9% of blocked
main-thread time) all attributable to this path.
Reuse the browser's pre-measured contentBoxSize / contentRect from the
observer entries — no extra layout read. Also skip sub-pixel updates
(<2px) that the spring absorbs imperceptibly anyway, cutting per-token
spring work when tokens add tiny height deltas.
* perf(ui): coalesce markdown parse to one per animation frame
During LLM token streaming, the Markdown render effect ran
temp.innerHTML = content + morphdom on every content update. SSE
tokens arrive at 60–200Hz and each delta reparsed the entire
accumulated HTML. CPU profile of a 7s streaming window showed 2,940
ParseHTML events totaling ~619ms (~46% of blocked main-thread time).
Queue the latest content in a component-scoped pending variable and
run the morphdom pass inside requestAnimationFrame. K rapid updates
before the frame fires now collapse to one parse. The onCleanup
handler cancels any queued frame so it doesn't touch an unmounted
DOM. Fast-path is preserved untouched so non-streaming first paint
stays synchronous.
* chore(changeset): consolidate streaming-perf changesets into one
Per-commit changesets produced four nearly-identical release-note
entries. The user-visible change is a single perceptual improvement —
streaming is smooth in long sessions — so roll them up into one
feature-oriented entry.
* test(vscode): consolidate streaming perf tests + wire into CI
Replace three synthetic reactivity tests with a single end-to-end
streaming perf benchmark that:
- Renders the real TextShimmer component and asserts zero setTimeout/
clearTimeout calls during a 100-toggle burst (TextShimmer fix).
- Asserts per-key Solid reactivity: 100 text deltas on one message
must re-run only that message's consumer, not O(N) consumers
(DataBridge cascade fix).
- Uses only count-based assertions against deterministic APIs
(setTimeout/clearTimeout/innerHTML setter) — no wall-clock
thresholds, so it doesn't flake under CI load.
Also wire `bun run test:webview-reactivity` into the test-vscode
workflow so the benchmark runs on every PR that touches
packages/kilo-vscode, packages/ui, or packages/kilo-ui. Without this
wiring the perf regression guard would have shipped dormant.
* test(vscode): declare @happy-dom/global-registrator as devDep
The streaming-perf benchmark imports @happy-dom/global-registrator to
get a DOM for mounting the real TextShimmer component. It resolved
locally through workspace hoisting but CI's clean install didn't have
it. Make the dependency explicit.
* test(vscode): import TextShimmer via package export so JSX resolves
Using the deep relative path (../../../ui/src/components/text-shimmer)
made Bun's test transpiler apply kilo-vscode's tsconfig — which has no
`jsxImportSource` — so the .tsx file was compiled with the default
React runtime, producing "React is not defined" in CI.
Resolving through the package export (@opencode-ai/ui/text-shimmer)
picks up packages/ui/tsconfig.json which sets
`jsxImportSource: solid-js`. Works consistently across Linux/macOS/
Windows CI without needing bunfig-level JSX overrides.
* test(vscode): address bot review — add runtime coverage for Markdown + GrowBox, drop empty smoke test
Two kilo-code-bot findings on the streaming perf bench:
1. The 'benchmark completes quickly' smoke test timed an empty block,
so `elapsed` was always near zero and the assertion never fired.
Drop it — the real benchmarks below already complete in ~90ms.
2. The original file installed spy counters for innerHTML writes and
getBoundingClientRect but never asserted against them, leaving
Markdown rAF coalescing and GrowBox layout-read regressions silently
uncaught.
Add two runtime mirrors:
- Markdown rAF pattern: 100 async content updates coalesce to <20 parses
(would be exactly 100 pre-fix).
- GrowBox ResizeObserver pattern: 100 synthetic resize callbacks using
contentBoxSize/contentRect trigger zero getBoundingClientRect calls
(would be exactly 100 pre-fix).
Source-level regression guards in tests/unit/markdown-raf-coalesce.test.ts
and tests/unit/growbox-no-layout-thrash.test.ts cover the actual
component code. The runtime tests here prove the patterns the guards
require actually deliver the perf property at runtime.
Benchmark runs in ~90ms, 5 consecutive local runs all green.
* test(vscode): drop unstable TextShimmer runtime mount from streaming perf bench
The benchmark tried to mount the real @opencode-ai/ui TextShimmer to
assert zero setTimeout/clearTimeout calls. That required Bun's test
runner to transpile text-shimmer.tsx with Solid's JSX runtime, which
depends on tsconfig resolution walking up to packages/ui/tsconfig.json.
In CI (fresh workspace, different node_modules layout) this resolution
was unstable and kept falling back to React JSX ("React is not
defined").
Keep the three runtime patterns that don't need JSX transpilation
(DataBridge cascade, Markdown rAF coalescing, GrowBox contentRect),
plus the source-level regression guard at
tests/unit/textshimmer-no-timer.test.ts which asserts text-shimmer.tsx
contains no setTimeout/clearTimeout/createEffect/data-run. Together
these cover all four fixes without CI flakiness.
5 consecutive local runs pass in ~80ms.
* test(vscode): remove streaming perf tests
Static source-parsing guards and pattern-mirror runtime tests didn't
actually exercise the fixed component code — a regression in the real
code could have left them green. Remove them along with the
test:webview-reactivity script, the workflow step, the
@happy-dom/global-registrator devDep, and the tests/webview-reactivity
directory. The four perf fixes stand on their own; adding dubious
guards was worse than adding none.
* test(vscode): restore static perf-regression guards wired to real source
Restore four guards that each parse the actual fixed component source
and fail loudly if the fix pattern is removed:
- databridge-shape.test.ts reads webview-ui/src/App.tsx, asserts
`data` is not wrapped in createMemo
- textshimmer-no-timer.test.ts reads ui/src/components/text-shimmer.tsx
+ .css, asserts no setTimeout/
clearTimeout/createEffect, animation
gated on data-active
- markdown-raf-coalesce.test.ts reads ui/src/components/markdown.tsx,
asserts the render createEffect uses
requestAnimationFrame + cancelAnimationFrame
- growbox-no-layout-thrash.test.ts reads kilo-ui/src/components/grow-box.tsx,
asserts the ResizeObserver callback
does not call gBCR, uses contentRect/
contentBoxSize, and has the sub-pixel
delta guard
Verified by mutation: each guard fails when its fix pattern is removed
from the real source file and passes again once restored. Runs as part
of the existing test:unit script (no extra CI wiring).
Remove the standalone `packages/util` and `packages/server` workspace
packages, migrating all imports across app, kilo-ui, opencode, and ui
to use `@opencode-ai/shared/util/*` paths instead. Also fix the
VITE_KILO_CHANNEL env variable name in the type declaration and add a
null guard in the titlebar channel badge rendering.
* fix(vscode,ui): keep user scroll position while session is busy
Virtua's measurement-driven resize events race ahead of the debounced
user-scroll detection in createAutoScroll, snapping the viewport back
to the bottom while the user is mid-gesture. The QuestionDock's focus
call on mount also triggers the browser's focus-into-view behavior,
which yanks the view down whenever the user has scrolled up.
Fixes#9198
* fix(ui): guard recentlyInteracted against initial lastInteraction=0
* feat(kilo-ui): enhance MCP tool display with input/output sections and improved styling
- Add subtitle and args display to MCP tool triggers for better context
- Separate input and output sections with labels and dividers
- Format input and output as JSON with proper syntax highlighting
- Reposition copy button tooltip to prevent clipping in tool output
- Add styling for MCP section labels and tool dividers
* feat(kilo-ui): i18n for MCP input/output labels
* chore: add changeset
---------
Co-authored-by: Sylwester Liljegren <sylwester.liljegren@softronic.se>
* fix(agent-manager): preserve scroll position when agent edits files in diff viewer
Prevent the diff viewer from resetting scroll when the agent modifies files
in a worktree session. The root cause was twofold: mergeWorktreeDiffs created
new object references for every item on each poll, causing Solid's <For> to
re-render all <Diff> components (full Pierre DOM teardown); and the summarized
toggle caused unmount/remount cycles that collapsed container heights.
Now mergeWorktreeDiffs preserves referential identity for unchanged items and
keeps the existing reference for stale files (metadata changed but cached
content preserved), while immediately re-requesting fresh content in the
background. The <Diff> component also pins min-height during re-renders to
prevent layout shifts from Pierre's innerHTML teardown.
* test(agent-manager): update diff-state tests for MergeResult return type
The trailing / in directory paths was visually disappearing in diff
viewer headers because CSS direction:rtl interacted poorly with the
weaker LRE/PDF bidi embedding characters (\u202A/\u202C). Replace with
LRI/PDI isolate characters (\u2066/\u2069) which fully prevent the
surrounding RTL context from reordering boundary characters like /.
Update DiffVirtualApp and PermissionDiff to reconstruct before/after
content from the patch field when explicit before/after values are not
provided. Export the `text` helper from kilo-ui session-diff and add
`patch` to the PermissionFileDiff type.
In message-v2, refine stripMessageMetadata to only strip oversized
patches (exceeding MAX_DIFF_SIZE) rather than all patches, preserving
small patches so the UI can render inline diffs without additional
fetches.
Replace before/after content fields with unified patch field across
the diff pipeline. WorktreeDiff now generates patch strings via
createTwoFilesPatch, and all consumers (summary stripping, size
checks, content hashing, TUI sync, ingest queue) operate on the
patch field instead of before/after.
Consolidate model variant into the model object, removing the
separate variant field from MessageV2.User, PlanFollowup inject,
and resolveCodeModel interfaces.
Additional changes:
- Add session-diff module export to kilo-ui package
- Remove batch tool (batch.ts deleted)
- Fix WebSocket proxy to convert Uint8Array to ArrayBuffer
- Switch VscodeSessionTurn from useDiffComponent to useFileComponent
- Update SnapshotFileDiff type references in vscode extension
* tweak: use theme tokens for debug bar surface
* chore: update nix node_modules hashes
* feat(tui): add heap snapshot functionality for TUI and server (#19028)
* ci
* change model for changelog
* release: v1.3.2
* fix(opencode): skip typechecking generated models snapshot (#19018)
* Revert "fix(app): more startup efficiency (#18985)"
This reverts commit 98b3340cee.
* Revert "fix(app): startup efficiency (#18854)"
This reverts commit 546748a461.
* effectify Worktree service (#18679)
* fix: increase operations-per-run to 1000 and pin stale action to v10.2.0
The stale-issues workflow was hitting the default 30 operations limit,
preventing it from processing all 2900+ issues/PRs. Increased to 1000
to handle the full backlog. Also pinned to exact v10.2.0 for reproducibility.
* Add close-issues script and GitHub Action
- Create script/github/close-issues.ts to close stale issues after 60 days
- Add GitHub Action workflow to run daily at 2 AM
- Remove old stale-issues workflow to avoid conflicts
* Fix close-issues workflow permissions
- Add contents: read permission for checkout
- Use github.token instead of secrets.GITHUB_TOKEN
* Process issues sequentially to avoid rate limits
* Change issue close reason from not_planned to completed
* fix(opencode): avoid snapshotting files over 2MB (#19043)
* fix: provide merge context to beta conflict resolver (#19055)
* tweak: only spawn lsp servers for files in current instance (or cwd if instance is global) (#19058)
* fix: beta resolver typecheck + build smoke check (#19060)
* fix: unblock beta conflict recovery (#19068)
* electron: add createDirectory to open directory picker (#19071)
* electron: remove file extension from electron-store wrapper (#19082)
* app: pre-warm project globalSync state when navigate project via keybind (#19088)
* fix(app): move message navigation off cmd+arrow (#18728)
* Reapply "fix(app): startup efficiency (#18854)"
This reverts commit a379eb3867.
* Reapply "fix(app): more startup efficiency (#18985)"
This reverts commit cbe1337f24.
* fix(app): hash inline script for csp
* Revert "fix(app): startup efficiency"
* Reapply "fix(app): startup efficiency"
This reverts commit 898456a25c.
* fix(app): opencode web server url
* chore(app): markdown playground in storyboard
* chore(app): markdown playground in storyboard
* feat(core): initial implementation of syncing (#17814)
* chore: generate
* chore: bump modelcontextprotocol/sdk to 1.27.1 (#19064)
* chore: storybook tweaks
* feat: restore git-backed review modes with effectful git service (#18900)
* chore: generate
* chore: update nix node_modules hashes
* chore: cleanup
* chore: remove dead code for todoread tool (#19128)
* chore: storybook tweaks
* fix(opencode): classify ZlibError from Bun fetch as retryable instead of unknown (#19104)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
* fix(task): respect agent permission config for todowrite tool (#19125)
* fix(app): agent normalization (#19169)
* fix: Windows e2e stability (CrossSpawnSpawner, snapshot isolation, session race guards) (#19163)
* fix+refactor(mcp): lifecycle tests, cancelPending fix, Effect migration (#19042)
* effectify Bus service: migrate to Effect PubSub + InstanceState (#18579)
* file: use Effect.cached for scan deduplication (#19164)
* ignore: update disavowed list (#19184)
* skill: use Effect.cached for load deduplication (#19165)
* chore: generate
* fix: bump gitlab-ai-provider to 5.3.3 for DWS tool approval support (#19185)
* test: restore 5 workers on Windows e2e (#19188)
* fix(opencode): image paste on Windows Terminal 1.25+ with kitty keyboard (#17674)
* chore: update nix node_modules hashes
* wip: zen
* wip: zen
* go: do not respect disabled zen models
* fix: ensure enterprise url is set properly during auth flow (#19212)
* revert: roll back git-backed review modes (#19295)
* chore: generate
* tui: bypass local SSE event streaming in worker (#19183)
* feat: embed WebUI in binary with proxy flags (#19299)
Co-authored-by: BlankParticle <blankparticle@gmail.com>
* release: v1.3.3
* chore: generate
* changelog ci tweaks
* refactor(lsp): effectify LSP service with InstanceState (#19150)
* chore: generate
* feat: add gpt prompt so non codex gpt models have their own system prompt modeled after codex cli (#19220)
* feat(core): remove workspace server, WorkspaceContext, start work towards better routing (#19316)
* effectify Config service (#19139)
* chore: generate
* refactor(config): use cachedInvalidateWithTTL, bump effect to beta.37 (#19322)
* fix(mcp): close transport on failed/timed-out connections (#19200)
* fix(app): more startup perf (#19288)
* chore: generate
* chore: update nix node_modules hashes
* fix(app): don't bundle fonts (#19329)
* chore: generate
* fix(app): default shell tool to collapsed
* fix(app): remove fork session button
* fix(ui): reduce markdown jank while responses stream (#19304)
* fix: web ui bundle build on windows (#19337)
* refactor(effect): yield services instead of promise facades (#19325)
* chore: generate
* refactor(vcs): replace async git() with ChildProcessSpawner (#19361)
* fix(opencode): ignore generated models snapshot files (#19362)
* fix(ui): keep partial markdown readable while responses stream (#19403)
* chore: update nix node_modules hashes
* fix(app): persist queued followups across project switches (#19421)
* refactor(tool-registry): yield Config/Plugin services, use Effect.forEach (#19363)
* chore: generate
* tui plugins (#19347)
* chore: generate
* effectify Skill service internals (#19364)
* chore: update nix node_modules hashes
* effectify Plugin service internals (#19365)
* refactor(core): split out instance and route through workspaces (#19335)
* chore(app): more spacing controls
* fix(ui): make streamed markdown feel more continuous (#19404)
* fix(app): resize layout viewport when mobile keyboard appears (#15841)
* fix(desktop-electron): match dev dock icon inset on macOS (#19429)
* fix(app): default file tree to closed with minimum width (#19426)
* fix flaky plugin tests (no mock.module for bun) (#19445)
* tweak: add additional overflow error patterns (#19446)
* no theme override in dev (#19456)
* feat: AI SDK v6 support (#18433)
* refactor(session): effectify Session service (#19449)
* refactor(core): move more responsibility to workspace routing (#19455)
* chore: update nix node_modules hashes
* refactor(format): use ChildProcessSpawner instead of Process.spawn (#19457)
* chore: generate
* Single target plugin entrypoints (#19467)
* refactor(session): effectify SessionCompaction service (#19459)
* feat(ci): use Azure Artifact Signing for Windows releases (#15201)
* fix(app): more startup efficiency (#19454)
* update effect to 4.0.0-beta.42 (#19484)
* chore: update nix node_modules hashes
* tweak: adjust bash tool description to increase cache hit rates between projects (#19487)
* refactor(session): move context into prompt footer (#19486)
* refactor(prompt): remove variant cycle display from footer (#19489)
* feat: add model variant selection dialog (#19488)
* fix: restore subagent footer and fix style guide violations (#19491)
* tweak(session): add top spacing and remove obsolete docs prompt
* upgrade opentui to 0.1.91 (#19440)
* refactor(file): use AppFileSystem instead of raw Filesystem (#19458)
* chore: generate
* chore: update nix node_modules hashes
* kv theme before default fallback (#19523)
* feat: open dialog for model variant selection instead of cycling (#19534)
* refactor(session): effectify session processor (#19485)
* feat: dialog variant menu and subagent improvements (#19537)
* use theme color for prompt placeholder (#19535)
* fix: update opencode-gitlab-auth to 2.0.1 (#19552)
* chore: update nix node_modules hashes
* prompt slot (#19563)
* fix: respect semver build identifiers for nix (#11915)
* fix: nix embedded web-ui support (#19561)
* ignore: kill todo (#19566)
* chore: update nix node_modules hashes
* wip: zen
* wip: zen
* zen: ZDR policy
* ci: cancel stale nix-hashes runs (#19571)
* release: v1.3.4
* refactor: kilo compat for v1.3.4
* fix: migration types
* refactor: upgrade kilo-gateway to ai sdk v6
* refactor: improve upstream merge script
* fix: fix some tests
* style(kilo-vscode): adjust indentation and formatting in parts-util and PopupSelector
Normalize boolean expression indentation in isCompletionResult to use
consistent 4-space alignment and reformat PopupSelectorProps generic
interface declaration to split the Omit type across multiple lines.
* docs(kilo-docs): update auto-generated source links
Remove outdated URLs and add new bug report issue link pointing to
anomalyco/opencode repository. Drop references to kilocode bug report
template and config precedence order docs, reducing total unique URLs
from 262 to 261.
* fix(kilo-ui): remove unused NerdFonts story and MONO_NERD_FONTS import
Drop the NerdFonts story from font.stories.tsx along with the unused
MONO_NERD_FONTS import, as the exported constant is no longer available
from the @opencode-ai/ui/font module.
* chore: update visual regression baselines
* fix(opencode): move Show conditional wrapper outside box in home onboarding
Relocate the Show component to wrap the box element instead of being
nested inside it, preventing the empty box from rendering when the
onboarding tip is not visible.
---------
Co-authored-by: Jay V <air@live.ca>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Brendan Allan <brendonovich@outlook.com>
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: André Cruz <acruz@cloudflare.com>
Co-authored-by: Ariane Emory <97994360+ariane-emory@users.noreply.github.com>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: BlankParticle <blankparticle@gmail.com>
Co-authored-by: Sebastian <hasta84@gmail.com>
Co-authored-by: Burak Yigit Kaya <byk@sentry.io>
Co-authored-by: Caleb Norton <n0603919@outlook.com>
Co-authored-by: Imanol Maiztegui <imanol.mzd@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Regenerate openapi.json, types.gen.ts, and sdk.gen.ts to include
branded type patterns (^ses.*, ^msg.*, ^pty.*, ^prt.*, ^wrk.*)
added upstream in v1.2.25, while preserving Kilo Remote/Network routes
- Keep "auth" as primary CLI command name (upstream renamed to "providers"),
added kilocode_change markers with "providers" as alias
- Regenerate CLI docs reflecting new upstream commands (config, completion)
and session list flags (--all, --search)
- Fix RemoteStatusService typecheck: SDK Remote methods now take
optional (directory, workspace) params from global query middleware
The upstream merge rewrote the theme color generation system
(color.ts + resolve.ts), shifting scale indices for critical tokens
and removing the queued feature. This restores visual parity with
main by pinning affected tokens and restoring removed functionality.
Theme overrides (kilo.json):
- Pin surface-critical-weak, surface-critical-strong, surface-base,
text-on-critical-base/weak, icon-critical-hover/active to pre-merge
values — upstream changed generateScale() and index mapping
CSS fixes:
- Restore tool-error and queued styles in kilo-ui message-part.css
(upstream rewrote these in v1.2.25, but the kilo-ui message-part.tsx fork still uses them)
- Add transparent bg/border on tool-error-card (base card now has
visible bg/border after our earlier card.css restore)
- Remove card borders in kilo-vscode theme context
- Override error-details card background to match CI baseline
Restored features (removed in upstream):
- Restore queued prop/memo/rendering in session-turn.tsx
- Thread queued prop through upstream message-part.tsx Message →
UserMessageDisplay (needed because SessionTurn uses upstream
message-part via relative import, not the kilo-ui fork)
Guard streaming auto-scroll behind a near-bottom check so the user can
scroll up without being snapped back on the next token. Track explicit
user opens in a module-level Set so auto-collapse is skipped when the
user has the block open.
Closes#8586
Co-authored-by: Thomas Brugman <thomas.brugman.teb3@gmail.com>