159 Commits

Author SHA1 Message Date
DIYgod 291599da72 fix(desktop): restore a maximized window when it is first shown
The restore waited for the window's show event, but on macOS the main
window shown with show() after the renderer is ready never emits it, so
the window always came back unmaximized. WindowManager.showMainWindow now
shows the main window and maximizes it the first time when it was
maximized at quit, and every place that shows the main window uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:29:42 +08:00
DIYgod 64792d2c98 fix(desktop): translate the download and data reset toasts
The main process showed "Download failed!" and the app data reset
results in English whatever the app language. Translate them like the
download success message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:06:10 +08:00
DIYgod 92d110978e fix(desktop): report menu update checks and wait for one in progress
Check for Updates in the app and tray menus discarded the result, so
nothing happened when there was no update or the check failed. A check
started while another was running, such as the background check at
launch, returned "no update" at once, which the About page then
reported as up to date. Failures of the update decision also escaped
the check's error handling.

Wait for a running check instead, report menu checks with a toast, and
treat a failed decision as a failed check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:05:36 +08:00
DIYgod 31b816f442 fix(desktop): reopen the window on its display, maximized if it was
The main window was always fitted into the primary display's work area,
so a window left on another display came back on the primary one, and
shrunk when that display was smaller. Whether it was maximized wasn't
saved either, so it came back as a window as big as the screen that
wasn't maximized. A Windows 11 branch read state that nothing set.

Restore the window on the display it was saved on, save the size it
returns to from maximized together with whether it was maximized, and
maximize it again once shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:04:29 +08:00
DIYgod b8fa1f6db0 fix(desktop): stage renderer updates next to the installed bundles
Renderer hot updates were downloaded and extracted under the system
temp folder and then renamed into the app data folder. On Linux, where
/tmp is often a tmpfs, the rename failed across filesystems, so the
update never applied and the whole bundle was downloaded again every 15
minutes. A shared /tmp also let another local user replace the files
between the hash check and the extraction. Stage them inside the app's
render folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:03:24 +08:00
DIYgod f5dca68f97 fix(desktop): measure and trim the HTTP cache correctly
The cache limit evicted the newest files first, aimed 50MB above the
limit so it ran again every ten minutes, and stat'ed every file inside
the sort comparator, blocking the main process. Size, limit and the
open folder button also used a lowercase cache path, which doesn't
exist on Linux, and clearing the cache deleted Chromium's files while
it was using them, through a batch file in a console window on
Windows.

Evict the oldest files down to 50MB below the limit from one pass of
stats, leaving the index alone, use the Cache folder, and clear the
cache through the session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:02:57 +08:00
DIYgod 7178f1661b fix(desktop): find npx for the CLI settings in packaged apps
The CLI settings reported Node.js as missing for most users of the
packaged app: an app opened from the Dock or a launcher only gets the
system PATH, without Homebrew, nvm or the Node installer's directories,
and running npx.cmd without a shell fails with EINVAL on Windows since
CVE-2024-27980. Ask the user's interactive login shell for npx, run
npx.cmd through a shell, and let the desktop login sync without npx
now that it no longer needs it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:01:33 +08:00
DIYgod be72132c67 fix(desktop): write the CLI login without running npx
Syncing the desktop session to the CLI spawned npx folocli@latest login,
which a packaged app often can't find from the Dock or Finder, fails
with EINVAL for npx.cmd on Windows, downloads the latest CLI each time
and puts the session token in the process arguments. The config was
also written readable by other users and not atomically.

Write the config the way folocli login does, with the decoded token,
into a directory and file only the user can read, replacing the file
in one step. The npx command helpers it no longer uses are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:00:15 +08:00
DIYgod 3de8daf476 fix(desktop): sync the CLI login only after the CLI is connected
Every launch and every sign-in ran npx folocli@latest login with the
session token, whether or not the user had ever connected the CLI, so
the session token was written to ~/.folo/config.json for everyone and
a disconnected CLI was signed in again on the next launch. The token
check also compared the URL-encoded cookie with the decoded token the
CLI stores, so it never matched and npx ran on every launch.

