Skip to content

[26.2/33] A file's history, seen: the panel, the administrator's list, and one reader for text - #417

Merged
vikramsoni2 merged 2 commits into
nxzai:mainfrom
cerede2000:upstream-26-history-panel
Sep 26, 2026
Merged

vikramsoni2 merged 2 commits into
nxzai:mainfrom
cerede2000:upstream-26-history-panel

Conversation

@cerede2000

Copy link
Copy Markdown

The second half of batch 26. #415 made a file's history real and readable through the API; nothing showed it. This is the panel, the administrator's list, and reading text wherever it lives.

What this adds

A history you can see. A file with earlier versions carries a small mark in the listing, with how many. Clicking it opens the panel: what was kept, who saved it, where it came from, and — per version — read, download, restore, name, pin, delete. Restoring keeps what it replaced, so going back loses nothing. The mark is counted once per folder rather than once per row, and a user preference turns it off, which takes the query away as well as the icon.

An administrator's view. Every file that has a history, wherever it is, with what it takes up; a history can be emptied from there. Those routes name a history by its own id rather than by a path: the ones worth finding include files that no longer exist, and a history whose file was deleted outside the application has no path to ask about and no file to authorise against. They sit behind ensureAdmin for the same reason.

One reader for text. The editor, a version and a file still in the trash now read through the same service — the last two are what #415 and #416 left out.

Defects fixed on the way

All four live in the editor's own read and save path, which this batch owns.

  • A UTF-16 file was refused as binary. Every letter of English in UTF-16 is accompanied by a zero byte, and a zero byte was the test for binary. That is what Out-File wrote by default until PowerShell 6 and what Notepad still offers as "Unicode": an export or a log from a Windows machine could not be opened at all. A mark is believed when there is one; otherwise the pairing of zeros decides, which is what tools writing UTF-16 without a mark leave behind.
  • A save wrote UTF-8 over whatever the file was. It reads perfectly well here and breaks whatever wrote it. A save now keeps the file's encoding.
  • The size limit was checked when opening and not when saving. A paste larger than the limit was written, and the file could then never be opened again. Checked now on the bytes about to be written — in UTF-16, twice the characters.
  • The editor opens 2 MiB and saves through a JSON body whose limit was Express's default of 100 kB. A file between the two opened and could never be saved, answered "request entity too large", which names neither limit. They are one decision now: the body limit is derived from what the editor may open, and a limit somebody set is a ceiling that is never raised from here — the editor is what gives way.

And one found while wiring the panel up: marksForFolder, listFilesWithVersions, readFileVersions and deleteFileVersions all called store helpers that were never added. Nothing called those four, so nothing noticed; all of them threw. The queries are here, and each has a test.

Also: the editor's read is now a GET the browser keeps and revalidates, answered 304 while the file is unchanged — so opening the editor from the Markdown preview stops downloading the same file twice. The identity includes the inode, because a save writes a new file and renames it over the old one.

Left for later, deliberately

  • Taking a version out as a new file, and putting a version over another file: both need the destination dialog, which is not here.
  • Reading a version of an office document in its editor: needs the office editors to accept one — batch 28.
  • The activity-log entry for a purge: waits for the log.

Gates

  • Lint and formatting on the changed files.
  • npm run build; every backend module loads.
  • The whole backend suite: 1,949 pass, against the two that fail on main before any of this (auth.test.js, browse-hidden-files.test.js — measured on main itself, not assumed).
  • 40 new tests. Every claim above fails when the change behind it is put back; UTF-16 detection was mutated twice, once for the mark and once for the pairing, because they are two mechanisms.
  • The image is the gate for the screens. Built, started, driven in a browser: two saves, the mark reading 2, the panel opening from the mark, a version read in the read-only viewer, a version restored from the panel with the history growing to three, a file deleted and then read from the trash by double-clicking it, and the administrator's list showing the file with its space. That is how the last defect here turned up — the dialog confirming a restore teleports to the body and stacked below the panel's overlay, so its buttons could not be clicked. No unit test would have seen it.

Independent of the stack; applies on current main.

The engine that keeps what a save replaces, and the API that reads a history
back, arrived with the batch before this one. Nothing showed them: no mark in a
listing, no panel, no way for an administrator to see what histories cost, and
no way to read an earlier version at all.

This is that half.

