Repository navigation
Browse in tabs, and let a tab keep what it was holding - #488
Closed
cerede2000 wants to merge 1 commit into
Closed
cerede2000 wants to merge 1 commit into
cerede2000 wants to merge 1 commit into
Conversation
cerede2000
force-pushed
the
p3-59
branch
4 times, most recently
from
October 1, 2026 20:40
327bd46 to
36e8cec
Compare
Off by default, and turned on in Settings → Preferences → Browse in tabs. A tab is an address rather than a folder, so the strip holds anything the application can be at: a folder, a document, the text editor, a terminal, the trash, the settings. Each tab keeps what it was on — its listing, what was selected in it, the rename it was in the middle of, the place it was scrolled to. The clipboard is deliberately not per tab: copying in one and pasting in another is the point of having two. One rule opens anything behind: the platform modifier held with the gesture that opens the thing — the double click on a row, the single click on a favourite, a volume or a place on the home page, and Enter on whatever the keyboard is on. On a row that modifier with a single click still adds to the selection, which is worth more than a tab. The middle button says the same thing without a modifier, anywhere. What a tab holds stays alive while another is in front. A document is not opened again when you come back to it; the text editor comes back where you left it without reading the file again — what you typed and had not saved, the selection, and the line you were reading, put back before the editor is shown so the file does not appear at the top and jump. A folder tab comes back the same way, and is refreshed quietly behind what you were already looking at. A tab opened behind is got ready before you reach it: the folder listed, the document opened in its viewer, the text file read, the shell started. That is Settings → Preferences → Prepare tabs opened in the background, and it is capped at three prepared editors at once. While a tab is still working it shows a spinner where its icon goes, as a browser does — for a document, until it is really in the editor rather than until the panel exists. Back and Forward belong to the tab, not to the window: each keeps its own trail, so going back in one never takes you where another was looking, and the buttons grey out when that tab has nowhere to go. The rest of what the strip does: tabs are dragged along the row, duplicated, kept (pinned to the front as their icon alone, with no cross and immune to "close them all"), closed one at a time or all at once, and closed by double click for whoever turns that on. Files and folders dragged out of a listing can be dropped onto a tab, which moves them into the folder that tab is on — or copies them with the modifier held. The strip never scrolls: tabs share the room and stop at a number an administrator sets under Settings → Tabs. A terminal is a place now. What a terminal *is* came out of the drawer that showed it, so a page at an address of its own can show one too and there can be as many shells as there are tabs; the drawer opens per tab, and a new tab only on the gesture that asks for one. Closing a tab is the one gesture that destroys what a tab was holding, so a screen with something to lose can leave a question for its own tab. Asking it meant the application had to be able to ask at all: `window.confirm` answers by stopping the world, which a dialog inside the page cannot, so the question is the application's own dialog and everything from the guard to the cross on the tab waits for the answer. The browser's boxes are gone from the rest of the window with it — twenty-three of them, `confirm`, `prompt` and `alert`, replaced by that dialog and by the notifications this application already had. The browser journey comes with it, and with the bench it needs. Most of what tabs do is what a browser does to a page that is not on screen, and that is not a question a unit test can ask: a page unmounted the moment another tab comes forward, a menu clipped away by the strip's own overflow, a shell that dies when the layout holding it does. Each of those passed its unit suite and was broken in a browser. So `frontend/e2e/app` holds one journey through the whole application — set up, sign in, upload, share, delete and restore, read a version, edit, browse in tabs — run by a second Playwright project against the real server entry point serving the real build, in a throwaway install the journey also reads from disk to check what actually landed there. It runs under `npm run test:e2e` beside the specs already here, and takes about half a minute. Two readers in one folder, and the two things they were sharing that they should not have been. Where a reader is in a folder has always been remembered against the folder. With tabs that is no longer enough, because two tabs can be in one folder at once and be in different places in it — so a tab's place is kept against the tab as well. It was, and it was then read as a number: `tabPlace(key) || get(folder)`. A tab sitting at the top answers nought, nought is false, and the fallback is the folder's memory that every tab on it shares. Two tabs on one folder, the first scrolled to the bottom and left there, the second never moved from the top: the second came back down beside the first. The top is an answer, so the question is now whether this tab has a place here at all. The permission to be put back was shared the same way. It is granted for a folder when somebody walks back up into it — and consumed by whichever tab is drawn on that folder next, which need not be the tab that was promised it. One tab walking up out of a subfolder, or coming back out of a file, moved another reader. The permission names its tab now, and the rule that grants it moved out of the router's guard into `router/restorePermission.js` so that it can be asked directly, the way the other rules there are: it had no test, and what had no test is what went wrong. The Tabs page sits with the other preferences in the settings list rather than at the end of it.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This sits on
mainas it stands, and the file comparison that follows sits onthis one.
Closes #486
Tabbed browsing, off by default and turned on under Settings → Preferences → Browse
in tabs. With it off the application behaves exactly as it does today.
A tab is an address, not a folder, so the strip holds anything the application can
be at: a folder, a document, the text editor, a terminal, the trash, the settings. Each
tab keeps what it was on — its listing, what was selected in it, the rename it was in
the middle of, where it was scrolled to. The clipboard is deliberately not per tab:
copying in one and pasting in another is the point of having two.
What a reader gets
One rule opens anything behind. The platform modifier held with the gesture that
opens the thing: the double click on a row, the single click on a favourite, a volume or
a place on the home page, and Enter on whatever the keyboard is on. On a row that
modifier with a single click still extends the selection, which is worth more than a
tab. The middle button says the same without a modifier, anywhere. Several entries
chosen open one tab each — the first in front, the rest behind — and stop when the row
is full.
What a tab holds stays alive while another is in front. A document is not opened
again when you come back to it. The text editor comes back where you left it without
reading the file again: what you typed and had not saved, the selection, and the line
you were reading — put back before the editor is shown, so the file does not appear at
the top and jump. It is re-checked quietly afterwards, and what somebody else wrote
takes over unless you have unsaved work of your own. A folder tab comes back the same
way and refreshes quietly behind what you were already looking at.
A tab opened behind is got ready before you reach it: the folder listed, the document
opened in its viewer, the text file read, the shell started. Settings → Preferences →
Prepare tabs opened in the background turns it off; no more than three prepared
editors at once. While a tab is still working it shows a spinner where its icon goes,
as a browser does — for a document, until it is really in the editor rather than until
the panel exists.
Back and Forward belong to the tab. Each keeps its own trail, so going back in one
never takes you where another was looking, and the buttons grey out when that tab has
nowhere to go.
The strip. Tabs are dragged along the row, duplicated, kept (pinned to the front as
their icon alone, with no cross and immune to "close them all"), closed one at a time or
all at once, and closed by double click for whoever turns that on. Files and folders
dragged out of a listing can be dropped onto a tab, which moves them into the folder
that tab is on — or copies them with the modifier held. A tab shows the icon of the
favourite it is inside. The strip never scrolls: tabs share the room and stop at a
number an administrator sets under Settings → Tabs (5, 10, 15 or 20; 10 unless
changed).
A terminal is a place. What a terminal is came out of the drawer that showed it, so
a page at an address of its own can show one too and there can be as many shells as
there are tabs. The drawer opens per tab; a new tab only on the gesture that asks for
one.
What holds it together
A tab is an address, and the kinds are a table. One place says what addresses a tab
may hold, what each is called, what icon it wears and whether it is restored — rather
than every screen deciding for itself.
A page is unmounted the moment another tab comes forward. So anything that must
survive lives outside the page: the preview session, the editor's draft, the folder's
place. This is why coming back is not a reload, and it is the single idea most of this
batch is built on.
Closing asks, when there is something to lose. A screen leaves a question for its own
tab, and whoever closes the tab asks it. A folder leaves none — being asked about a
folder trains everybody to click through the question without reading it.
The application asks in its own voice.
window.confirmanswers by stopping theworld, which a dialog inside the page cannot, so the guard and everything from it to the
cross on the tab waits for the answer. The browser's boxes go with it: twenty-three
across the window (
confirm,prompt,alert) replaced by that dialog and by thenotifications this application already had. What those drew was the browser's box, in
the browser's chrome, headed by the server's address and port, over a question about
somebody's file.
Settings
Checked
Built and booted on
mainwithmain's lockfile, every client call matched to a route,and the suites run in that tree: 3 097 frontend, 4 048 backend, plus the browser
journeys that only a browser can answer — a tab opened behind being ready on arrival,
the spinner, the drop onto a tab, the per-tab trail.
Two readers in one folder, and the two things they were sharing
Pushed since this was opened, because both are faults this branch introduces and
neither needs a second pane to show itself.
Where a reader is in a folder has always been remembered against the folder. Two
tabs can now be in one folder at once and be in different places in it, so a tab's
place is kept against the tab as well — and it was then read as a number:
tabPlace(key) || get(folder). A tab sitting at the top answers nought, nought isfalse, and the fallback is the folder's memory that every tab on it shares. Two
tabs on one folder, the first scrolled to the bottom and left there, the second never
moved: the second came back down beside the first. The top is an answer, so the
question asked now is whether this tab has a place here at all.
The permission to be put back was shared the same way. It is granted for a folder when
somebody walks back up into it, and was consumed by whichever tab was drawn on that
folder next — which need not be the tab it was promised to. One tab walking up out of a
subfolder, or coming back out of a file, moved another reader. It names its tab now,
and the rule that grants it moved out of the router's guard into
router/restorePermission.jsso that it can be asked directly, the way the other rulesthere are: it had no test, and what had no test is what went wrong.
Also here: the Tabs page sits with the other preferences in the settings list rather
than at the end of it.