Remember when the CLI is connected or disconnected in the settings and
sync only while it is connected, compare decoded tokens, and clear the
CLI login on sign-out even when removing the notification token fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:59:06 +08:00
DIYgod 5bc2179c12 fix(desktop): keep custom integration secrets out of the log file
Custom integration requests logged the start of the request body, the
start of the response and every response header, and masked request
headers only by name, so cookies and secrets sent in the body or in
custom headers ended up in main.log, which users attach to issues. Log
only header names, the response content type and sizes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:56:18 +08:00
DIYgod 2ca1603f4a fix(desktop): replace the colon of titles like "Q: ..." in Obsidian notes
The Obsidian note name came from a path sanitizer that keeps a leading
drive letter, so a title such as "Q: Why..." kept its colon. On Windows
the note was written to an NTFS alternate data stream of a file named
Q, reported as saved, and never showed up in Obsidian.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:55:56 +08:00
DIYgod 7720a4d26f perf(desktop): reuse the code language model in the main process
Guessing the language of an unlabeled code block created a new model
for every block, which reloaded the model and kept the previous weights
in memory, blocking the main process each time; a reuse is about five
times faster. Keep one instance, and stop logging whole code blocks at
debug level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:55:30 +08:00
DIYgod c1992d2054 fix(desktop): keep polling the dock badge after a failed request
The dock unread count polling ended for good on its first failed
request, for example right after waking up before the network is back,
so the badge stopped updating until a restart. Each wait also left an
abort listener behind. Skip a failed round and keep polling, and remove
the listener when the wait ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:55:00 +08:00
DIYgod 8cd1188103 fix(desktop): open the main window even when startup cleanup fails
The main window was created only after the auth cookie migration, the
cookie dedupe and the removal of old renderer bundles had finished, none
of which handled errors. Any of them failing, such as a renderer file
held by an antivirus on Windows, left the app running without a window,
and a large cleanup delayed the first window. Log cookie cleanup
failures and remove old bundles after the window opens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:54:41 +08:00
DIYgod 6bff15dc58 fix(desktop): open deep links that launch the app
A folo:// link that started the app was lost: Windows and Linux pass it
as a launch argument that nothing read, macOS sends open-url before the
listener was registered after several startup steps, and links routed
to a window that was still loading called renderer handlers that didn't
exist yet. Opening a link from the browser launched Folo without
following the feed or signing in.

Queue deep links from launch arguments, open-url and second instances
until the main window's renderer reports it is ready, and hold them
again while it reloads or is recreated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:54:15 +08:00
DIYgod 99fd346399 fix(desktop): relaunch after installing an update on Windows and Linux
Restart to update destroyed the main window and waited a second before
quitting to install. On Windows and Linux destroying the last window
quit the app at once, so the update was installed silently on quit and
the app never came back, looking as if it had crashed. Quit to install
right away there; quitting destroys the windows before they can cancel
it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:52:08 +08:00
DIYgod f9b493281e fix(desktop): reload a hot-updated renderer from the app:// origin
Restarting after a renderer hot update loaded the new bundle with
loadFile, a file:// origin, while the app normally runs from app://.
Local storage and the IndexedDB-backed database are per origin, so the
restarted app came up signed out with default settings and an empty
database, and that session's changes vanished on the next launch. Load
it through the app:// URL the window started with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:51:18 +08:00
DIYgod 9e51f06c69 refactor(desktop): remove dead integration, discover and AI settings code
Nothing imports CustomIntegrationValidator, DiscoverForm (replaced by
UnifiedDiscoverForm), TimelinePromptSection (taken off the AI settings
page in 691307925) or AiOnboardingModal. URLSchemeHandler.getExamples,
the CustomIntegrationManager validate helpers, formatFileSize and
withDisplayStateHandler have no callers. Remove them along with the
locale keys only they used.

The aiTimelinePrompt setting stays because the server still reads it
when it scores the AI timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:51:16 +08:00
DIYgod 9603533ed4 fix(i18n): translate hard-coded UI strings and complete every locale
Move the hard-coded English left in the desktop renderer, the Electron
main process, the mobile app and the shared packages to i18next, with
en, zh-CN, zh-TW, ja and fr-FR translations. SSR share pages are
unchanged.

- Fill every namespace in every language, delete keys no code reads
  (server error codes stay) and update translations left behind by
  later English changes.
- Stop HTML-escaping interpolated values in the desktop renderer.
  React already escapes text, so names with & or quotes showed entities.