A file that has earlier versions carries a small mark in the listing, with how
many; clicking it opens the panel, which lists what was kept, who saved it and
where it came from, and offers reading a version, downloading it, putting it
back, naming it, pinning it and deleting it. Putting one back keeps what it
replaced, so nothing is lost by going back. The mark is counted once per folder
rather than once per row, and a preference turns it off — which takes the query
away as well as the icon.

An administrator gets the list of every file that has a history, wherever it
is, with what each one takes up, and can empty one. Those routes are addressed
by the history's own id rather than by a path, because the ones worth finding
include files that no longer exist: a history whose file was deleted outside
the application has no path left to ask about, and no file to authorise
against. They sit behind the administrator check for the same reason.

Reading text is now one reader, used by the editor, by a version and by a file
still in the trash — the two the batch before this one left out. That reader
fixes what the editor used to do with it:

  - A UTF-16 file was answered "this file appears to be binary and cannot be
    opened". In UTF-16 every letter of English is accompanied by a zero byte,
    and a zero byte was exactly the test for binary. That is what `Out-File`
    wrote by default until PowerShell 6 and what Notepad still offers as
    "Unicode", so an export or a log from a Windows machine could not be
    opened. A mark is believed when there is one; otherwise the pairing of
    zeros decides, which is what tools that write UTF-16 without a mark leave
    behind.

  - A save wrote UTF-8 over whatever the file was. That reads perfectly well
    here and breaks whatever wrote it, so a save now writes back in the
    encoding the file already had.

  - The size limit was checked when opening and not when saving: a paste larger
    than the limit was written, and the file could then never be opened again.
    It is now checked on the bytes about to be written, which in UTF-16 are
    twice the characters.

  - The editor opens two megabytes and saves through a JSON body, whose limit
    was Express's own default of 100 kB. A file between the two opened and
    could never be saved, answered "request entity too large" — which names
    neither limit. The two are one decision now: the body limit is derived from
    what the editor may open, and a body limit somebody set is a ceiling that
    is never raised from here, so it is the editor that gives way.

The editor's read is also a GET the browser may keep and revalidate, answered
304 while the file is unchanged, so opening the editor from the Markdown
preview no longer downloads the same file twice. The identity is taken from the
file's metadata, including the inode — a save writes a new file and renames it
over the old one, so a save that comes out the same size within one clock tick
still differs.

An earlier version and a file in the trash open in the editor as text to read:
no save, no shortcut that saves, and an editor that does not take typing.
Closing goes back where it came from — the folder with the history open again,
or the trash, inside the deleted folder the file was read from.

Found while wiring this up: `marksForFolder`, `listFilesWithVersions`,
`readFileVersions` and `deleteFileVersions` all called store helpers that were
never added. Nothing called them, so nothing noticed; all four threw. The
queries they need are here, and every one of them now has a test.

Left for later, and named here so it is not lost: taking a version out as a new
file somewhere, and putting one over another file, both need the destination
dialog, which is not here yet; reading a version of an office document needs
the office editors to accept one, which comes with the batch that finishes
them; and the entry an activity log would want for a purge waits for the log.

Gates: lint and formatting on the changed files, `npm run build`, every backend
module loads, and the whole backend suite — 1,949 pass, against the two that
fail on `main` before any of this. Every claim above has a test that fails when
the change behind it is put back; the detection of UTF-16 was mutated twice,
once for the mark and once for the pairing, because they are two mechanisms.

The screens were checked in the built image: two saves, the mark showing 2 in
the listing, the panel opening from it, a version read in the read-only viewer,
a version restored from the panel with the history growing to three, a file
deleted and read from the trash by double-clicking it, and the administrator's
list showing the file, its space and its history. That is also how the last
defect here turned up: the dialog that confirms a restore teleports to the body
and stacked below the panel's own overlay, so its buttons could not be clicked.
No unit test would have seen it.
@cerede2000 cerede2000 changed the title [26/33] A file's history, seen: the panel, the administrator's list, and one reader for text [26.2/33] A file's history, seen: the panel, the administrator's list, and one reader for text Sep 26, 2026
Which keys are preferences was decided twice: once in the settings service,
which sanitises the value, and once in the settings route, which decides
whether the key is written at all.

