Early build · 1.0.0 · Windows, macOS, Linux

Two panels. Everything else is a plugin.

A dual-pane file manager in the Total Commander tradition, built entirely in Flutter and extended with Python. The core does one thing: manage files in two panels. Network transports, viewers, editors, archive support — every one of them is an extension.

Xverb running on a Windows desktop, two panels side by side

The list of features is not the interesting question. The interesting question is what it does that the one you already have does not.

The Xverb window: two file listings, a path bar and the function-key row

Two panels, and the keyboard reaches everything

Copy, move, delete, rename, create folder — across any two file systems, with the orthodox commander bindings exactly where they have always been.

Marks, not a selection

Commands act on the marks, or — when nothing is marked — on the row under the cursor. One rule, no dialog asking which you meant.

The function keys are still the interface

F3 view, F4 edit, F5 copy, F6 move, F8 delete. Every switch has a letter, and the letter is shown next to the switch that does the same thing — a control only the mouse can reach is not a control.

A path bar of buttons

The drive drops the drives-and-connections menu — including the stick you plugged in while the menu was open. Every level above the current folder is a button that goes back to it.

Extensions

Everything is a plugin, and there are two kinds

The way it works in Blender. Two runtimes, and you take the weaker one that does the job.

Python — real code

A folder, a manifest, an entry script

Arbitrary code in its own process, talking to the app over JSON-RPC. Plugins target one pinned Python 3.12 the app carries itself, so a plugin written once behaves the same on every platform.

from xverb import Plugin, table

plugin = Plugin("com.example.zip")

@plugin.viewer("zip.list", "Contents", extensions=["zip"])
def view(url):
    ...  # read_file works on any file system

plugin.run()

Declarative — data, no code

Pure JSON over the host's primitives

No code and no interpreter, so it loads on the platforms where running plugin code is impossible at all — iPhone included. The bundled text, image, CSV and JSON viewers are nothing but this.

{
  "id": "com.example.csv", "apiVersion": 1,
  "runtime": "declarative",
  "viewers": [{
    "title": "Table", "extensions": ["csv"],
    "render": { "kind": "table",
                "source": "csv" }
  }]
}

The proof is that FTP is a plugin.

Not because it had to be, but so that the plugin surface has to be good enough to build a real file system on. Enter on a .zip opens it as a folder — and the core never learns what a ZIP is.

Settings → Plugins is a feed of everything the app knows about. Install is one press.

The viewer

Reading a file is a place you go to, not a window floating over the panels

Code is coloured by grammars that ship as data in a plugin, scanned by one scanner in the host. A grammar names roles rather than colours — so the palette decides what a keyword looks like, and a new language is a file rather than a release.

  • Long documents grow a structure panel, read out of that same colouring
  • A held heading is the heading itself, not a strip that looks like one
  • Escape goes back, one thing at a time
plugin.pyF3
14class Plugin:
15    # one process, one channel
16    def __init__(self, ident):
17        self.ident = ident
18        self.views = {}
19
20    def viewer(self, ident, title):
21        """Claim an extension."""
22        return self._register(ident)

PDF

A PDF is shown as a document, not as a page

Every other reader draws the page, and drawing the page is the problem. A PDF is a set of instructions for putting marks on paper, so a viewer that follows them faithfully hands you back the paper — the margins, the furniture, and a line break every sixty characters that no reader wants.

The page, as drawn
— 14 —
Chapter 2 · The Method
The reading

The Method

Nothing is rendered and nothing third-party ships

No PDFium, no system engine, no raster mode. The reader is Python on the standard library, about two thousand lines, identical on all three systems — which also meant writing out RC4 and AES, because a locked PDF is usually locked with an empty password.

Where it is only half true

Roughly two documents in five carry the tags that say what is a heading and what is a table; for those the reading is simply right. Without them it is worked out from the page, and the reading says at the top when it is a guess. A scan has no text in it at all, and this says so in a sentence rather than showing an empty page.

Pictures

A folder of photographs, walked with the arrow keys

Full screen, with a strip of the neighbours standing on the picture itself — no band, no bar, no panel. The picture runs the whole height of the window and passes underneath the thumbnails.

A thumbnail is the shape of its picture, and they all stand on one floor: a portrait fills the height and is narrow, a landscape runs out of width first and stands lower.

The arrows mean the same thing either side of F3

Down in a listing is the next file; press F3 and Down is still the next file. While the strip is up the arrows walk the folder and the picture is panned with Shift; T puts the strip away and all four are the picture's again. The rule is never in anybody's memory — it is on the screen.

Nothing blinks

Walking to the next photograph does not empty the page. The one on screen holds full opacity until the last fifth of the fade, and the new one arrives over it — two pictures crossing at half opacity are together dimmer than either was alone, and that dip is a blink drawn slowly.

Three sizes rather than two

Fit puts the whole picture inside the window and never enlarges. Fill puts the window inside the picture, so there is nothing down the sides. 1:1 counts device pixels, so one pixel of the file lands on one pixel of the screen. Whichever you chose is remembered — next picture, next session.

Appearance

The appearance is pressed, not configured

Every colour is chosen by pointing at the thing it paints in a live preview of the window, rather than by finding a row named after it in a list.

Colours come in pairs, a fill and its ink together, never one without the other: a palette that recoloured a surface and left the writing on it unreadable is not a palette. A saved palette carries the whole appearance — font, density, opacity, every pair — and it is a file, so it can be passed around.

XverbFileMarkCommandsView
NameSize
..[..]
Contacts<DIR>
Desktop<DIR>
Downloads<DIR>
Pictures<DIR>
NameSize
..[..]
Music<DIR>
Pictures<DIR>
source<DIR>
Videos<DIR>
F3 ViewF5 CopyF6 MoveF8 Delete

Nothing changes in a single frame. Every movement has to state something true: a listing being left goes out and the one arrived in comes in; opening a folder moves forward and leaving one moves back, so the direction says which way you went.

One scale sets all of it. At Off nothing runs at all — off means off, not “very fast”.

Platforms

One core, five platforms, and one honest gap

Spawning a Python interpreter is not possible on iOS at all, and impractical on Android without embedding a runtime. Declarative extensions cover that gap for viewing.

Platform Core Declarative Python plugins
Windows
macOS
Linux
Android
iOS

Status: early. Builds and runs on Windows; other platforms are less travelled.

Language

Seven languages, and English is never missing

English, Russian, Spanish, German, French, Korean and Japanese travel in the app bundle — a language is not a plugin, so the interface is readable on a machine with nothing installed. The key is the English sentence, which is why a missing translation degrades to English by construction rather than by a rule someone has to remember.

Download

Two files to hand over, and nothing fetched at install time

The archive and the installer beside it. The target machine needs no clone, no Flutter SDK and no network. It installs where it can rather than failing with a permission error, so neither sudo nor “Run as administrator” is required to end up with a working application.

To remove it: sh install.sh --uninstall. Settings, connections and installed plugins are left in place.