- Share… in the grid's photo menu and the viewer's ⋮ menu opens the Share
dialog by uid. The daemon accepts a uid from the Photos timeline for
sharing (resolve_shareable), since the photos volume is in no mount.
Photos in an album someone else shared are not offered.
- Copy Image (Ctrl+C) puts the shown still on the clipboard. The viewer
has its own toast overlay, so this and Save a Copy confirm over it
instead of behind it.
- A thumbnail strip along the viewer's bottom edge, on the gallery model,
marks the shown photo and jumps on click. Several pictures can now
wait for one thumbnail, so the strip and the grid tile don't steal it
from each other.
- PhotoItem carries place_id (serde default); the line under the
viewer's title reads "Berlin · 12 of 340".
The bar's white-on-translucent-white buttons vanished over a bright photo,
and eight look-alike icons, a floppy disk and an open-document glyph among
them, left the user guessing. The bar now holds the favorite, the details
and a menu with Slideshow, Save a Copy, Open With, Add to Album, Show on
Map and Move to Trash, on dark discs that read over white and black
alike.
A shot stored as more than one file shows a labelled pill ("RAW", "Live",
"2 of 3") instead of a paged icon, lit while the second file is shown.
The title is the localized full date instead of a raw timestamp.
Delete asks first, as the grid does: the dialog opens over the viewer,
and the album prompts can open there too. Alt+Left closes the viewer and
F10 opens the menu; the shortcuts window lists F5, F10 and Alt+Left.
Back, Forward and Up in My files restore the folder's scroll offset and
selection as they were when it was left; opening a folder any other way
still starts at the top.
Closing the photo viewer scrolls the grid to the photo it showed last.
Alt+Left and Backspace go back a level in Shared with me and Computers
and leave an open album or place; Escape leaves an album or place too,
after ending selection mode. The shortcuts window lists it.
A fresh Photos load, or leaving the grid for Albums or Places, ends a
scrubber jump that was still paging.
A folder opened in My files, Shared with me or Computers kept the scroll
offset of the folder before it, because the rows are swapped in place to
keep a refresh steady. Another folder, a new search and a fresh Photos
load (filter, album, place) now start at the top; a refresh still keeps
its place.
Closing a photo opened from the Places map returns to the map instead of
leaving the town's timeline behind, and the pending open is dropped when
the user leaves the town before it loads.
load_gallery no longer swaps Albums or Places for the timeline when a
page lands late (viewer paging, upload, trash Undo); the timeline is
marked stale and read when the user switches back. Coming back to Photos
reuses the timeline unless it is stale, and the timeline returns
filtered the way the kind tabs and date show after an album or place.
A search in My files is no longer replaced by the folder listing on file
changes, F5 or returning to the page. A favorite set in the viewer shows
on its tile at once.
Scrolling with Ctrl held over the icon grid steps the icon size, as it
does in Photos. Touchpad deltas add up to a whole notch before each
step, so a small swipe does not jump through every size.
The icon grid's size slider stops at 256 px instead of 144 px, which is
big enough to tell photos apart by their thumbnail. Thumbnails are
512 px, so they stay sharp at that size on a 2x screen. Names may wrap
wider on the larger tiles.
Ctrl+plus, Ctrl+minus and Ctrl+0 step and reset the size while the grid
is shown, as they do in Photos. The size is saved in the config as
files_view.icon_size once the slider rests; configs without it keep the
72 px default.
Lay the timeline out in justified rows. Each photo keeps its own shape,
clamped between 1:2 and 5:2, and every row but a section's last spans
the width to the pixel. "Square Tiles" in the page menu restores the
even grid, and the Year zoom stays square. The zoom level and the
tile shape persist in a new `photos_view` config section.
Move Timeline/Albums/Places into the header as an icon-and-label
switch that shows only icons in a narrow window. The filters take a
single row whose kind tabs scroll sideways.
Selection mode follows GNOME apps:
- the header shows "N selected" and Cancel;
- an ActionBar along the bottom offers Favorite (or unfavorite), Add
to Album, Remove from Album, Save Copies and Move to Trash;
- each section heading gets a check that selects the whole section.
One function, sync_header, decides the header for the page, an open
album or place, and selection mode.
The heading of the section at the top of the grid floats over the
grid. Favorites get a star badge, and video placeholders a video
glyph. New bundled icons: pdfs-photos, pdfs-album and pdfs-slideshow.
Upload takes several photos and videos at once, and files dropped on
the timeline upload too. The media type comes from the MIME database,
except that a recording-sized .ts counts as video.
The viewer's details panel moves into an OverlaySplitView that lies
over the photo below 800sp.
My files, Shared, Trash and the launcher now ask the daemon for video
thumbnails, not only image ones. Grid tiles with a video thumbnail show
a small play badge.
A `.ts` file is a video from 1 MiB up and a TypeScript source below
that. One rule in pdfs-core (`is_video_file_name`) decides it for the
file icon, the thumbnail gate, the launcher icon and preview, and the
opener's `@media` / `@text` classes. The daemon also checks the MPEG-TS
sync byte before it runs ffmpeg, so a large TypeScript bundle gets no
retries.
A video thumbnail no longer downloads the whole file into memory (B283):
- the server thumbnail is tried first, at no content cost;
- files up to 24 MiB come whole;
- larger ones bring a 16 MiB head, plus the MP4/MOV `moov` box when it
lies past the head, into a sparse staging file that ffmpeg reads.
The 256 MiB cap is gone. Without ffmpeg, a video answers "no thumbnail"
at once. The folder thumbnail build includes videos.
A remote change to a pinned file called `ContentCache::evict`, which does
not check pins, and only the pin request itself ever downloaded anything.
An edit from another device, or even a rename, left the file listed as kept
offline with no copy, so the next read downloaded it or failed without a
network. A file added to a pinned folder elsewhere was never downloaded,
and a folder pin whose downloads failed while offline never retried them.
A remote change no longer evicts a pinned file's copy. The copy is checked
against the revision's (mtime, size), so a newer revision does not read
it, and a rename keeps the mtime (B123), so the copy stays valid. A new
pdfs-pin-keeper thread checks pins again and downloads files that are
missing or stale:
- the pins a batch of remote events changed, after the batch's listings
are dropped, including a pinned folder that gets a new file;
- every pin at startup, when the link returns, and after the event feed
loses continuity;
- a file whose version is restored.
Failed downloads retry with a backoff of 30 s to 30 min. Pinning and the
keeper settle a provisional size first: a copy stored under the
ciphertext size never matched the plaintext it holds.
Fixes B282.
`pdfs pin` and `pdfs unpin` resolved every path against the primary mount
with `rel_to_mount`. An absolute path in an on-demand synced folder such as
~/Pictures was refused as "not under the mountpoint", and a relative path
named a file in My files instead. A synced folder's file is a different
Drive node from a same-named file in My files, so keeping one offline did
nothing for the other, and every program reading the synced copy
downloaded it.
Pin and unpin now route through `route_to_mount`, like ls, refresh, rm and
mv since B86, and run on the mount that owns the path. A path in a mirror
folder gets an error saying its files are already on this computer. A pin
made through an on-demand folder records the absolute local path, so
`pdfs pins`, the prompt and the HUD open the file where it was kept; pins
under the primary mount still record the mount-relative path.
Fixes B281.
`pdfs completions <shell>` prints a completion script for bash, zsh, fish,
elvish or PowerShell, built on clap_complete's dynamic engine: on Tab the
shell calls `pdfs` back, which completes commands and options and also the
values the running service knows, each with a description: paths in My
files, synced-folder and queue ids, computers, trash, albums, items shared
with me, invitations, bookmarks, conflict copies, and a file's versions or
a share's members and public link once its path is typed.
A completer that gets no answer from the service within two seconds offers
nothing rather than hanging the prompt. Listings that ask Proton are kept
for 30 s in $XDG_RUNTIME_DIR (0600), so repeated Tabs do not wait again.
Completion is triggered by PDFS_COMPLETE rather than clap's generic
COMPLETE, because the same binary runs the daemon.
Share roles are now checked by the CLI before the request. The package
recipes install the bash, zsh and fish scripts via packaging/completions.sh.
`pdfs completions <shell>` prints a completion script for bash, zsh, fish,
elvish or PowerShell, built on clap_complete's dynamic engine: on Tab the
shell calls `pdfs` back, which completes commands and options and also the
values the running service knows, each with a description: paths in My
files, synced-folder and queue ids, computers, trash, albums, items shared
with me, invitations, bookmarks, conflict copies, and a file's versions or
a share's members and public link once its path is typed.
A completer that gets no answer from the service within two seconds offers
nothing rather than hanging the prompt. Listings that ask Proton are kept
for 30 s in $XDG_RUNTIME_DIR (0600), so repeated Tabs do not wait again.
Completion is triggered by PDFS_COMPLETE rather than clap's generic
COMPLETE, because the same binary runs the daemon.
Share roles are now checked by the CLI before the request. The package
recipes install the bash, zsh and fish scripts via packaging/completions.sh.
Every image upload decodes the whole file to build its thumbnail, and
nothing limited how many decodes ran at once. With a queue of large
photos all 16 drain workers decoded together; 100-megapixel JPEGs held
about 9 GiB, swapped the machine, and the watchdog killed the daemon on
every restart.
Each decode now takes a share of a process-wide 512 MiB budget, sized
from the encoded length and the header's pixel count. Phone photos still
decode side by side; a photo larger than the budget waits and runs alone.
Records B280.
A daemon killed by the watchdog left its FUSE mounts dead. A dead mount
answers ENOTCONN, which create_dir_all took for an absent directory, so
every restart failed with EEXIST and retried forever without reaching the
code that clears stale mounts. mount_once now goes through
prepare_mountpoint, which clears a dead mount first.
Startup on a large account (70 s hydrating 48k nodes) plus the supervisor's
first ping one full period later outlasted WatchdogSec=120, so a restart
that did mount was aborted again. The daemon now asks for a 10-minute
watchdog before startup, the supervisor pings on its first tick, and the
unit's own timeout is restored once the control socket answers.
Records B279.
Schema 45 separates attachments from device backups so stopping sync or
changing mode cannot trash My files folders or evict their local data.
Exclude attachments from device profiles and validate device restores.
Verify attached roots before each pass and cover both local views,
concurrent edits, removal guards, and root loss across daemon restarts.
Refs #77
`pdfs sync list` and `pdfs devices list` printed the last sync as epoch
seconds, such as `synced: 1793570850` in issue #46. Both columns now go
through `last_synced`, which formats it like `pdfs activity` and prints
`never` for a folder that never synced. `--json` output is unchanged
(B278).
The service can run while its mount fails on every try, for example over
a folder with files in it (B277). The page then said "Can't open your
Drive", but the sidebar status read "Not connected / Proton Drive isn't
running", which was wrong. paint_disconnected now reads the same failure
record the pages read, once the service is known to be running, and
shows "Can't open your Drive" with the reason. A reply from the daemon
that lands while it looks wins.
The account run of `scripts/fuse-acceptance.sh --journal-check --durability`
on 2026-11-01, against a build of main, passed 464 cases with 103 skipped.
Its eight daemon restarts left no panic in the journal (B276), and
`journalctl -p warning` listed every WARN line the run logged (B258).
The wipe guards refused a pass only when every synced path was missing
on one side. One surviving file cleared them, so a half-mounted disk,
a restored backup that brought back one file, or a script that emptied
the folder and wrote one back made the next pass trash everything else
on Drive, and on every computer syncing the folder (B270).
Both guards now also refuse when nine in ten or more of the tracked
paths of a folder with at least 20 are missing at once. A smaller
folder still stops only on a total loss, since an ordinary cleanup can
delete most of it. The reason now gives the count.
B270 stays partly fixed: a smaller loss still goes through, and the
app does not yet offer to confirm the deletions.
FUSE mounts over a non-empty folder without complaint, so files already
in the My files folder were hidden under the mount: not uploaded, and
back only once the service stopped, so they looked deleted (#67, B277).
- pdfs-core: new `mountpoint` module. `not_empty` lists what is in a
folder (hidden entries count); `mounted_fs_type` moves here from the
CLI so the daemon can use it. `MountFailure` gains an optional
`not_empty` field, which older readers ignore.
- pdfs-fuse: `mount_with` checks the folder before the root fetch and
before any worker starts. A dead mount is cleared first; one of our
own live mounts is left to `spawn_session`, as before.
- pdfs-cli: `cmd_daemon` records the folder in the failure record. The
refusal backs off and retries like any mount failure, so emptying the
folder is enough.
- pdfs-gui: the status pages explain the refusal, and a dialog offers
to open the folder or choose another one, once per run of failures.
The folder picker refuses a folder with files in it.
Translated in all catalogs. Docs: BUGS (B277), CHANGELOG, FAQ,
TROUBLESHOOTING, CONFIGURATION.
A stop shut the tokio runtime down after joining the workers it owns.
Threads it never joins, such as a size lookup or a profile backup that
a request started during the stop, then panicked on their next timer
with "A Tokio 1.x context was found, but it is being shutdown". The
process exits right after a stop, so the runtime is now left for the
exit to end. A mount that is retried still shuts its runtime down with
the 2 s limit.
The acceptance journal check now counts panics, and no longer counts
the ENOENT reply errors a stop causes by aborting its FUSE connections.
docs/BUGS.md B276.
0.10.1 decrypts address key tokens with every account key that opens,
not only the active ones, and leaves out an address whose keys do not
open instead of failing the account. That is the likely cause of issue
#40, a mount that retried "missing key" forever (B265).
A write open copies the queued revision's blob into its scratch file.
When the upload landed between the sample and the copy, the drain had
already deleted the blob, and the open failed with EIO (B274, seen once
in CI). The drain drops the pending entry before the blob, so a failed
copy whose entry moved on now goes around again, as the re-check after
the copy already does.
Two simulations raced the daemon rather than it:
- a_file_deleted_after_a_restart_leaves_drive_with_what_its_create_sent
let the create's reply through before the kernel's release attached
the second bytes. That attach resets the attempts, so the retry
landed before the test could see the failure. It now waits for the
bytes to be attached.
- a_file_deleted_offline_while_open_still_reads_and_takes_writes read
the cache as the queue emptied, which is a moment before the
eviction. It now waits for the cache to empty.
A listing that failed anywhere in the remote walk ended the whole pass,
so a first sync of a large folder on a flaky link walked the tree again
and again and never downloaded anything (B271, issue #46). A failed
listing below the root now records that folder as a gap: nothing under
it is deleted, the rest of the pass applies, and the report names it.
A restore that skipped every folder was logged as a successful activity
(B269). The daemon now gets the restored and skipped folders back, logs
any skip as a failed activity with the reasons, and sends the Rewatch
for folders already restored when the database insert fails partway.
`pdfs devices join` points to `pdfs activity` for the outcome.
diagnose dropped most of the Status reply and never looked at the synced
folders, so it said "No problems found" while a folder would not sync
(B268, issue #53). It now warns about writes that keep failing, a write
a day old, a pause, folders in error or conflict, and a control socket
nothing listens on.
from_api took every error that was not a ProtonError for a transport
failure. Pin, unpin, the searches and node loads hand it pdfs_core
errors from the cache and the database, so a failure there said
"You're offline" (B266, the rest of B205). The source chain now
decides, and only an error with neither type reads as offline.