Changelog
Current version: v2.10.1
v2.10.0
LatestNew
- Filarr Send — anonymous E2EE file sharing without an account, available at `filarr.com/send`. Drag-drop, optional expiry / max downloads / password, byte-by-byte progress bar. Dedicated D1 table `ephemeral_shares`, separate R2 prefix `send/{id}/`, unauthenticated Worker `/send/*` endpoints. Anonymous caps: 2 GB max file, 7 days max, 10 max downloads, 3 active sends / IP. Creator IP stored hashed (SHA-256), never in plain text.
- Per-plan share limit grid rebalanced for strictly monotonic progression: Free (1 GB / 14d / 10 active / 50 downloads), Solo (10 GB / 90d / 100 active / 1000 downloads), Pro (50 GB / 1 year / unlimited / unlimited). The initial pass left Pro at the same max file size as anonymous Send (5 GB vs 5 GB) — fixed. Anonymous stays useful for one-off sends (2 GB matches WeTransfer free) while Free and above clearly dominate on expiry, active count, features (revoke, history, 1-per-IP).
- Owner-side per-share audit log — new D1 table `share_views` (migration 0008) logs timestamp + country (`request.cf.country`) + `/16` subnet on every chunk_0 served. Never the full IP. New endpoint `GET /sync/share/:id/views` (owner-auth only) + expandable "History" button in the /shares page, lazy fetch + per-shareId cache.
- Separate counters for visits vs downloads — migration 0010 adds `info_view_count` to `shares` and `ephemeral_shares`. The /shares page now shows 2 chips: 👁 page opened, ⬇ completed download. Useful on password-protected shares where lots of visits don't convert (wrong password, abandonment).
- "One download per IP" option in `ShareFileModal` (migration 0011: `one_download_per_ip` on `shares` only). Reuses the `/16` from `share_views` for dedup — no extra IP storage. Chunks 1+ are not IP-gated (interrupted download can resume). UI hint about the NAT caveat (carrier-grade NAT / corp office share the limit).
- Dedicated `/shares` page in the sidebar (entry between Automation and Trash), with a large header + subtitle explaining E2EE. Clean empty state for local-only vaults: "Sharing requires cloud sync" + CTA to Settings. Removed from Settings → Encrypted shares.
- URL recovery via localStorage `filarr-shares-keys-v1` (`{shareId → K_share_base64url}`) — the "Copy link" button in /shares reconstructs the URL even if the modal was closed too fast. Auto cleanup via `pruneLocalShareKeys()` on every refresh. Clear message if K_share is absent (share created on another device).
Improved
- Recipient page `filarr.com/s/[id]` unified for both modes: tries `/share/{id}/info`, falls back to `/send/{id}/info` on 404. The recipient sees the same URL pattern regardless of mode (authenticated or anonymous).
- Meta row spacing in the /shares list: `gap: spacing-2 / spacing-3` instead of 1-5, separators rendered as 4px circles at 40% opacity (middots were glued to text before).
Fixed
- Page `filarr.com/send` no longer 404s — the `next-intl` middleware was redirecting `/send` to `/{locale}/send` which doesn't exist. Mirrored the `/s/` exclusion for `/send`.
v2.9.0
New
- E2EE file sharing — right-click any file then "Share via link". Filarr generates a `filarr.com/s/{id}#k={key}` URL that decrypts in the recipient's browser, no account required. The `k` key lives in the URL fragment and is never sent to our servers (RFC 3986) — zero-knowledge intact, even with full Filarr compromise.
- "Re-encrypt at share creation" pipeline: the client reads the file plaintext locally, chunks it in 16 MB pieces, re-encrypts each chunk with a fresh `K_share`, and uploads to a share-specific R2 prefix. Practical consequence: works for all files, including those uploaded before v2.8.0 (legacy internal storage format).
- Optional password on shares (Solo and Pro) — `HKDF-SHA-256(K_share + password, salt)` derives the actual decryption key, the server never sees the password or the key.
- Configurable per-share limits: expiry (1h / 24h / 7d / 30d / 90d depending on plan), maximum download count, manual revocation from Settings → Encrypted shares.
- Settings → Encrypted shares panel: lists active and recently inactive shares (revoked/expired/limit reached), lets you re-copy the URL of an existing share (the key is cached locally on creation) or revoke.
- Public recipient page `filarr.com/s/{id}` is live: 100% in-browser decryption (WebCrypto), password prompt when required, chunk-by-chunk progress bar, language auto-detected via `Accept-Language` with FR/EN toggle. Tiny bundle (~7 kB), strict CSP, `noindex` — the page never shows up in search engines.
Improved
- Share modal now theme-aware — uses Modal/Button/Checkbox from the design system and accent CSS variables, no more hardcoded colors.
- The "Limit downloads" and "Password" options are now interactive cards that visually reflect state (accent border when selected, 60% opacity when the plan doesn't allow it) — no more invisible 16px checkboxes.
- Real byte-by-byte progress bar during share creation (useful for larger files — creation takes ~1s per 16 MB).
- Recipient-side counter now reads "downloads" instead of "views" — the server only increments on the first chunk fetch, not on page open. The wording now matches reality.
Fixed
- Worker `[placement] mode = "smart"` re-enabled in `wrangler.toml` (regression: the setting was lost after a rebase).
- D1 migration 0007: drop `wrapped_fek` / `wrapped_fek_iv` columns (legacy from the abandoned v1 design) and add `status TEXT NOT NULL DEFAULT 'pending'`. Shares stay in `pending` until all chunks are uploaded — public endpoints reject `pending` with a 404, so half-baked shares can't be fetched.
v2.8.0
New
- Client-side compression (deflate level 5) before encryption — applied to all text/JSON/Office/code files. The server still only sees opaque ciphertext, but you store ~2-3× more original data in the same R2 quota. Each blob carries a version marker so old files keep decrypting after the rollout.
- Storage quotas bumped at no extra cost — Solo 10 → 25 GB, Pro 50 → 150 GB. Free stays at 1 GB during the 30-day trial.
- Max file size 500 MB → 5 GB. Large uploads now use the R2 multipart protocol (16 MB parts, parallelism 3) which sidesteps the Cloudflare Workers 100 MB request cap and supports resumption after a network drop.
- All file types accepted — the extension blocklist (.exe, .msi, .bat, .dmg, .deb…) is gone. Filarr is end-to-end encrypted: a client-side block was cosmetic (a user could always rename). You can now store installers, scripts, ISOs, with no workaround. The UI shows an informational note reminding you we can't scan encrypted files.
- Cloud manifest is now compressed with deflate — on accounts with many files the manifest can run to several MB of JSON. ~5× compression on this payload, sync latency drops accordingly.
Improved
- GET /sync/manifest now honors If-None-Match / 304 — the client sends its local version, the Worker returns 304 with no body if nothing has changed. Saves a full fetch + decrypt on every sync cycle in steady state (which is most cycles).
- Worker Smart Placement enabled — Cloudflare's runtime now colocates execution with D1+R2 instead of the eyeball. 50-200 ms latency win on /sync/* calls depending on your location.
- Compression skipped for already-compressed formats (mp4, jpg, png, pdf, docx, zip, 7z…) — detection by extension AND magic bytes to stay robust. CPU saved, ratio unchanged.
Fixed
- Decryption fallback tries v0 (legacy, marker-less) format on GCM authentication failure of v1/v2 — covers the case where a legacy IV happens to start with a byte that looks like a 0x01/0x02 marker.
- Worker quota guard now checks projection against current usage AND the delta of any in-flight multipart upload, preventing overshoot when two concurrent multipart uploads both pass the pre-check.
v2.7.0
New
- Kanban — horizontal swimlanes with 4 modes (None, Notebook, First tag, Pinned). Each lane is collapsible, state persisted in localStorage. Drag-drop intentionally intra-lane only (cross-lane would change the grouping criterion, too implicit for a gesture). Inline title edit (double-click) + per-column "+" button that creates a note pre-assigned to that column and the current notebook.
- Graph view — detailed side panel on node click: incoming/outgoing/wordcount, resolved tags, last modified, 240-char preview, clickable ↙/↗ neighbors to pivot without leaving the canvas, "Open note" button for the old behavior. Directional arrows on wiki-link edges (SVG markers, reciprocal links merged into a single bidirectional edge).
- Mind map — per-branch collapse + expand (−/+ chevron on each node with children), per-note state in localStorage (empty entries pruned). "Collapse all" / "Expand all" buttons. Clicking a heading node → the note opens and the editor automatically scrolls to that section (bridge via transient pendingScrollToHeading flag, NoteEditor computes the ProseMirror position in a requestAnimationFrame).
- Calendar — mounted as a first-class view in the mode bar. Day modal on cell click with scheduled notes + edits + "Create daily note" button that pre-fills title Daily YYYY-MM-DD and scheduledDate. Dedicated scheduledDate field on the note (drop on a cell no longer overwrites createdAt). Configurable agenda range (past + future tunable).
- Sticky view — per-note resize (bottom-right handle, dimensions stored in viewSizes, MIN/MAX clamped) + scrollable content. "Fit all" button that reframes the canvas to encompass all stickies. Zoom + pan persisted in localStorage, restored on remount.
- Masonry — dedicated sort (updatedAt/createdAt/title/wordcount) + filter (tag/notebook/pinned) toolbar, persisted in filarr.masonryView.config.v1.
- Save-state indicator in the editor header — 4 states (synced / saving / pending / error), distinct colors, aria-live="polite" for screen readers. Distinct from the cloud sync badge: local-only users finally have a state signal for their work.
Improved
- Stable colors in sticky and masonry — derived from note.id (hash) rather than list index, so colors no longer jump on filtering/reorder. Dark-mode palette revisited to stay distinguishable and readable.
- Database view config persisted (visible columns, order, widths, sort, filters) in localStorage per dataset.
- Calendar readability on warm themes — chips and sub-view tabs use --color-text-primary with opacity (instead of --color-text-secondary which became invisible). isValidCssColor regex guard added to reject custom colors polluted as "x,y".
- Disconnected graph clusters now stay closer — d3 force tuning (gravity slightly increased, repulsion reduced between connected components) so orphan subgraphs no longer drift to infinity.
- Graph localStorage errors surfaced via warning toast instead of silently swallowed (QuotaExceededError, access denied).
- coverColor → viewPositions['sticky'] migration — the Sticky view was writing "x,y" into coverColor, polluting the cover display in other views. One-shot migrateStickyPositionPollution reducer detects the pattern, parses the coords, clears coverColor.
Fixed
- "Collapse all" in mind map breaking the view — collectCollapsibleIds was pushing root into the list, so Collapse all collapsed root → empty screen with just a "+". Root is now excluded, and loadCollapsed strips any legacy 'root' from localStorage (self-healing).
- Timeline preserves createdAt/updatedAt across cloud sync — sync was overwriting timestamps with Date.now() during cloud rehydration, the entire timeline was rewritten on every cycle. Fix in StorageService.addItemToFolder which now propagates the received timestamps.
- Timeline shows every match under an active filter — a silent 100-entry cap was truncating search results with no indicator.
- Active note visible in the mode bar across all views (previously: list only).
- Trashed notes hidden immediately from Kanban columns (n.deletedAt == null filter, no refresh needed).
- Intra-column drag in Kanban — preserves order via a new kanbanOrder field + reorderKanbanCard reducer. Cross-column drops still update kanbanStatus as before.
- Clicking a note in non-list views now opens the editor (shared handleOpenNote which switches to list mode + sets the editing note).
- Calendar currentDate syncs across sub-views — clicking a cell in Month mode now switches to Day mode positioned on that day.
- Split panel in Notes mode — each pane now has its own viewMode and editingNote, no more state leaks between the two halves.
- Infinite recursion in handleViewModeChange (an unfortunate replace_all had made the function call itself). Restored dispatch(setNotesViewMode(mode)) in the else branch.
v2.6.0
New
- OS downloads watcher — Filarr can now watch an OS folder (typically Downloads) and automatically import each new file into a Filarr inbox of your choice. Chokidar watcher with awaitWriteFinish 2 s to avoid catching files mid-write, top-level watch only (depth: 0), ignoreInitial: true to avoid re-importing in a loop at startup. Multiple folders supported, profile-scoped config, automatically restarted on profile switch.
- Defense-in-depth security — hardcoded blocklist of executable extensions (.exe, .msi, .bat, .cmd, .ps1, .vbs, .js, .scr, .lnk, .jar, .app, .dmg…) applied before any user filter. Refusal to watch system paths (C:\Windows, C:\Program Files, /etc, /usr/bin…) — a tampered appConfig cannot make a protected folder be read. deleteSource IPC restricted to paths actually surfaced by the watcher AND under a watched root.
- Opt-in extension filter — addable/removable chips in Settings (empty = everything is imported except the executables blocklist, never disableable). Strict main-side sanitization: trim, lowercase, strip leading dot, regex [a-z0-9]+ only.
- "Scan" button — one-shot catch-up scan that enumerates each watched folder and defers each file to the same handleNewFile pipeline (so applies blocklist + extension filter + size limit + dedup). Returns { queued, skipped } for feedback. Disabled until the watcher is active.
- New automation trigger file_imported_from_os — fires alongside file_created when the watcher imports. Lets you write rules targeted at OS imports only (e.g. "PDF imported from OS → Documents", without touching PDFs added in-app). New os_source_path condition to match on the origin path (with Windows/Unix separator normalization).
- 3 automation templates using the new trigger: "Route OS downloads", "Sort downloaded PDFs", "Sort downloaded images".
- Settings UI with runtime feedback — live status badge (Active / Starting… / Error / Inactive) with watched-folder count. "X files imported" counter + last import name, live-updated via the status-changed IPC channel (no polling). Red error banner if state === error, amber banner if some configured folders are missing on disk. Master toggle disabled until config is complete, with inline hint showing what is missing.
Improved
- Auto-routing for files added in-app — the automation engine had file_created as a trigger for a long time but nothing ever emitted it, so user rules did not run on drag-drop / import button. Fix: addFileToFolder now dispatches processFileEvent('file_created', …) after every successful upload. New "Route imported files" template (no-condition + move_to_folder). Anti-loop guard: move_to_folder / copy_to_folder become silent no-ops if source == target.
- Honest end-of-sync reporting — Sync complete: X/N uploaded with a separate failed counter, instead of X uploaded which lied about the real outcome.
- In-memory cache of zombie profiles (profilesFailedToRestore) — a cloud profile that fails to restore (typically because its encryptionKey was wrapped with another OS / DPAPI / Keychain key and is not decryptable here) is no longer retried on every sync cycle. Cleared on profile switch.
Fixed
- CRITICAL — meta:* uploads unblocked at the Cloudflare worker. The pentest hardening from Apr 25 had restricted FILE_ID_REGEX to [a-zA-Z0-9_-]{1,128}, which silently rejected any fileId carrying the meta: prefix (meta:notes, meta:<id>). Production consequence: newly edited notes no longer synced, the remote manifest was incremented while the underlying R2 blobs were never written. Fix: allow : in the regex (it is the only structural separator we use for <namespace>:<id>). Worker redeployed on 2026-04-29.
- uploadFile / downloadFile now rethrow after log + enqueue retry — they were swallowing their exceptions, so processQueue’s try/catch reached markSuccess on every retry, ending in a syncQueue Completed log even when R2 had returned 400. Promise.allSettled in batch loops absorb the rejection without breaking the cycle.
v2.5.0
New
- Per-note sync badge — 4 distinct states (Synced, Syncing…, Unsynced, Offline) derived from client-side dirty-tracking by crossing note.updatedAt with sync.lastSyncAt + sync.state. Two variants: full (icon + label) in the editor header, compact (8 px colored dot) in each card of the list view. Auto-hidden in local-only mode.
- "Show notebooks" toggle in graph view — off by default to preserve the current philosophy, persisted in localStorage. When enabled: purple diamond node per notebook (only those with a note in the graph), discreet dashed edges notebook → notes (~22% opacity) that do not compete with wiki-links. Click on a notebook = filter by notebook + switch to list view.
Improved
- Typing in a long note ~300× to ~9.5M× faster — the hot-path onUpdate → Redux was doing a JSON.stringify of the whole document on every debounced keystroke. lastSyncedContentRef caches the last emitted string, turning the comparison into an O(1) identity-compare on internal round-trips. On a 5.9 MB note, the old path burned 30 ms of main-thread per second of typing, the new one consumes 4 ns.
- Opening a long note: INP 743 ms → 230 ms (~70% reduction) — removed key={editingNote.id} on NoteEditor that was unmounting/remounting TipTap (30+ extensions, ProseMirror schema, child panels) on every click. Three safeguards added to prevent state leaks between notes: reset effect on note.id, prevOnUpdateRef snapshot, synchronous flush in onBlur.
- Double JSON.parse eliminated on open — the sync ref is seeded with note.content at initialization, the effect sees equality on first pass and skips.
- Side panels deferred with useDeferredValue — BacklinksPanel was doing three synchronous O(N) to O(N·M) scans on note-switch. React now slices: urgent commit to make the editor interactive, then recompute at low priority.
- 17 unit tests added (8 syncEditorContent + 9 getNoteSyncState) + reproducible microbenchmark scripts/bench-note-editor-sync.mjs to validate any future refactor of the hot-path.
Fixed
- Icon pollution by Kanban — the Kanban view was writing the column id (inbox, in-progress, review, done…) directly into the icon field, overwriting the chosen emoji on every drag-drop. New dedicated kanbanStatus field + idempotent one-shot migrateKanbanIconPollution reducer that heals the existing fleet (kebab-case heuristic).
v2.4.2
Improved
- Auto-healing of the storage counter — syncService calls POST /sync/recalculate/:profileId once per session per profile after a successful sync, to reconcile the counter from R2 truth without waiting for user action.
- One-shot admin endpoints — POST /sync/admin/reconcile-all (full bucket scan, aggregates by profile, overwrites storage_used_bytes — ran in prod: 78 objects, 5 profiles corrected, total down to 102.7 MB) and POST /sync/admin/list-keys/:userId/:profileId (R2 inspection with manifest / file-chunk categorization + top files, useful to identify orphans).
Fixed
- Cloud storage counter was massively over-counting — every re-upload of an R2 chunk was incrementing storage_used_bytes by the full size of the new body without subtracting the existing object size. After ~26 cumulative re-uploads, the counter diverged by a factor of ×26 (D1: 2.5 GB reported vs R2: 104 MB actual, observed in prod). Fix: R2 HEAD before put + delta update with MAX(0, …) to harden against a negative counter.
- Symmetric quota presign — the quota check in POST /sync/presign/upload compared used + newSize > limit without accounting for the size of the already-stored chunk. A user near their limit could no longer re-upload an existing chunk. Now: used − previous + new > limit.
v2.4.0
New
- Note version history — automatic encrypted snapshots on every save (hash-based dedup, 2-min throttle, 50-version or 30-day retention), 3 interchangeable UI modes: side list, fullscreen scrapbook with collapsed diff, or Figma-style horizontal timeline scrubber with floating date bubble. Safe restore that creates a fresh snapshot automatically.
- Cover overhaul — 40 presets in 6 categories (Colors, Pastels, Dark, Tropical, Minimal, Mesh) + personal image upload with automatic JPEG compression (1.2 MB cap after compression). Three sliders to zoom (30-300%, including zoom-out) and reposition the image horizontally and vertically live.
- Hybrid icon picker with 3 tabs — 280 curated emojis across 8 categories with keyword search and 18 persisted recents, 145 Lucide SVG icons across 17 categories, or upload a small personal image (project logo).
Improved
- Full TipTap extension set in the version viewer (callouts, math, toggle, wiki-links, file embeds, lowlight code blocks, tables) — no note renders blank in history regardless of complexity.
- Defensive fallback when a snapshot has a desynced content/plainText: yellow banner "Render unavailable" + plain-text fallback rendering so users never face a blank panel.
Fixed
- Version restore not applying in the editor (NoteEditor was only resyncing TipTap with Redux on note.id change, not note.content).
- History modal closing on every cloud sync (loadNotesFromDisk.fulfilled was unconditionally nulling editing pointers, unmounting NoteEditor).
- Duplicate history entries after restart (empty memory cache treated first save as "first seen") — lazy disk warm-up before dedup decision.
- Clicks passing through behind the title bar in Timeline mode (native window controls at z-index 99999) — top padding added on the wrapper.
- Version previews rendering blank: combined cause, TipTap instance recreation without repaint + missing extensions dropping custom nodes. Fix: imperative setContent + forced remount + complete extension list.
- Range slider track invisible on light themes (accent-color only colored the thumb) — explicit styling of webkit/moz pseudo-elements.
v2.3.3
New
- PIN sync across devices — the PIN hash now travels inside the FEK-encrypted manifest. Configuring a PIN on one device automatically propagates it to the others (last-write-wins on timestamp). Sync is triggered immediately on PIN change instead of waiting for the next periodic cycle.
Improved
- Lock screen now routes to the PIN lock as soon as a PIN is received from another device (instead of falling back to the vault password).
v2.3.2
Improved
- Reactive FEK indicator — new disk check (4 candidate paths cloud/local × dev/prod) instead of a renderer memory variable that was always inactive in cloud mode.
- Sync-side log throttling — "not-authenticated / offline / no-fek" skips now only log on state transitions, removing the every-15-20s spam.
- Vault Password screen: Filarr logo + real profile name (via fallback chain profile → localProfile → email local-part), i18n keys added (FR/EN).
Fixed
- CRITICAL — Note loss via premature auto-save: on profile open, saveNotesToDisk could run with empty state before loadNotesFromDisk finished, overwriting up to 30 KB of notes. Two-pronged guard: renderer-side (auto-save blocked until a load has completed) + main-side (refuses writing < 200 bytes over an existing file > 200 bytes).
- Enhanced Lock (before-quit handler): the async handler let Electron quit before .fek_safe was deleted. Replaced with event.preventDefault() + explicit quit after completion — .fek_safe is now guaranteed to be deleted before the quit.
- Enhanced Lock (launch enforcement): handleProfileSelected was not setting isLocked=true when Enhanced Lock was enabled but .fek_safe was missing. The app skipped straight to the main screen. Pre-check added.
- Post-unlock reload: after unlock, folders and notes are reloaded from disk (Enhanced Lock skips these loads at startup, they need to be replayed).
- Graph view: edges visible on light themes — edge stroke was almost invisible at low density.
v2.3.1
Improved
- Maintenance release — minor UI fixes for the Pomodoro widget introduced in v2.2.0 (progress ring, settings toggles).
Fixed
- Pomodoro ring: time and label now stacked vertically instead of overlapping.
- Pomodoro settings checkboxes replaced with proper visible switches.
- Mode tabs contrast improved (white text on accent instead of white on white).
v2.3.0
Improved
- Electron 39.2.1 → 41.2.1 — fixes 18 CVEs accumulated in the Electron 39 line: context isolation bypass, AppleScript injection, header injection, multiple use-after-free bugs, path injection in setAsDefaultProtocolClient (Windows), USB device spoofing, and more.
- electron-builder 24.13.3 → 26.8.1 — aligned with Electron 41.
- npm audit: 55 → 47 vulns, 0 critical, 0 direct high runtime. Only react-scripts remains (build-only, not exposed in production).
- Runtime smoke test passed: authentication, cloud sync, manifest merge, upload/download, no regression on Electron APIs in use (BrowserWindow, webContents, session, Tray, autoUpdater, safeStorage, dialog, shell.openExternal, contextBridge).
v2.2.2
Improved
- Full Electron fuses — 8 explicit fuses with strictlyRequireAllFuses: RunAsNode, EnableCookieEncryption (cookies encrypted via DPAPI/Keychain/libsecret), EnableNodeOptionsEnvironmentVariable, EnableNodeCliInspectArguments, EnableEmbeddedAsarIntegrityValidation, OnlyLoadAppFromAsar, LoadBrowserProcessSpecificV8Snapshot, GrantFileProtocolExtraPrivileges. A future Electron version cannot silently introduce a fuse with an insecure default.
- HKDF-SHA-256 on the ECDH shared secret for multi-device pairing — the raw 256 bits are now expanded via HKDF using the pairing code as salt and a domain separator "filarr.pairing.wrap.v1" as info label. More aligned with AES-GCM security assumptions, and isolates the wrap key from any other future use of the shared secret.
Fixed
- Missing i18n keys (i18next::translator missingKey warnings in console at launch): commandPalette.commands.focusTimer/focusTimerDesc, settings.font/fontDesc, settings.importVaultModalDesc, settings.importSelectFile.
v2.2.1
Improved
- Full security audit post-v2.2.0 — 24 findings verified directly in the code, 8 patches shipped immediately, remaining ones deferred and documented.
Fixed
- CRITICAL — Path traversal via import:readFile / import:readDirectory: an XSS in the renderer could exfiltrate ~/.ssh/id_rsa, .aws/credentials, git history, etc. Handlers now require a whitelist of paths approved via dialog.showOpenDialog + symlink rejection + 20,000-entry walk cap.
- CRITICAL — ZIP bomb in vault:importZip: no decompression limit. Caps added: 500 MB total, 50 MB per entry, 10,000 entries max. Absolute paths or entries containing ".." rejected before extraction.
- HIGH — Predictable UUIDs: generateUUID / generateUniqueId used Math.random() (non-cryptographic). Switched to crypto.randomUUID() / crypto.getRandomValues() (OS source).
- HIGH — Service Worker disabled: a SW in Electron could serve stale JS after an update (the user would keep running vulnerable code after patch). Replaced with unregister() + Cache Storage purge at startup.
- HIGH — Timing attack on hash comparison: filePasswordService compared legacy SHA-256 and PBKDF2 hashes with "===". Replaced with constant-time comparison (XOR over full length).
- MEDIUM — URL protocol check: shell.openExternal was guarded by a naive startsWith("https://"). Replaced with strict new URL() parsing rejecting anything but http/https, ASCII control chars, and URLs > 8 KB. Also applied to billing URLs (defence-in-depth against compromised backend).
- MEDIUM — DOMPurify FORBID_TAGS widened: MarkdownPreview now blocks svg, math, foreignObject, annotation-xml, object, embed, base, meta, link, and srcdoc/formaction/ping attributes (historically used to bypass the sanitizer).
- MEDIUM — safeStorage fail-closed: hybrid:storeFEK silently returned false if safeStorage was unavailable. An inattentive caller could proceed without encrypted persistence. Now throws an explicit error.
v2.2.0
New
- Focus / Pomodoro timer — persistent floating widget with 3 modes (focus 25 min, short break 5 min, long break 15 min — all configurable). Automatic focus → break cycling. Two states: minimized pill (clock + play/pause) and expanded card with progress ring, mode tabs, daily counter and settings panel. 90-day daily stats. System notifications. Accessible via header button or command palette.
- Forest theme — deep pine-green dark theme with amber fireflies and green light rays through the canopy. Designed for long writing sessions.
- Atkinson Hyperlegible font — designed by the Braille Institute to maximize readability (distinctive letterforms reduce b/d and 0/O confusion). Useful for low-vision users and everyone else.
Improved
- Dependency audit — 59 vulnerabilities (1 critical) down to 55 (0 critical). Upgraded jspdf 4.2.0 → 4.2.1 (fixes critical XSS GHSA-wfv2-pwc8-crg5, CVSS 9.6), axios 1.7.7 → 1.15.0 (DoS via __proto__, SSRF NO_PROXY, header injection), dompurify 3.3.1 → 3.4.0 (5 sanitizer bypasses).
v2.1.0
New
- Import from Obsidian — 4-step wizard (source → file → options → progress). Parses YAML frontmatter, wiki-links [[]], #tags, callouts > [!note]. Auto-maps top-level folders to Filarr notebooks.
- Sakura theme — light cherry-blossom theme, soft pink on warm cream.
- Crépuscule theme — dark twilight theme, amber and purple on deep brown.
- Space Grotesk font — new typographic option in Appearance.
- Atmospheric halos — subtle gradient overlay on effect themes (Space, Aurora, Sakura, Crépuscule).
Improved
- Canvas2D graph — replaced SVG rendering with Canvas2D (10-50x faster). d3-force simulation with Barnes-Hut, frustum culling, HiDPI, zoom to cursor.
- Organic graph — link strength formula 1/√max(degree), sub-structures emerge naturally instead of forming a ball.
- Node size proportional to √connections (6-40px), hubs are visually prominent.
- Interactive drag — moving a node restarts the simulation, neighbors react. Node stays pinned where dropped.
- Smart labels — visible based on zoom level and node importance (hubs shown first).
- Node limit raised from 500 to 2,000.
- Virtualized NotesList — react-virtuoso reduces ~99% of DOM nodes for smooth scrolling with thousands of notes.
- Clickable wiki-links — imported [[note]] are kept as plain text for Filarr decoration plugin.
- External links — Ctrl+Click opens http(s):// URLs in the browser.
- Wiki-suggestions capped at 20 results (perf with 6,000+ notes).
- Inline parser — support for ==highlight==, [[wiki-link|alias]], and HTML→Markdown conversion.
Fixed
- Billing webhook — fallback +30 days if Stripe doesn't return current_period_end.
- NoteEditor — adapted background and text colors per theme.
- Obsidian preprocessor — cleanup of %% comments, HTML buttons, shields.io badges.
v2.0.0
New
- Zero-knowledge cloud sync — client-side AES-256-GCM encryption, R2 storage in EU (Frankfurt)
- Secure multi-device pairing — ECDH P-256 key exchange via 6-digit code
- Subscription plans — Free (1 GB, 1 device), Solo (10 GB, 3 devices, €4/mo), Pro (50 GB, unlimited, €8/mo)
- Stripe integration — checkout, customer portal, webhooks, automatic plan change detection
- Profile sync between devices — name, avatar, folder structure
- Auto-lock — configurable (5/15/30/60 min), clears FEK from memory
- Enhanced lock mode — deletes .fek_safe on exit, vault password required on every launch
- Exportable recovery key — JSON file encrypted with PBKDF2 + AES-GCM, 2-step restore
- Cloud-only indicator — gray badge on files only available online
- filarr.com/account page — manage devices, subscription and delete account from browser
- Multi-device onboarding — automatic primary vs secondary device detection
Improved
- Cloud manifest encrypted with FEK (not local key) — multi-device compatible
- Device deduplication — device ID persisted locally, upsert on login
- Auto-login after email verification in cloud onboarding
- Storage quota recalculation from R2 (POST /sync/recalculate)
- Resilient token refresh — no longer logs out on Windows file lock (EPERM)
- Notes deleted with parent folder (soft delete, no more orphans)
- Password change synced with cloud account
Fixed
- Storage quota desynchronized after local deletions
- Pairing shown on first device (false positive device count)
- FEK not found after StorageService reinitialization (multi-path search)
- metadata.json unreadable on Device B (encryption key not shared)
- .fek_safe not found after active profile change
- Sync did not start automatically after onboarding
- Next button visible during pairing step
- Onboarding flag not persisted to disk for secondary device flow
v1.7.1
New
- Tab / Shift+Tab indentation in lists (checkboxes, bullets, numbered)
- Collapse chevron on checkboxes — fold/unfold nested content
- Automatic OS language detection at onboarding
- Language selector on welcome page (🇫🇷/🇬🇧)
- Single instance lock — prevents multiple launches, reactivates existing window
Improved
- Theme applied in real-time during onboarding
- Silent update — NSIS installer runs in background, like VS Code and Obsidian
- Auto-install on close when an update is ready
- Theme persistence on disk via IPC (survives updates)
- Theme pre-applied on load — no more white flash in dark mode
- Reduced heading margins (aligned with Notion/Obsidian)
- Interactive release pipeline — choose which platforms to build
- Conditional CI workflow — separate jobs per platform
- Windows artifact name with dashes (fixes auto-updater 404)
- Telemetry reconnected — Cloudflare KV stats resume
Fixed
- setupAutoUpdater() called twice, doubling event listeners
- Space, Lofi, Sky themes were not restored on restart
- Multiple instances could launch simultaneously
- release.js script lost JSON quotes in tag message
v1.6.3
New
- Encryption password at onboarding — KEK derivation and FEK initialization on profile creation
- Recovery phrase (12 words, MetaMask-style) to restore the encryption password
- PIN reset via encryption password or recovery phrase
- 3 new themes: Space (starfield), Lofi (warm earth tones), Sky (airy blues)
- Enhanced code blocks — language selector (24), copy, line count, fold/collapse
- Full vault import (ZIP) — notes, folders, tags, settings, supports plain and encrypted
- Enriched export — TipTap JSON content, extended metadata, templates, flashcards, automations
- New Filarr SVG logo (theme-adaptive component) + all assets updated
- Collapsible Notes side panel (Ctrl+B)
- 30+ accent colors and 36 avatar colors for profiles
Improved
- Graph view — instant centering on first frame, adaptive repulsion and springs
- Flashcards — fixed TipTap parsing, more permissive Q:/A: pattern
- Wiki-links — detection via TipTap raw text instead of plainText (fixes undetected links)
- Link hover preview — popover above link, 400ms delay, click navigates
- Dual tab / Split view — each panel manages its own noteId independently
- Window persistence — position, size, and maximized state saved
- Theme selector — dynamic grid with thumbnail preview
- Onboarding — scrollable container, increased width
Fixed
- Wiki-links inserted via [[ did not appear in linkedNoteIds
- Link popover and "Link a note" button overlapped on hover
- Flashcards found no Q/R pairs due to TipTap double line breaks
- In split mode, clicking a note in one panel also changed the other panel
v1.6.2
New
- Note import (.md, .html, .txt, .filarr, .json) with automatic TipTap JSON conversion
- Enhanced global search — finds notes (title + content with context excerpt) and settings sections
- Editor dark mode themes — automatic dark variants for Default, Writer and Developer presets
Improved
- Complete note export — preserves headings, lists, code blocks, tables, task lists, callouts, math formulas, Mermaid and inline formatting
- Obsidian-style graph view — infinite space force simulation, Coulomb repulsion, Hooke springs, auto-fit viewport
- Dark mode on Masonry, Sticky Notes, Dataview views and theme selector
- Redesigned SearchResults with type-specific icons and direct navigation
- DashboardStats: dark mode compatibility for SVG donut chart
- FolderView: thumbnails hidden for locked protected files
- ManageProfilesModal: full reset with onboarding flag persisted to disk via IPC
Fixed
- Dark mode CSS fixes: BacklinksPanel, CalendarWidget, NoteEditor, NotesList, PeriodicNotes, TasksAggregator
v1.6.1
Improved
- Update toast in bottom-right corner with progress bar and "Restart" button
- Available version and download percentage now transmitted to renderer
Fixed
- Update banner did not transmit the available version
v1.6.0
Improved
- Instant launch — backend services initialize in parallel with UI rendering
- Migration to HashRouter for Electron production compatibility
- Auto-update via releases.filarr.com (no more GH_TOKEN needed)
- Automatic update check every 4 hours
- Upload .yml manifests and .blockmap (delta updates) to R2
- Fixed icon paths in packaged mode
v1.5.3
New
- Built-in wiki translated EN/FR (170+ keys): guides, shortcuts, editor, views, graph, templates
- Bug report and suggestion form in settings
Improved
- Redesigned built-in wiki: category navigation, Markdown rendering, search
v1.5.2
New
- Associate notes with a parent folder (NoteFolderPicker)
- Persistent flags system on disk (survives localStorage resets)
- SSRF protection: block requests to private networks and cloud metadata endpoints
- Path validation and sanitization to prevent path traversal
Improved
- Strengthened PBKDF2: 600,000 iterations (×6) + SHA-512 for exports
- Random salt per installation instead of fixed derived salt
- Restricted file permissions (0o600) for keys and exports
- PIN lockout after 3 failed attempts instead of 5
- Complete onboarding redesign (820+ lines)
v1.5.1
New
- Frameless window VS Code / Obsidian style with native overlay controls
- Drag zone in title bar (WebkitAppRegion)
Improved
- Starter templates redesign (1200+ lines)
- Side panel in NotesView with backlinks and parent folder
- Global CSS updated for frameless mode (342 lines)
v1.5.0
Fixed
- Fixed basename quoting in CI workflow
- Improved multi-OS release workflow
v1.4.6
New
- Ko-fi and Stripe donation links in settings
Improved
- Dev/prod detection via app.isPackaged
- Dynamic icon resolution (dev vs packaged)
Fixed
- Fixed ProfileMetadata type (avatarImage field)
- Fixed window renderer error
v1.4.0
New
- Custom profile avatar images
- Full UI Tour with automatic sidebar opening
- Automated release script
Improved
- Extended color palette for profiles
- PIN lock screen with image support
v1.3.0
New
- Multi-profile system with PIN lock and data isolation
- Local KEK/FEK encryption: FEK derived from password, transparent encryption
- Rich note editor with wiki-links [[]], multiple views (kanban, masonry, canvas, mind map...)
- Full EN/FR internationalization
- Electron security: fuses, rate limiting, IPC allowlist, CSP
Improved
- Full codebase overhaul (394 files)
- TypeScript migration (99.26% test coverage)
- Scroll optimization: lazy thumbnails, content-visibility, memo
Fixed
- FEK pipeline for hybrid encryption
- 17 critical production issues resolved
- 7 backend TypeScript errors fixed
v1.2.6
Improved
- Updated encryption system
Fixed
- Various bug fixes
- Fixed auto-update bug
↓ 18 months of solo development — complete rewrite of encryption engine, note editor, graph view, multi-profile.
v1.2.0
Improved
- Production deployment with environment variables
v1.1.0
New
- Initial software skeleton
- Local storage with AES encryption
- File and folder customization (colors, icons)
- Multi-select with batch actions
- Reminder system with desktop notifications
- Auto-update (electron-updater)
Fixed
- File synchronization
- Rename error
- Deletion in subfolders
- Copy/paste
- Reminder notifications after completion