Reporter has a lot of nested cad / slicer directories and wanted
"folders that just got a new 3MF" surfaced without scrolling the
alphabet. Tree was always alphabetical; LibraryFolder.updated_at
only bumps on rename / move, not on file-add inside the folder.
Backend exposes latest_activity_at = max(folder.updated_at,
max(immediate-child file.updated_at)) on FolderResponse +
FolderTreeItem. The /folders tree route picks up a sibling
func.max(updated_at) group-by alongside the existing file-count
subquery; the by-project / by-archive / single-folder routes
collapse count + max into one trip. Recursion across subfolders
is intentionally not computed - bubbles immediate parent only,
keeps the query a single GROUP BY rather than a recursive CTE.
Frontend adds a folder-sidebar sort dropdown (By name / By recent
activity) plus an asc / desc arrow, persisted in localStorage.
sortedFolders memo applies the comparator recursively so order is
consistent at every depth. Empty folders fall back to name within
the activity bucket so they never elbow a recently-used folder to
a random position. Both the desktop sidebar and the mobile selector
consume the sorted list so order is identical across breakpoints.
External folders: LibraryFile rows are created for scanned external
files too, so the aggregate works on them - but the timestamp
reflects last scan, not filesystem mtime. Documented in the wiki.
Same change also fixes File Manager list-view column alignment:
header and body were sibling grids with min-content as the trailing
column, computed independently. Header empty trailing div resolved
to 0; body action strip to ~220px. Different trailing widths gave
the 1fr Name column different remaining space, shifting every fixed
column to its right. Replaced min-content with fixed 220px in both
auth-on / auth-off grid templates.