Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@ metadata. Preserve copyright headers, upstream notices, license provenance,
`@author` tags, and historical attribution unless the user explicitly asks for a
specific removal and the legal/provenance impact has been considered.

For code that remains live in the repository, authorship and copyright
information must stay attached to that code. Removing or moving dead code may
remove the corresponding attribution only when the attributed code is removed as
well.

Comment cleanup may update stale technical claims, but it must not erase who
wrote or contributed the original code.
426 changes: 411 additions & 15 deletions BUGS.md

Large diffs are not rendered by default.

134 changes: 131 additions & 3 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Source: forked from https://github.com/mucommander/mucommander to https://github
| **28** | done | **Remove dead FreeBSD mount shell-out** — deleted the unsupported `/sbin/mount -p` path and kept Linux mount discovery on `/proc/mounts`. | this PR |
| **29** | done | **JUnit 5 + protocol scope cleanup** — migrate legacy tests to JUnit 5, improve S3 endpoint URL parsing, remove retired-protocol future scope, and evaluate NFSv4 replacement options. | landed in #37 |
| **30** | done | **Architecture refactor batch** — archive format `ServiceLoader`, remove vendored `apache-bzip2`, centralize runtime tunables, and make javac unchecked/deprecation warnings fail the build. | landed in #38 |
| **31** | in progress | **Repo skill + architecture/docs/check sweep** — add repo-local Java GUI slop cleanup skill, document current architecture, align stale docs/comments with the implementation, and harden CI against fake-green checks. | this PR |
| **31** | done | **Repo skill + architecture/docs/check sweep** — add repo-local Java GUI slop cleanup skill, document current architecture, align stale docs/comments with the implementation, and harden CI against fake-green checks. | landed in #39 |
| **32** | in progress | **Repo-wide Java GUI slop sweep** — run the repo-local slop-cleaning skill against current `origin/main`, record each finding in `BUGS.md`, fix actionable issues, and keep continuity docs current. | this PR |

**Hard rule**: only one branch / one PR is in flight at a time. The user — not the LLM — decides when a PR is ready and when the next one starts. The LLM does not autonomously open new PRs to fan out work in parallel.

Expand Down Expand Up @@ -1241,13 +1242,140 @@ has passed `./gradlew cleanTest test --stacktrace`, `./gradlew check
fatJar :cyclonedxDirectBom --stacktrace` plus the CI artifact assertions), after
the Claude second-opinion fixes were applied. The requested noninteractive
`claude` review was approved by the user and completed; actionable findings were
applied. Before handing the PR back, push the branch and verify GitHub Actions on
PR #39.
applied. PR #39 was merged by the user.

**Exit criteria**: repo-local skill and architecture docs are present; stale
current-facing docs/comments are aligned without stripping authorship; no-op
checks fail loudly; full checks and CI are green.

### Phase 32 — Repo-wide Java GUI slop sweep (this PR)

Phase 32 starts from `origin/main` after PR #39 was merged. The active branch is
`phase-32/slop-sweep`; no PR exists yet. Scope is a single-PR sweep using the
repo-local `clean-java-gui-slop` skill: search for fake-green checks, Swing/EDT
mistakes, background work lifecycle leaks, swallowed failures, stale comments,
and accidental Windows support. Every new finding must be recorded in
`BUGS.md` before its fix is made, and this section must be updated after each
substantial chunk so work survives compaction or a fresh context.

Current state: local `main` was fast-forwarded to `origin/main`, this branch was
created from `origin/main`, and the initial skill sweep recorded and fixed three
findings in `BUGS.md`: SFTP stream-open cleanup now logs handler-close failures,
S3 metadata probes now log non-missing lookup failures instead of silently
returning false/zero, and `OsFamily` has been narrowed to macOS/Linux/unknown
with focused tests. The OS/2-only `FileURL.pathEquals()` branch was removed.
The next GUI pass recorded and fixed two EDT findings: native "Open With"
application discovery now applies menu mutations from `SwingWorker.done()` and
discards stale refresh results, while `FolderPanel` no longer creates Swing/AWT
location controls from an unmanaged background thread. `AGENTS.md` now also
states the live-code rule that authorship/copyright stays attached to code that
remains in the repository. The follow-up GUI/threading pass recorded and fixed
three more findings: `StatusBar` now marshals disk-space label updates onto the
EDT, `FileTable` uses a Swing `Timer` for delayed single-click edit actions,
and `QuickListWithIcons` starts only one icon loader per item while keeping
spinner/repaint mutations on the EDT. The cleanup/interrupt pass then fixed
queued-trash interrupt handling and made S3 spilled-upload temp-file cleanup
failures visible in logs. The startup/text-viewer pass moved
`CheckVersionDialog` to a `SwingWorker` split so network lookup stays in the
background while dialog UI runs on the EDT, and coalesced text-editor miss beeps
through a daemon single-thread executor. The connect-dialog pass made SFTP, NFS,
and S3 invalid port commits fail visibly through `ServerConnectDialog` instead
of silently reusing the previous spinner value. The ignored-catch pass then
made SFTP random-access seek propagate close failures, surfaced About-dialog
homepage browse failures, and logged S3 provider connection-close failures.
It also made the benign shutdown-hook removal race visible at DEBUG instead of
silently swallowing the state. Local validation passed with `git diff --check`,
`./gradlew cleanTest test --stacktrace`, and `./gradlew check --stacktrace`.
User added one more required deliverable for this same PR: add a MinIO-backed
Testcontainers S3 integration path, alongside the existing LocalStack coverage,
so S3-compatible endpoint behavior is tested against a real MinIO server as
well. The MinIO image tag confirmed from Docker Hub metadata is
`minio/minio:RELEASE.2025-09-07T16-13-09Z`; the implementation should keep the
same Docker-unavailable skip behavior as the LocalStack tests. The new MinIO
test exposed and fixed one additional S3 correctness issue: successful
`S3Object.delete()` now invalidates local metadata so the same object instance
does not keep reporting `exists() == true`. Next work is to rerun local
validation, then commit, push, open the single Phase 32 PR, and monitor CI.
Final local validation after the MinIO addition passed with
`./gradlew :barebones-protocol-s3:test --tests ...S3MinIOIntegrationTest
--stacktrace`, `./gradlew cleanTest test --stacktrace`, and `./gradlew check
--stacktrace`.
After PR #40 opened, the user requested one more sweep and a Claude Code CLI
review attempt in the same PR. The extra sweep has so far recorded and fixed
two more EDT issues: `NotificationPopup` now uses a non-repeating Swing timer
instead of a non-daemon `java.util.Timer` that hid popups off the EDT, and
`FileFrame` now installs viewer menu bars and shows/disposes error UI from the
EDT path after async loading. It also made `AsyncPanel` loader failures logged
and visible instead of leaving a permanent loading spinner. Claude Code CLI is installed locally
(`claude --version` reports 2.1.143 and `claude --help` confirms `-p/--print`
noninteractive mode), but the smoke invocation currently fails immediately with
`Not logged in · Please run /login`; `claude doctor` then hung without output
and the started process was stopped. `claude auth status` confirms
`loggedIn: false`, and the dedicated noninteractive `claude ultrareview 40
--timeout 1` path fails immediately with "Ultrareview is currently
unavailable." Local validation after the extra sweep passed with `git diff
--check`, `./gradlew cleanTest test --stacktrace`, and `./gradlew check
--stacktrace`; next work is to commit/push these extra fixes onto PR #40 and
watch CI again.
The user confirmed Claude login, and rerunning outside the sandbox showed
`claude auth status` logged in. A tool-using `claude -p` review launched but
hit `Reached max turns (20)` without findings, so the PR diff was piped into a
one-turn `claude -p` review. Claude reported actionable follow-ups that were
recorded in `BUGS.md` and fixed: explicit MinIO `@TestInstance(PER_CLASS)`,
failed `FileFrame` async layout short-circuit, `OpenWithMenu` loading-item
separator guard, EDT marshaling for `NotificationPopup.displayNotification`,
EDT construction of `AsyncPanel` fallback label, and simplified S3 metadata
failure logging without unsynchronized identity comparison. Next work is to
rerun validation, commit/push, and watch PR #40 CI again.
The continued Claude-guided sweep recorded and fixed three lower-risk async
cleanup items: `TextEditorImpl` no longer owns an unclosed static beep
executor, `OpenWithMenu` now accepts same-URL async Open With results instead
of requiring the same `AbstractFile` instance, and `QuickListWithIcons` now
uses `SwingWorker` for icon loading instead of raw per-item threads. The same
sweep also recorded a larger remaining GUI-threading issue: initial
`MainFrame`/`FolderPanel` construction still happens from `MainFrameInit` and a
worker executor instead of consistently on the EDT. That requires a dedicated
startup lifecycle refactor because a partial constructor-only edit would leave
the frame visibility and preload invariants ambiguous. Next work is to rerun
validation, commit/push, and watch PR #40 CI again.
A second current-diff Claude review eventually completed. Its concrete findings
were recorded as `BUGS.md` 1.59 and patched: failed file-presenter opens now
throw through `AsyncPanel` instead of returning half-initialized UI; async panel
loader failures include `Throwable` and skip replacement after the panel has
been disposed; `QueuedTrash.waitForPendingOperations()` returns immediately
after preserving interruption; `CheckVersionDialog` schedules modal result UI
after `SwingWorker.done()` returns; `NotificationPopup` is constructed on the
EDT; quick-list spinners are instance-owned; S3 metadata state is synchronized
across reads and mutations; and SFTP random-access streams clear stale handles
when seek/close closes the old stream. Next work is to rerun local validation,
commit/push, and watch PR #40 CI again.
Local validation after the second-review fixes passed with `git diff --check`,
focused compile plus `:barebones-protocol-s3:test --tests
dev.barebones.commander.commons.file.protocol.s3.S3MinIOIntegrationTest`,
`./gradlew cleanTest test --stacktrace`, and `./gradlew check --stacktrace`.
Next work is to commit/push the Phase 32 follow-up patch and watch PR #40 CI.
The user then requested fixing all remaining bugs in the same open PR. The pass
confirmed the old open real-bug entries were either already fixed by earlier
Phase 32 code or still concrete. The concrete changes in this pass route
non-EDT `WindowManager.createNewMainFrame(...)` calls through the EDT, remove
the `MainFrame` worker executor that built Swing components off the EDT,
marshal the delayed startup update-check dialog back to the EDT, and make S3
`exists()` treat non-missing metadata lookup failures as unknown/last-known
state instead of false absence. `BUGS.md` now marks the stale/fixed real-bug
entries as fixed/resolved and leaves only architecture/refactor review notes as
open headings. Next work is to rerun validation, commit/push to PR #40, and
watch CI.
Validation for this all-bugs pass passed with `git diff --check`, focused core
compile plus MinIO S3 integration test, `./gradlew cleanTest test
--stacktrace`, and `./gradlew check --stacktrace`. Next work is to commit/push
to PR #40 and watch CI.

**Exit criteria**: all actionable findings discovered in this sweep are either
fixed or explicitly documented as deferred; local validation includes at least
`./gradlew cleanTest test --stacktrace`, `./gradlew check --stacktrace`,
package-smoke artifact checks if CI/build wiring changes, and `git diff
--check`; a single PR is opened and GitHub Actions are green.

## 7. Compatibility with upstream

We may want to **pull bug fixes from upstream muCommander** for at least 1 year. To keep this cheap:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import dev.barebones.commander.commons.file.protocol.search.SearchFile;
import dev.barebones.commander.commons.file.protocol.search.SearchSchemeParser;
import dev.barebones.commander.commons.file.util.PathUtils;
import dev.barebones.commander.commons.runtime.OsFamily;
import dev.barebones.commander.commons.util.StringUtils;

/**
Expand Down Expand Up @@ -899,10 +898,8 @@ public boolean portEquals(FileURL url) {
* @return <code>true</code> if the path of this URL and the given URL are equal
*/
public boolean pathEquals(FileURL url) {
boolean isCaseSensitiveOS = !OsFamily.getCurrent().equals(OsFamily.OS_2);

String path1 = isCaseSensitiveOS ? this.getPath() : this.getPath().toLowerCase();
String path2 = isCaseSensitiveOS ? url.getPath() : url.getPath().toLowerCase();
String path1 = this.getPath();
String path2 = url.getPath();

if(path1.equals(path2))
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,6 @@ public enum OsFamily {
MAC_OS("macOS"),
/** Linux */
LINUX("Linux"),
/** Solaris */
SOLARIS("Solaris"),
/** OS/2 */
OS_2("OS/2"),
/** FreeBSD */
FREEBSD("FreeBSD"),
/** AIX */
AIX("AIX"),
/** HP-UX */
HP_UX("HP-UX"),
/** OpenVMS */
OPENVMS("OpenVMS"),
/** Haiku */
HAIKU("Haiku"),
/** Other OS */
UNKNOWN_OS_FAMILY("Unknown");

Expand Down Expand Up @@ -93,25 +79,13 @@ public static OsFamily getCurrent() {
* <ul>
* <li>{@link #LINUX}</li>
* <li>{@link #MAC_OS}</li>
* <li>{@link #SOLARIS}</li>
* <li>{@link #FREEBSD}</li>
* <li>{@link #AIX}</li>
* <li>{@link #HP_UX}</li>
* <li>{@link #UNKNOWN_OS_FAMILY}: the reason for this being that most alternative OSes are Unix-based.</li>
* </ul>
*
* @return <code>true</code> if the current OS is UNIX-based
*/
public boolean isUnixBased() {
return this==MAC_OS
|| this==LINUX
|| this==SOLARIS
|| this==FREEBSD
|| this==AIX
|| this==HP_UX
|| this== UNKNOWN_OS_FAMILY;

// Not UNIX-based: OS/2 and OpenVMS
|| this==LINUX;
}

/**
Expand All @@ -137,33 +111,10 @@ static OsFamily parseSystemProperty(String osNameProp) {
if (osNameProp.startsWith("Mac OS X")) {
return MAC_OS;
}
// OS/2 family
if (osNameProp.startsWith("OS/2")) {
return OS_2;
}
// Linux family
if (osNameProp.startsWith("Linux")) {
return LINUX;
}
// Solaris family
if (osNameProp.startsWith("Solaris") || osNameProp.startsWith("SunOS")) {
return SOLARIS;
}
if (osNameProp.startsWith("FreeBSD")) {
return FREEBSD;
}
if (osNameProp.startsWith("AIX")) {
return AIX;
}
if (osNameProp.startsWith("HP-UX")) {
return HP_UX;
}
if (osNameProp.startsWith("OpenVMS")) {
return OPENVMS;
}
if (osNameProp.startsWith("Haiku")) {
return HAIKU;
}

// Any other OS
return UNKNOWN_OS_FAMILY;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2026 barebones-commander contributors
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package dev.barebones.commander.commons.runtime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

/**
* A JUnit test case for {@link OsFamily}.
*/
public class OsFamilyTest {

@Test
public void parsesSupportedFamilies() {
assertEquals(OsFamily.MAC_OS, OsFamily.parseSystemProperty("Mac OS X"));
assertEquals(OsFamily.LINUX, OsFamily.parseSystemProperty("Linux"));
}

@Test
public void unsupportedFamiliesAreUnknown() {
assertEquals(OsFamily.UNKNOWN_OS_FAMILY, OsFamily.parseSystemProperty("Windows 11"));
assertEquals(OsFamily.UNKNOWN_OS_FAMILY, OsFamily.parseSystemProperty("SunOS"));
assertEquals(OsFamily.UNKNOWN_OS_FAMILY, OsFamily.parseSystemProperty("OS/2"));
assertEquals(OsFamily.UNKNOWN_OS_FAMILY, OsFamily.parseSystemProperty("OpenVMS"));
}

@Test
public void onlySupportedFamiliesAreUnixBased() {
assertTrue(OsFamily.MAC_OS.isUnixBased());
assertTrue(OsFamily.LINUX.isUnixBased());
assertFalse(OsFamily.UNKNOWN_OS_FAMILY.isUnixBased());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ public void stopAll() {
if (ShutdownHook.performShutdownTasks() && shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
} catch (IllegalStateException ignored) {
// VM is already shutting down.
} catch (IllegalStateException e) {
LOGGER.debug("Shutdown hook could not be removed because the VM is already shutting down", e);
}
}
System.exit(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,8 @@ private void run() {
if (MuConfigurations.getPreferences()
.getVariable(MuPreference.CHECK_FOR_UPDATE, MuPreferences.DEFAULT_CHECK_FOR_UPDATE)) {
CompletableFuture.runAsync(() -> {
new CheckVersionDialog(WindowManager.getCurrentMainFrame(), false);
SwingUtilities.invokeLater(() ->
new CheckVersionDialog(WindowManager.getCurrentMainFrame(), false));
}, CompletableFuture.delayedExecutor(10L, TimeUnit.SECONDS));
}

Expand Down
Loading