v0.7.3
Before you double-click
This build is not signed or notarized, so macOS blocks the first launch. Allow it once:
- macOS 15 Sequoia and later — double-click, dismiss the warning, then open
System Settings ▸ Privacy & Security and click Open Anyway. Apple removed the
Control-click shortcut for unsigned apps in macOS 15, so right-clicking does not help.- macOS 13–14 — right-click the app ▸ Open, then confirm.
Only the first launch asks.
Markdown and HTML leave the core: both formats are now drawn by a plugin that renders
diagrams and mathematics on your Mac, on every surface that shows a file — the viewer, the preview
panel, Quick View and the gallery. Searching inside archives reaches every archive the app can open,
says what it could not read, and no longer leaves extracted copies behind. Amazon S3 joins the drives,
the assistant summarises whole files and looks through your disk for one, and the documentation
website stops reporting its readers to anyone.
Added
-
Searching inside archives now means every archive the app can open. Turning on Search inside
archives used to descend into the zip family only, so a.tar.gzholding a config file with the
search term reported nothing found — even though pressing Enter opens that same file. Search, the
panel's Enter, unpack, Test Archive and archive reload now share one authority for "is this an
archive and who opens it", so tar, tar.gz/tgz, 7z, rar, xz, zst, iso, cpio, squashfs, single-file
.gzstreams, split zips (name.zip.001) and anything a packer plugin or the Extra archive
extensions setting adds are all searched wherever they are browsable. Two long-standing gaps
closed with it: unpacking a plugin-only format (Alt+F9 on a 7z) used to fail with "select an
archive first" on a file the panel opens happily, and reloading an archive could silently swap a
plugin's mount for the built-in zip reader. -
A search that could not look somewhere now says so. Archives that were unreadable, encrypted,
over the size ceiling or nested deeper than four levels used to be skipped in silence, which reads
exactly like "the term is not in there". The status line now ends with how many were not searched. -
Results found inside archives are usable. F3 opened them with a beep and Feed to Listbox
dropped them without a word; both now work, as does copying one out with F5, because a hit carries
the archive chain it came from instead of a path string nothing could resolve. -
Amazon S3 and S3-compatible storage as a drive. Net ▸ Amazon S3 Connect… connects to Amazon
S3, MinIO, Ceph, Cloudflare R2, Wasabi, Backblaze B2 or DigitalOcean Spaces, and the bucket list
becomes the top level of a panel with each bucket a folder below it. Reading, writing, new folders
and buckets, deleting, renaming and moving all work, and copies happen on the server rather than
through your Mac. Profiles from the AWS command line are offered if you have them; secret keys go
into the Keychain. A remembered connection becomes a chip in the drive bar that connects when you
click it. Storage Class and ETag are available as panel columns.It is a plugin, so you can turn it off or remove it in Configuration ▸ Plugins…. The help topic
Amazon S3 and S3-compatible storage describes what to expect of it — including that a bucket
cannot be renamed, that an archived object must be restored before it can be read, and that unlike a
disk, every request to a paid service costs money. -
You can agree to part of the assistant's plan. When a plan covers several files — renaming a
folder full of them, clearing out your Downloads — each one is a ticked line above the buttons. Untick
what you want left alone and press Confirm & run: the rest goes ahead and the unticked files are
not touched. Until now the only answers were all and nothing, so wanting all-but-three meant rejecting
the plan and describing the exception in words for the assistant to get right on a second try.
Unticking everything is the same as cancelling, and it says so rather than reporting that it did
nothing. -
Ask the assistant to find a file and it looks through your whole disk. "Find the PDF invoice from
last month", "where are all my node_modules folders?", "which file mentions the Aachen contract?"
— including words inside files, which the ordinary search can only do once you point it at a
folder. It uses the index macOS already keeps, so there is nothing to build and no waiting for it to
catch up, and it tells you where it looked: your home folder, the whole computer, or just the folder a
panel is showing. Two honest limits: macOS keeps some places out of its index, so "nothing found" is
not proof a file is absent, and a file created moments ago may not be indexed yet — Find Files
walks the folders itself and will still see it. -
One click puts one panel's folder in the other, and says which side. Go ▸ Left = Right shows
the right panel's folder on the left, Go ▸ Right = Left does the reverse, and both are on the
button bar by default. Target = source (Ctrl+=) has always done this relative to whichever panel is
active — which is the wrong shape for a button, because the same click then means two different
things depending on where the focus happens to be. These name the side outright. An existing button
bar gains the two buttons once; remove them and they stay removed. -
The whole empty space at the right of a path bar opens the path for typing. Not just the pencil,
which is eighteen points wide. Clicking a folder in the breadcrumb still goes there, and the narrow
gaps between folder names still do nothing, so a click that just misses a name is a miss rather than
a mode change. A click on a path bar now also makes that panel the active one. -
The assistant summarises a whole file, however long. Its on-device model takes in a few
thousand words at a time, so "summarise this report" failed outright on anything past about six
kilobytes — measured on this machine: a four-kilobyte slice is answered, an eight-kilobyte one is
refused, and the assistant was asking for sixty-four. It now reads a long file in slices and folds
the slice summaries into one, so the length of a file costs time instead of failing. A 38 KB report
comes back summarised, including what its last page says. -
What the assistant did, and taking it back. Actions ▾ in the chat shows every change it
made — what was asked of it, how it turned out, and the attempts the autonomy setting refused —
and takes back the last change that has an inverse: a rename is renamed back, a move is moved back.
Where nothing can be taken back the list says why, rather than offering a button that would lie.
An external agent connected over MCP writes to the same log. You can also just ask the assistant
to undo it. -
An "AI ▸" action over a whole selection. Mark forty files and the action runs over all of
them, one after another, with progress in the status line; Stop ends the run between files. This
is the part a two-panel file manager was missing: the assistant could only ever act on the file
under the cursor. -
Answers you can act on. The chat renders the model's Markdown: a table is a table, a fenced
block is a code block, a list is a list, and a path is clickable. (Make a table produces a
well-formed Markdown table by construction, and the chat used to show it as rows of pipe
characters.) Suggest a name now ends in a Rename button carrying the proposed name —
pressing it is the approval, so nothing is asked twice. -
Your own "AI ▸" actions. What each action asks the model is a file you can edit
(aichat/skills.json,aichat/folder-skills.json), written out with the built-in wording on
first run — and an action you invent is a real command: nameplugin.ai.skill.<your-id>in the
user menu, on the button bar or on a keyboard shortcut and it runs. A plugin can now declare that
a command family is open to ids it does not itself list, which is what makes this possible without
the host having to load a plugin to find out what it offers. Name an id that does not exist and
the assistant says so rather than doing nothing. -
An AI Summary panel column. It shows the first line of the summary for each file the assistant
has already summarised, and stays empty for the rest — the column shows work already done and
never starts a model itself. The plugin's other column, which detects a text file's language
without any model, is now called Language rather than "AI Language". -
The Git panel and its windows follow the colour scheme. In every dark palette the Git panel in the
side panel was a white rectangle with white labels on it; the history, blame, branches, conflict and rebase
windows ignored the scheme entirely. They now take their colours from the app — and follow a change while
they are open. -
PDFs and Word documents render in the preview, with zoom. The side panel's preview, Quick View and
the info page now draw PDFs themselves — page by page, with the same zoom buttons a picture has (zoom in,
zoom out, actual size, fit) — and show Word, OpenDocument and RTF documents as formatted, selectable text.
Everything else is still previewed by macOS Quick Look. Reported as "PDF and DOCX are no longer rendered":
what those formats went through before renders outside the application, where nothing inside it can tell a
rendered page from a blank one — so this also makes the preview something that can be checked. If you
prefer the system's preview for everything, switch Render PDFs and documents in the preview off in
Configuration ▸ Edit/View. -
Plugin column headers are translated, and can carry an icon. Git Status and the other plugin columns
showed English headers in every language; they now use the plugin's own translations — while your saved
column sets keep working, because only the header changed and not the column's identity. Git's status
column also shows a real icon next to the word instead of a text glyph. -
Blame in the editor's gutter.
Git ▸ Blame in the Editoropens the file and writes who last touched
each line next to the line numbers, with the commit, author, date and subject on hover; clicking a line
opens that commit against its parent in the compare window. The mechanism is a new host service, so any
plugin can annotate lines this way — coverage, a linter, anything per line. -
The bundled plugins are the pitch now, not a footnote. Seventeen plugins ship inside the
app and the landing page said so in one run-on sentence, two thirds of the way down, under a
heading about the SDK — while the feature card above it advertised the ability to write
plugins rather than the fourteen that are switched on the moment you launch. There is now a
showcase near the top: what each one does in a line, the three that are off by default marked
as such, the plugins window as proof that every one of them is there and individually
switchable, and Disk Map, Git and the Uninstaller shown rather than described. The SDK keeps
its own section further down, where it belongs. -
Card grids were choosing their column count by font size. Every grid on the landing page
used aremtrack minimum, and Material setshtml { font-size: 125% }— so 1rem is 20px on
a default browser and was measured at 24px on another. Aminmax(14rem, …)meant for three
columns silently rendered two in an 826px content column. The minimums are in px now; the
gaps stay in rem, because spacing should scale with type. -
The terminal and the log viewer are shown, not just listed. Two of the most visual bundled
plugins had no screenshot anywhere. Both are captured now — the terminal with the shell sitting
in the folder the panel above it shows, the log viewer with a service log colour-coded by level
— and the showcase strip on the landing page carries five pictures instead of three. Both help
pages embed theirs too, in all nineteen languages, so they are no longer the only plugin pages
without a picture.
Changed
-
The AI assistant now arrives switched off. Turn it on in Configuration ▸ Plugins…; leave it
off and nothing about it appears — no AI ▸ menu, no chat, no column. It is in beta, and it can rename,
move and delete files and run shell commands for you, each behind a plan you approve. That is a lot of
reach to hand a new feature by default, and it is the same standard the filesystem-image and
decompiler plugins already ship under. Without an API key it works entirely on your Mac, so this is
about the reach and not about anything leaving the machine. The AI Column plugin, which fills a
panel column from the same model, arrives switched off with it. -
Markdown and HTML are now drawn by a plugin, and can do much more. Press F3 on a
.mdor
.htmlfile and you get nested lists, task lists with their boxes, tables with alignment,
reference links and strikethrough — a real Markdown parser rather than an approximation. Diagrams
written as```mermaidblocks are drawn, and mathematics written between dollar signs is
typeset. Both happen on your Mac: the engines ship inside the plugin, nothing is downloaded,
and no part of your document is sent anywhere. A document with neither loads neither.The same rendering now appears in the preview panel and Quick View, so a preview and a full
view of one file no longer disagree, and the gallery shows a small picture of a Markdown
file's beginning instead of a generic icon. Apple's own Quick Look (Cmd+Y) is unchanged — that
panel belongs to macOS.It is a plugin called Markdown and HTML, so you can switch it off in
Configuration ▸ Plugins…; both formats then open as text, with the outline and syntax colouring
intact. Its own settings page turns diagrams and mathematics on or off separately, sets the size
above which a file opens as text, and says which engine version is in use and where it came from.
If you need a different build of Mermaid or KaTeX, you can drop it in a folder and it is used
instead.Two things it will not do, on purpose. A rendered page loads nothing over the network: an image
whose address begins withhttpstays blank, because fetching it would tell that server when you
opened the file. And a document's own scripts never run — HTML inside a Markdown file is shown as
text, and an.htmlfile is displayed with scripting switched off. -
Code blocks in a rendered Markdown file are now coloured. Press F3 on a
.mdfile and the
Rendered view keeps the language written on each fence —```swift,```python—
and colours comments, strings, numbers and keywords the way the editor does. A fence naming
something the app has no lexer for, such asmermaid, stays a plain block rather than failing. The
rendered page still loads nothing from the network. -
The assistant is offered only the tools it is allowed to use. Under "read-only" the write and
delete tools are no longer offered and then refused: for a model with a few thousand tokens of
context, a round of attempts that can only fail is the budget for the real answer. Memory
(remember/recall) and the semantic search reached only the cloud path before — the on-device
default, which is what most people run, had no memory at all. -
"Which file is about X" finds it. The semantic search ranked file names only, with an
English-only embedding, so a German query fell back to counting shared words and the "semantic"
part quietly did nothing. It now reads the beginning of each file too, follows the language of the
query, and returns what is close to the best match instead of the whole folder. -
A copy or a move is reported as done when it is done. Both tools queued the transfer and
returned immediately, so the assistant announced a copy before a byte had moved — and a plan of
several steps ran against a queue that had not started. They now wait for the transfer, which is
still an ordinary background job in the Transfer Manager. -
Reading, hashing and searching no longer block the window. The automation tools ran on the
main thread, and "find duplicates" mapped every file into memory there. Hashing is streamed now,
and the file-system tools run off the main actor. -
A model change in Settings takes effect at once. The chat kept the provider and the system
prompt it was built with, so switching to a cloud model looked like it did nothing until the panel
happened to be rebuilt. -
The MCP server follows the autonomy setting. It was fixed at "confirm changes", so "read-only"
on the AI page held for the assistant in the window and not for an external agent on the socket —
both of which are configured on that same page. -
The Git menu reads like a menu. Inside a submenu already called Git, every entry said "Git" again:
Git ▸ Git Status…. The titles are nowStatus…,Stage,Commit…,Push,Pull,Panel,Diff…,
History…next toFile History…,Branches, Stashes & Tags…andBlame (list)…beside the new
Blame in the Editor— and the entries are in a sensible order instead of the order they were added in.
Fixed
-
Two libraries the app had started using were missing from its attributions. The Markdown
plugin's parser — Swift Markdown, and the cmark-gfm it is built on — were pinned as dependencies
but named nowhere, so Help ▸ Open Source & Third-Party Software… did not list them and their
licences were not shipped. Both are there now, with their full texts. The generator had been
looking in one place for the licence files while these two are resolved into another, and it warned
about exactly that on every build without anyone acting on the warning. -
A large folder now fills in as it loads. A directory with thousands of entries — a big folder on
disk, an FTP or SFTP listing, an S3 bucket — showed an empty panel until the very last entry had
arrived. The rows now appear as they come in. Navigating away while a folder is still loading no
longer lets the slower listing win, and if a listing fails partway through, the panel goes back to
showing the folder it was actually in rather than half of the one it could not open. -
The same bytes now give the same answer inside an archive as on disk. Content search stopped
after 16 MB of an archive member while a loose file had no such limit, so a match further into an
archived log was reported as no match at all. Members past that point are extracted and searched
exactly as local files. -
Searching no longer leaves extracted archives behind. Descending into a nested archive wrote a
temp directory that nothing ever removed; the extraction now belongs to the descent that made it,
and an archive mount — built-in or plugin-backed — cleans up everything it extracted when it goes
away. Anything earlier builds already left behind is cleared out at the next launch. -
Opening an archive with many files in one folder is no longer quadratic. A tar holding 20,000
files in a single directory took 30 seconds to open — an unpacked source tree or anode_modules
tarball is exactly that shape. Under three seconds now. This one is older than the archive-search
work and surfaced only once its performance budgets were written. -
A search says which archives it could not read, not just how many. The new Details… button
in Find Files lists each one with the reason: it could not be opened, it is password protected
(the names inside were searched, the contents were not), it is larger than a search opens, or it
is nested deeper than a search descends. The button stays hidden when a run had nothing to report. -
A password-protected archive is reported instead of quietly passed over. Its member names were
always searched and its contents never were, and nothing said so. -
A condition on the Plugins tab no longer discards every result found inside an archive. The
condition was checked against the result's displayed path, which for an archive hit is not a file,
so those rows were dropped without a word as soon as any condition was set. -
Editing a file inside an archive says so instead of losing the change. F4 opened a copy that
Save wrote into while the archive stayed as it was, and nothing said so. Copy the file out with F5
and edit the copy. Editing over SFTP, FTP and WebDAV still writes back as before, and a branch view
still edits the real file. -
A long search can be stopped while it is opening an archive. Cancelling checked only between
archives, so a search that had just started on a large one had to see it through first. -
Entering an archive twice no longer reads it twice. An open archive is remembered — up to 32 of
them, and only while they stay small enough to be worth remembering — so leaving one and going back
in, or unpacking it afterwards, no longer re-reads the whole directory. -
Reading a tar is no longer paid for in full. The reader read whole files into memory before it
could tell whether they were tars at all — so a large.xz,.zstor.7zwas read cover to
cover only to be handed on, and adump.sql.gzwas decompressed entirely before being rejected. -
Images in an assistant answer are shown as images. An answer pointing at a picture on your Mac
used to print the Markdown for it, brackets and all. It now appears, scaled to the width of the chat.
Only files on this Mac are shown: an image address on the internet is left as visible text and is
never fetched, because loading one would tell that server when you read the answer. -
The documentation website no longer tells anyone that you are reading it. Every page used to
fetch a font from Google and the repository's star count from GitHub, so opening a page reported
it to two third parties — and none of the typography worked without a network. Both are gone; the
site now uses the fonts your Mac already has. The only remaining request is the download button on
the front page asking GitHub for the newest release, which cannot be known offline and still works
without JavaScript. -
.mdx,.mkdnand.mdwnfiles are now treated as Markdown everywhere. Three places in the
app disagreed about which extensions count: one of them gave.mdxan outline but would not render
it, another rendered.mkdnbut would not reformat it. All three read one list now. -
Diagrams on the documentation website now render without an internet connection. The
architecture pages draw 34 diagrams, and the engine that draws them was fetched from a third-party
CDN the moment a page was opened — so the diagrams were missing offline, and reading one told that
CDN which page you were on. The engine now ships with the site. -
Uploading to a server now shows progress and can be cancelled. Copying files into an FTP,
SFTP, WebDAV or plugin panel used to run with no progress window and nothing to press — for one
large file, an application that looked hung — and reported only a count at the end. It now uses the
same transfer window as a local copy, with Cancel, pause and the speed limit. On a plugin drive the
bar moves within a single file; on FTP and SFTP it advances a file at a time. -
A cancelled download no longer reports success. Stopping a transfer on a plugin drive could
report it as complete for a file that was never written. -
Columns a plugin adds are no longer blank. A plugin can contribute extra columns for its own
entries — the Task Manager's CPU and PID, an S3 drive's storage class. For a drive whose name
contains a dot, which is every server address, the column stayed empty however it was configured. -
A plugin drive in the drive bar now connects when you click it. A plugin that offers several
saved connections showed one chip each, and clicking one opened the connection dialog instead of
connecting — the chip looked like a shortcut and was not. It connects that saved connection
directly now. If its password is no longer in the Keychain it says so, rather than quietly
connecting without one. -
Copying, creating a folder and renaming now work inside a mounted plugin drive. On a WebDAV
server — or any drive a plugin provides — three keys did the wrong thing quietly. F5 into the drive
handed the remote path to the local copy engine, so the file was written to a same-named folder on
this Mac and reported as copied. F7 created a local folder named after the remote path. F6 renamed
nothing and blamed the files. All three now go to the server, and a failure says which item it
happened to. Cancelling a transfer also reaches the plugin now, instead of being noticed after the
last byte had already arrived. -
Summarising a very long file no longer fails at the last step. The assistant reads a long file in
slices and combines the results; combining them was itself one request, and for a long enough file —
or a talkative enough model — that request was too big for the on-device window. It failed only
sometimes, and when it did the assistant reported it as though the file were the problem. The
combining now happens in rounds, so it stays within the window however long the file is. -
A folder that cannot be opened no longer moves the panel there. Opening one that macOS keeps
private, or that permissions refuse, used to leave the panel claiming to be in it: the tab and the
path said the new folder, the path bar said the old one, and the file list belonged to neither. The
folder was even written to the session, so the next launch started somewhere it could not read. The
panel now stays where it was, and everything on screen agrees about where that is. -
And it says why, when macOS is the reason. A location such as your iOS device backups is visible,
belongs to you, and its permissions say you may read it — and opening it is still refused, because
macOS gates it on the app rather than on you. No amount of administrator rights helps, so "could
not open" sent you looking for a permission that was never the problem. The panel now names it:
macOS keeps private — see Commands ▸ Full Disk Access…. -
FEATURES.mdlisted "Archives" twice. One feature record of eighty-eight said
category: archiveswhere the rest sayarchive; the label table has no plural, so the
generator fell back to capitalising the raw value — which produces the same heading the
singular already produces. The record is fixed, and the generator now refuses a category that
is not in the registry's own list instead of inventing a heading for it. -
The other eighteen languages got the same site, and a front page. The translated help
was published as a flat list of twelve sections with a three-line stub for a landing page —
seventeen of the eighteen languages had a heading and the name of their language, nothing
else. They now have the same five tabs as the English site, named in their own language, and
a real front page: the opening paragraph of that language's own introduction topic followed
by every topic grouped the way the tabs group them. No prose was invented for it — all of it
is text a translator already wrote. -
Sixteen languages showed a stray English "Plugins" section in the in-app help. Seven
plugin topics sat under the translated word and six under the English one, because whoever
added the later plugin pages copied the Englishsection:along with them. Every reader of
those sixteen languages saw two plugin sections in the Help Book, one of them untranslated. -
The documentation site opened with the API reference. The first two navigation entries
were API reference and Developer guide; Getting started came third, and the user
guide, the plugins and the tutorials were far below. Nobody had decided that: the site's
navigation is derived from each page'sorder:, a section ranks by the lowestorder:of
its pages, four sections tied at10— and the tie fell to alphabetical directory order.
The API reference won outright because its generator wroteorder: 5, the smallest number
in the corpus. The site now has seven tabs in reading order — Get started, Using Peach
Commander, Customise, Plugins, Tutorials, Reference & help, Develop — with the twenty-one
former sections as collapsible groups beneath them, so no more than a handful of entries
is ever on screen at once. The API reference is in the last tab. The landing page opens
with three doors instead of a wall of prose: coming from Total Commander, new to two-panel
managers, or here to write a plugin.This is a website-only key, so the in-app Help Book keeps its own structure and is
byte-for-byte unchanged, and the eighteen translated Help subsites are untouched. -
A user help page was invisible on the website.
help/filesystem-images.mdand
plugins/filesystem-images.mddeclared the same slug. Pages are staged flat, so the
developer page overwrote the user page while the navigation kept an entry for both — two
titles, one file, and the user-facing Filesystem Images help was not published at all. The
developer page is renamed, and a duplicate slug now stops the build instead of quietly
winning. Also: the site said "MacOS & privacy" because section labels were auto-capitalised. -
266 dead links in the shipped in-app help. Seven topics link to each other as
](slug.md), which resolves on the website; an Apple Help Book bundle contains no Markdown
at all, so every one of them was a dead href — fourteen links in each of the nineteen
languages. The table of contents was always correct, which is why nothing looked wrong from
the outside. -
Toggling hidden files could kill the app, and switching panels showed the wrong thing first.
Both commands ran their handler on a background thread and reached straight into AppKit from there:
cm_SwitchHidSyscalledNSTableView.reloadDatawhile the main thread was drawing, and AppKit's
layout engine raised an exception nobody catches — an abort, mid-session, with the panel already
showing the wrong directory for a while beforehand throughcm_SwitchPanel. The cause was one
annotation that does less than it reads like:CommandHandleris a@MainActorfunction type, but
that only isolates the closure literals written at ahandler:argument. A reference to a
separately declaredfunckeeps its own isolation, so all 37 namedcm_*_handlerfunctions were
running wherever the command registry's continuation happened to be. They are main-actor-isolated
in their own right now, andWindowControllerProtocolsays so too, so the compiler will hold the
rule instead of a comment claiming it.Tools/check-command-handler-isolation.pyis the gate: the
build could not catch this one, because the project compiles in the Swift 5 language mode and the
conformance that crashed produced no warning at all. -
Two panel properties and two macOS callbacks crossed the same boundary.
PanelControllerProtocollooked safe because its methods areasync— an async witness hops — but
it also had three synchronousvarrequirements that did not.NSServicesMenuRequestorand
QLPreviewPanelDataSourceare ObjC protocols with no isolation of their own, so their witnesses
now assert the main thread they are actually called on rather than assuming it quietly. -
A crash used to make the next automated run hang instead of report. The launch-time crash-report
prompt and the Full Disk Access prompt are both modal, and a modal owns the main queue an automation
script is driven from: the script ran on inside the nested runloop, wrote its files, and then never
quit. Both are skipped under-AutomationScript. The crash watermark is deliberately left alone, so
the report still greets the user on their next ordinary launch.