MiMiNavigator is a dual-panel file manager for macOS, built with Swift 6.2 and SwiftUI. Inspired by Total Commander and Norton Commander.
- NEVER commit/push without explicit user request — Wait for user to explicitly ask
- NEVER add AI signatures in code — No AI attribution comments or markers
- Always run
Scripts/git_cleanup.zshbefore any git commit - Use zsh only — Never bash or default shell for MiMiNavigator work
- Commit Packages/ submodule changes separately (cd into Packages dir first)
- Git commit messages: short English, lowercase, no slangy
- No file over 400 lines — extract to new files
- English comments only — no Russian/German in code
#colorLiteralfor colors — never hardcoded RGB strings- Logging tags:
[Component]format (e.g.[Rename],[Scan],[FileOps],[Selection]) // MARK: - Namedirectly above every class/struct/enum/non-trivial method- No blank lines inside method bodies
nonisolated(unsafe)for Swift 6 NSCache statics and event monitors indeinit
- Builds only on user's Mac via osascript (Control your Mac), never on remote
- Reading, writing, analysis on remote is OK
⌘Rin Xcode orScripts/build_debug.zsh- Before build: run
zsh Scripts/stamp_version.zshto sync version from git tag
Scripts/refreshVersionFile.zsh— main script: writescurr_version.asc+ updatesMARKETING_VERSIONin pbxproj from git tagScripts/stamp_version.zsh— thin wrapper callingrefreshVersionFile.zsh- Version in window title reads from
CFBundleShortVersionString(plist) - DEV BUILD badge reads from
curr_version.asc(date + host)
| Pattern | Usage |
|---|---|
@Observable + @MainActor |
AppState, MultiSelectionManager, TabManager |
@MainActor + @Observable |
ContextMenuCoordinator — singleton, all context menu actions |
actor |
DualDirectoryScanner, ArchiveManager, FindFilesEngine |
AsyncStream |
FindFilesEngine streaming results |
| Swift Package (dynamic) | FavoritesKit, LogKit, NetworkKit |
macOS firmlinks (/tmp ↔ /private/tmp, /var ↔ /private/var, /etc ↔ /private/etc) cause:
URL.resourceValues(forKeys: [.isDirectoryKey])returningisDirectory == falsefor/tmpCustomFile.urlValuestoring/private/tmp/Xwhile file is at/tmp/X- Always use
FileManager.fileExists(atPath:isDirectory:)as fallback for directory checks - Use
resolveSourceURL()pattern for file operations on firmlink paths
- Edit
MiMiNavigator.xcodeproj/project.pbxprojautomatically when adding/removing files - Never ask user to do manual Xcode changes
- Never git push automatically — only commit
- Iakov pushes manually himself
- Commit message style: short English, e.g.
"fix rename: firmlink resolve, panel tracking"
GUI/Sources/
├── App/ # Entry point, AppBuildInfo, AppToolbarContent
├── States/AppState/ # Global state, selection, navigation, refresh
├── Features/
│ ├── Panels/ # File panels, table, rows, ZebraBackgroundFill
│ ├── Tabs/ # Tab system
│ ├── Network/ # SMB/AFP discovery
│ └── ConnectToServer/# SFTP/FTP connectivity
├── ContextMenu/
│ ├── ActionsEnums/ # FileAction, DirectoryAction, etc.
│ ├── Dialogs/ # RenameDialog, HIGAlertDialog, PackDialog, etc.
│ ├── Menus/ # Context menus
│ └── Services/
│ ├── Coordinator/ # FileActionsHandler, DirectoryActionsHandler, ActiveDialog
│ └── FileOperations/ # FileOperationsService + extensions (Delete, Rename, SymLink)
├── Services/
│ ├── Archive/ # VFS, extract, repack
│ └── Scanner/ # DualDirectoryScanner, FSEventsDirectoryWatcher
├── FindFiles/ # Search UI and engine
├── BreadCrumbNav/ # PathAutoCompleteField (NSPanel popup, click-outside dismiss)
├── HotKeys/ # Keyboard shortcuts
└── Settings/ # Preferences UI
Packages/ # git submodule → github.com/senatov/MiMiKits
├── ArchiveKit/
├── FavoritesKit/
├── FileModelKit/ # CustomFile model
├── LogKit/
├── NetworkKit/
└── ScannerKit/
- Create file in appropriate directory
- Edit
project.pbxprojto add file reference and build phase
cd /Users/senat/Develop/MiMiNavigator
zsh Scripts/git_cleanup.zshzsh Scripts/stamp_version.zsh- Console: SwiftyBeaver to stdout
- Sandboxed:
~/Library/Containers/Senatov.MiMiNavigator/Data/Library/Application Support/MiMiNavigator/Logs/MiMiNavigator.log - External:
/private/tmp/MiMiNavigator.log
- Over-Engineering: Adding "defensive" code not requested. Three similar lines > premature abstraction
- Guessing Before Reading: Always read the file before suggesting changes
- Wrong shell: Must use zsh, not bash
- Forgetting Packages/: Submodule changes need separate commit
- Firmlinks: Never trust
URL.resourceValuesfor/tmp,/var,/etc— use FileManager fallback - Scanner race conditions: Always call
scanner.clearCooldown(for:)before explicitrefreshFilesafter file operations - Panel detection: When both panels show same directory,
panelForPathis ambiguous — passpanel: PanelSideexplicitly
- SwiftyBeaver — logging
- Citadel — SSH/SFTP (orlandos-nl/Citadel)
- p7zip — archive formats (
brew install p7zip)