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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.
- 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>
- 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>