They had drifted. The preference added here — the mark on files that have
versions — reached the service's list and not the route's, so the toggle moved
on screen, the save answered success, and nothing was stored. It would have
read as "my setting does not stick", with two lists to find before anyone could
say why.

One list now, named and exported by the service; the route asks it. A test
walks every key the service calls a preference and checks the route keeps it,
so the two cannot drift again.
@vikramsoni2
vikramsoni2 merged commit 8bae71e into nxzai:main Sep 26, 2026
1 check passed
cerede2000 pushed a commit to cerede2000/NextExplorer that referenced this pull request Sep 26, 2026
The plan's own rule is that a batch carries its documentation. The trash landed in
nxzai#405 and nxzai#416, file versions in nxzai#406 and nxzai#417, and neither brought a page: there is
nothing under `docs/` that describes either, so the retention settings, what a restore
keeps, who sees what in the trash and how versions are thinned are all readable only
in the source.

Two new pages, and nine existing ones brought up to what the application now does —
the environment reference gains the variables the last twenty batches added, the
feature and workflow pages gain the trash, versions, archives and search index, and
the sidebar gains the two new entries in both of its shapes.

Both pages were trimmed to what `main` has, rather than copied. Three things are
deliberately left out and travel with the batch that brings them:

- restoring to a folder of your choosing. `POST /api/trash/restore` accepts a
  destination, but nothing on the screen offers one and `restoreTrashItems` does not
  send one, so the page would describe an action nobody can reach.
- `COPY_PRESERVE_PERMISSIONS`, `PREVIEW_MAX_RENDER_SIZE`, `BULK_DELETE_CONCURRENCY`,
  `MAX_BROWSABLE_ARCHIVE_SIZE`, `ARCHIVE_CACHE_MAX_SIZE`,
  `UPLOAD_CHUNKED_AUTO_FALLBACK` and the six `PERFORMANCE_DIAGNOSTICS_*` — twelve rows
  for variables the configuration does not read yet.
- recent destinations, per-folder preferences and the inline quick-actions menu.

## Checks

`npm run docs:build` — and it is worth saying that it was failing before this last
pass: three links pointed at an installation page that only exists in the fork, and
vitepress treats a dead link as an error. Every internal link in the eleven pages was
resolved against the tree, which is how those turned up.

`npm run format:check` reports the same 22 files as `main` does on its own; the
eleven pages and the config are clean. `npm run build` and the backend module load
both pass, untouched by a documentation batch.
vikramsoni2 pushed a commit that referenced this pull request Sep 26, 2026
The plan's own rule is that a batch carries its documentation. The trash landed in
#405 and #416, file versions in #406 and #417, and neither brought a page: there is
nothing under `docs/` that describes either, so the retention settings, what a restore
keeps, who sees what in the trash and how versions are thinned are all readable only
in the source.

Two new pages, and nine existing ones brought up to what the application now does —
the environment reference gains the variables the last twenty batches added, the
feature and workflow pages gain the trash, versions, archives and search index, and
the sidebar gains the two new entries in both of its shapes.

Both pages were trimmed to what `main` has, rather than copied. Three things are
deliberately left out and travel with the batch that brings them:

- restoring to a folder of your choosing. `POST /api/trash/restore` accepts a
  destination, but nothing on the screen offers one and `restoreTrashItems` does not
  send one, so the page would describe an action nobody can reach.
- `COPY_PRESERVE_PERMISSIONS`, `PREVIEW_MAX_RENDER_SIZE`, `BULK_DELETE_CONCURRENCY`,
  `MAX_BROWSABLE_ARCHIVE_SIZE`, `ARCHIVE_CACHE_MAX_SIZE`,
  `UPLOAD_CHUNKED_AUTO_FALLBACK` and the six `PERFORMANCE_DIAGNOSTICS_*` — twelve rows
  for variables the configuration does not read yet.
- recent destinations, per-folder preferences and the inline quick-actions menu.

## Checks

`npm run docs:build` — and it is worth saying that it was failing before this last
pass: three links pointed at an installation page that only exists in the fork, and
vitepress treats a dead link as an error. Every internal link in the eleven pages was
resolved against the tree, which is how those turned up.

`npm run format:check` reports the same 22 files as `main` does on its own; the
eleven pages and the config are clean. `npm run build` and the backend module load
both pass, untouched by a documentation batch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants