Repository navigation
Conversation
fix(models): explain searched build fit failures
fix(studio): trust the server format catalog
fix(local): stop a cancelled podcast or office job at the next boundary
feat(local): pass a JSON schema from run_model, starting with the quiz
…tions fix(studio): translate unavailable reasons as sentences
fix(local): discard a chat turn that ends with no answer text
fix(chat): send no citation rules when retrieval finds nothing
fix(local): read the size in a Featherless-shaped listing row's id
…oyment-mode fix(sunset): gate the web redirect on DEPLOYMENT_MODE
fix(sunset): pause hosted recurring user work
ci(web): run the surfsense_web unit tests on pull requests
…-410 Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # docs/architecture/sunset.md
fix(sunset): include sunset_url in the 410 export-only body
…rge-guards Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # docs/architecture/sunset.md
…inspect Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # docs/architecture/local-models/selection.md
test(sunset): cover hosted account purge guards
fix(local): inspect llama.cpp model size at selection
A thinking model writes its trace in markdown, and the box showed it as literal ##, ** and fences under an answer that renders properly. The trace now goes through Streamdown with the shared code and math plugins, the same link safety as the answer, and the answer's harden limits: http, https and mailto links only, no images, no data images. Streamdown's own prop is a rehype plugin list, so the limits are its default raw and sanitize passes plus its harden pass re-run with those options, the way the assistant-ui primitive builds them for the answer. Default mode, since the trace streams. The box keeps its fold, fade, follow-the-newest-line scrolling, region role, name and tabIndex; whitespace-pre-wrap goes, markdown handles it. The scroll tests find the viewport by its role now that the text sits inside elements. The chat doc's gap line is cut down to the thinking switch (#1950). Fixes #1949
license-settings.test.tsx expects "Sep 10, 2027" for an expiry of 2027-09-10T00:00:00Z, but the component formats the instant in the machine's local zone, so west of UTC it reads "Sep 9, 2027" and the suite fails on a laptop while passing in CI. Pin the zone vitest runs in rather than every assertion that spells out a day.
…lient-disconnect # Conflicts: # docs/architecture/chat.md # surfsense_local/backend/tests/integration/chat/conftest.py # surfsense_local/backend/tests/integration/chat/test_chat.py
…ompt The tier fallback keyed on the provider name, so a small model served by LM Studio or Ollama on localhost arrived as openai_compatible and got capable prompts. Fingerprint now carries loopback, set by SelectedModel.fingerprint from its connection's host through host_destination(); the connection loads joined, so the tier stays computed on read with no lazy query and nothing stored. Fixes #1987
Store each upload and cloud import alone in documents/<id>/ under its own name, made safe with pathvalidate and capped at 120 bytes, instead of original.<ext>. The name the file manager and the opened file's window show is now the one the user gave it. No name is stored in a row: the folder's one file is the original. Stop writing extracted.md. Nothing ever read it back; the parsed text lives in documents.content, which search, chat, Studio and any future reindex use. Folders written before this keep original.<ext> and their extracted.md and are never rewritten or reindexed. The lookup in the backend and in Electron skips extracted.md, so a legacy folder whose original was deleted reads as missing, and an upload named extracted.md is stored as "extracted (1).md".
The expired notice told the user to renew "from your account", and the portal has no accounts (ADR 0024). The empty state named no place a license comes from at all. Settings › License now links out through ExternalLink: with no license stored, to the trial form on /downloads and to /pricing; in the expired notice, to /pricing, with the body saying a renewal comes as a new file to add here. The file_expired reason, mirrored in the backend, points at the resend form instead of an account. Nothing is gated: the links are an offer, never a wall. Fixes #1961
budget.py priced the question at 1,024 tokens and said MessageText enforced it, but MessageText had no maximum, so a pasted page walked past its share and llama.cpp cut the reply off wherever the window ran out, with no error. MessageText now refuses more than QUESTION_CHARS, 4,096 characters: QUESTION_TOKENS at the four-characters-a-token estimate history.py already prices turns with, which moves to budget.py as CHARS_PER_TOKEN so there is one ratio. The refusal is a 422 at the wire, before any model is resolved or retrieval runs. One global cap rather than one per window, because it runs before the model is known and the budget prices the question at the same 1,024 for every window. The composer stops at the same cap through maxLength and, once there, says so in a status line, so nobody finds out on send. Fixes #1943
enforce-placeholders reads values only from an object literal, so the three calls in model-ready.tsx that handed it a variable, one through a spread, were never checked: dropping `source` from the first passed lint. Two no-restricted-syntax selectors beside the formatjs rules now refuse a formatMessage whose second argument is not an object literal, and a spread inside that literal, so the shape cannot come back. The three calls write their values out; ModelReady renders the same sentences and en.json is unchanged. Fixes #1966
…es move POST /llm/installs accepted a curated build whose exact fit was TOO_BIG here: the only thing in the way was the screen's disabled Download, and onboarding, a stale catalog, a second window or a warm job id reached the route past it. The job then downloaded gigabytes, wrote a record, and left a model that fails when chosen. The plan learns its entry's committed shape when resolve_install() builds it, where the manifest is in scope, and LlamaCppEngine.check() prices a curated build through the same price() its row uses. Only TOO_BIG refuses, with the sentence the searched path gives; a build that spills installs with no confirmation, as the screen's rule says. The searched path is unchanged. Fixes #1976
ModelCard's "The local runtime is unavailable." and BuildAction's refusal were wired to a runtimeAvailable that every call site hard-coded true, so with llama-server down the screen looked normal: Download ran, the job sat in "Preparing" for thirty seconds, and Use then failed with nothing naming the runtime. useLocalRuntime() reads the healthy flag GET /llm/providers already reports for llamacpp, under the models query key, asked again every 15 s and on window focus. DownloadChatModels shows one notice above the rows while it is false; unknown is not down. Download stays offered, as the runtime doc's failure list specifies: an install still downloads and ends with an honest message. The never-false prop goes from BuildAction, ModelCard and the five call sites, with the per-card message; the image and audio pages read no server state, which stays their own gap. Fixes #1980
… called installed download_build() wrote one record after every file of a build had landed, so a connection that dropped between the weights and the projector left the weights under their final name and nothing written down. The curated row then matched the file by name, read installed with Vision, and the model loaded as text only; nothing offered Download for the projector. The record is now written as each file lands, and InstalledBuild carries the files still to come as `pending`. llama.cpp's rows and sd.cpp's both treat an unfinished record as not installed, so the row keeps offering Download, and a retry fetches only what is missing through the resume _holds() already gives. Old records read as before: `pending` defaults to empty. Fixes #1973
…and the selection
The remote catalog already marked a model unusable, with the manifest's
reason, when its provider is unreachable or it is served only on
/responses or through another protocol. GET /llm/connections/{id}/models
and choose_model() ignored both, so the same model was offered, saved,
and failed at the first real request.
The rule now lives once, as RemoteLookup.unusable_reason(): the remote
catalog's rows and the connection listing both call it. A listed model
it names stays in the listing with selectable_for empty and its
unusable_reason, so every picker and the catalog agree, and choosing it
is refused with that reason unless the user confirms a manual selection,
the same deliberate override an unlisted model takes. A custom
connection names no provider and is never told; a model no entry
describes stays unknown, not unusable.
Fixes #1951
feat(local): write notes and rename sources from the sources panel
fix(local): stop Chromium's spellchecker from downloading dictionaries
feat(local): add a switch to turn thinking off for chat answers
…nd thinking The "+" opens a dropdown with Attach images, Upload sources and a Thinking switch, replacing the image-only button and the separate Thinking button beside the model name. Each row's description shows as a tooltip, and is also in the row as screen-reader text; held rows stay hoverable so their reason can be read. Upload sources reuses the sources panel's upload. Adds the shadcn. Switch and swaps the brain icon for ThoughtBubble.
The sidebar, error toasts and the macOS Help menu still open the issue report dialog.
…s main action - Split the Settings sidebar into Settings, Models, System and App groups, and let it scroll now that the groups are taller than the dialog - Translate the Models and System headings into all nine languages - Make Start a free trial the filled button when there is no license; Add license becomes an outline button and See pricing the only link - Use one block skeleton in Resources, as Network and Embedding do
- Opening a source preview now swaps out the left sidebar and widens it to the detail width, as an inspected artifact does the right panel - The right panel no longer closes while a source is previewed - The preview header shows one Open file button, as citations do; the eye and Show in folder buttons are removed (Show in folder stays on each source row) - Update documents.md and overview.md to match
- Add Settings › Report issue in the App group, below About, with the full report form inline and Copy system info under it - Share one form between the tab and the dialog; error toasts, the crash notice and the macOS Help menu keep opening the dialog, since they fire where Settings isn't open - Remove the sidebar's Report issue row and About's Troubleshooting block - Update about.md and issue-reports.md
- On the dashboard, Help › Report Issue… opens Settings on the Report issue tab; on loading, onboarding and the startup error screen it still opens the popup, since no Settings dialog exists there - Error toasts and the sidecar crash notice keep opening the popup - Note in each component who opens the popup and who opens the tab - Tighten the Settings sidebar's row and group spacing so every tab fits without scrolling - Shorten the Report issue label in Spanish, French and Russian - Update issue-reports.md
- On the text generation step, models downloaded from Hugging Face now lead the "On this computer" list; the curated list below keeps its order, starred model first - Their details line says "From Hugging Face", so an unreviewed model isn't mistaken for a recommendation - Downloaded curated models say "Downloaded" instead of "On this computer", which repeated the section heading - Update selection.md
… menu without a workspace - The source preview column can shrink, down to the sidebar's width, so on a narrow window it narrows instead of pushing the open right panel out of view - Help › Report Issue… claims Settings only while a workspace is active; on the "No workspaces" screen, where Settings isn't rendered, it opens the popup instead of doing nothing
feat(local): source preview in the sidebar, grouped settings, Report issue tab, composer add menu
[Agent|Release] Keep opencode off until a model passes the agent test
[Web|Lint] Format the files Biome flags on dev
[CI|Node] Build the website and the installers on Node 24
[Local|Release] Set the desktop version to 2.1.0
…node-24 [CI|Release] Run the artifact actions on Node 24
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedToo many files! This PR contains 917 files, which is 617 over the limit of 300. To get a review, reduce the PR to 300 files or fewer by splitting it into smaller PRs or changing its base branch. Usage-priced reviews support at most 300 files. ⚙️ Run configuration
⛔ Files ignored due to path filters (42)
📒 Files selected for processing (917)
You can disable this status message by setting the
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings
mainup todevfor desktop 2.1.0. Thev2.1.0tag goes on this PR's merge commit.SURFSENSE_LOCAL_OPENCODE_ENABLED=1, and the API mounts its routes only beside one ([Agent|Release] Keep opencode off until a model passes the agent test #2163).surfsense_localis 2.1.0 ([Local|Release] Set the desktop version to 2.1.0 #2164), including the website download pin and the license-email pin.The Desktop Release dry run 37102724459 passed on Mac, Windows and Linux at 0847e12, the head of
dev. No installer carries opencode, no job warns about Node 20, and the Mac build was notarized once the Apple Developer agreement that took effect on 1 Oct was accepted.Once this deploys, the site's download grid asks GitHub for
v2.1.0and shows the releases link until that release is published.Follows #1932