* Studio: UI polish for sidebar, menus, hub and toasts
- Pinned chats: add a Pinned section above Recents with a Pin/Unpin menu
action and a hover unpin button on each pinned row
- Sidebar: align the Recents and Pinned section labels with Train, and
switch the chat Archive icon to archive-03
- Dropdown menus: pull separators in to match item width, and even out the
projects menu padding and radius to match the composer menu
- Account menu: match the divider width to the row hover width
- Hub toolbar: equalize the format, capability and sort filter widths so
the search field has more room
- Model run bar: make the quant selector hover a full pill
- Tooltips: slightly larger corner radius
- Auth: reduce horizontal padding on the submit button
- Toasts: center the model load content so top and bottom padding match
* Studio: shrink pinned unpin icon, match projects menu to composer menu
* Studio: inset dropdown separators slightly from item width
* Studio: move archived chats from the sidebar into chat settings
Archive now shows a toast pointing to Settings. Archived chats are listed
and managed (open, unarchive, delete) under Settings, Chat, Data.
* Studio: make the archive toast open the archived chats list
* Studio: keep chats archived when opened from the archived list
* Chat settings: optional delete confirmation and menu cleanup
Add a "Confirm before deleting" toggle in Settings > Chat > Data. When
turned off, deleting a chat from the sidebar skips the confirm dialog
and removes it instantly. The preference is stored client-side.
Add a separator above Archive in the chat row menu so Archive and
Delete read as a group separate from Export.
Drop the duplicate divider above "Clear all chats". The section already
draws a divide-y separator between rows, so the destructive row no
longer adds its own top border.
* Hub, dialog and page polish
Hub catalog:
- Drop "inference" from the Hub subtitle.
- Make the HF token shield a circle in the header.
- Equal, narrower filter triggers that stay a fixed width; long labels
truncate with an ellipsis instead of clipping.
- Tighter gap between the label and the dropdown chevron so one more
character fits.
- Keep the toolbar on one row at desktop widths.
- Align the model list and row hover with the Discover tab, add right
side breathing room.
- Make the dark-mode scrollbar lighter so it is easier to see.
- Filled size and quant chips no longer draw a border.
- Column divider uses the sidebar border color.
- Hub card extends into the bottom space.
Filters and channels:
- Add an MLX format option; detect MLX repos by library or tag.
- "Fine-tune ready" now includes 16bit safetensors, not only bnb-4bit.
- Shorten the curated channel descriptions.
Shared UI:
- New standard chevron icons (down and submenu right) shared across the
Hub, dropdown menu, menubar, project switcher and model selector.
- Dialogs get more top and bottom padding and a larger heading.
- Guided Tour hides itself on pages with no tour.
Other pages:
- Export and Data Recipes use the same page padding as the Hub.
* Soften popup overlays and refine hub filters
Dialogs, alert dialogs and sheets now use a light blur with a slight
darken instead of a heavy black scrim, so popups stay legible without
dimming the whole screen. Edit System Prompt and Edit Chat Template
drop their custom overlays and share the same look.
Also limit the Fine-tune ready channel to checkpoints Unsloth can
actually fine-tune (drops fp8, nvfp4, w4a16 and similar), revert chip
borders so filled chips stay visible, nudge the Run icon to center it,
and shrink the submenu chevron a touch.
* Inline chat rename and review-feedback fixes
Rename a chat inline as a rounded pill on the row instead of opening a
dialog. Enter saves, Escape cancels, clicking away saves. Projects and
training runs keep the existing dialog.
Address review feedback:
- Route the no-confirm chat delete through the shared cleanup helper so
it gets the same error toast and pin removal as the confirmed path.
- Archived chats now confirm before deleting (respecting the setting)
and pass the active thread, so deleting the open chat resets to a new
chat instead of leaving a stale thread URL.
- Only offer the MLX format filter on Discover, since downloaded
inventory rows are never tagged mlx and would filter to empty.
- Tighten the non-finetunable name match so a dot-delimited token such
as .tflite is also excluded from the Fine-tune ready channel.
* Sidebar: borderless inline rename, no name flash, align titles
Rename now edits the title as plain highlighted text with no pill or
box. While the debounced sidebar refresh catches up after a rename the
row shows the new name optimistically, so the old name no longer flashes
back in. Recent chat titles now line up flush under the Recents label.
* Profile: preferred name and avatar shape options
Add a "What should Unsloth call you?" preferred name that drives the
new-chat greeting, falling back to the first name then the login id.
Add a toggle to show the avatar as a full circle or a rounded
rectangle, applied everywhere the avatar appears.
* Settings: show version block at the top of General
Surface the Unsloth and package versions at the top of General, not
just in About. Both tabs share one StudioVersionSection so the version
fetch and markup live in a single place.
* Profile: pick a sloth sticker as your avatar
Add a curated set of sloth stickers users can choose as a profile
picture, shown below the picture shape toggle in Settings > Profile.
- Only squarish, low-whitespace stickers are listed so each one fills
the avatar frame cleanly.
- Selecting one reuses the existing avatar persistence (localStorage),
so it sits alongside the photo upload with no backend changes.
- Image avatars now get a neutral background so transparent stickers
read cleanly in both shapes.
* Profile: keep transparent avatars transparent
A transparent upload is now kept as WebP and shrunk to fit instead of
falling back to JPEG, which has no alpha and was painting a background
behind the image. The image avatar is also explicitly transparent.
* Profile: fall back to PNG for transparent avatars on Safari
Safari cannot encode WebP via canvas, so a transparent upload was
throwing instead of saving. Fall back to PNG, which keeps alpha and
works in every browser, before giving up. Still never uses JPEG.
* Archived chats: reset nav when deleting the open compare too
Compare panes do not write the active thread to the store, so the pair
id only lives in the route search. Derive the open chat id from the
route (thread or compare) like the sidebar does, so deleting the open
archived compare resets to a new chat instead of a dead URL.
* Profile: trim three sloth stickers from the picker
Drop the duplicate computer sloth without text on the screen (keeping
the Local one), the nature-screen sloth, and the gift sloth.
* Chat: tighten model selector right padding
Use less right padding than left so the trailing chevron does not leave
a heavy gap on the right of the trigger.
* Address review feedback on profile, reset and rename
- Give each sloth avatar button an accessible name so screen readers
can tell the options apart.
- Reset all local preferences now clears the delete-confirmation pref,
so it returns to the safe confirm default.
- A no-op Enter in inline rename now closes the editor instead of
leaving the row stuck as an input with its blur suppressed.
* Chat: rebalance model selector padding and chevron
More left padding than right and pull the chevron close to the label so
the trigger reads balanced around the text instead of heavy on the right.
* Hub: shrink the external link dialog icon
Set the icon to size-6 so it sits proportionally in the media circle
instead of filling it.
* Hub: shrink the external link dialog media circle
Override the media circle to size-12 with a size-5 icon on this dialog
only, so it is more compact without changing the shared component.
Polishes the in-app update notification banners and the sidebar account menu.
Update banners (web and llama.cpp):
- Add a download icon next to the title, aligned with the title text.
- Even out the action row so the buttons share balanced padding and equal spacing.
- Let the row wrap on narrow viewports so Copy command is never clipped under overflow-hidden.
Sidebar:
- Use the setting-07 cog for the profile button and the Settings menu item.
- Use the sun-03 icon for the Light Mode menu item.
- Slightly enlarge the profile cog.
Frontend only. Build and typecheck pass.
* Fix model picker keyboard navigation
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Address model picker review feedback
* Fix model picker tab order with row actions
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Remove model picker keyboard contract test
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Etherll <61019402+Etherll@users.noreply.github.com>
DiffusionGemma serves via the visual runner, which streams per-step
canvas frames so the answer resolves live in the bubble. The agentic
tool loop (generate_chat_completion_with_tools) does not forward those
frames, so whenever a tool pill (Search/Code) was on the live canvas
silently vanished while text still streamed. DiffusionGemma is not a
tool-calling target anyway, so report supports_tools=False for it: the
chat always takes the frame-forwarding path, and the Search/Code pills
disable themselves (a local model has no builtin web search either).
Also turn the artifacts canvas on by default for DiffusionGemma so a
full-HTML answer (e.g. a playable game) renders as an interactive
sandboxed card without the user flipping the global artifacts toggle.
* Studio: use a clearer check mark for the app-wide tick
Route every check mark through a shared tick-icon module with a plain
tick geometry, sized slightly larger than the stock icon so it reads
clearly in menus. No Pro icon assets are vendored.
* Studio: even out plus-menu hover padding on all sides
Trim the plus-menu side gutter to 9px so it matches the 0.5rem top/bottom
padding, and drop the container radius to 21px to keep corners concentric.
* Studio: bake the tick fallback stroke at 1.5 to match the icon set
---------
Co-authored-by: shimmyshimmer <info@unsloth.ai>
* Studio: make project sources work with RAG and polish project UI
Projects had a disabled Sources tab with an Add sources placeholder.
This wires it up end to end on top of the RAG engine:
- Add a project scope to the RAG store, ingestion and retrieval
- New endpoints: POST/GET /api/rag/projects/{id}/documents
- search_knowledge_base resolves kb, project and thread scopes; an
explicit KB stays exclusive, project and thread scopes combine
- Multi-scope search: FTS uses scope IN (...), vec0 KNN runs per
scope and merges by cosine score
- Lazy ALTER TABLE adds documents.project_id on existing databases
- Deleting a project also removes its indexed sources
- Sources tab now uploads with progress chips and drag and drop
- Chats inside a project auto-enable retrieval over project sources
when the project has indexed documents (cached probe, no Docs pill
needed); external providers still never receive rag_scope
UI polish:
- Rounder project cards with folder icon chip and softer shadow
- Project header icon in a rounded chip
- Chats/Sources pills and Add sources button without borders
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: match Add sources button shadow to the chat composer in light mode
* Studio: round project switcher hover pill and pad the folder icon
* Studio: remove border from project sources box
* Studio: grey hover on project cards and menu, move search into header, widen page spacing
* Studio: shorten sources copy, white header pills with composer shadow, fixed-width search, hub-size page headings
* Studio: align project landing blocks to the composer width
* Studio: restore muted background and flat look on projects header controls
* Studio: darker grey hover on project cards in light mode
* Studio: soften project card hover grey
* Studio: keep project card menu button visible while its menu is open
* Studio: drop focus outlines and rings on buttons and clickable icons, keep input focus styles
* Studio: address review feedback on project sources
- Remove uploaded files from disk when a project is deleted, confined
to the uploads root
- 404 project uploads when the project does not exist, matching the KB
endpoint
- Guard lexical search against an empty scope list
- Re-invalidate the project sources probe after uploads and removals
settle so a chat sent mid-upload cannot cache a stale negative
- Keep keyboard focus rings: only mouse focus drops the Tailwind ring,
the browser default outline stays removed
* Studio: add a green New badge to the project Sources tab
* Studio: unify New pills, fully round with soft emerald fill and no border
* Studio: a touch more vertical padding on New pills
* Fix project RAG source edge cases for PR #6205
* Fix duplicate RAG upload cleanup for PR #6205
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
* Studio: serve DiffusionGemma GGUFs with the on-device visual decoder
* Studio: render the DiffusionGemma denoising canvas live in chat with honest stats
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: harden DiffusionGemma runner resolution (Windows .exe, build/bin lookup, clear stale audio flag, safe PYTHONPATH, Linux-only pdeathsig)
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Studio: refine menu chevron, tick icon, and one-line plus-menu shape
- Shrink submenu > chevron to size-3.5 in the dropdown, context, and
menubar sub-triggers so it stays centered but reads lighter.
- Use the tick-02 duotone-standard geometry for the app-wide check mark
via a shared tick-icon module.
- Drop the fully rounded pill on one-line plus-menus so they match the
standard menu shape.
* Studio: nudge submenu chevron down to 13px
* Studio: nudge submenu chevron down to 12px
* Studio: drop vendored Pro tick geometry, keep free check icon
The tick-02 duotone-standard path is a Hugeicons Pro asset and is not part
of the MIT free pack, so vendoring it into the source is a licensing risk.
Revert the check mark to the bundled free Tick02Icon and remove the shared
tick-icon module. Keeps the chevron sizing and one-line plus-menu changes.
---------
Co-authored-by: shimmyshimmer <info@unsloth.ai>
Co-authored-by: Unsloth <michaelhan@Michaels-MacBook-Pro.local>
* new-chat shortcut, composer draft autosave, archive threads
* fix
* Studio: harden chat UX additions for legacy threads and unavailable storage
Two robustness fixes on top of the new chat UX features:
- groupThreads: coerce archived to a boolean before comparing
(Boolean(t.archived) !== archived). Threads from the older browser-only
Studio, or any record predating the archived field, can carry
archived === undefined/null. The raw `!== archived` comparison dropped
those from BOTH the Recents and Archived sidebar groups, hiding existing
chats. Treat missing as not-archived so legacy chats still appear in
Recents.
- composer draft autosave: wrap the localStorage read and write in
try/catch. When storage is unavailable (private mode, disabled cookies,
blocked storage) or full (quota exceeded), getItem/setItem throw; the
throw in the restore effect would surface to React and break the chat
page. Draft persistence is best-effort, so degrade quietly.
Verified with bun unit tests on the real groupThreads (legacy undefined no
longer vanishes) and Playwright across chromium, firefox and webkit (draft
save/restore/isolation/clear, new-chat shortcut + crypto.randomUUID, and
localStorage blocked/quota throw handling). tsc clean; no new eslint findings.
* Fix composer draft bleed and orphan cleanup for PR #5771
Centralize composer draft storage in a small util and tighten two edge cases:
- New chat draft bleed: every new chat shared the chat-draft:__new__ slot, so
starting a fresh chat could restore the previous one's half-typed text. Clear
that slot at every new chat entry point (sidebar buttons and Cmd/Ctrl+Shift+O).
- Orphan drafts: deleting a thread left its chat-draft:<id> key behind. Clear
the draft for every deleted thread id.
New util utils/composer-draft.ts owns the key format and wraps localStorage in
try/catch (private mode, blocked storage, quota), replacing the inline copy in
thread.tsx so reads and writes stay best effort everywhere.
* address review
* fix: remove unused chat sidebar binding
---------
Co-authored-by: Daniel Han <michaelhan2050@gmail.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
* Studio: fix Downloaded model list disappearing and order it by last download
The chat model picker scan for cached GGUF and safetensors models aborted
whenever an auxiliary Hugging Face cache dir (such as ~/.cache/huggingface/hub)
was unreadable, returning an empty list. That hid the Downloaded section and
let already downloaded models appear under Recommended. Isolate each cache
probe so an inaccessible directory is skipped instead of failing the scan.
Also order Downloaded newest-first using cached blob mtimes (multi-quant repos
group by their most recent quant), keep the section visible while searching,
and make the per-quant downloaded check per-snapshot and mmproj aware so a
Recommended quant is never falsely marked downloaded.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: harden gguf-variants scan and dedupe by newest timestamp
Guard f.stat() per file so a broken symlink or unreadable file in a
snapshot no longer aborts the downloaded check early, and match quant
labels case-insensitively. When the same repo is present in multiple
caches with equal size, keep the newest last_modified so Downloaded
ordering reflects the most recent copy.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: apply cache-scan guards to sibling endpoints found in review
Extend the inaccessible-cache guard and mmproj/stat hardening to the
parallel HF cache code paths flagged in review:
- list_local_models and the Hub inventory scan now skip an unreadable
auxiliary cache instead of returning 500.
- The GGUF download-progress endpoint excludes mmproj adapters and
guards f.stat() so one bad file does not zero a repo's progress.
- The offline snapshot scanner guards its is_dir() probes.
- The chat-only picker no longer renders a blank list when a search
matches only cached non-GGUF models.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Rounds out the Hub chrome and aligns a handful of icons with the rest
of the app. The header stat pills, HTTP/Xet toggle, HF token button,
info chips, status badges and the single-row download card are now full
pills, and the All formats style dropdown matches the composer + menu
geometry. Also widens the chat options menu so "Move to project" fits
on one line, and swaps a few icons to their HugeIcons equivalents.
Changes:
- hub.css: hub-tag-soft/hub-tag-meta pills go fully rounded; a single-
row hub-download-card reads as a full pill via :has(), while the
expanded card keeps its 22px radius, border and background.
- hub-option-menu.tsx: dropdown surface and items match the composer
+ menu (22px container, 10px side padding, 12px item radius).
- hf-token-indicator.tsx, transport-toggle.tsx: fully rounded with a
fixed 26px height so they line up with the neighbouring pills.
- model-inspector.tsx, dot-tag.tsx, gguf-download-card.tsx: status and
info chips go fully rounded.
- models-header.tsx: Eject uses RemoveCircleIcon; active model pill
fully rounded.
- on-device-folders-dialog.tsx: folder rows use Folder02Icon.
- pickers.tsx: Recommended star uses the HugeIcons star.
- command.tsx, chat-search-dialog.tsx, prompt-storage-dialog.tsx: search
fields use the HugeIcons Search01Icon.
- app-sidebar.tsx: chat options menu widened to w-56.
Pure styling and icon swaps, no behavior changes.
Web "New Unsloth version" banner now matches the llama.cpp banner design
(borderless rounded card, drop shadow, heading title) and moves to the
bottom-right, stacked with the llama.cpp banner in a shared container. Keeps
the Release notes link, and the copy command is platform aware (curl for
macOS/Linux/WSL, irm for Windows).
The web banner is checked once per launch only, with no polling; a re-check
just happens the next time the user opens the app. Copying the install
command no longer dismisses it for good: it hides and returns on the next
launch while the install is still behind. The X button still dismisses the
version permanently.
llama.cpp progress bar: poll the job every 500ms and smooth the displayed
value so it animates with the real download instead of snapping to the ~90%
post-download hold and finishing.
Adjustable items (Chat with Files, MCP, Saved prompts, Compare chat,
Export chat, Canvas, Projects) can be pinned to the top level of the
composer plus menu from Settings -> Chat. Unpinned items move into the
More submenu, which hides itself when empty. Saved prompts can be
pinned individually so they surface in the Saved prompts submenu.
Rounder corners and more padding on the login card, a submit button
that sizes to its label, and the sidebar chat and run rows nudged 2px
left with the section headers brought flush to the row text so the
labels line up like Gemini's sidebar.
Removes the empty band under Eject loaded model, centers the trigger
chevrons, moves the model reload Apply and Reset buttons above Chat
Template so they sit under the settings they apply, matches the
settings nav divider to the sidebar line, and bumps the settings tab
headings slightly.
* chore(studio/frontend): normalize line endings to LF
45 source files under studio/frontend/ were committed with CRLF or mixed
line endings while the rest of the repo and the JS/TS tooling assume LF.
Add a scoped `studio/frontend/** text=auto eol=lf` rule to .gitattributes
and run `git add --renormalize studio/frontend` so these files are stored
with LF in the index. The rule is scoped to the frontend tree (not a
repo-wide *.ts/*.tsx/... policy) so it cannot force LF on files elsewhere;
text=auto leaves binary assets (logos, fonts) untouched.
This commit is whitespace-only (CRLF -> LF) — no source content changed
(verified with `git diff --ignore-cr-at-eol`). It is intentionally
isolated so it can be listed in .git-blame-ignore-revs and skipped by
reviewers and `git blame`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: ignore the frontend LF-normalization commit in git blame
Add .git-blame-ignore-revs listing the whitespace-only line-ending
normalization commit so it doesn't pollute `git blame` output. GitHub
applies this file automatically; locally run
`git config blame.ignoreRevsFile .git-blame-ignore-revs`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Studio: use rounded rectangles for menu item hover states instead of pills
Dropdown, select, and model picker items previously used fully rounded
pill highlights. Switch them to an 11px rounded rectangle so hover and
selected states match across the plus menu, profile menu, run settings,
selects, and the model picker. Also add a small side gutter to the plus
menu so item highlights sit slightly inset from the menu edge.
* Studio: concentric menu corners, wider gutters, single-item pill menus
Container radius now equals the item hover radius plus the side gutter
(12px + 10px = 22px) so the curves run parallel. Menus with a single
item render as fully rounded pills. The profile menu gets the same
gutter and hover radius. Model picker rows go back to their original
fully rounded hover.
* Studio: Add inline confirmation (Allow/Always allow/Deny) for tool calls
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix race in tool-call confirmation gate
* Studio: gate built-in tool calls and harden the confirmation handshake
The Allow / Always allow / Deny controls only lived in the fallback tool
card, but the built-in tools (web search, python, terminal, code
execution, image generation) render with their own components and so
never showed the buttons. Those calls paused after tool_start with no way
to approve them, hanging until the 1 hour timeout. Only MCP tools, which
use the fallback renderer, actually worked.
Render the controls for every tool card by wrapping each registered tool
component (and the fallback) in thread.tsx with a shared
ToolConfirmationControls, so the gate applies uniformly.
Also make the handshake robust:
- The gate keys on a per-call approval_id minted by the backend and
echoed in tool_start, instead of session_id alone, so a stale or
concurrent confirmation can no longer resolve the wrong call.
- The approval slot is registered before tool_start is yielded, closing
the race where a fast click or an auto "Always allow" could reach the
backend before the waiter existed.
- The frontend resolves with the same session id the request was sent
with (plus the approval_id), fixing the new-thread mismatch where the
confirmation targeted a different session than the blocked stream.
- The confirm endpoint returns {resolved}; the UI keeps the buttons and
shows a retry hint until the backend confirms a match, instead of
hiding them on a failed or mistargeted post.
- The gate runs after the disabled-tool and duplicate-call checks, so a
call that will not execute is not put up for approval. A denied call is
still excluded from duplicate detection, so re-issuing and approving it
works.
- "Always allow" is scoped per session to match the backend gate.
Add backend tests for the approval registry, the SSE no-deadlock
handshake, and the loop integration (allow, deny, disabled, duplicate,
re-issue after deny).
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Move "Confirm tool calls" to the Tools section
* Studio: Keep tool group open while a tool call awaits confirmation
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix tool confirmation session scope for PR #5869
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix confirmation follow-ups for PR #5869
* Apply pre-commit formatting for PR #5869
* Fix confirmation cleanup for PR #5869
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Harden confirmation lookups for PR #5869
* Studio: make the tool-call confirmation decision immutable
resolve_tool_decision accepted a second confirmation for the same approval_id
and overwrote slot["decision"] in the window before the waiter reads it and
pops the slot, so a duplicate or out-of-order POST could flip an Allow to Deny
(and returned a misleading resolved:true). Reject once the slot's event is
already set so the first decision wins. Adds a regression test.
* Fix/adjust tool confirmations for PR #5869
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
* Studio: llama.cpp update banner redesign, About tab license info, inline system prompt editing, naming cleanup
- Redesign the llama.cpp update banner to match the chat composer surface
(borderless rounded card, composer shadow, Hellix Medium title), rename
actions to Update and add a 15 minute Remind me later snooze
- Keep the banner up until the user explicitly acts on it; drop the
outside click dismissal
- Add a Settings > General > Notifications toggle to disable the banner
for training-only setups (on by default)
- Rename the Help settings tab to About and add a License section
(Unsloth Studio AGPL-3.0, Unsloth Core Apache-2.0) linking to the
license files in this repo
- Make the run settings system prompt box an inline editable textarea;
the popup editor opens when the prompt overflows the box
- Pointer cursor on the preset dropdown chevron
- Dark mode toasts use the chat composer surface color
- Replace standalone Studio with Unsloth in user facing strings; keep
Unsloth Studio, LM Studio, Fine-tuning Studio, Recipe Studio and CLI
commands unchanged
* Studio: open the system prompt popup on box click, balance banner padding
- The system prompt box opens the Edit System Prompt dialog on click,
matching the pencil action
- Slightly more bottom padding on the llama.cpp update banner so the
spacing reads even next to the action pills
* Studio: replace unsloth studio update with the installer commands in update guidance
- The unsloth studio update command no longer works, so the About tab
update section now shows the one-line installer (curl or irm) for
PyPI and unknown installs, and git pull plus the local installer for
checkouts
- Add a short note that unsloth studio update is no longer supported
- Link the Installation, Updating and Windows install docs pages
- The package update banner now copies the platform installer command
instead of unsloth studio update
* Studio: rounder account menu, inline system prompt box with popup from the label
- Account menu corners go from 14px to 18px via a specific override,
since list menus pin border-radius globally
- llama.cpp banner bottom padding 22px
- System prompt is an inline editable textarea again; clicking the
System Prompt label opens the popup editor, and an overflowing
prompt opens it on box click
* Studio: show the standard install commands in the About update section
- Both one-line install commands (MacOS/Linux/WSL and Windows
PowerShell) are always shown, labeled like the docs, since running
them again updates an existing install
- Drop the unsloth studio update deprecation note
- Add the Mac install guide to the docs links
* Studio: clearer platform toggle and layout in the About update section
- Section heading is Update
- Platform picker is a pair of pill buttons, MacOS / Linux and Windows,
and only the selected platform's install command is shown
- Intro reads: To install or update Unsloth
- Local update heading separates checkout guidance from the standard
install command
* Studio: report GitHub branch instead of dev for source checkouts
A source checkout not on an exact release tag now shows
GitHub <branch> (e.g. GitHub main) as the Studio version in About.
Detached or unusual HEADs still fall back to dev.
* Studio: tighten the About update section copy and toggle styling
- Platform toggle buttons are borderless pills
- Shorter local update wording and restart note
- Docs links read Mac and Windows
* Studio: tighten line spacing in the sidebar account button
* Studio: fix vanishing compact MCP icon on hover, single line pill tooltips
- Compact caret pills (MCP, RAG) keep their icon on hover for inactive
pills too; the off switch hover rules hid the icon while compact mode
hid the X, leaving an empty slot
- Compact icon tooltips and single line compact tooltips render as full
pills; wrapped tooltips keep the 9px corners. TooltipContent measures
line count in a ref callback since Radix mounts portal content
without re-rendering the wrapper
- 1px gap between the name and Unsloth lines in the sidebar account
button
* Studio: Projects hover plus button, align recents with the label
- Hovering the Projects nav item reveals a plus button that opens the
New project dialog, with the same circular hover treatment as the
chat row actions
- Recent chat titles start at the same x as the Recents label
- The system prompt overflow lock only engages for a non-empty prompt
with a laid-out box, so a mis-measure cannot turn clicks into the
popup
* Clip system prompt overflow inside the rounded box
Wrap the inline system prompt textarea in a rounded overflow-hidden
surface so scrolled text and the scrollbar stay inside the box. The
focus ring moves to the wrapper via focus-within.
* Add updating progress bar to llama banner and shorten settings copy
While an update is applying, the banner action row becomes an
indeterminate progress bar that keeps animating under reduced motion,
matching the other loading indicators. Settings descriptions across
General, Profile, Appearance, Chat, Connections, API, and About are
trimmed without losing meaning.
* Address review: desktop update note, server platform detection, zh-CN keys
The About tab no longer shows terminal install commands in the desktop
app, where the bundled backend updates through the built-in updater;
it shows a short note and the docs links instead.
fetchDeviceType now sends the auth token to /api/health, which only
reports the server platform to authed callers, and caches only a
server-reported value. Copied install commands then match the host
platform rather than the browser when they differ (WSL, SSH).
zh-CN gains translations for the new notification and license keys,
the renamed About tab title, and the desktop update note.
* Real download progress for llama.cpp updates, prompt and sidebar polish
The update worker now streams the installer output and parses its
download percent lines into job progress, exposed via the update-status
API. The installer emits finer non-tty milestones when
UNSLOTH_PROGRESS_PERCENT_STEP is set; the worker requests 5 percent
steps. The banner renders a determinate bar from the reported fraction
and falls back to the sweep until the first percent arrives.
Also removes the focus ring on the inline system prompt box and
slightly shrinks the Projects hover plus icon.
Follow-up to #6097. The update banner appeared 8s after load and auto-hid after
about 10s. Show it about 1s after a newer prebuilt is detected and keep it up
until the user dismisses it (click outside or the X) or runs the update; it
stays during an in-progress update so the progress is visible.
Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
Adds an in-app "Update llama.cpp" banner and button to Unsloth Studio. When the installed prebuilt is behind the latest published release, a non-invasive banner appears; clicking Update downloads the latest prebuilt for this host and swaps it in place in the background, with no restart.
Detection reuses the freshness check from #5529. The update re-runs install_llama_prebuilt.py the same way setup.sh and setup.ps1 do after #5963: it forwards the published repo and the AMD gfx target derived from the install marker, and does not pass the removed --simple-policy or the arm64-only --cpu-fallback.
While the installer swaps binaries the backend enters a maintenance state (flag set under the serial load lock, active server unloaded) so a concurrent load cannot start a server from a half-swapped binary; the next load uses the new build. The banner also handles refused responses and jobs started in another tab so it never sticks on "Updating...".
Verified end to end on an NVIDIA B200: installed b9493, detected the update, applied it, and confirmed the binary at the same path advanced to b9585 in the same process. Hermetic backend tests and the frontend type-check pass.
* Chat template: restore the preview box with a smaller font
Bring back the template preview under the Chat Template label at 10px
instead of 13px, clamped to four lines. The label is a plain span
again; the preview box and the edit icon open the editor.
* Circular hover for chat action buttons; smaller template edit icon
- Message action bar buttons (copy, retry, delete, more) and the
branch picker chevrons hover as circles instead of 10px rectangles
- The three dot button next to sidebar chats gets the same circular
hover and open state
- Chat template pencil icon drops from 14px to 12px
* Chat template: drop the preview box, add Reset to the editor dialog
- Remove the template preview under the Chat Template label; the row
is just the label with the revert and edit icons
- The Apply and Reset buttons in the panel no longer trigger on
template-only changes, they remain for model settings that need a
reload
- The editor dialog gets a Reset button that restores the default
template in the draft, disabled when the draft already matches
* Sidebar: Gemini-style row vs action hover; clickable template label
- Hovering the 3 dot action next to a chat shows only the action's
hover circle; the row pill highlight is suppressed while the action
is hovered and applies only when hovering the row itself
- Chat Template label opens the editor and the gap below the row is
slightly tighter
* Plus menu More submenu styling and order; sidebar action contrast
- The More submenu was missing the unsloth-plus-menu class, so its
items hovered with the green accent instead of the shared grey
- Saved prompts moves above Compare chat
- Opening the 3 dot menu no longer highlights the whole chat row, only
the action circle; the circle is a step darker than the row hover so
the two read separately
- Panel icon buttons (template pencil, revert) hover as circles
* Chat template: confirm saves with a toast
Template-only edits no longer surface the panel Apply button, so a
saved override sat pending with no indicator. Saving now shows a toast
stating the change applies on the next model reload, with a separate
message when the save clears the override.
* Chat welcome: raise the greeting block to 27.5vh
* Composer: tighten trailing padding on caret pills
The RAG and MCP pills end in a chevron that carries its own
whitespace, so their hover pill looked over padded on the right.
Pills with a caret drop from 10px to 6px right padding; label only
pills keep the wider padding.
* Compact RAG and MCP pills open their menu instead of toggling off
When the composer collapses pills to icons, the RAG and MCP glyph is
the whole button, so its turn-off click handler made the menu
unreachable. In compact mode the glyph click now falls through to the
dropdown trigger, and the hover X swap is skipped since the click no
longer turns the pill off. Full size pills keep the icon-as-off-switch
behavior.
* Compact pills: name tooltip on hover; pill shaped tooltips
- Collapsed composer icons (Search, Code, Images, Fetch, Canvas, RAG,
MCP) show their name in a hover tooltip via data-pill-label, since
the label itself is hidden in compact mode
- Tooltips (.tooltip-compact and the new pill tooltip) are rounded
pills instead of 10px rectangles
* System prompt edit icon and reset; soft borderless dialogs
- System Prompt section header gets the same pencil icon as Chat
Template, opening the prompt editor; CollapsibleSection grows an
optional headerAction slot so the icon is not a button inside the
toggle button
- The prompt editor dialog gets a Reset button that clears the draft,
disabled when already empty
- All dialogs swap the border for a shared dialog-soft-surface class:
borderless with the chatbox shadow in light mode, flat card surface
with no shadow in dark mode
* Editor dialogs: borderless text areas
The system prompt and chat template textareas drop their border to
match the borderless dialog surface; the soft fill alone defines the
input area.
* System prompt: tighten the gap between the header and the text box
* Studio: sync detected model capabilities into models[] after load
The chat composer gates audio upload on activeModel.hasAudioInput, but
/api/models/list omits audio fields for default and active-GGUF entries
and the single chat load path never wrote the load response's
capability flags back into the store. Audio-capable models such as the
Gemma 4 GGUFs therefore never unlocked audio input in the main chat,
while the compare composer (which does sync) worked.
Add syncModelCapabilities and call it after a successful load and after
the status fetch in refresh, so the flags also survive F5 and are not
clobbered by stale catalog data.
* Studio: merge audio upload into the Add photos & files picker
Remove the separate Upload audio row from the composer plus menu and
register an AudioAttachmentAdapter in the shared attachment pipeline,
so the standard picker and drag-drop accept wav, mp3, m4a, ogg, flac
and webm directly. Gating matches images: the picker always lists
audio and models without audio input get a toast at add() time. The
50MB limit is kept and the file shows as a normal attachment chip.
On send the adapter emits an audio content part on the attachment and
findLatestUserAudioBase64 now also scans attachment content, so the
request still carries audio_base64 exactly as before.
* Studio: extract AudioAttachmentAdapter into its own module
Move the adapter out of runtime-provider.tsx so it is importable in
isolation, export the audio send-path and capability-sync helpers for
tests, and guard attachment id generation for non-secure contexts
(crypto.randomUUID is undefined over plain HTTP on a LAN, matching the
existing guard in startCompare).
* Studio: do not claim .webm by extension in the audio adapter
A video/webm file would match the .webm extension entry and route to
the audio adapter. Real audio webm (MediaRecorder output) always
reports the audio/webm MIME, so matching webm by MIME only keeps video
files out while keeping recorded audio working.
* Studio: only send audio from the newest user message
audio_base64 switches the backend onto the audio generation path
(generate_whisper_response ignores chat messages entirely and
generate_audio_input_response bypasses the normal streaming path), so
replaying audio from an older turn hijacked text-only follow-ups:
Whisper would retranscribe the stale clip instead of erroring cleanly,
and audio VLMs lost tools and streaming. Stop the scan at the newest
user message, matching the consumed-on-send semantics of the legacy
pendingAudio path. Regenerating the audio turn itself still resends
its audio since it is the newest user message in that run.
Also guard extractAudioPartBase64 against null parts in deserialized
history content.
* Studio: forward audio input to llama-server for GGUF models (#6096)
* Studio: forward audio input to llama-server for GGUF models
* Studio: harden GGUF audio input handling (multi-format decode, size cap)
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: carry GGUF audio in the message list so it works with tools
* Studio: bound decoded audio length and make the soundfile decoder optional
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Handle audio attachment edge cases
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: gate audio file picker by loaded model capability (#6142)
* Gate audio attachments by loaded model
* Use conditional spread for audio attachment adapter
* Preserve audio fallback while filtering picker
---------
Co-authored-by: Unsloth <michaelhan@Michaels-MacBook-Pro.local>
Co-authored-by: oobabooga <oobabooga4@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
* Studio: mascot images degrade gracefully instead of showing alt text
Mascots loaded from raw public-folder URLs with no error handling, so a
missing or unreachable file (version skew during updates, subpath
mounts, transient blips) painted the alt text, like the empty mascot on
the training start overlay. A shared MascotImg now resolves against
BASE_URL, retries once with a cache-buster, then swaps to a 4 KB
fallback sloth bundled as a data URI that cannot 404. Applied to the
chat greeting, 404 page, training start overlay, auth form, onboarding
splash and wizard.
* Reset mascot retry state via key remount instead of render-time tracking
* Use MascotImg for the artifact generating panel sloth
* Studio UI polish: search dialog shadow, picker pills, sidebar spacing, white Hub
- Chat search dialog: keep a page-bg shadow in dark mode so the dialog
does not merge into same-color content behind it
- Model selector: hover and selected rows are fully rounded pills
- Sidebar: nudge nav items, recents and section labels 2px right and
widen the hover pill 2px left so its side gaps match
- Hub: pure white page background in light mode and drop the ambient
card glow that tinted the page gray
* Lighten sidebar border, widen model picker rows
- Sidebar border is #f2f2f2 in light mode
- Model picker rows extend 4px further left and right, lining up with
the search bar
* Plus menu: restore the intended 18px corner radius
The global 14px !important dropdown radius overrode the menu's own
18px, so the plus menu and its submenus rendered squarer than designed.
Mark the menu radius !important so it wins.
* Projects: borderless import/export button; picker search border #f2f2f2
- The import/export button on the Projects page drops its outline
border and matches the Sort by pill next to it
- The model picker search inputs use a #f2f2f2 border in light mode,
dark mode keeps the default
* Model selector: align the popup with the trigger label
The popover lined up with the trigger button edge, 14px left of the
label text. A 10px alignOffset starts it just before the label.
* Model picker: shorten search placeholder to Search models
* Pill-shaped model trigger and panel controls; chat template edit button
- Select model trigger: rounded pill with slightly tighter horizontal
padding
- KV cache, speculative decoding, draft N and preset controls in the
chat settings panel are rounded pills
- Chat Template: replace the three-line preview box with an edit icon
button next to the label; it opens the existing editor dialog
* Training overlay: borderless console, sloth attached to the box
- Drop the terminal border for the training start console
- Remove the gap between the sloth image and the console so they touch
* Settings panel: more left padding in pill controls, smaller template edit icon
- f16, Auto and draft N pills get 12px left padding, widths bumped 4px
to keep the values from truncating
- Chat template edit icon shrinks to 14px and the Chat Template label
also opens the editor
* Model picker: more padding inside row pills, hugeicons delete icon
- Row pills get 12px horizontal padding so text is not pressed against
the highlight boundary
- The cached model delete button uses the hugeicons Delete02 icon
instead of the lucide trash icon
* Address review: hub bg layering, dead shadow utilities, merged paddings
- Remove bg-background from the Hub root: the white rule lives in the
base layer and the utility was winning the cascade, so the white
background never applied
- Drop the inline shadow utilities on the chat search dialog: the
chat-search-surface rules sit later in the utilities layer and
already win, making the inline ones dead
- Merge symmetric pl/pr pairs into px on the sidebar group wrappers
* Studio: fix nested dropdown submenus clipped by the menu alignment nudge
The 3px alignment nudge on DropdownMenuContent used translate, which
makes the scroll container the containing block for nested
position:fixed submenu wrappers, so overflow clipped every submenu
(plus menu More and Projects, sidebar chat menus, Recents export).
Use margin for the same visual shift without a containing block.
* Portal dropdown submenus so ancestor styling can never clip them
Rendered inline, the fixed submenu wrapper lives inside the parent
menu's scroll container, so any transform there (like the 3px nudge)
re-enables overflow clipping. Portaling to body removes the submenu
from that subtree entirely, same as DropdownMenuContent.
* Studio: bulk export and import in Settings Chat Data, MCP pill off switch
- Settings -> Chat -> Data gains Export Recents and Projects (Recents or
Recents + Projects, Raw JSONL / CSV / ShareGPT, combined or per chat)
and Import chats, reusing the sidebar Recents menu actions
- Extract bulkExportConversationsByScope so the sidebar and Settings share
one implementation; expose the export and import helpers via the chat
feature index
- MCP composer pill icon now swaps to an X on hover like Search, Code and
RAG; clicking it turns MCP off without opening the server menu
- en and zh-CN locale strings added (parity check passes)
* Reveal the pill X on hover for off-switch icons regardless of active look
The X was gated on data-active, so an MCP pill with no servers enabled
(or a RAG pill without a model) closed on icon click but never showed
the affordance. Off-switch pills only render while their feature is on,
so hover now always reveals the X.
* Replace the Recents hamburger menu with an Export all chats link to Settings
Bulk export and import now live in Settings -> Chat -> Data, so the
sidebar Recents header menu is gone. Each chat's Export submenu gains
Export all chats, which opens Settings on the Chat tab.
* Studio: refine chat UI shadow, background and sidebar divider
- Give both chat composers a Gemini style drop shadow with a short
transition, and add a visible shadow in dark mode.
- Set the main chat surface to a soft off white (#fbfbfc) in light mode
so the white composer reads as a card; dark mode is unchanged.
- Remove the divider line between the side menu and the page.
* Studio: lighten chat background to #fcfcfd
* Studio: make dark mode composer shadow visible
* Studio: drop composer shadow in dark mode
* Studio: unify all light mode shadows on the chatbox shadow
* Studio: unify dark mode surface colors and make hover states visible
* Studio: fix barely visible dark mode hover in account and plus menus
* Studio: lift settings dialog off the page background in dark mode
* Studio: drop greeting periods and restyle projects page to match the chatbox
* Studio: chatbox shadow on search dialog, grey sort pill on projects
* Studio: slimmer sidebar profile row, borderless login card
* Studio: match select popups to their trigger, taller profile hover target
* Studio: bigger projects search pill with the original chatbox glow
* Studio: keep select trigger shape while open
* Studio: center and narrow the projects search pill like Gemini
* Studio: borderless export card with the chatbox shadow
* Studio: chatbox shadow for selectable pills, warmer composer, search pill tweaks
* Studio: one warm background token for every page, softer search glow
* Studio: keep off white depth when warming the page background
* Studio: distinct sidebar surface from the page background
* Studio: white sidebar on the warm page background
* Studio: soften projects search shadow
* Studio: nudge the chat zero state up 5px
* Studio: darker dark mode page background, 28.5vh welcome offset
* Studio: dark mode shadows match the chatbox geometry, borderless recipes empty state
* Studio: revert dark mode shadows, desaturate dark recipe cards
* Studio: lift dark recipe cards, no hover shadow in dark
* Studio: drop the pale hover halo on recipe cards
* Studio: lighter dark recipe text, flat dark menus, flush select popups
* Studio: page-bg shadow on model selector, flush popovers and dropdowns
* Studio: zero menu offsets so dropdowns sit flush against triggers
* Studio: one 14px radius for list menus and sidebar buttons
* Studio: 14px buttons, pill hover shapes, narrower slider inputs
* Studio: pill buttons, keep profile row rectangular
* Studio: pill Save and Delete, small gap under account menu
* Studio: skinnier dropdown popups, larger account menu gap
* Studio: dropdown popups slightly wider than their trigger
* Restore light mode sidebar separator line
* Size panel number input pills to their content
* Lighten light mode page background
* Pure white composer, nudge page background whiter
* Use inline ch width for panel number pills
* Fix panel number pills at uniform 4ch width
---------
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
* fix(studio): block arbitrary external image URLs in markdown renderer
Model-emitted <img src="http://attacker.com/..."> tags were causing the
browser to issue HTTP requests to arbitrary origins, leaking the user's
IP address, User-Agent, and Referer header to any domain a prompt-injected
model could emit (tracking-pixel vector, issue #5596).
Add a urlTransform function passed to <Streamdown> that only allows:
- data: URIs (inline images, mermaid SVG, user attachments)
- blob: URIs (locally generated object URLs)
- relative paths without a scheme (same-origin assets)
All other schemes (http:, https:, ftp:, etc.) return null, causing
Streamdown to omit the <img> element entirely.
Existing iframes are already stripped by Streamdown's default sanitizer;
event-handler attributes (onerror, onload, etc.) are also stripped by
the default schema.
* fix(studio): strip control chars and block backslash URL variants
Two bypass vectors found after review:
1. Backslash-normalised URLs: \\attacker.com\pixel has no colon and does
not start with // so the earlier guards allowed it as a relative path.
Browsers normalise leading backslash pairs to // before resolving, so
the request still reaches the external origin.
2. Embedded control characters: /\n/attacker.com passes trim() unchanged,
startsWith("//") is false, and no-colon check passes it as relative.
Browsers strip ASCII controls (U+0000-U+001F, U+007F) before URL
resolution, so the value resolves to the attacker origin.
Fix: strip all ASCII control characters from the raw URL before any guard,
then block any URL whose normalized form starts with two chars from [/\\]
to cover //, \\, /\, and \/ in one regex.
* fix(studio): delegate non-image URLs to defaultUrlTransform
Returning the raw URL for non-img nodes bypassed Streamdown's built-in
link sanitization, allowing model-emitted javascript: hrefs to reach the
DOM unfiltered. Pass non-image URLs through defaultUrlTransform so the
library's own javascript:/data: sanitization stays active for links.
* fix(studio): use scheme regex instead of includes() for colon check
A colon anywhere in the URL (e.g. /api/image?id=model:v2 or
/snapshots/2026-06-04T12:00:00Z.png) was incorrectly treated as an
explicit scheme and the URL was dropped. Replace the includes(':') check
with a proper scheme regex that only matches when a valid scheme token
appears before any path separator.
* Studio: shorten safeImageUrl comments in markdown renderer
---------
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Adds a self-contained RAG stack to Studio: knowledge bases with chunked indexing, hybrid (dense + lexical) retrieval, and an automatic first-pass context inject into chat. Embeddings run through a local llama-server GGUF backend (default unsloth/bge-small-en-v1.5-GGUF) with a sentence-transformers fallback. The chat tool loop gains a search_knowledge_base tool, a per-turn re-search cap, and source citation, layered on top of the shared ToolLoopController.
Adds the Studio Hub and download manager: browse Hugging Face models and datasets, download GGUF and safetensors with live progress and cancellation, and manage on-device inventory. The Hub does not require a GPU, so it is available on chat-only hosts.
CI: all substantive checks pass, including the three Core jobs after unsloth-zoo#736. The two red checks are non-code flakes, a transient npm-registry DNS resolution failure in the package scan and one quantized vision-model output assertion whose sibling shards passed.
Trim and tighten code comments across studio/frontend TS/JS. Comment-only: every changed file verified code-identical to main via the TypeScript printer signature comparison.
* Studio: keep chat in place when composer attachments resize it
Attaching or removing a file in the chat composer could yank the whole
conversation to the bottom, and the grown composer covered the end of
the chat with no way to scroll it back into view.
Root cause: the Viewport composes refs with an identity that changes on
re-render, so React re-runs our scroll ref on unrelated renders and the
autoscroll hook treated every rebind as a fresh mount, pinning to the
bottom. On top of that the viewport reserved a fixed 160px under the
last message regardless of composer size.
- Treat same-element ref rebinds as no-ops in the autoscroll hook; only
a genuinely new viewport element pins and resets detach state
- Size the bottom spacer from the measured composer height plus a 24px
gap so the chat can always be scrolled above the composer
- On composer growth, detach from the bottom instead of auto-scrolling;
the user scrolls down to reveal the covered lines
- On composer shrink, defer the spacer shrink until it cannot clamp
scrollTop, then release it invisibly on scroll or on bottom-pinning
moments (run start, thread switch, thread load)
* Studio: release deferred composer spacer when a run owns the bottom
Sending with attachments cleared the chips after thread.runStart had
already fired, so the spacer shrink was deferred while the user sat
pinned at the bottom, leaving a permanent extra gap above the composer.
Apply shrinks immediately while a run is active or within 1s of run
start; the run-start pin owns the bottom then, so the clamp is the
intended glide. Caught by a cross-engine Playwright pass (Chromium,
Firefox, WebKit) over the pre and post builds.
* Studio: track the viewport element in state so listeners survive remounts
The deferred-shrink scroll listener was attached once against a ref, but
the keyed overlay provider remounts the viewport subtree on thread
switches, leaving the listener bound to the unmounted element. Removing
an attachment near the bottom in the new thread then left the oversized
spacer stuck until a run started. Track the viewport element in state so
the listener and the clamp math follow the new element.
Reproduced and verified with a thread-switch scenario on Chromium,
Firefox and WebKit; full matrix re-run green.
* Studio: release deferred composer spacer shrink when at the bottom (#6070)
---------
Co-authored-by: shimmyshimmer <michael@unsloth.ai>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
* Studio: refine tool call and reasoning trigger UI
Tool call triggers:
- Chevron fades in on hover or keyboard focus and sits next to the
label instead of being pinned to the right edge, matching the other
collapsible triggers.
- Labels wrap instead of truncating so long tool names and search
queries stay fully readable.
- Smaller chevron for a lighter look.
Reasoning trigger:
- Smaller chevron to match.
- Thinking box drops its bottom padding and raises the streaming max
height so more of the thinking text is visible.
* Studio: pointer cursors and sidebar 3-dots polish
Collapsible triggers:
- Pointer cursor on the reasoning, tool call, and tool group triggers
so they read as clickable.
Chat sidebar:
- Swap the chat row 3-dots menu to the vertical more-vertical icon.
- Pointer cursor on the chat row and its menu button.
- Chat row right padding opens up on hover (pr-4 at rest, pr-8 on
hover) so the title keeps a comfortable gap and clears the menu.
* studio: refine tool-call spinner, chevron, and reasoning spacing
- Use the lucide arc spinner for running tool calls and the app-wide
Spinner, so loading states match the rest of the UI.
- Collapse long tool-call labels to a single line with an ellipsis,
reveal the full label when the row is expanded, and fix the clipped
descenders.
- Keep the collapse chevron next to the label and add top spacing above
the reasoning trigger.
- Remove the redundant nested spinner in the web search running state.
* Studio: drop tool call group background fill
The ghost tool call group used a translucent bg-muted/10 fill that read
as a faint lighter box around every group in dark mode. Remove the fill
and rounding so the group sits flush on the chat background.
---------
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
The composer dock backdrop was a solid block with a hard top edge, so
chat text scrolling underneath got visibly clipped. Replace it with a
gradient that fades the top 28px to transparent.
Also shorten the plus to x rotation in the composer from 300ms to 250ms,
including the reduced motion override.
* studio: redesign chat composer
Reworks the new-chat composer and the compare composer into a single
rounded pill surface with a softer, lighter look.
- New welcome screen with a time-of-day sloth mascot and a lighter
heading.
- One rounded composer surface with a soft drop shadow. The input grows
inline as you type and collapses back to a single row when cleared.
- Tools and attachments live in a single plus menu; the thinking control
is a compact pill with a reasoning-effort submenu.
- Inlined glyphs for the thinking, send, and dictate controls, kept in
sync across the main and compare composers.
- Toast notifications match the composer surface: no border line, the
same drop shadow, and the same dark surface color, with a ring-less
close button.
- Dark mode: the side-menu shadow blends into the background, hovered
menu rows read clearly, and their roundness matches light mode.
- Composer styles use dedicated unsloth- prefixed classes so compare
mode keeps its own stacked layout.
* studio: sync compare-composer reasoning state and harden compare id
- Compare composer: keep "Preserve thinking" consistent with reasoning,
matching the main composer. Enabling it now turns reasoning on, and
disabling reasoning (the None option or the Thinking toggle) turns it
off, so the invalid "preserve on while thinking off" state can't occur.
- Guard crypto.randomUUID in the Compare action. It is undefined in
non-secure contexts (HTTP over a LAN IP) and would throw; fall back to
a timestamped random id, matching createNavigationNonce.
* studio: reflect pre-selected Search/Code tools when no model is loaded
The Search and Code pills only lit up when the tool was usable right now
(a model loaded and capable), so a tool turned on from the + menu showed
as off in the pill while the menu showed it on. toolsEnabled is persisted
and takes effect once a capable model loads, so the pill should reflect it.
The pills now disable only when a loaded model lacks the capability, and
otherwise reflect the selected state. Applied to the main and compare
composers.
* Studio: link MCP Servers heading to its PR and fix composer pill cursors
Make the "MCP Servers" heading in the chat Configuration sheet link to the
MCP PR, keeping the chevron as the toggle. The label and chevron are rendered
as siblings so we don't nest an <a> inside a <button>.
Also add cursor-pointer to the composer pills and the thinking pill so hovering
a clickable pill shows the hand cursor instead of the default arrow.
* Studio: refine chat composer and add compare-mode parity
- Composer expands to two rows only once the input wraps to a second line,
not on the first keystroke. Re-measure the autosize textarea on the width
swap so expanding no longer leaves a stray blank row.
- Light-mode composer shadow now matches Gemini's soft elevation.
- Plus menu: replace Canvas with a More submenu (Canvas, Compare chat, RAG)
and add Code above MCP. Active Web search/Code items use medium weight.
- Compare mode: the plus side menu, Search/Code toggles, and a Compare exit
pill now match single chat, with the thinking control on the right.
- Projects menu entries link to their tracking PR (#5725).
- Add cursor-pointer to the composer plus button.
* studio: refine composer controls and chat search shadow
- Active tool pills show an x on hover to signal click-to-disable
- Plus button rotates into an x when the tools menu opens
- Composer surface uses a 32px radius and a taller single-line height
- Even, ChatGPT-style spacing between the plus and tool pills in both the single and compare composers
- Send and mic circles resized and spaced, with the arrow centered in the circle
- Chat search box gets a borderless, soft Gemini-style shadow
* studio: size the pill hover x to match the icon it replaces
Cross-engine checks (Chromium, Firefox, WebKit) flagged the active-pill
hover x as a fixed 14px, so it popped smaller than the 19px Code icon.
Fill the glyph slot instead so the x tracks whatever icon it covers.
* studio: do not persist Kimi search/thinking mutual-exclusion in single composer
The single-chat composer flipped the other control off when toggling
search or thinking on Kimi, but without { persist: false }, so it
overwrote the user's saved preference. Match shared-composer and keep
the side effect session-only.
* studio: pointer cursor on model selector trigger and menu items
Add scoped marker classes so the model picker trigger and every
clickable element in its menu (tabs, model rows, delete, eject) show a
pointer cursor; disabled items stay not-allowed.
* studio: pass baseUrl when resolving reasoning caps in single composer
The docked composer omitted baseUrl, so a custom Gemini OpenAI-compat
gateway still advertised the native thinking ladder the backend cannot
honor. Pass selectedExternalProvider.baseUrl like the compare composer
so the resolver hides it.
* studio: grey side-menu hover, green pill hover, thinking hover x
- Plus side-menu items hover grey in light mode, not the green accent
- Thinking pill hovers green like the Search and Code pills
- The plain Thinking toggle shows an x on hover when active, matching
Search and Code; the effort dropdown trigger keeps its bulb
* studio: make the pill hover x a uniform size
The x filled the icon slot, so the wider Code chevron gave a bigger x
than Search and Compare. Pin it to a fixed 15px, centered, so every
pill's x matches.
* studio: broaden chat attachments, fix active hover color, gemini shadow
- Accept svg, source code and many text/config files as drag-and-drop
or picked attachments, matched by extension since their MIME is
unreliable; html keeps its own adapter
- Active (green) side-menu items keep their text and icon color on
hover instead of switching to the accent color
- Composer surface uses Gemini's soft centered shadow 0 0 20px rgba(0,0,0,0.04)
* studio: keep the thinking pill full height when icon-only
The inactive thinking pill has no label, so its flex row collapsed to
the icon height and the hover box looked short. Reserve one text line
(min-height: 1lh + padding) so it matches the Search and Code pills.
* studio: refine composer menu, drop overlay and greetings
- Open the MCP servers dialog directly from the composer plus menu
- Redesign the drag-and-drop affordance Gemini style, drop the badge and border, make the whole chat page a drop target
- Swap in Hugeicons for the RAG, attachment chip and new project icons
- Add time-based randomized welcome greetings, each matched to a fitting sloth
* studio: rename artifacts toggle to Canvas and make it opt-in
- Label the toggle Canvas everywhere, matching the plus menu
- Stop greying out the Canvas menu item; it toggles like the other items
- Only show the Canvas pill in the composer row once it is turned on, since it is less central than Search and Code
* studio: wire Canvas and MCP composer toggles, even out the pill row
- Open the MCP servers dialog from the menu, or toggle MCP on/off once a server is enabled
- Force MCP off when no server is enabled, so the toggle stays honest
- Show Canvas and MCP as opt-in pills that appear in the order they were toggled on
- Expand the composer and light up the pill when Canvas or MCP is on, like Search and Code
- Keep Compare directly after Code in the compare composer
- Use the same Code icon on both composers and give every pill an even icon slot
* studio: tidy composer toggle row and fix MCP enable/disable lifecycle
- Enable MCP automatically after a server is configured via the toggle flow
- Force MCP off everywhere once the last enabled server is removed
- Collapse the pill labels to icons only when more than 4 pills show, keeping Compare labelled
- Order Compare first in compare mode, before Search and Code
- Use the same Code icon and an even 19px icon slot across both composers
- Match the compare composer surface padding and send button inset to normal chat
* studio: revert compare composer padding change that cramped the input
Matching the surface padding to normal chat clipped the textarea text and
left a white strip on top. Restore the compare composer's own padding, which
gives proper top spacing. The send button inset fix stays.
* studio: center welcome greeting and soften composer scrollbar
Center the sloth and title together over the composer instead of
shifting the row left, which left the greeting sitting off to the side.
Keep the composer textarea scroll thumb faint by default and only darken
it when the thumb is hovered or dragged, so a tall draft no longer shows
a heavy dark rail.
* studio: match composer plus-menu tool gating to the pills
The new plus-menu tool entries did not carry the gating the visible pills
already enforce, so the menu and pills could disagree about a loaded
model's capabilities.
- Web search and Code menu items now disable when a loaded model lacks
the capability, while still allowing preselection with no model loaded.
- Enabling Web search from the menu on a Kimi model now flips thinking
off as a session-only change, since Kimi forbids search and thinking
together. This matches the Search pill.
- Added an Images menu item, shown only for image-generation models and
disabled until a model loads, so a short prompt has an entry point.
Applied to both the single-chat and compare composers.
* studio: round the active-pill hover x and even out pill padding
The hover x sat bare and the trailing label was tighter to the pill edge
than the leading icon, so the pill looked lopsided.
- Give the hover x a soft circular background that fills the icon slot,
matching the ChatGPT-style toggle and the icon it replaces.
- Add a little more trailing padding so the label and the leading icon
have even breathing room, and keep icon-only compact pills symmetric.
* studio: nudge the thinking bulb icon up by 0.5px
Bump the thinking lightbulb from 15px to 15.5px in the single-chat and
compare composers so it sits a touch larger next to the other controls.
* studio: drop the hover x circle on icon-only pills
When pills collapse to icon-only, the circle around the hover x is too
cramped in the small chip, so show a bare x there and keep the circle
only on the full-width labelled pills.
* studio: space the compare send button like normal chat
In compare mode the Thinking control sat right against the send button.
Match the normal composer's control spacing (gap-1.5 plus a send margin)
so Thinking has the same breathing room before send. The send button
keeps its 14px inset, so its position is unchanged.
* studio: make collapsed pill hover a circle, not a wide pill
Icon-only pills were wider than tall, so their rounded-full hover
highlight read as a fat rounded rectangle. Make the compact button a
square and center the glyph so the hover (and the x it reveals) sits in
a clean circle.
* studio: fix compare pane drops and audio picker lifetime
- Skip the page-level drop handler when the composer is hidden, so files
dropped on a compare pane are not swallowed by a hidden composer; the
shared compare composer keeps handling drops through its own dropzone.
- Build the audio file input on document.body instead of inside the plus
menu, so the menu closing on select no longer unmounts the input before
the OS picker returns and drops the file.
* studio/chat: stop projects list from white-screening on older backends
The projects list API returned data.projects directly, so a backend that
omits the field handed back undefined. useChatProjects cached that value,
then the next mount read undefined.length and crashed the whole chat page.
Default the projects and threads list APIs to an empty array and keep the
hook null-safe so a bad response can never poison the cache.
* studio/chat: align MCP dropdown with the + menu and add a chevron
Reuse the + menu surface (unsloth-plus-menu) for the MCP dropdown: rounded
corners, narrower width, neutral grey hover, and enabled rows shown as green
text with a right-aligned check instead of the emerald underlay. Add a
chevron to the MCP pill so it reads as openable, matching the Thinking pill.
* studio/chat: make MCP an opt-in pill and fix its dropdown placement
- MCP is back in the + menu as a toggle. The pill now only shows in the
composer when MCP is on, matching Canvas, instead of always sitting there.
- The dropdown follows the composer side like the + menu (opens down in the
welcome composer, up when docked) rather than always opening upward.
- Drop the dropdown caret when pills collapse so the icon is not squished.
- Stop force-syncing mcpEnabledForChat to the server count; the + menu owns it.
* studio/chat: MCP expands the composer, drop sidebar Compare, tidy scrollbars
- Toggling MCP now expands the composer and shows the tool pills, the same as
Canvas, instead of leaving the row collapsed.
- Remove the Compare item from the sidebar now that it lives in the + menu, and
point the compare tour step at the side-by-side view instead of the old button.
- Both sidebars only show their scrollbar on hover, and run settings reserves
the scrollbar gutter so the close button no longer shifts when it appears.
* studio/chat: tighten toggle gap, fix run-settings close button, collapsed Train
- Reduce the composer toggle gap by 2px (gap-1 to gap-0.5) in both composers.
- Move the run settings header out of the scroll area so the close button keeps
its position whether or not the scrollbar shows, and sits flush with the
topbar open button again instead of shifting left.
- Surface Train as an icon in the collapsed sidebar (it already has a labelled
section when expanded).
* studio/chat: tighten Thinking pill X padding, create projects inline
- The Thinking pill used px-2.5, so the hover X sat further in than the left
pills. Match their pl-2 so the X lines up.
- The + menu New project now opens a create dialog and jumps straight to the
new project, instead of routing to the projects list. Shared by both
composers via a small NewProjectDialog.
* studio/chat: soften account menu, hover scrollbars, show collapsed chevrons
- Account menu drops its border ring for the composer's soft shadow and opens
centered over its trigger.
- Settings and search reuse the hover-only scrollbar via a shared
hover-scrollbar class, matching the sidebars.
- Train and Recents keep their chevron visible while collapsed so it is clear
they can be expanded.
* studio/chat: roomier, more rounded account menu
Widen the account menu, add more left and right padding on the rows, bump the
row height and text a touch, and round the corners more, closer to the GPT
account menu.
* studio/chat: trim account menu width and nudge it up 2px
Pull the account menu in slightly on the left and right (narrower box, a touch
less row padding) and lift it 2px higher above the trigger.
* studio/settings: drop outline ring, circular close hover, pointer cursors
- Remove the settings dialog outline ring, keeping just the soft shadow.
- The close button hover is now a circle instead of a rounded rectangle.
- Every clickable control in the settings dialog uses a pointer cursor.
* studio/chat: bump MCP pill icon to 14.5px
Nudge the MCP icon up 0.5px so it sits even with the other pill glyphs.
* studio/chat: bump MCP pill icon to 15px
Nudge the MCP icon up another 0.5px.
* studio/settings: add a Settings title above the tabs
Put a Settings heading at the top of the sidebar so the tabs sit below it,
matching the Claude settings layout. Hidden on mobile where the nav is a row.
* studio/settings: rounder tab hover, bigger title, less-round search dialog
* studio/sidebar: round nav row hover boxes 2px more (10px to 12px)
* studio: drop settings dark shadow + divider, add tab left padding, tune hover roundness
* studio/model-selector: roomier padding, borderless box, rounder hover rows; settings divider light-only
* studio/search: match chat box shadow (soft light, none dark)
* studio/sidebar: borderless chat context menus, rename submenu to Projects with folder-export icon
* studio/model-selector: match light corner radius in dark, drop dark shadow, more visible dark hover
* studio/sidebar: chat context menu matches + side menu styling; relabel submenu Move to project
* studio: borderless message export menu (no dark shadow), match dark corner radius to light on export menu and settings
* studio/sidebar: open chat options menu GPT-style (down-right) and widen so Move to project fits one line
* studio/chat: message export menu uses the chatbox shadow in light mode
* studio/sidebar: narrow chat options menu slightly (w-60 to w-56)
* studio: unify all download icons to Hugeicons download-01; round profile button hover 1px more
* studio/run-settings: bump header to 16px
* studio/sidebar: trim chat options menu width slightly (w-56 to 216px)
* studio/sidebar: trim chat options menu width to w-52
* studio/profile: camera-01 Hugeicons glyph and chatbox shadow on avatar button
* studio: match dark-mode corner radius to light globally (single --radius token)
* studio/recipes: borderless New Recipe menu with chatbox shadow in light, none in dark
* studio: borderless dropdowns globally, chatbox shadow in light, none in dark
* studio: extend borderless + chatbox/none shadow to select, combobox and popover overlays
* studio/mcp: nudge MCP dropdown radius to 20px so its wider box reads as round as the + menu
* studio: restore dark dropdown shadow to avoid same-color merge; greet name ~1/3 of lines; bigger sloth + more gap
* studio/train: active tab is a borderless pill (no underline), roomier padding, more tab gap and bottom spacing
* studio/chat: nudge welcome up ~5px (still vh-based) and trim sloth image to 44px
* studio/train: active tab pill is white with chatbox shadow in light, taller padding
* studio/chat: welcome offset to calc(30vh - 10px)
* studio/chat: welcome offset to 28vh (drop the -10px)
* studio/chat: tighten sloth-to-text gap by 1px (16px to 15px)
* studio/train: revert light active pill to grey fill, drop white bg + shadow
* studio: app-wide hand cursor on every clickable control (disabled excluded)
* studio/chat: welcome offset to 26vh
* studio/chat: welcome offset to 28vh
* studio/chat: harden project and thread list guards against non-array payloads
* studio/sidebar: give the profile row more height and breathing room
* studio/sidebar: trim the profile row top and bottom padding slightly
* studio/sidebar: reduce Train and Recents section label size slightly
* studio/sidebar: trim the profile row top and bottom padding a touch more
* studio/sidebar: enlarge the profile hover area top and bottom
* studio/sidebar: increase profile hover roundness by 1px
* studio/sidebar: trim the profile row top and bottom padding slightly
* studio/sidebar: trim the profile row top and bottom padding slightly
* studio/chat: cache composer line metrics so wrap detection runs once, not per keystroke
* studio/chat: restore the prior view when exiting compare opened from the + menu
* studio/tests: drive Compare from the composer + menu after it moved out of the sidebar
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio/tests: open Compare from the composer + menu in the extra UI suite too
* studio: fix chat dictation microphone access
* studio: snappier plus-to-x spin and steady composer expand gap
Speed up the composer plus icon morph from 480ms to 300ms.
Add row-gap on the expanded composer line so the space between the text
and the controls row stays the same whether the box expanded from
wrapped text or from a toggle being on. The gap sits on the line, not the
input, so the placeholder max-height clamp never crops it.
* studio: only show composer tool pills once a model is loaded
Persisted Search/Code/Canvas/MCP toggles were surfacing the composer pill row on a fresh page load before any model was selected, so an empty composer looked different from the clean just-ejected state. Gate the composerExpanded tool checks on modelLoaded so a model-less composer stays collapsed, while saved preferences still apply the moment a model loads.
* studio: hide RAG composer menu item temporarily
Hide the placeholder RAG entry from the composer plus menu in both single chat and compare until the feature is ready, and drop the now-unused DatabaseIcon import.
* studio: let composer tools pre-select before a model loads
Selecting Web search, Code, Canvas or MCP from the + menu with no model
loaded did nothing visible: the toggle turned on but the composer never
expanded, so the pill stayed hidden. Drop the model-loaded gate from the
expand check so an active tool always surfaces its pill.
Align MCP with the Search/Code pattern too: grey it out only when a loaded
model lacks tool support, so MCP stays toggleable and the pill stays
clickable before a model is loaded instead of looking disabled.
---------
Co-authored-by: Unsloth <michaelhan@Michaels-MacBook-Pro.local>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: Daniel Han <23090290+danielhanchen@users.noreply.github.com>
* fix: toast cancel and style
* fix: align model load toast Cancel and dismiss on the right
* fix: show short cased model name in loaded toast and removed prefix org
* revert: chat load toast refactor to visual-only changes
* fix: align model load toast close button
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: guard empty toast label and dedupe toast padding CSS
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Roland Tannous <rolandtannous@gravityq.ai>
Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com>
* feat: align project sidebar UX with ChatGPT
* feat: align project sidebar UX with ChatGPT
* feat(chat): load stored project list
* feat(chat): add project sidebar workflows
* fix: stabilize project page navigation
* fix: projects chat loading
* fix: show project chat thread
* style: sidebar project spacing and hover clipping
* style: add expandable project chat history and move-to-project submenu
* feat: polish project sidebar
* feat: persist project sandbox paths
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* fix: only create sandbox project workspace dir
* feat: add optional project workspace deletion from delete dialog
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* fix: stabilize chat projects CI failures
* fix: polish project chat navigation
* Studio: manage chat history with projects
Group chats into projects with a dedicated projects page and route.
Sidebar shows recents with per-row actions and a vertical more-vertical
menu, and the sidebar scrollbar stays hidden so rows never shift on
hover. Includes chat settings and composer refinements.
* Studio: projects sidebar and breadcrumb polish
Sidebar:
- Remove the Compare nav item.
- Widen the sidebar to match the projects layout.
- Replace the scroll-gated bottom fade with a static fade pinned above
the profile box, so it no longer attaches to Recents or lags the
collapse and expand animation.
Topbar breadcrumb (chat-page):
- On a project landing show "Projects" linking to the projects list.
- Inside a project chat show the project name and chat title, with the
project name linking back to that specific project page.
- Drop the divider between the model selector and the breadcrumb.
* Studio: make project workspace delete test cross-platform
test_chat_project_delete_files_removes_workspace rooted the project under
pytest tmp_path, which resolves to /private/tmp on macOS. The workspace
delete guard refuses paths under the system denylist by design, so the
test passed on Linux CI but failed on macOS.
Add a workspace_projects_home fixture that keeps tmp_path on Linux and
Windows (CI unchanged) and falls back to a home subdir only when the temp
root is on the platform denylist. Derive the workspace path from the
created project so it tracks the projects home.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: satisfy import-hoist check for new path re-exports
documents_root and project_workspaces_root are re-exported from
utils.paths but only referenced as __all__ string literals, which the
import-hoist safety net does not count as a use. It flagged the two newly
added re-exports as unused imports and failed Source lint.
Name-load both via a module-level _REEXPORTED tuple so the check sees
them used. No behaviour change; consumers still import them from
utils.paths.
* fix: avoid projects empty-state flash
* fix: batch chat search indexing
* Studio: polish chat sidebar, run settings, and search
- Use the native OS scrollbar for the chat sidebar, Run settings panel, and chat search list instead of a custom scrollbar
- Highlight the active run in the sidebar and keep chat search available during training
- Stop the training log view from replaying when navigating back to a run
- Rename the chat settings panel to Run settings and align its toggle icon and position
- Tighten heading and sidebar letter spacing and lighten the Train and Recents labels
- Match the search dialog corner style across light and dark and drop the stray border
- Make the MCP Servers section header plain text instead of a link
- Remove a stray .orig backup file
* studio/frontend: restore Compare entry point in the sidebar
The chat-projects sidebar redesign dropped the Compare nav item and moved
it to thread-sidebar.tsx, which is not imported or rendered anywhere. That
left no way for a user to start a new model comparison (enterCompare only
fired from the guided tour and the training handoff), and broke the
Compare/Recipes/Export UI smoke test that clicks [data-tour="chat-compare"].
Re-add the Compare NavItem to the New Chat / Search group, carrying
data-tour="chat-compare" and the same new-comparison navigation as before.
* studio/frontend: use Unsloth green for the fallback profile avatar
Switch the initials-avatar background from blue to #14b789 so the sidebar
and edit-profile avatar match the Unsloth brand colour.
* studio/frontend: turn project breadcrumb into a project switcher dropdown
* studio/frontend: stop project card kebab clicks from opening the project
* studio/frontend: hide project switcher outside projects
* studio/frontend: stabilize project switcher loading
* style: project switcher alignment
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: shimmyshimmer <107991372+shimmyshimmer@users.noreply.github.com>
Co-authored-by: Unsloth <michaelhan@Michaels-MacBook-Pro.local>
Co-authored-by: Roland Tannous <rolandtannous@gravityq.ai>
Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com>
Move MCP from the chat settings sidebar to a composer pill next to Artifacts. The dropdown lists keyless presets (Context7, Exa, Hugging Face), all off by default, created on first enable and deduped by URL. Add custom MCP opens the existing manage dialog. Enabled rows show a green underlay and a tick that becomes an X on hover to remove; enabling Exa turns off the built-in Web Search and shows a hover tooltip. Reconciles the per-chat MCP flag on load so already-enabled servers activate the pill. No backend changes.
The SourcesGroup measurement container renders every citation badge
off-screen with `invisible absolute` so we can measure how many fit in
two rows. Absolute descendants still contribute to the parent's
scrollable overflow region, so long source lists added hundreds of
pixels of phantom scroll space below the assistant message that the
user could scroll into.
Wrap the measurement container in an absolute, `h-0`, `overflow-hidden`
box so the off-screen pills are clipped out of the scrollable overflow
region. Measurement still works because offsetTop is read relative to
the positioned wrapper.