mirror of
https://github.com/cline/cline.git
synced 2026-09-21 13:21:23 +08:00
35583ee8bb2cfaec39c248a826bf060a376a1eaa
169
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
35583ee8bb |
feat(desktop): interactive welcome hero graphic (#13399)
* feat(desktop): add interactive welcome hero * feat(desktop): support composable welcome hero variants |
||
|
|
eef7958cad |
fix(core): report truthful session status so desktop checkpoint restore stops wedging (#13418)
* fix(core): keep hub session status truthful across queue-drained turns Queue-drained turns settle only through the event stream, but the hub runtime host mistranslated their lifecycle in two ways: - session.updated events carrying only a snapshot (persistence updates) defaulted the projected status to "running". When one trailed the final idle update after a turn, clients that track busy state from status events (the desktop sidecar's workspace restore gate) stayed busy forever. Use the snapshot's real status and emit nothing when neither source reports one. - the per-run agent.done dedup was only reset by run.started, which the daemon-side queue drain never publishes, so a drained turn's done was swallowed as a duplicate of the previous turn's. Reset the dedup on session.pending_prompt_submitted, and suppress stale run.completed events that land inside a drained turn's window so they can neither emit a phantom done nor consume the drained turn's dedup slot. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * test(desktop): cover restore unlock after an event-settled queued turn Exports the sidecar's core-session event handler so the queued-turn lifecycle (busy via status events, cleared by the done agent event, restore allowed afterwards) is testable end-to-end. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(core): start interactive sessions without a prompt as idle The runtime host reported every new session as "running" until its first turn ended. Interactive hosts (the desktop app) start sessions with no prompt and dispatch turns through separate send calls, so a created-but-never-prompted session stayed "running" forever — wedging clients that gate workspace operations (checkpoint restore, message edit) on active turns. Interactive no-prompt starts now begin idle, start emits the session's actual status (resumed sessions no longer masquerade as running), and markTurn* transitions keep tracking in-memory status for lazily persisted sessions so the first turn still reports running -> idle. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * style: format hub-runtime-host test filter Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * refactor: drop the drained-turn done bookkeeping, keep the minimal fix The stuck restore is fully explained by the two status defects (fabricated "running" from snapshot-only session.updated events, and never-prompted interactive sessions reporting "running"). The done-dedup machinery for queue-drained turns addressed a separate cosmetic gap (queued turns emit no chat_done, pre-existing) and required fragile run-window heuristics, so it is removed to keep this change reviewable. Sidecar test now settles the queued turn through the status event, matching the shipped mechanism. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * docs(sdk): document the truthful session-status contract --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
e6f4d5fef7 | feat(desktop): refresh app icons and branding (#13400) | ||
|
|
7903c76812 |
feat(desktop): recommended-feed badges and descriptions in provider settings (#13416)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* feat(desktop): recommended-feed badges and descriptions in provider settings
Review suggestion on #13410: the provider settings page has room for
more model detail than the composer's picker. The cline/cline-pass
provider cards now refresh their model list through
list_provider_models (the catalog snapshot deliberately skips the
recommended-feed overlay so the startup catalog fetch never blocks on
the feed) and render Recommended/Free tier badges plus feed tags (NEW)
next to the model name, with the model description underneath. The
refreshed list also surfaces the live entries instead of the bundled
snapshot.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): scope the settings featured model list to its provider and revision
The fetched featured list was unscoped component state: switching
between cline and cline-pass reused the component instance, so the
previous provider's models stayed visible while the new request was
pending (or forever, when it failed), and the retained copy shadowed
later provider.modelList updates — adding a second custom model
submitted the stale list as the complete configuration and dropped the
first addition.
The fetched list now only applies to the provider and modelList
revision it was fetched for (falling back to the catalog snapshot
otherwise and refetching on membership changes), and add-model submits
the union of the displayed and configured ids so an update can never
silently unconfigure existing entries.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
|
||
|
|
b060cecc05 |
refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK (#13415)
* feat(ui): sectioned model picker support in SearchCombobox
Adds option sections with headers, badges (NEW/Free pills), keyboard
navigation (arrows/Home/End/Enter with active-row tracking and
aria-activedescendant), substring match highlighting, a configurable
panel width, a trigger chevron, and a cleaner borderless search row.
All additions are backwards compatible; bumps @cline/ui to
0.2.0-next.6.
* feat(desktop): recommended and free model tiers in the composer picker
The composer's model selector showed raw provider/model ids and listed
the entire catalog alphabetized by id. It now labels providers and
models by display name and, for the cline provider, leads with the
Recommended and Free tiers from the recommended-models feed (NEW/Free
badges, descriptions) ahead of an All models section — matching the
CLI's featured picker and the kanban selector. cline-pass gets
Subscribed/Free tiers. A new list_cline_recommended_models sidecar
command exposes @cline/core's fetchClineRecommendedModels (display-ready
names, bundled offline fallback); feed ids resolve against the catalog
with a unique-slug fallback for Vercel/OpenRouter alias spellings, and
unresolvable entries are dropped rather than rendered unselectable.
* fix(desktop): widen the provider trigger for display names
Provider labels are now display names (e.g. "Cline Usage-Billing"),
which truncated badly at max-w-28.
* chore(desktop): drop unused featured-models test helper
* style(desktop): align workspace/branch picker search rows with the model picker
The composer's workspace/branch popover and the welcome screen's
workspace and branch pickers used a boxed inner search shell that now
clashed with the model picker's borderless search row sitting next to
them. Behavior unchanged.
* feat(ui): center the selected option when SearchCombobox opens
Opening a long list previously scrolled the selection just into view at
the panel edge; it now lands centered, and keyboard/hover navigation
falls back to minimal nearest-edge scrolling.
* style(desktop): picker row contrast, transparent search fields, centered open
The workspace/branch pickers' rows had a nearly invisible
surface-hover-lighter hover; rows now hover with surface-hover and mark
the current entry with the accent background plus check, matching the
model picker. The search inputs drop the Input base class's
dark:bg-input/30 tint that rendered a gray box inside the panel in dark
mode. Opening a picker now centers the current workspace/branch via a
shared scroll helper instead of starting at the top of the list.
* fix(ui): visible option hover/selected states and no scroll-jump on hover
The option row stacked bg-transparent with the conditional state
backgrounds; at equal specificity the later-sorted bg-transparent
utility won, so hover/selected rows rendered with no background at all.
The background classes are now mutually exclusive.
Mouse-driven active-row changes also reused the keyboard scroll-into-
view effect: hovering a row at the panel edge scrolled it into view,
which moved the list under the cursor and re-triggered hover — an
endless jump. Scroll mode is now per-source: center on open, nearest
for keyboard/typing, none for hover.
* fix(desktop): show only subscribed and free tiers in the cline-pass picker
The ClinePass offer is exactly the feed's subscribed + free tiers, but
stale bundled/cached catalog entries (e.g. a nemotron model) leaked
into an "All models" tier. Match the CLI's featured picker: hide
catalog leftovers, and only fall back to the full catalog when the
subscribed bucket is empty so a subscriber is never limited to free
models offline.
* fix(ui/desktop): strengthen the selected-row highlight in light mode
The selected row used the semantic accent surface (violet step 3),
which is nearly white in light mode. SearchCombobox and the desktop
workspace/branch pickers now highlight the selected/current row with
accent step 4 (with a fallback to --accent), which reads clearly in
both themes without touching the shared --accent token that shadcn
hover states depend on.
* fix(desktop): fit full provider display names in the composer trigger
"Cline Usage-Billing" — the default provider — truncated to
"Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56,
which fits the longest built-in provider names.
* style(ui/desktop): animate picker panels open like the shadcn dropdowns
The thinking-effort Select (shadcn/Radix) animates open while the
model/provider/workspace/branch pickers popped in instantly. All picker
panels now share the same open treatment — 150ms fade + slight zoom,
sliding from the trigger side. SearchCombobox uses a self-contained CSS
keyframe (consumers may not ship tw-animate-css); the desktop's custom
panels use the app's tw-animate utilities. Both respect
prefers-reduced-motion.
* chore(desktop): drop stale eslint-disable comments in picker search rows
This repo lints with biome; the jsx-a11y/no-autofocus disables were
inert leftovers. Flagged in review.
* refactor(core/desktop): stamp recommended-feed tiers onto ProviderModel in the SDK
Review feedback on the composer picker: tier joining should live where
the SDK serves model lists so each client doesn't fetch and join the
recommended-models feed itself (the CLI and now the desktop each did).
ProviderModel gains description and featured ({tier, rank, tags});
getLocalProviderModels overlays the feed's recommended/free tiers onto
cline models and subscribed/free onto cline-pass via
applyClineFeaturedModels, matching feed ids through the
Vercel/OpenRouter alias rules. The feed access is a new cached wrapper
(getCachedClineRecommendedModels, 5-minute TTL, in-flight dedupe) —
this path runs on every picker open, and the bundled offline fallback
is cached too so offline users don't re-pay the 5s timeout per list.
The desktop webview now reads tiers straight off the models: the
list_cline_recommended_models sidecar command, the webview feed fetch,
and its unique-slug alias matching are all deleted. toProviderModel
also carries ModelInfo.description generally.
* fix(ui): hand focus back to the combobox trigger on selection, close on Tab
Selecting an option (Enter or click) unmounted the focused search input
without a new focus target, dropping keyboard users' focus to <body> —
only Escape restored it. And since the search input is the panel's only
tabbable element, Tab always moved focus outside the component while
leaving the popup open behind the new focus target.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(desktop): keep the composer model selection inside the picker's visible offer
The active/remembered model was validated against the provider's full
catalog while the picker can intentionally hide models (the ClinePass
offer is exactly its subscribed/free tiers), so a stale remembered model
could become the selection while being absent from the dropdown.
Remembered and default selections (including on provider switch) now
resolve against the picker's visible options, and an explicitly
configured model that falls outside the offer stays active but is
surfaced under a 'Current model' section so the selection is always
visible and re-selectable.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* feat(core): stamp featured tiers onto the provider catalog synchronously
listLocalProviders deliberately skipped the feed overlay so the catalog
never blocks on the network — but that left the composer's very first
picker open after a cold boot rendering an untiered flat list until the
per-provider fetch landed. Blocking was never required: stamp tiers from
a synchronous peek at data already in memory (the cached live feed when
fresh, else the bundled fallback, whose recommended ids resolve against
the bundled cline catalog). The per-provider model-list path still
refreshes with live feed data moments later.
* fix(core): harden featured-tier matching and the feed cache reset
Review findings on the tier overlay:
Vendor-prefix mismatches now match by unambiguous id slug (two-pass, so
a catalog carrying both spellings of a model stamps one row, and a slug
shared by two feed entries stamps nothing) — the bundled fallback feed's
vendor-prefixed ids can otherwise miss cline-free/-prefixed catalog
entries, leaving them untiered in degraded mode.
resetClineRecommendedModelsCacheForTests now bumps a generation so an
in-flight feed request resolving after a reset cannot repopulate the
cache it just cleared.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
|
||
|
|
4d1bafc443 |
feat(desktop/ui): recommended and free model tiers in the composer model selector (#13410)
* feat(ui): sectioned model picker support in SearchCombobox Adds option sections with headers, badges (NEW/Free pills), keyboard navigation (arrows/Home/End/Enter with active-row tracking and aria-activedescendant), substring match highlighting, a configurable panel width, a trigger chevron, and a cleaner borderless search row. All additions are backwards compatible; bumps @cline/ui to 0.2.0-next.6. * feat(desktop): recommended and free model tiers in the composer picker The composer's model selector showed raw provider/model ids and listed the entire catalog alphabetized by id. It now labels providers and models by display name and, for the cline provider, leads with the Recommended and Free tiers from the recommended-models feed (NEW/Free badges, descriptions) ahead of an All models section — matching the CLI's featured picker and the kanban selector. cline-pass gets Subscribed/Free tiers. A new list_cline_recommended_models sidecar command exposes @cline/core's fetchClineRecommendedModels (display-ready names, bundled offline fallback); feed ids resolve against the catalog with a unique-slug fallback for Vercel/OpenRouter alias spellings, and unresolvable entries are dropped rather than rendered unselectable. * fix(desktop): widen the provider trigger for display names Provider labels are now display names (e.g. "Cline Usage-Billing"), which truncated badly at max-w-28. * chore(desktop): drop unused featured-models test helper * style(desktop): align workspace/branch picker search rows with the model picker The composer's workspace/branch popover and the welcome screen's workspace and branch pickers used a boxed inner search shell that now clashed with the model picker's borderless search row sitting next to them. Behavior unchanged. * feat(ui): center the selected option when SearchCombobox opens Opening a long list previously scrolled the selection just into view at the panel edge; it now lands centered, and keyboard/hover navigation falls back to minimal nearest-edge scrolling. * style(desktop): picker row contrast, transparent search fields, centered open The workspace/branch pickers' rows had a nearly invisible surface-hover-lighter hover; rows now hover with surface-hover and mark the current entry with the accent background plus check, matching the model picker. The search inputs drop the Input base class's dark:bg-input/30 tint that rendered a gray box inside the panel in dark mode. Opening a picker now centers the current workspace/branch via a shared scroll helper instead of starting at the top of the list. * fix(ui): visible option hover/selected states and no scroll-jump on hover The option row stacked bg-transparent with the conditional state backgrounds; at equal specificity the later-sorted bg-transparent utility won, so hover/selected rows rendered with no background at all. The background classes are now mutually exclusive. Mouse-driven active-row changes also reused the keyboard scroll-into- view effect: hovering a row at the panel edge scrolled it into view, which moved the list under the cursor and re-triggered hover — an endless jump. Scroll mode is now per-source: center on open, nearest for keyboard/typing, none for hover. * fix(desktop): show only subscribed and free tiers in the cline-pass picker The ClinePass offer is exactly the feed's subscribed + free tiers, but stale bundled/cached catalog entries (e.g. a nemotron model) leaked into an "All models" tier. Match the CLI's featured picker: hide catalog leftovers, and only fall back to the full catalog when the subscribed bucket is empty so a subscriber is never limited to free models offline. * fix(ui/desktop): strengthen the selected-row highlight in light mode The selected row used the semantic accent surface (violet step 3), which is nearly white in light mode. SearchCombobox and the desktop workspace/branch pickers now highlight the selected/current row with accent step 4 (with a fallback to --accent), which reads clearly in both themes without touching the shared --accent token that shadcn hover states depend on. * fix(desktop): fit full provider display names in the composer trigger "Cline Usage-Billing" — the default provider — truncated to "Cline Usage-Bi…" at max-w-36; the trigger now allows up to max-w-56, which fits the longest built-in provider names. * style(ui/desktop): animate picker panels open like the shadcn dropdowns The thinking-effort Select (shadcn/Radix) animates open while the model/provider/workspace/branch pickers popped in instantly. All picker panels now share the same open treatment — 150ms fade + slight zoom, sliding from the trigger side. SearchCombobox uses a self-contained CSS keyframe (consumers may not ship tw-animate-css); the desktop's custom panels use the app's tw-animate utilities. Both respect prefers-reduced-motion. * chore(desktop): drop stale eslint-disable comments in picker search rows This repo lints with biome; the jsx-a11y/no-autofocus disables were inert leftovers. Flagged in review. * fix(ui): hand focus back to the combobox trigger on selection, close on Tab Selecting an option (Enter or click) unmounted the focused search input without a new focus target, dropping keyboard users' focus to <body> — only Escape restored it. And since the search input is the panel's only tabbable element, Tab always moved focus outside the component while leaving the popup open behind the new focus target. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(desktop): keep the composer model selection inside the picker's visible offer The active/remembered model was validated against the provider's full catalog while the picker can intentionally hide models (the ClinePass offer is exactly its subscribed/free tiers), so a stale remembered model could become the selection while being absent from the dropdown. Remembered and default selections (including on provider switch) now resolve against the picker's visible options, and an explicitly configured model that falls outside the offer stays active but is surfaced under a 'Current model' section so the selection is always visible and re-selectable. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
74a8e06e41 |
Desktop: unify Plugins, MCP, and Skills into one Plugins hub with a dedicated Marketplace page (#13411)
* Unify desktop plugins, apps, MCP, and skills into one Plugins hub with a Browse directory mode Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Open the marketplace directory as a modal over the Plugins hub instead of swapping the page Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Rename directory to Marketplace: Browse Marketplace button, Marketplace modal title with icon, search placeholder Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Fix search input focus ring clipped by the Marketplace modal scroll container Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Address Greptile review: keep selected tag chip visible when its count drops to zero, and remount installed tab when a marketplace install completes after the modal closed Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Track marketplace modal mutation flag in a ref so a close click racing a queued render cannot skip the inventory remount Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Make Marketplace its own settings page under Customizations and restore Channels as a standalone page Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Remove icon from Marketplace page header for consistency with other settings pages * Notify mounted inventory views when the marketplace invalidates the cache so late install completions refresh the Plugins hub --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
f291a269a6 |
fix(desktop): don't show "No sessions found" while session history is still loading (#13414)
* fix(desktop): don't show 'No sessions found' while session history is still loading Replace the isLoadingHistory flag with hasLoadedHistory, set only once the backend has actually answered a list_discovered_sessions request. The sidebar and Sessions view now keep their loading state until that first definitive response, so the empty-state copy can no longer appear while history is still being fetched (or while a failed fetch is being retried). Also retry a failed initial fetch on the 2s event cadence instead of stranding the UI until the 12s periodic poll, which is what stretched the misleading empty state to ~10 seconds after a webview reload when the websocket lost the race with the page load. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(desktop): stop history fast-retry from re-arming after hook unmount A failed initial fetch that settles after the hook unmounted could schedule a new retry timer after cleanup had already cleared the refs, leaving the abandoned hook polling the backend every 2s. Guard scheduleRefresh with a disposed ref set by the mount effect's cleanup. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
06013b9c08 |
fix(desktop): remove settings gear hover state while Account screen is open (#13408)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
c14cc2c696 |
fix(desktop): work summary undercounts wall time when pre-tool thinking attaches to the answer (#13413)
* fix(desktop): anchor work summary duration on the answer row, not attached pre-tool reasoning The collapsed 'Worked for Xs' row undercounted wall time whenever a turn's assistant message contained thinking + tool_use with no narration text: the canonical projection emitted the reasoning-only row after the tool row (both stamped before the tool executed), the webview attached that row to the final answer, and collapseCompletedWork used the answer's earliest attached reasoning timestamp as the end anchor - excluding the entire tool execution (e.g. 'Worked for 5s' for a turn with an 8s command). - webview: end the work span at the answer row's own timestamp, clamped to the last collapsed row so a fallback answer bubble with a synthetic early timestamp cannot shrink the duration either - sidecar: flush pending thinking before a tool_use row so rehydrated transcripts keep the live-stream order (thinking before its tool call) and pre-tool reasoning no longer rides on the next answer Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(desktop): keep interleaved thinking between the tool calls it separates Address Greptile review: when one assistant message interleaves thinking between multiple tool_use blocks, each reasoning segment now projects at its own position (attached to a text row from its own segment when present, otherwise as its own row) instead of merging into the first reasoning row, which displayed later thinking before a tool call it actually followed. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
0a9f45a9c6 |
fix(ui): stack code block lines when streamdown lineNumbers is off (#13412)
streamdown renders each Shiki token line as a bare inline span with no newline text between non-empty lines, and only applies its block line class when lineNumbers is on. With lineNumbers off (the desktop app's config) every multi-line fenced block collapsed into one run-on line. Make the direct line spans under code-block-body display: block in the shared markdown.css; empty lines keep their height via their lone "\n" child under white-space: pre. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
4a63821d57 |
fix(desktop): treat ClinePass as OAuth-managed in the chat credential gate (#13404)
* fix(desktop): treat ClinePass as OAuth-managed in chat credential gate ClinePass shares the Cline account OAuth credentials (its auth handler stores under the "cline" provider), so the webview never sees a plain API key for it. The chat pre-flight check only exempted cline/oca/ openai-codex, so switching to ClinePass while signed in via OAuth blocked with "Missing API key" even though the sidecar resolves the stored access token fine (which is why the CLI worked). Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * style: format helpers.test.ts with biome Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
ff14ab601f |
feat: allow agents to create scheduled tasks (#13331)
* feat(core, desktop): add durable todo agenda * fix(desktop): secure todo approvals and track tool usage * fix(desktop): clean up failed approval delivery * fix(desktop): authenticate approval connections * fix(desktop): cancel approvals on broadcast failure * fix(desktop): authenticate development approvals * fix(desktop): harden development approvals * test(core): make task paths cross-platform * fix(desktop): serialize approval readiness * refactor(core): unify todo and schedule tools * feat(core): distinguish user todos from agent suggestions * fix(core): hide tasks tool in yolo mode * fix(core): enforce schedule workspace scope * fix(core): bind schedule scope to hub connection * fix(core): establish task scope at hub startup * fix(core): scope task automation by workspace * test(core): normalize workspace path expectations * test(core): serialize Windows CI workers * fix(core): reject unregistered schedule authority * fix(desktop): guard task execution commands * fix(core): avoid polynomial regex in mention parsing * fix(core): address schedule tool review feedback * fix(core): bind websocket clients to hub workspace * fix(core): flatten tasks tool input schema * fix(core): authorize multi-workspace hub clients * test(core): type hub transport authority mock * fix(cli): register a workspace client for remote schedule commands (#13398) --------- Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> |
||
|
|
878faf0a95 |
Rename desktop app from "Cline Code" to "Cline" (#13401)
* Rename desktop app from Cline Code to Cline Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Format touched Rust test assertions Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
dfa34ecea8 |
fix(desktop): strip user_input envelope when copying a user message (#13369)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Bee <abeatrix@users.noreply.github.com> |
||
|
|
98d3e52a02 |
fix(clients): filter non-chat models from chat pickers (#13317)
* fix(clients): filter non-chat models from chat pickers * fix(clients): align chat model eligibility |
||
|
|
f80e6a5df8 | chore(desktop): release v0.0.14 | ||
|
|
9cf60cd43a |
fix(desktop): finalize queued turns on chat_done with canonical history reconcile (#13330)
Turns that settle through the event stream (queued prompts, including the first prompt of a fresh session) resolve their send() RPC early, so nothing cleared the streaming shimmer or reconciled live-streamed content against the persisted transcript at turn end. A turn whose deltas were incomplete stayed visually streaming forever and only healed when a later non-queued send rehydrated history. chat_done (and chat_session_ended / the queue-drain double check) now clears the active assistant streaming id and schedules a short-delayed read_session_messages + applyCanonicalHistory, guarded by turn epoch, session id, and in-flight send submissions so it never clobbers a newer turn or duplicates the blocking send path's own finalization. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
2fd8d0383a |
fix(desktop): align system prompt with session mode (#13361)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> |
||
|
|
3705aec28f |
fix: skill slash commands load via the skills tool instead of expanding into the user message (#13327)
* fix(desktop): show typed slash command instead of expanded skill markdown The sidecar expands /skill and /workflow tokens into their instructions before dispatching, so the runtime's persisted transcript only contains the expanded text. After a turn (and when reopening a session) the webview re-hydrates from that history and rendered the whole SKILL.md body as the user's message; queue events echoing the expanded prompt could also add a second user bubble, and fresh sessions were titled with the markdown's first line. The CLI never shows this because its TUI keeps the typed text in its own transcript and only sends the expanded prompt to the model. Mirror that separation inside the desktop sidecar's display boundaries: - history projection (readSessionMessages) inverts user text that starts with a configured command's instructions back to '/name remainder', which also repairs sessions recorded before this fix - queue snapshots and chat_queued_prompt_start events echo the typed prompt recorded at expansion time, so the webview's optimistic-bubble re-key matches again - an untitled session sent an expanded prompt gets titled from the typed command instead of the instructions' first line Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(desktop): don't overwrite a mid-turn rename with the typed-command title The untitled check ran before dispatch, so renaming a fresh slash-command session while its first turn was running got clobbered by the post-turn typed-command title. Re-check at write time and only replace a missing title or the one the runtime auto-derived from the expanded prompt. Also documents the inherent prefix-inversion ambiguity flagged in review: text hand-typed with a command's exact instructions persists byte-identically to that command's expansion, so stored history alone cannot distinguish them. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(desktop): stop expanding skill commands; let the skills tool load them Pasting the skill body into the prompt is why the transcript could ever show it: the desktop webview re-hydrates from the runtime's persisted history, so whatever the sidecar splices into the user message renders as if the user typed it. The runtime already registers the skills tool, whose description requires the model to invoke it whenever the user references a slash command — so send the typed /skill text through and let the tool deliver the instructions as a tool result (previously they arrived twice: pasted and via the tool). The persisted user message, session title, and queue entries are then simply the typed command, which deletes the typed-prompt registry, the queue event/snapshot rewriting, and the title machinery from the previous approach. Workflows are not served by the skills tool and keep textual expansion, so the read-time display inverter stays: it collapses expanded workflow prompts — and skill prompts persisted before this change — back to the typed /command in the history projection. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * feat(core): option to keep skill slash commands typed for the skills tool resolveRuntimeSlashCommandFromWatcher (and the hub snapshot proxy) accept expandSkillCommands: hosts whose sessions register the skills tool pass false so the typed /skill goes through and the model loads the instructions as a tool result, keeping the persisted transcript as what the user typed. Workflows always expand — the tool does not serve them. isSkillsToolAvailable exposes the catalog check hosts use to decide (yolo preset and the skills tool toggle leave textual expansion as the only delivery path). Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(cli): skill slash commands load via the skills tool instead of expanding The TUI user-command wrap and buildUserInputMessage now keep a typed /skill as-is when the session's mode/toggles register the skills tool, matching the desktop app; workflows keep expanding, and yolo (zen) keeps expanding skills because its preset has no skills tool. This also fixes CLI resume/history surfaces showing the skill body: the persisted user message is now the typed command. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(vscode): keep configured skill slash commands typed for the skills tool expandSlashCommands no longer splices a configured skill's instructions into the model text; the SDK session's skills tool delivers them as a tool result (previously they arrived twice). Builtin pseudo-skills like /deep-planning are not served by that tool and keep expanding, as do workflows. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * refactor(desktop): use the shared skill-expansion option in the sidecar Replaces the sidecar's workflow-detection dance with core's expandSkillCommands option and gates on isSkillsToolAvailable, restoring textual expansion where the tool is missing (yolo mode or the skills tool toggle) — a gap in the previous desktop-only change. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * refactor(desktop): drop the display inverter for expanded transcripts Accepted trade-off to keep the change minimal: sessions recorded before skills switched to the skills tool, workflow sends (deprecated), and yolo-mode skill sends persist expanded instructions and now render that text as-is instead of being collapsed back to the typed /command at projection time. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
61b95a62ee |
feat(desktop): stream run command output (#13179)
* feat(desktop): stream run command output * fix(sdk): clean up detached command logs * fix(sdk): reap detached logs after hub restarts * fix(sdk): preserve live detached command logs * fix(desktop): harden live command progress * fix(sdk): recover detached logs for local hosts * fix(desktop): reconcile command output tool rows * fix(sdk): retain logs for surviving commands * fix(core): prevent PID reuse from retaining detached logs * fix(core): preserve detached logs on probe failures * fix(core): retain detached logs during probe outages * fix(desktop): resolve leftover merge conflict in messages projection test Combine both sides of the assertion: main's incremented per-block createdAt projection and this branch's toolCallId/hookEventName meta. --------- Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
be56c505e4 |
feat(ui): share the markdown pipeline, chat polish, and ThinkingBlock across products (#13323)
* feat(ui): share the markdown pipeline, chat polish CSS, and ThinkingBlock The desktop app and the cloud dashboard both consume @cline/ui yet rendered assistant output differently, because Markdown policy and the thinking-trace row lived app-side. This moves the shareable parts into the package: - components/markdown (new export): the lazy Shiki code highlighter (GitHub light/dark, pinned language set) and agentMarkdownControls — the standard Streamdown configuration. streamdown/shiki/@shikijs/* become optional peer dependencies, mirroring @pierre/diffs. - components/markdown.css: the desktop's chat polish moves in — chat-scale headings, outside list markers, single quiet code blocks with a hover-revealed copy control, table cards. Kept unlayered so it beats Streamdown's layered Tailwind utilities without !important. - ThinkingBlock + formatThoughtLabel in agent-chat: the standard thinking row (brain icon, Thinking/Thought-for-Ns label, streaming shimmer, rail presentation, capped scrollable body). The shimmer and the reasoning-hover-suppression rule move into agent-chat.css; triggers gain the color transition the desktop applied locally. Version bumps to 0.2.0-next.5 for the dashboard to pick up. * refactor(desktop): consume shared markdown and thinking primitives from @cline/ui The local Shiki highlighter, Streamdown controls, chat markdown polish CSS, streaming-title shimmer, and reasoning hover-suppression rule are deleted in favor of the @cline/ui versions (the highlighter test moves to the package's suite). ReasoningBlock becomes a thin wrapper that hands MemoizedMarkdown to the shared ThinkingBlock, and formatThoughtLabel re-exports from the package so grouping code and tests keep their import path. globals.css now imports @cline/ui/components/markdown.css (unlayered, so the polish keeps beating Streamdown's layered utilities); the app keeps only what is genuinely app-specific: link/image policy in markdown.tsx, selectability rules, accent palettes, and the view-enter transition. * style(ui/desktop): make thinking-trace prose legible Thinking body text rendered too faint: plain muted-foreground plus the desktop's font-thin weight. The shared thinking content now leans 75% of the way back toward the body text color (still slightly de-emphasized), and the desktop drops the thin font weight. |
||
|
|
3f9c9c3f33 |
feat(ui/desktop): collapse finished runs into a work summary and remove hover-state dead space (#13315)
* feat(ui): add WorkActivity collapsed-run summary and float message actions as a pill WorkActivity/WorkActivityTrigger/WorkActivityContent fold a finished agent run's working rows (tool calls, thinking traces, narration) behind a single "Worked for 4m 12s · 14 tool calls" disclosure built on the shared animated disclosure primitives, with formatWorkActivityLabel/formatWorkDuration exported for consumers. Message hover actions no longer rely on the transcript reserving blank space below each message: the action row is now a self-backed pill (border, blurred background, shadow) that floats over whatever follows, so conversations can pack rows tightly without hover chrome colliding with the next message. * feat(desktop): collapse finished runs into a work summary and tighten chat spacing collapseCompletedWork post-processes the grouped transcript: once a run ends on assistant text with no further tool calls, its working rows fold into one expandable WorkActivity row while the final answer stays visible. Runs are delimited by user messages; the trailing run only collapses when the session has stopped running and actually produced an answer, so live streams and cancelled/failed tails keep their rows. Assistant messages carrying images or media are treated as deliverables and never collapse. The conversation list gap drops from gap-8 to gap-4 now that hover actions are self-backed pills that need no reserved space, and user messages add their own top margin so turn boundaries stay visually distinct. * refactor(ui/desktop): work summary label wording, flat expansion, stable in-run rhythm Feedback round on #13315: - Label reads "Worked for 4m 12s and made 14 tool calls" instead of joining with a dot; without a duration it falls back to "Made N tool calls". - Expanded work rows render at transcript level — no rail or extra indent — since tool rows and thinking traces already carry their own nesting when expanded. The work content keeps the tight working-row rhythm. - Live working rows (thinking traces + tool calls) now group into a 'run' render item with the same tight 0.25rem rhythm, so there is no oversized gap under a "Thought for Ns" row and every row keeps its exact position when the finished run folds into the work summary. A trailing answer-in-progress stays outside the group at transcript level, and pure prose spans keep normal spacing. - The transient "Thinking..." indicator moves inside the transcript column and mirrors a trigger row's geometry, so the first real row replaces it in place with no jump. * style(ui/desktop): hover-pill metrics, right-pointing work chevron, scroll and spacing fixes Another feedback round on #13315: - Hover action pill: +2px internal padding, a trailing inset after the timestamp (it sat flush against the pill border), and more clearance between the message content and the pill (2px -> 6px; the hover bridge grows to match). - The work summary chevron points right while collapsed and continues counterclockwise to point up when expanded. - Conversation bottom padding drops pb-20 -> pb-8: the composer sits below the scroller, so the padding only needs to clear a pinned action pill. - Sending a message scrolls back to the bottom even if the reader had scrolled up (new AutoScrollOnSend on the user-message count, which ignores optimistic-bubble re-keying; @cline/ui now exports useConversation for this). - An assistant answer directly under its run's working rows pulls itself 0.5rem closer than the full transcript gap. * style(desktop): leave a visible gap between a pinned action pill and the composer pb-8 exactly matched the pill's ~40px footprint, so the last row's hover actions sat flush against the composer top; pb-12 restores ~8px of daylight. * style(desktop): widen the gap between the pinned action pill and the composer to ~24px pb-12 left only ~8px of daylight under the pill; pb-16 reads comfortable without reverting to pb-20's dead space. * fix(desktop): keep the thinking indicator at the working-row offset mid-run The indicator matched a trigger row's geometry but sat a full transcript gap (1rem) below the last working row, while the tool/thinking row replacing it joins the tight run group at 0.25rem — a visible upward jump. When the last transcript item is working rows (or streamed assistant output), the indicator now pulls up to the same tight offset; only at the start of a run, under the user message, does it keep the normal gap. * style(ui): calm the hover actions surface per team feedback Borderless rectangle instead of the bordered pill: radius drops to var(--radius), the side padding goes entirely (the icon buttons carry their own hit areas), and the vertical padding halves. Blurred background and shadow stay so it remains legible over following content. * feat(ui/desktop): full-band hover reveal and iOS-style disclosure easing The hover actions only appeared while the pointer was inside the message box itself. The invisible bridge under each message now spans the full height of the band the floating actions occupy (full row width), so hovering anywhere in that strip reveals them. Sibling row types (.cline-chat-tool, .cline-chat-work, and the desktop's run/tool groups) become position: relative so they paint above the bridge — their own content keeps its hover and clicks, and the bridge only wins in the band's genuinely empty space. All expandable rows (work summary, tool panels, thinking) open and close on a 240ms symmetric ease-in-out cubic-bezier instead of the 60ms snap, with chevron rotation on the same curve. Reduced-motion still disables both. * revert(ui/desktop): drop the full-band hover reveal; quicken disclosure easing to 180ms The full-band hover bridge (and the position: relative changes that made it safe) is reverted per feedback — back to the narrow bridge that only spans the gap under the message. The iOS-style ease-in-out on disclosures stays but speeds up from 240ms to 180ms. * fix(ui): recover live tool diffs that mount as a blank pierre skeleton Live-streamed edit rows could show an empty diff for the whole run, with the diff only appearing after the collapsed work row was expanded (fresh mount). Root cause, confirmed by driving a live session and inspecting the element: React StrictMode double-invokes @pierre/diffs' ref callback; the first instance's async highlight work aborts on its immediate cleanup, and the second instance adopts the abandoned half-rendered shadow tree as if it were complete prerendered output — zero height, no code, no theme stylesheet, permanently. A rendered diff always carries style[data-theme-css] in its shadow root, so ToolFileDiff now checks for it shortly after mount and remounts FileDiff (bounded attempts) when missing; the fresh host element takes the normal render path and recovers within ~400ms. Verified live: the diff now renders during the run. * fix(desktop): keep interrupted runs expanded even with partial trailing text The trailing-run collapse gated on 'ended with assistant text', which misread a Stop that landed mid-answer as a finished run and folded the tool calls the user wants to inspect. The gate is now the terminal status itself: only completed (or restored-idle) sessions collapse the trailing run; cancelled/failed/error tails keep their rows regardless of partial text. (Greptile P1 on #13315 — matches the PR's stated rule.) |
||
|
|
cf07572a07 |
feat(desktop): show provider web-search support under the settings toggle (#13328)
* feat(desktop): show provider web-search support under the settings toggle The global Web search toggle silently does nothing unless the session's provider offers native web search, which made the setting read as if it worked with any provider. The desktop General settings row now explains that only providers with built-in web search honor it, and shows a live status line: which connected providers are ready to use it (no extra setup needed), or an amber warning with a link to the Models section when none of them support it. Support is resolved in the webview via a new providerOffersModelTool helper in @cline/llms (browser export), sharing the same builtin-manifest source of truth as the runtime's supportsModelTool attachment check. * fix(desktop): address review — refetch web-search status on catalog invalidation, clarify per-model support Greptile P2: the one-time catalog fetch could race an in-flight provider save and show stale status; the row now refetches when the provider catalog cache is invalidated (fired after saves complete). Greptile P1: the ready line implied every model on the provider works; Vertex excludes Claude routes, so the copy now scopes the promise to models that support it. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
508a5322af |
feat(desktop): native notifications (#13166)
* feat(desktop): native notifications * macos target * fix(desktop): isolate macOS dev app identity * fix(desktop): address notification review feedback --------- Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> |
||
|
|
d4b415f8ab |
fix(desktop): trim the persisted transcript on checkpoint restore (#13259)
A restore that reuses the source session id rolled the workspace back but left the persisted transcript describing the discarded turns, so the chat kept showing turns whose file changes had just been reverted. Before #13075 the restore reply carried the trimmed messages and the webview rendered them directly. Now the webview always re-reads through read_session_messages, which prefers the persisted file over the live session, so the trimmed history the sidecar puts on the live session is never read. Persist it as well. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
6b3f034bce |
fix(desktop): align usage table columns CLINE-2996 (#13325)
* fix(desktop): align usage table columns * fix(desktop): show usage link for empty history |
||
|
|
05a6974ef8 |
feat(desktop): surface beta channel identity in-app (#13322)
Beta builds (prerelease versions from desktop-experimental, shipped as 'Cline Code Beta') now identify themselves everywhere users look: a Beta pill in the sidebar footer, the product name in the sidebar hover card, an About row in Settings > General with version + channel, the runtime window title, and the tray menu/tooltip (via package_info, which carries the overlay's productName). Channel detection is a pure version-string check (-beta suffix) in the new webview/lib/app-channel.ts — the version is baked into package.json at build time and reported by the sidecar's get_process_context, so it works in both the Tauri shell and web dev mode with no new plumbing. Stable builds render no channel UI at all. |
||
|
|
5ad2dd5fc8 |
feat(desktop): beta release channel from desktop-experimental branch (#13321)
* feat(desktop): add beta release channel from desktop-experimental branch Adds a 'channel' input (stable|beta) to desktop-publish.yml. Beta releases are tagged desktop-vX.Y.Z-beta.N on the desktop-experimental branch, built with the tauri.beta.conf.json overlay (Cline Code Beta / bot.cline.app.beta, side-by-side install with stable), published as prerelease GitHub releases, and served by a separate rolling desktop-beta update feed. Both channels dispatch from main so the PublishDesktop signing gates are unchanged. Guards: stable channel now rejects prerelease tags (previously a beta tag could clobber desktop-latest and auto-update every stable install onto it), feed selection is fail-closed and cross-checked in the release job, and the build asserts the compiled binary embeds exactly its own channel's feed URL. Changelog extraction is exact-version now that stable and beta sections interleave across branch merges. Process doc in apps/examples/desktop-app/EXPERIMENTAL.md; publish-desktop skill now asks stable-or-beta. * docs(desktop): warn against renaming the desktop-latest feed * docs(desktop): document the code-trust model for publish approvals The beta dispatch-from-main invariant protects the workflow definition, not the checked-out tag's build scripts, which run with signing secrets in scope for stable and beta alike. Make explicit that the PublishDesktop reviewer approval is the trust gate for that code, and that desktop-experimental therefore needs main-grade merge controls. |
||
|
|
87be867599 | fix(desktop): make routine selectors clickable (#13324) | ||
|
|
eed78103a4 |
feat(ui): AskQuestion component redesign (#13236)
* feat(ui): support explicit follow-up question submission * Fix Enter handling for question options * Strengthen question keyboard regression test |
||
|
|
456f86bb3a |
style(desktop): session hover cards styling (#13256)
* style(desktop): simplify session hover cards * docs: add hover card screenshots * chore: remove PR screenshot assets * style(desktop): address hover card review |
||
|
|
e86d988234 |
feat(ui): add animated reasoning and tool disclosures (#13254)
* feat(ui): add animated disclosure presentation * docs: add disclosure screenshots * docs: remove PR screenshots * fix(ui): support inert across React versions |
||
|
|
8bbdde2a5c |
feat(llms): add model-driven image generation (#13025)
* feat: add image generation support * fix(llms): preserve mixed image model behavior * fix(llms): validate generated image models * fix(llms): preserve mixed image response streaming * fix(llms): preserve runtime tool ownership * fix(llms): address image generation review feedback * fix(desktop): relay images for attached hub sessions * chore(llms): regenerate provider and model catalog Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(vscode): preserve SDK model capabilities across the catalog boundary The new modelSupportsToolCalling gate treats a populated capability list without "tools" as authoritative. But the VS Code host round-trips model metadata through the legacy ModelInfo shape, and toSdkModelInfo reconstructed capability arrays from the legacy booleans alone — which have no "tools" projection. Every model with any capability flag set came back as "cannot call tools", so sessions registered zero tools and the file-edit e2e failed on all platforms (the editor tool call resolved to "Unknown tool" and the edit never reached disk). Fix, following the modalities-passthrough pattern so stacked capability PRs can reuse it: - Preserve the SDK capability list verbatim on legacy ModelInfo at the catalog boundary (adaptSdkModelInfo); union user overrides into it without ever fabricating a list from overrides alone. - Seed toSdkModelInfo from the preserved list, and when none survived, emit an explicit "tools" signal (honoring legacy supportsTools=false) so reconstructed arrays can never silently disable tool calling. - Add a shared modelHasCapability(model, capability, {assumeWhenUnspecified}) helper: missing or empty capability lists carry no signal and each check declares its own default. Future capability gates should route through it instead of reading model.capabilities directly. Verified: file-edit e2e (Single Root + Multi-Roots) passes locally; shared/core/llms/model-catalog/session-factory suites and typechecks pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(llms): refresh generated model catalog --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
da05eeb02d |
feat(desktop): add microphone transcription input (#13023)
* feat(desktop): add voice input * fix(desktop): harden voice transcription input * fix(llms): scope voice transcription models * fix(desktop): guard batch voice transcripts * refactor(voice): defer chat model filtering * fix(desktop): invalidate stale streaming transcripts * fix(desktop): preserve batch transcription lifecycle * chore(llms): refresh voice model catalog * Mic Icon * Auto * test(desktop): align speech input icon assertions |
||
|
|
2be49cf91b | chore(desktop): release v0.0.13 | ||
|
|
2e46676952 | style: apply biome formatting to files that drifted on main | ||
|
|
942f76e8e7 | feat: add web search settings toggle to VS Code extension and desktop app (#13245) | ||
|
|
afab68f3a3 |
fix(core): defer replacing a Hub that is serving live sessions (#13231)
* fix(core): stop concurrent Hub installs from retiring each other Two Cline installations on different builds would shut each other's Hub daemon down in a loop, and every session died with an abnormal socket close (code=1006) as its daemon was killed mid-handshake. The retire decision was a one-sided predicate: each client independently asked "may I reuse this Hub?", and two clients on differing builds both answered no. #13177 added build-epoch ordering to break the tie, but left every unordered case - missing epoch, missing build id - retiring as before, so any pair involving a build from before epochs were embedded still looped. Derive the decision from a total order instead. compareHubBuilds orders two builds by embedded epoch, then core release version, then build id, and is antisymmetric by construction, so at most one side of a pair can ever decide to retire. A Hub that is newer or cannot be ordered is attached over the compatible wire protocol and left to the build-mismatch watcher to prompt about. Genuine protocol incompatibility still replaces. Identity is now read from the same fields on both sides. Filling in a coreVersion locally that the wire record omits made a build's identity depend on which role it was playing, and the two directions of a pair were then decided by different tiers with both concluding they were newer - a second, independent way to produce the loop. Also: - Scope the development Hub owner by build id, so differing dev builds run their own daemon side by side instead of contending for one record. Production keeps its singleton. - Break the circuit after repeated retirements of the same URL, bounding any future ordering bug to a stale-build prompt rather than an unusable Hub. - Report `cline doctor fix` honestly: separate processes that survived a kill from ones that appeared while the fix ran, name the live parent respawning a daemon, and mark a startup lock held by a running process as held rather than leaked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cli): only blame a live parent for processes seen during doctor fix The advice printed under "started during fix" asserted that every such process was respawned by a live parent, and told the user to go stop it. A process can also start on its own mid-repair - someone opening a new session - and then the instruction points at an unrelated process, or at none at all. Derive the wording from whether a live parent actually exists: name it when every process has one, state the facts when none do, and split the list when it is mixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(core): order the builds in the stale-discovery hub server case The case stubbed two build ids and expected the second to replace the first, but supplied nothing that says which came first: no epochs, and both servers report the same core version. Ordering therefore fell to the build-id tiebreak, where "new-build" sorts before "old-build" and the replacement was judged the older of the two. Give the case the epochs its name implies, and add the missing sibling for an unorderable pair, which is attached to rather than retired - the behavior that keeps two installations from shutting each other down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(core): defer replacing a Hub that is serving live sessions Retiring a Hub kills its established WebSockets, so replacing one under a running session ends that turn with an abnormal close (code=1006). The replacement is correct - the newer build should own the Hub - but the timing is not the user's to absorb mid-turn. Defer instead while the Hub reports live sessions: the newer client attaches to the older Hub over the compatible wire protocol, and the swap happens once those sessions end. Attaching rather than spawning matters - a second daemon would race the busy one for the port. Deferring silently would be worse than the interruption it avoids, because a long-lived session pins the Hub to old code indefinitely with nothing to show for it. The build-mismatch watcher only ever prompted in the direction where updating the client resolves the mismatch; its own comment notes that older Hubs "are retired and replaced automatically, so prompting would only flash a stale dialog", which stops being true once replacement can be deferred. Add the missing direction as `outdated_hub`, reported only when a mismatch survives consecutive checks - an idle older Hub is replaced within moments of being seen, so a single sighting would flash exactly the stale dialog the original comment warns about. The CLI and desktop dialogs render it as information rather than an update prompt: nothing to install, the Hub swaps itself when the sessions end. The direction is decided by compareHubBuilds rather than reusability, because a Hub that is newer and one that carries too little metadata to order are both "reusable" but need opposite advice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(core): key the outdated-Hub check by daemon instance, not build The consecutive-sighting check that keeps a routine replacement from flashing an informational dialog was keyed by build id. Two daemons from the same build share one, so an outdated Hub replaced by another daemon of the same older build satisfied the check and reported exactly the churn the check exists to hide. Carry a hubInstanceId on the mismatch event - the Hub's own id, falling back to pid and start time - and key the pending sighting by it. A replacement instance now restarts the count instead of confirming it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(core): source Hub instance identity from the discovery record The instance id added in the previous commit was read from the probe response, but the watcher probes without an auth token and `/health` deliberately reports only build and address fields - no hubId, pid, or startedAt. So the id was always undefined in production and the check it guards still conflated two daemons of the same build. The test missed it by injecting a hubId into a mocked probe, a shape `/health` never returns. Take identity from the discovery record instead, which every daemon version writes with all three fields and which a replacement daemon rewrites as its own. The probe is still preferred when it does carry an id, since that is the process just spoken to. The tests now use the real `/health` payload shape and vary identity through the discovery record, including the pid-and-start-time fallback for records written before Hubs carried an id. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(core): confirm the Hub record still describes the daemon just probed Instance identity is read from discovery before the probe and build data comes back after it, so a daemon replaced between those two steps was described with its predecessor's identity - the replacement then satisfied the prior daemon's pending sighting and emitted the notification the consecutive-instance check exists to suppress. Re-read discovery after the probe and report nothing when the record no longer describes the same daemon. A Hub mid-swap is churn; the next check sees whatever it settles into. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * revert(core): drop the watcher instance-identity hardening Reverts the three follow-up commits that keyed the outdated-hub consecutive-sighting check by daemon instance ( |
||
|
|
6d7e745fb7 |
feat(core, llms): add provider-aware web search tools (#13075)
* feat(core, llms): Cline custom provider & web search
* fix(llms): preserve reasoning model token parameter
* fix(llms): keep ClinePass provider options on the wire in the shared Cline provider
The shared Cline provider hardcoded the AI SDK provider name to "cline",
but the openai-compatible model reads request-body passthrough options from
providerOptions[<name>]. Option routing emits ClinePass options under the
"cline-pass"/"clinePass" buckets, so gateway reasoning (extended thinking
budgets) silently stopped reaching the wire for cline-pass after it moved
off the generic openai-compatible module.
Thread the gateway provider id through as the provider name, and restore
strictJsonSchema: false for the new "cline" provider-options target so the
wire format matches the previous openai-compatible behavior. Add cline-pass
coverage at both the option-routing and request-body levels.
* feat(sdk): persist provider-executed tool activity (#13077)
* feat(core, llms): Cline custom provider & web search
* fix(llms): preserve reasoning model token parameter
* feat(sdk): persist provider-executed tool activity
* fix(vscode): restore state proto and settings section reverted by merge
The merge of origin/bee/websearch into this branch resolved conflicts by
keeping this branch's pre-#13126 copies of apps/vscode files, which
deleted the auto_approve_all_toggled = 174 proto field (without reserving
the number) and dropped a formatting line in FeatureSettingsSection.tsx.
Neither file is in scope for this PR. Restore both to main's content so
the proto source matches the checked-in generated code again.
* chore(vscode): match main byte-for-byte in FeatureSettingsSection.tsx
The pre-commit biome hook strips a blank line that exists on main, which
kept this out-of-scope file in the PR diff. Commit the exact main content
with --no-verify so the PR no longer touches apps/vscode at all.
---------
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* refactor(llms): key ClinePass provider options to the shared cline bucket
Both Cline gateway ids (cline and cline-pass) are served by the same
shared "cline" AI SDK provider and hit the same Cline API, so threading
the gateway provider id through as the AI SDK provider name (
|
||
|
|
fcd5a9e0fb |
feat(desktop): add app font size bootstrap script (#13232)
* feat: add app font size bootstrap script - Import and inject APP_FONT_SIZE_BOOTSTRAP_SCRIPT in root layout - Call syncAppFontSize on app initialization - Add aria attributes (describedby, label, labelledby, valuetext) to Slider component - Replace thumb key generation with useId hook for better stability - Add settings view tests for font size functionality * feat(desktop): add native zoom menu shortcuts |
||
|
|
274ad3bdb2 | chore(desktop): release v0.0.12 | ||
|
|
5b22490d41 | fix(desktop): filter scheduled Core sessions (#13213) | ||
|
|
d03e88e50b |
Render desktop diff view hunks with the shared @pierre/diffs renderer (#13201)
* Render desktop diff view hunks with shared @pierre/diffs renderer Replace DiffView's hand-rolled DiffHunk +/- line rows with ToolFileDiff from @cline/ui (backed by @pierre/diffs), matching the chat tool rows. Hunks carrying complete new contents (created files) render with real line numbers; fragment hunks hide them, mirroring ToolCallRow. All of DiffView's chrome (collapse, copy, open-in-editor, counts) is unchanged. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * Make ToolFileDiff syntax palette follow the app theme, not browser preference @pierre/diffs declares 'color-scheme: light dark' on its shadow :host, so its light-dark() token colors resolve from the browser's preferred scheme. Apps themed by the .dark class (desktop app) got the light palette's near-black text on dark surfaces. Inline colorScheme: inherit on the host wins over the :host rule and follows the app's color-scheme, which the @cline/ui theme already flips with .dark. Skipped when a caller pins an explicit themeType. Also key diff-view hunks by index so repeated same-shaped hunks (a file created twice with identical contents) don't collide. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
4559f12c66 |
feat(desktop): temporarily disable welcome prompt suggestions and center input (#13170)
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> |
||
|
|
cde49170db |
fix(desktop): first turn of a fresh session no longer wedges the composer (#13122)
* fix(desktop): don't let a stale queued send response wedge the composer
A fresh session is still busy while its interactive loop starts, so the
sidecar coerces the first send onto the pending-prompt queue and replies
{queued:true} with a queue snapshot taken at enqueue time. The turn itself
runs via the runtime's queue drain and completes through stream events
(chat_queued_prompt_start -> deltas -> chat_done). On cold/slow sidecars the
RPC response lands only after those events; the webview then applied the
stale snapshot and unconditionally set status back to "running", leaving
the composer on "Agent is working..." forever and resurrecting a phantom
queue entry.
Webview: capture the turn epoch at send dispatch; chat_queued_prompt_start
bumps it, so a mismatch when the queued response arrives means the stream
already advanced the turn lifecycle and the response is ignored. Aborts now
resolve the queued branch to "cancelled" like the direct path.
Sidecar: the queued send response no longer routes its enqueue-time snapshot
through applyPendingPrompts, which overwrote the event-maintained
session.promptsInQueue and rebroadcast the stale list to every webview.
Includes deterministic regression tests for the stale-response orderings
plus temporary [P0DBG] debug instrumentation (region-marked, to be removed
after runtime verification).
* fix(desktop): ignore stale hub 'running' status after turn settles
The sidecar core is hub-attached, so chat_session_status events are
asynchronous projections of the hub's session record. A stale 'running'
can trail the stream's chat_done and flip a settled turn back to busy,
wedging the composer on 'Agent is working…' with nothing left to
reconcile. Track the epoch at which the turn settled and drop 'running'
status events until a new turn bumps the epoch.
* chore: remove stray QA screenshot artifacts from repo root
* chore(desktop): remove P0 debug instrumentation and fault injection
Strips all [P0DBG] logging, the /p0dbg sidecar route, the webview log
mirror + heartbeat, and the P0DBG_STARTUP_BUSY_MS /
P0DBG_DELAY_QUEUED_RESPONSE_MS fault-injection paths used to reproduce
the stuck-composer P0. The two real fixes (stale queued-response epoch
guard + stale-running-after-settle guard in the webview, and the
sidecar's non-clobbering queued-send snapshot) and the regression tests
remain.
* refactor(desktop): replace turn-epoch guards with an explicit turn lifecycle
The stuck-composer fixes left the hook with two hand-rolled epoch refs
(turnEpochRef / turnSettledEpochRef) mutated and compared inline across
eight call sites. Extract the rules into a pure TurnLifecycle module that
is now the only writer of the session status:
- a settled turn cannot be reopened: stale hub 'running' projections and
stale queued-send acknowledgements are dropped by the lifecycle instead
of by inline epoch comparisons
- async work (send RPC responses, queue reconciliation) captures an opaque
token and the lifecycle decides whether the world moved on, instead of
handlers comparing counters
- every status write goes through a named operation (begin, turnStarted,
settle, projectStatus, apply, reset), so the state machine is explicit
and unit-testable in isolation
No behavior change: the 5 wedge regression tests and the full hook suite
pass unchanged, plus 10 new unit tests for the lifecycle module itself.
* Revert "refactor(desktop): replace turn-epoch guards with an explicit turn lifecycle"
This reverts commit
|
||
|
|
e703f7af2a |
style(desktop): refine session chat layout (#13205)
* style(ui): refine chat message surfaces and actions * feat(desktop): refine session transcript layout * style(desktop): refine conversation composer * style(ui): format chat theme changes |
||
|
|
3d460d7add |
feat(hub): directional Hub upgrades with update-and-restart prompts in CLI and desktop (#13177)
* feat(hub): prompt update and restart when another install replaces the shared Hub * feat(hub): make managed Hub build-watch interval configurable via CLINE_HUB_BUILD_WATCH_INTERVAL_MS * feat(hub): reuse newer managed Hub builds instead of retiring them Embed a build epoch alongside the deterministic runtime fingerprint so managed-Hub compatibility can order builds in time. When fingerprints differ, a Hub produced after the client's own build is attached over the compatible wire protocol (and the build-mismatch watcher prompts the user to update) instead of being retired, so concurrent installations converge on the newest build rather than replacing each other's daemons. Older, unordered, or metadata-less Hubs are retired and replaced as before. * refactor(hub): simplify mismatch status derivation and dedupe sidecar event encoding * fix(cli): only watch for managed Hub build mismatches in hub-attached sessions Yolo and sandbox sessions force the local backend and never attach to the shared managed Hub, so a newer Hub owned by another installation must not interrupt them with the blocking update dialog. * fix(desktop): stage an app update before hub-mismatch restart 'Update and restart' previously invoked restart_to_apply_update directly, which only relaunches the current bundle. With no update staged by the background 2h updater loop, the app came back on the same version, hit the same newer Hub, and re-prompted immediately. Add a check_for_update_now Tauri command that runs one updater check/download/stage cycle on demand and reports the resulting status. The dialog now stages the update first and restarts only when the updater reports 'ready'; otherwise it stays open and explains that no update is downloadable yet (or that the check failed) instead of restarting into the same version. Addresses the outstanding Greptile P1 on the dialog. * fix(desktop): reset the no-update hint when a new hub mismatch arrives Without this, a dialog for a fresh mismatch reopened pre-set to 'Try again' with the previous prompt's stale hint. * fix(desktop): serialize updater cycles so overlapping checks cannot clobber a staged update The periodic update loop and the on-demand check_for_update_now command run the same check/download/stage cycle against shared state. Without exclusion, two overlapping cycles could download the same bundle concurrently, and the later one could overwrite a freshly staged "ready" status with "idle" or "error" decided from its stale pre-await ready_version snapshot - making the update dialog deny that a staged update exists. A tokio::sync::Mutex now serializes whole cycles; the ready_version snapshot is read under the lock, so it stays authoritative for the cycle that took it. |
||
|
|
354c80df7b |
fix(llms): update AI SDK deps so streamed tool calls with non-zero indexes don't crash (hasFinished) (#13123)
* fix(llms): update AI SDK deps to fix streamed tool calls with non-zero indexes
LiteLLM's Anthropic passthrough emits chat-completions tool_call deltas
whose index mirrors the Anthropic content-block index (1 when a text
block precedes the tool call; see BerriAI/litellm#11580).
@ai-sdk/provider-utils 5.0.18 stored streamed tool calls in a sparse
array keyed by that index and crashed at stream flush with
"Cannot read properties of undefined (reading 'hasFinished')",
aborting the agent turn. Upstream fixed this in provider-utils 5.0.21
("Fix streamed tool calls with non-zero, non-contiguous, reused, or
missing indexes.").
Update the ai / @ai-sdk packages so every chat-completions streaming
path resolves @ai-sdk/provider-utils 5.0.25, and drop the root
">=4.0.0" override on @ai-sdk/provider-utils: with intersect semantics
it pinned the workspace to the already-locked 5.0.18 even after parents
began requiring 5.0.25, and it force-upgraded dify-ai-provider two
majors past its declared ^3 range. Each package now resolves the
version line it declares.
Fixes #13119
* test(llms): pin non-zero streamed tool_call index regression (#13119)
Wire-level regression test: an openai-compatible SSE stream whose only
tool_call delta carries index 1 (Anthropic content-block numbering via
LiteLLM) must complete and emit the tool-call part instead of throwing
at flush.
* fix: address review findings from merge-conflict resolution
- Restore apps/vscode/proto/cline/state.proto to main's version: the
merge commit's pre-commit hook regenerated it with a stale generator,
deleting auto_approve_all_toggled = 174 and moving a reserved line,
creating drift against the checked-in descriptor. The deletion was
never intended.
- Restore FeatureSettingsSection.tsx to main's version (the same hook
reformatted main's file during the merge).
- Regenerate bun.lock narrowly from main's lockfile without --force so
the diff contains only the @ai-sdk family and its direct transitives;
drop the spurious webview-ui-scoped @radix-ui duplicate entries the
previous install introduced (hoisted resolutions still satisfy
webview-ui's unchanged ranges; verified with --frozen-lockfile).
- Align @ai-sdk/provider to ^4.0.7 in @cline/llms to match the rest of
the AI SDK family and avoid parallel provider resolutions.
Revalidated: wire repro streams to finishReason=tool-calls, @cline/llms
suite passes incl. the index-1 regression test, all workspaces
typecheck, SDK builds clean.
* fix: restore FeatureSettingsSection.tsx to main's formatting
The branch's pre-commit biome hook (--semicolons=as-needed, --write
--staged) strips a blank line from this file whenever it is staged,
which is how the unintended diff appeared in the merge commit. Commit
with --no-verify to keep the file byte-identical to main; this PR does
not touch the VS Code webview.
|
||
|
|
1be2d20349 |
refactor(desktop): extract chat message components to messages/ (#13155)
* refactor(desktop): extract chat transcript logic to messages/ Verbatim moves out of chat-messages.tsx (2,415 -> ~1,300 lines), with no behavior changes. - Extract shared constants, grouping and reasoning helpers, tool summaries, and tool icons into messages/. - Add unit tests for the extracted pure logic. - Update test:chat-ui to include tests under messages/. chat-messages.test.tsx remains unchanged and continues to pass. * refactor(desktop): extract chat message components to messages/ Moves MessageBubble, ReasoningBlock, ToolMessageBlock, ToolApprovalPanel (+ formatApprovalTimestamp and the ToolApprovalRequestItem type), and the image lightbox out of chat-messages.tsx into their own modules under messages/. Memo wrappers, comparators, and prop contracts are unchanged; chat-messages.tsx keeps only the ChatMessages orchestration (~780 lines). chat-messages.test.tsx is untouched and still passes. * refactor(desktop): extract chat transcript logic to messages/ Verbatim moves out of chat-messages.tsx (2,415 -> ~1,300 lines), with no behavior changes. - Extract shared constants, grouping and reasoning helpers, tool summaries, and tool icons into messages/. - Add unit tests for the extracted pure logic. - Update test:chat-ui to include tests under messages/. chat-messages.test.tsx remains unchanged and continues to pass. * refactor(desktop): extract chat message components to messages/ Moves MessageBubble, ReasoningBlock, ToolMessageBlock, ToolApprovalPanel (+ formatApprovalTimestamp and the ToolApprovalRequestItem type), and the image lightbox out of chat-messages.tsx into their own modules under messages/. Memo wrappers, comparators, and prop contracts are unchanged; chat-messages.tsx keeps only the ChatMessages orchestration (~780 lines). chat-messages.test.tsx is untouched and still passes. * refactor(desktop): extract chat message components to messages/ Moves MessageBubble, ReasoningBlock, ToolMessageBlock, ToolApprovalPanel (+ formatApprovalTimestamp and the ToolApprovalRequestItem type), and the image lightbox out of chat-messages.tsx into their own modules under messages/. Memo wrappers, comparators, and prop contracts are unchanged; chat-messages.tsx keeps only the ChatMessages orchestration (~780 lines). chat-messages.test.tsx is untouched and still passes. |