- Render error boundary fallbacks as components so they can use hooks.
- Localize zod validation messages with the app language.
- Throw ClipboardError from the clipboard helpers and show translated
  toasts. formatEstimatedMins takes a formatter so callers localize the
  units.
- Use one glossary term per concept in ja, fr-FR and zh-TW.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:50:23 +08:00
DIYgod 469412a1d9 fix(desktop): update push-receiver to stop a parser stack overflow
push-receiver 4.3.1 parsed MCS messages recursively and could crash the main process with "Maximum call stack size exceeded" (#4457); 4.4.0 parses them in a loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod ec0e193f77 fix(desktop): keep the bundle Dock icon in packaged builds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod d5ffb82f4c fix(desktop): start in the tray when launched at login
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod 2f392594d5 chore(deps): refresh dependencies and verify affected app flows 2026-09-22 16:20:40 +08:00
DIYgod d8835acd02 fix(desktop): route the remaining main-process requests through Chromium and keep the CLI token out of logs
After the API bridge moved to `net.fetch`, several main-process requests still used
Node's `fetch`, which ignores the app's proxy settings: the update manifest and policy,
the main-process API client, file downloads, text-to-speech, page fetching for the
reader, and the Eagle, qBittorrent and custom integration requests. They now go through
`net.fetch` as well. The readability package takes a `fetch` implementation for that;
downloads hand it to ky. Requests that manage cookies by hand pass
`credentials: "omit"`, so Chromium does not keep them too.

A failed CLI login sync used to be logged with the whole command line, token included.
The command is now described with the token redacted, and the command's output is
scrubbed the same way.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 17:40:26 +08:00
DIYgod ca0c85b20d fix(desktop): send API requests through Chromium's network stack so they follow the proxy
The main process sent the renderer's API requests with Node's `fetch`, which knows
nothing about the app's proxy settings: with only a system proxy configured, every API
request went out directly while images and pages went through the proxy. On networks
that need the proxy the API was unreachable.

`net.fetch` uses Chromium's network stack and therefore the session's proxy: the system
proxy, PAC files and SOCKS alike. Cookies stay attached by hand from the managed auth
cookies, so Chromium's own cookie handling is switched off for these requests. Verified
in a real main process: the proxy is resolved and used, a manual `Cookie` header is sent,
`Set-Cookie` is exposed through `getSetCookie()`, and `credentials: "omit"` leaves the
cookie store untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 16:47:43 +08:00
Komh 44f0e5df06 fix(tray): Linux tray icon duplicates and dead menus on minimize-to-tray toggle (#5086)
On Linux, `Tray.destroy()` does not remove the icon from StatusNotifierItem
hosts (waybar, KDE Plasma), so toggling "Minimize to tray" off and on stacked a
dead icon every time. Keep the single Tray instance for the app's lifetime
instead; the window close handler reads `getTrayConfig()` live, so a tray icon
that outlives a disabled setting is inert.

The icon cannot be removed from a running process, so `setTrayConfig` reports
when a restart is needed and the renderer offers one via a new `app.relaunch`
IPC. That relaunch runs from `$APPIMAGE` where set (AppImage's `execPath` points
into a mount that is gone by then), drops `--start-in-tray` so it never comes
back hidden, and uses `app.quit()` so `before-quit` still persists the window
bounds and flushes cookies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 18:10:14 +08:00
DIYgod 7c220c69a8 fix(desktop): allow custom URL schemes 2026-08-15 14:51:34 +08:00
DIYgod dd2c933936 chore(ci): format integration URL scheme test 2026-07-28 13:52:45 +08:00
Sebastion bd91b015ee fix(desktop/ipc): allowlist URL scheme protocols in openURLScheme (#5056)
The 'integration.openURLScheme' IPC method invokes 'shell.openExternal'
with a renderer-supplied string after only checking that it contains
'://'. Electron's documentation explicitly warns that passing untrusted
URLs to 'shell.openExternal' is unsafe: schemes such as 'file://',
'smb://', 'ms-msdt:', 'search-ms:', 'jar:', 'res:', 'javascript:',
'data:' and 'vbscript:' have well-known abuse chains (local file
disclosure, NTLM credential theft over SMB on Windows, MSDT/Follina-style
RCE, etc.). Because the renderer process can also reach this IPC via any
XSS sink in untrusted RSS feed content, the previous validation was not
sufficient.

Replace the substring check with strict URL parsing plus an allowlist of
protocols that match the integration use-cases documented in the UI
(Obsidian, Bear, Drafts, Things, Notion, DEVONthink) plus generic
http/https/mailto. All other protocols are rejected with a clear error.

Adds vitest cases for representative dangerous schemes (verifying that
'shell.openExternal' is never invoked) and for every scheme shipped as a
built-in example, so future regressions on either side are caught.
2026-07-28 13:40:13 +08:00
DIYgod 1158281f59 chore(deps): update frontend dependencies (#5038)
* chore(deps): update frontend dependencies

* fix(ci): format OTA verification commands
2026-07-10 22:36:12 +08:00
DIYgod 431ab0e0be fix(desktop): preserve native image context menu 2026-05-14 18:55:17 +08:00
DIYgod 9bfcfc805b fix(desktop): sanitize Obsidian file path separators 2026-05-14 14:45:50 +08:00
DIYgod a48123476d fix(desktop): improve Obsidian integration metadata 2026-05-14 13:58:30 +08:00
DIYgod 4be035d37e fix(desktop): keep tray instance when refreshing menu 2026-05-12 16:43:44 +08:00
DIYgod 98fcb71734 fix(desktop): persist auth cookies with max age 2026-05-12 16:12:34 +08:00
DIYgod 88ad11a2ae fix(desktop): dedupe auth session cookies 2026-04-26 09:57:42 +08:00
DIYgod acfb054bdc fix(desktop): ignore stale cached renderer bundles 2026-04-15 10:10:00 +08:00
DIYgod 51efb5d5a5 fix(release): repair ota and desktop update flows 2026-04-12 16:04:15 +08:00
DIYgod 33c8bb98b6 feat(ota): unify desktop updates with ota service 2026-04-12 15:39:13 +08:00
DIYgod 096422e034 feat(desktop): migrate tts to folo service 2026-04-10 14:38:20 +08:00
DIYgod aea74f80a7 chore(sync): merge main into dev
# Conflicts:
#	apps/desktop/layer/renderer/src/modules/settings/tabs/integration/index.tsx
2026-04-03 13:28:15 +08:00
wait 20cc43cf16 fix: resolve Obsidian EPERM on macOS by using native folder picker (#4242) (#4924)
- Replace text input with native dialog.showOpenDialog for vault path selection
- Add path validity detection with three-state UI (unselected/valid/invalid)
- Fix YAML frontmatter parsing failure with Chinese/special characters
- Fix ENOENT error when vault subdirectory doesn't exist
- Add AI summary (description) to Obsidian frontmatter export
- Add author fallback to feed title when entry author is empty
- Increase filename truncation limit from 20 to 80 characters for CJK titles

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 12:41:59 +08:00
DIYgod b7ccf7b872 fix(desktop): repair auth and cli sync 2026-04-03 12:38:01 +08:00
DIYgod 4cb761a501 fix(desktop): persist two-factor challenge cookies 2026-04-03 11:24:49 +08:00
DIYgod 43b5052b06 fix(desktop): persist session token after 2fa 2026-04-02 22:19:36 +08:00
DIYgod 8b3a1b8965 fix(desktop): use one-time token for cli sync 2026-04-02 21:02:46 +08:00
wait bd879d5f9e fix: resolve Obsidian EPERM on macOS by using native folder picker (#4242) (#4924)
- Replace text input with native dialog.showOpenDialog for vault path selection
- Add path validity detection with three-state UI (unselected/valid/invalid)
- Fix YAML frontmatter parsing failure with Chinese/special characters
- Fix ENOENT error when vault subdirectory doesn't exist
- Add AI summary (description) to Obsidian frontmatter export
- Add author fallback to feed title when entry author is empty
- Increase filename truncation limit from 20 to 80 characters for CJK titles

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 20:57:21 +08:00
DIYgod c799152a1e fix(desktop): repair cli settings sync 2026-04-02 19:43:05 +08:00
DIYgod a9eaae8c88 fix(desktop): export pdf via electron ipc (#4943) 2026-04-02 12:37:03 +08:00
DIYgod 99081f8e7f fix(desktop): support two-factor auth in electron 2026-03-24 13:03:18 +08:00