Repository navigation
[Local|Release] Release desktop 2.2.4 - #2351
Merged
Merged
Conversation
…odes-adr # Conflicts: # docs/adr/README.md
… download host The update prompt and the docs named only the platform's release file. A check first reads the releases feed on github.com, and GitHub serves release files from githubusercontent.com, which also sees the IP address. Translated into every locale.
… own header merge The header test only checked what attachUpdater assigns. It now also runs the installed electron-updater's merge with a real-looking ID, so a version that merged the other way would fail the suite. The comment now says the ID went on the feed and manifest requests, not downloads.
It cuts http(s) and ws(s) addresses up to the first space. An address without its scheme, with escaped slashes or with an unencoded space keeps the rest, so the docs no longer say a page or search never reaches a log.
…license key The success page already says so; the activation guide's lede still said only that the file is checked on your own machine.
A check can make a third request, for the fallback manifest, when the platform's release file is missing.
A report waits up to 5 s on the local API summary. A no given in that window now stops the POST.
…t repeat its prefix
Settings shows "Could not check for updates: {message}". The provider's error now names the hosts and their problems without that prefix.
The summary is the seam with the backend stream: a wrong path or parameter would only show as reports full of unknowns.
The redirect built the GitHub URL from the pinned tag without checking it exists. Vercel deploys a new pin while its release is still a draft, so for the hour before publishing every README, grid and licence-mail link 302'd to a 404, and a never-published `v` did the same. It now checks the tag against the newest 30 published releases (cached for an hour) and otherwise redirects to /releases/latest/download/<file>, recording the version as "latest". The handlers are built from injected GitHub, PostHog and after() seams, so the tests now prove a browser GET is counted once after the response and a bot, scanner or HEAD never is.
The day used to be marked before the check ran, so a launch while offline, or with both hosts down, used up the day's check. It is now marked once a manifest was read from either host; until then the hourly tick tries again, three tries a UTC day at most.
The two PyInstaller bundles shared 1,531 identical files that the NSIS archive packed twice. One bundle now runs as `api` or `worker <queue>`; the API role hides every module an API-only analysis did not collect, so it imports exactly what its own binary did. Leave out what no code path opens: RapidOCR's wheel copy of its models, onnxruntime's C API library, OpenCV's FFmpeg video plugin on Windows, torch's protoc and (Linux) C++ tests, every transformers model family Docling's layout engine does not load, dev tooling, faker/polyfactory, pywin32's MFC, hf_xet. torch and torchvision ship as bytecode; library bytecode drops docstrings where nothing reads them. Linux strips symbol tables, macOS strips local symbols. OpenCV is the headless wheel. CI stages the parser pack before the frozen checks and adds a Docling reading compared byte for byte with source, both roles of the binary, and an import of every native extension.
…ey are personless, not unlinkable installer_download carries where the link was, not a channel, and Stripe and Keygen records can match a trial or purchase count by time, so the page no longer says the counts cannot be linked to anyone.
The Payment Link collects card details and a billing country too; what the page means is that there is no account to create.
…sts to Individual The FAQ still said the $60 window was open for 30 days after launch, which ended on 18 Oct 2026, beside the checkout page's unconditional $60. The feature build request is an Individual licence benefit, not every paid licence's.
A modified click followed the href computed at mount, before PostHog had loaded, so Stripe's client_reference_id could differ from the event's channel.
…ies on The after-payment redirect and a one-time price are set by hand in Stripe; the page's download and no-renewal promises are only true with both.
…ept-Language Settings shows the last feed request as sent, but it left out the User-Agent the outbound session sets. That session also gets an empty Accept-Language list, so Chromium cannot add one from the system locale to update checks or usage reports.
… models on the device Agent turns live in their engine, plugin calls leave only a last-call time, and no reply records which kind of model answered, so the API keeps hourly counts of those three in usage-counts.json in the data folder. Counts only, pruned after 40 days, and marked with when counting began so a window that reaches back before it reads as unknown rather than zero. Nothing is sent.
GET /usage-statistics/summary?since= answers the weekly report's week, hardware and ui_language in the contract's buckets, computed at read time from surfsense.db, the on-device counts and the model runtime's device probe. Values that cannot be derived are unknown.
The docs, pricing, trial and cloud export links in the desktop app now carry utm_source=desktop, utm_medium=app, utm_content=<the place it was opened from> and app_version, so the site can count visits from the app by surface. No ID and nothing else is added; GitHub and Discord links stay plain, and the external-link allowlist passes surfsense.com with any query.
…tics in Settings Settings › About gains a Usage statistics switch beside App updates, each with a "Show exactly what's sent" dialog: the last update request with its URL and headers (or the only headers a check sends, before the first one), and the next usage report built but not sent, with the last one sent and when. Settings › Network names where checks go (updates.surfsense.com, falling back to github.com, or a policy's mirror) and lists usage statistics as a row. A setting an admin policy or DO_NOT_TRACK forces is held off and says why, and Check now is disabled while a policy turns updates off. The update consent prompt now says what a check sends: the IP address, the app's version, OS and architecture, no ID, with github.com behind our host. window.surfsense.privacy is declared as Electron main exposes it (contract 05, part C). Translated into every locale; "usage statistics" joins the glossary.
…tistics Right after the welcome, a Privacy choices step asks two separate questions: whether to check for updates (once a day from updates.surfsense.com, falling back to github.com; the app's version, OS and architecture, no ID) and whether to share usage statistics (a daily active mark and weekly counts in ranges; never documents, prompts, file names, model names, hosts, the license key or an ID). Neither is preselected, the Yes and No buttons weigh the same, and Continue waits for both answers before calling completeFirstRun. Each row opens exactly what it sends. A row a policy or DO_NOT_TRACK holds off is answered already and says why. Only a new install sees it: an install that finished onboarding never reaches this screen, and one that already answered (firstRunAsked) skips it, as does the app outside Electron.
…stics ADR 0068 lets the app send a daily update check through updates.surfsense.com (version, platform and OS, no ID, GitHub behind it) and, only with a separate yes, a usage report in ranges to usage.surfsense.com. It supersedes ADR 0016 and amends 0017, 0020 and 0060, whose status lines now say so. Contract 5 holds the shared contract for Electron main, the app's UI and API, and the two website routes, with the details the first build settled. usage-statistics.md describes what is sent, when, consent, the admin policy, DO_NOT_TRACK, the local summary endpoint and its tests; updates.md, egress.md, about.md and overview.md now say what a check sends and where, and that the app's surfsense.com links say where a visit came from.
…s send Section 3.2 lists updates.surfsense.com, falling back to github.com, with the version, platform and OS a check sends, and usage.surfsense.com with every field of the opt-in report. Section 3.3 replaces "no telemetry" and "we cannot tell whether you ever opened it" with what SurfSense can count when each is on, that neither carries an ID, and that the host uses the IP address only to route the request and derive a country. Section 5 names consent as the basis for usage statistics, withdrawn in Settings, with DO_NOT_TRACK=1 or by an administrator's policy, and the update service the user enabled for update checks. The meta description and the short version say the same.
…nd SPA Updates download only the changed blocks again: electron-updater's GitHub provider sends single ranges, which GitHub serves. A patch makes a range answered with the whole file fall back to a full download instead of writing the file once per range. Windows drops a stale current.blockmap before each check, and pending/ is emptied once its version is installed or withdrawn; installer.exe stays as the next update's base. eSpeak data keeps the 21 dictionaries a curated voice can reach, checked at staging by reading every kept dictionary's switches, with a dated note in the GPL notice. The dmg is LZFSE, smaller and faster to open than zlib. The SPA ships only the two Shiki themes it uses, no Oniguruma wasm, and only the woff2 of each font. An installer-size budget fails a build that grows past it. All Chromium locales stay: dropping a .pak changes Intl's default locale and Chromium's own text for that OS language.
…rote
PyInstaller's strip also stripped numpy's vendored OpenBLAS, which then
failed to load ("ELF load command address/offset not page-aligned") on
the first dry run. Strip in build_binaries.py instead, skipping
<package>.libs and hashed names. The API role test gives the first
catalog read, which probes the hardware in a child, room on a CI runner.
A Basic chat turn and an Agentic turn both marked the install as answered before the model replied, so a first turn that failed read as setup's first answer. Count the kind of model when a reply ends with text, count an Agentic turn once its engine took it, and drive both through the routes in tests that run without the bundled model.
… agent The update prompt had lost the download host, githubusercontent.com, and the first-run row, now the main consent, named neither the IP address nor the download. Both now say where a newer version comes from and that it installs on quit. The headers dialog lists the User-Agent the outbound session sets, the GitHub fallback says it sends it, and the usage report says the hosting provider's logs keep the address briefly. Without the privacy bridge Electron main still reads GitHub's releases, so the update prompt and Settings keep the GitHub wording there instead of naming updates.surfsense.com.
The redirect tests match with Next's own getPathMatch and prepareDestination, typed and called with the options its server uses, instead of its untyped bundled path-to-regexp. The blog post's MDX body takes MDXComponents. `pnpm typecheck` generates the MDX and route types first, so the new tsc-www hook works on a fresh clone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…folders refactor(www): colocate route code and rename website/ to www/
The site is now a pill under the text, as in the chat composer, with each site's own logo from the plugin icons. The dialog keeps a fixed height and scrolls its results beneath the box; before the first search it offers every site, one click each.
Separators between result rows, Show more as the last row of the scroll instead of a pinned bar, a straight arrow on the send button, an icon on the start screen, and no text selection in the dialog or its site menu.
…sage The 60-character cut left most titles ending mid-word in the chats dialog. A thread's name now runs as long as a chat's own title may be.
Long text scrolled under the new-chat composer's controls, which sat over the box, and stopped short of the top border in both composers. The controls are now a row under the text, and the text scrolls up to the border.
feat(sources): rework the add-from-web dialog around one search box
A second demo shows Add from the web followed by an HTML web page. Each video gets a bold caption above it, translated in every README, since a caption below one would read as the title of the next.
docs(readme): add the web-to-web-page demo and caption both videos
…hoices Privacy choices is now onboarding's first step, counted in the progress bar and shown every time onboarding runs, with earlier answers chosen. Back from the first model step returns to it. Each choice is one line, with the full description and every host behind an info icon and a link to the exact request or report. Yes and No are one control whose chosen half is filled. Settings › About uses the same text and info icon.
…UDA variant The Docker proposal moves the supervisor and runtime staging out of electron/ into sidecars/ and runtimes/, adds the headless entry in headless/, and puts the image in docker/surfsense/ built from the repo root. latest moves when the GitHub release is published. The GPU image is -cuda on CUDA 12.8, with -cuda13 later for arm64, and each runtime keeps one source shared with the desktop. ADR 0072 and 0073 record the layout and the variants; 0073 supersedes 0035's CUDA variant names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Docker proposal maps the container's networks and says why Caddy is the one listener. The GPU proposal closes its driver question: -cuda needs 570.26 on Linux and 570.65 on Windows, kept beside the llama.cpp pin and enforced by the installer, NVIDIA_REQUIRE_CUDA and the GPU check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user installs Claude Code in the container and signs in there; HOME on /data keeps both across updates. The Claude dialog shows the container's commands when the API reports it runs in Docker. Closes the proposal's question about the claude CLI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every OAuth callback returns to the origin the user's browser is on. Plugins, Notion included, try a CIMD client, then DCR, then the user's own app, then paste-back; ChatGPT follows the rule as far as OpenAI's client allows. Access from another machine takes HTTPS. Closes the proposal's last open question. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tle bar While an update downloads, a ring around a pill beside the right-panel toggle fills with the percentage electron-updater reports, sent once per whole percent. Once ready, the pill fills and restarts into the update on click. On hover or focus it widens to show "Downloading 64%" or "Update". The sidebar no longer shows updates. Check for Updates… in the macOS menu reports up to date or a failed check in a toast, since Settings may be closed.
docs(docker): one core with two entries, one CUDA variant, and sign-ins for the Docker image
feat(desktop): privacy choices as an onboarding step, and updates in the title bar
docs(changelog): v2.2.4
chore(local): release 2.2.4
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughWarning Review details and warnings were omitted to fit the comment limit. |
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.
What
Releases desktop 2.2.4:
devintomain. The changelog entry is #2349 and the version bump #2350.backend/(feat(backend): one backend for the plugins and the license portal #2337)legacybranch, and the site moved towww/(ci(legacy-split): remove the self-hosted image workflows from dev #2340, chore(legacy-split): move the self-hosted stack off dev and split surfsense_web into website #2341, refactor(www): colocate route code and rename website/ to www/ #2342)#2338's package-manager channels ride along but stay off: their secrets are not set, so
release-channels.ymlskips every channel.Why
The 2.2.4 release, per
surfsense_local/RELEASE.md. The tag goes on this pull request's merge commit.Fixes #
How to test
Before merging, with Save only (no redeploy):
surfsense_web→www; addDESKTOP_TELEMETRY_POSTHOG_KEY;updates.surfsense.comandusage.surfsense.comattached to the project.surfsense-plugin-host: base directory/backend, Dockerfile/server/Dockerfile, a volume at/srv/remote/usage,reddit,instagramadded toPLUGINS, and the license settings copied fromsurfprod-fastapi-api.surfsense-plugin-registry: base directory/backend/plugins/registry.After merging:
https://plugins.surfsense.com/healthanswersokand/plugins.jsonloads.https://api.surfsense.com/healthstill answers 200 (the old backend until the cutover).wwwfrommain;https://updates.surfsense.com/api/updates/stable.ymlanswers 200.v2.2.4on the merge commit and followRELEASE.mdfrom step 5.High-level PR Summary
This is the 2.2.4 desktop release from
devintomain. The repository has been dramatically restructured: the legacy self-hosted stack (surfsense_backend,surfsense_web,surfsense_mcp,docker/) has been moved to alegacybranch, while the desktop app continues onmain. A new unified backend inbackend/consolidates the license portal, plugin host, and SurfSense's own plugins. The site has moved fromsurfsense_web/towww/, which now serves update feeds and usage routes the desktop calls. Package managers (winget, AUR, Snap Store) can now install and update the desktop app. Release scripts have been added for builds (a minimal OpenCV, channel releases, stats collection), and the workflow infrastructure has been significantly reworked to support this new architecture. The changelog entry is #2349 and the version bump is #2350.⏱️ Estimated Review Time: 1-3 hours
💡 Review Order Suggestion
surfsense_local/RELEASE.md.github/workflows/release-local.yml.github/workflows/release-channels.ymlbackend/license/portal.pybackend/plugins/host/app.pywww/features/desktop-telemetry/update-feed/fixtures/stable.yml.github/CODEOWNERSREADME.mdCONTRIBUTING.mdAGENTS.md