This document describes the architecture present in repository commit ab8a3e827ed7f58eebf3c95bc5c88141f12a69a9 on branch main (reviewed 2026-08-09). It is an implementation guide, not a target-state redesign. Statements are based on the solution graph, headers, entry points, core implementation files, plug-in SDK, build workflows, installer, and executable-level UI tests.
The primary system is Open Salamander, a two-panel Windows file manager. It is written as a native Win32 C/C++ application without MFC, Qt, or another UI framework. The code uses a substantial internal framework, a versioned in-process plug-in SDK, worker threads, helper executables, shell extensions, registry-backed configuration, and resource DLLs.
The most accurate high-level classification is a Win32 modular monolith with a microkernel-style plug-in architecture:
salamand.exeowns the main message loop, two file panels, command routing, configuration, core disk operations, caches, and extension orchestration.- In-process
.splplug-ins add archive, viewer, menu, thumbnail, and virtual-filesystem capabilities through abstract SDK interfaces insrc/plugins/shared. - Auxiliary processes and DLLs isolate specific operating-system integration concerns: crash capture, process launching, shell integration, tracing, translation, and self-extraction.
- Modules are grouped into cohesive translation-unit families, but they share large objects and global runtime state. The apparent UI/business/service/data layers are organizational rather than strict dependency boundaries.
- Control flow is message-driven. The UI thread owns most window and panel state; worker and cache threads synchronize through Win32 events, critical sections, and
WM_USER_*messages. - File operations use a two-stage plan then execute pipeline: panel code builds a
COperationsscript of opcodes, and a worker interprets that script while a progress-dialog thread coordinates the user interaction.
- Main solution:
src/vcxproj/salamand.sln. - Main executable project:
src/vcxproj/salamand.vcxproj. - Solution scale: 93 Visual C++ projects; roughly 958
.cpp, 1,134.h, and 212.cfiles in the repository. - Shipped architectures: Win32 and x64 are represented in property sheets and the solution; the current installer workflow produces x64.
- Local projects and CI use the VS 2026
v145toolset. - UI test project:
tests/FileManager.UiTests, targeting .NET 10 Windows with NUnit and FlaUI/UIA3. - Configuration store: primarily versioned keys under
HKEY_CURRENT_USER, with optionalconfig.regimport from the executable directory or roaming AppData. - Localization: the host and plug-ins load
.slglanguage resource modules; resources and command identifiers are central to UI composition and automation.
flowchart TB
User["User / Windows shell"] --> Main["salamand.exe\nmain window and message loop"]
Main --> Panels["Two CFilesWindow panels"]
Main --> Commands["Menus, toolbars, command line, drag/drop"]
Panels --> Disk["Win32 filesystem and shell APIs"]
Panels --> Archive["Archive adapters"]
Panels --> VFS["Plug-in virtual filesystems"]
Commands --> Planner["COperations script builder"]
Planner --> Progress["Progress dialog thread"]
Progress --> Worker["Operation worker"]
Worker --> Disk
Main --> Plugins["CPlugins registry and encapsulation layer"]
Plugins --> SPL["In-process .spl plug-ins"]
SPL --> Archive
SPL --> VFS
SPL --> Viewers["Viewer, menu, thumbnail capabilities"]
Main --> Config["Registry configuration and .reg import"]
Main <--> Salmon["salmon.exe crash reporter\nshared memory and events"]
Main <--> ShellExt["salextx86/x64.dll shell extensions"]
Main --> Helpers["salopen.exe / salspawn.exe"]
Main -. debug trace .-> TServer["tserver.exe"]
| Boundary | Primary types/files | Responsibility |
|---|---|---|
| Process bootstrap | src/app_entry.cpp, src/app_globals.cpp, src/salmoncl.cpp |
Crash reporter startup, OLE/WinLib initialization, language and configuration discovery, subsystem startup, main loop, teardown |
| UI shell | src/mainwnd.h, src/mainwnd_*.cpp |
Top-level window, active-panel ownership, layout, menus/toolbars, command routing, idle work, shutdown |
| Panel model/controller | src/fileswnd.h, src/fileswindow_*.cpp |
Disk/archive/plug-in-FS navigation, listings, selection, sorting, filtering, item actions, refresh |
| Panel view/input | src/filesbox.h, src/filesbox_*.cpp |
Owner-drawn virtual list, columns, keyboard/mouse interaction |
| Operation planning/execution | src/worker.h, src/fileswindow_operations.cpp, src/fileswindow_delete.cpp, src/operations_core.cpp, src/async_copy.cpp |
Build opcode scripts, queue operations, execute filesystem changes, progress and cancellation |
| Plug-in host | src/plugins.h, src/plugins_*.cpp |
Discovery, version negotiation, loading, interface encapsulation, menus, archive and filesystem adapters |
| Plug-in contract | src/plugins/shared/spl_*.h |
ABI-stable abstract interfaces exposed between host and .spl modules |
| Archive service surface | src/zip.h, src/zip_*.cpp |
Host services and directory structures supplied to archiver plug-ins |
| Common native framework | src/common |
Window/dialog base classes, arrays, strings, handles, memory, trace, messages, regex, graphics helpers |
| Persistent settings | src/cfgdlg.h, src/mainwnd_config.cpp, src/reglib, plug-in LoadConfiguration/SaveConfiguration |
Versioned host and plug-in settings, import, migration, debounced save |
| Background services | src/cache.cpp, src/snooper.cpp, src/iconpool.*, viewer/find queues |
Temporary-file cache, directory monitoring, icon/thumbnail work, modeless window tracking |
| Auxiliary binaries | src/salmon, src/salopen, src/salspawn, src/shellext, src/tserver, src/translator, src/sfx7zip |
Crash handling, process/shell integration, diagnostics, localization tooling, self-extraction |
src/vcxproj/salamand.sln is the authoritative Visual Studio solution. It assembles the main executable, language resources, plug-ins, helper processes, shell extensions, third-party wrappers, and installer-support projects. This is a source-level modular monolith rather than one C++ project per architectural layer: most application subsystems compile into salamand.exe, while plug-ins and special OS integrations have their own binaries.
The common MSBuild policy is defined in src/vcxproj/sal_base.props:
- C++ compilation uses precompiled headers, parallel compilation (
/MP), and the source/common/dependency include trees. WINVERand_WIN32_WINNTare set to Windows 7 (0x0601) for compatibility.- Debug builds enable tracing and handle tracking; Release builds enable optimization and link-time code generation.
- Win32 and x64 builds are separated by platform-specific property sheets and output directories.
- The checked-in projects and CI use toolset
v145, keeping the effective toolset environment-independent.
The pull-request workflow compiles Debug Win32 and x64 configurations and treats warnings as errors. The release workflow builds x64 Release, stages the installer tree, and publishes artifacts. FTPS uses Windows SChannel rather than a bundled TLS runtime, so the installer does not download or ship OpenSSL DLLs. Installer scripts collect the executable, helper processes, shell extensions, language modules, toolbar assets, conversion tables, and plug-ins. Consequently, successful compilation of salamand.exe alone does not prove a distributable installation is complete.
| Artifact | Runtime role | Communication boundary |
|---|---|---|
salamand.exe |
Main UI, panels, command dispatch, configuration, file-operation planning and execution | Win32 messages, shared process state, worker events |
*.spl |
Archive, virtual filesystem, viewer, thumbnail, menu, and tool plug-ins | Loaded in process with LoadLibrary; C++ abstract-interface ABI |
salmon.exe |
Crash capture, minidump/report generation, later report UI/upload | Named shared memory and synchronization events |
salopen.exe |
Isolated association/open helper | Command line and host-created shared state |
salspawn.exe |
Console/external command launcher and exit-status handling | Command line, inherited process handles, exit codes |
salextx86.dll, salextx64.dll |
Explorer context-menu/copy-hook integration | COM plus shared memory/events back to the application |
tserver.exe |
Debug trace collection and viewing | Named pipe and multi-process trace protocol |
translator.exe |
Language-resource authoring/validation tool | Files on disk; not part of normal application runtime |
sfx7zip |
Self-extracting installer support | Packaged executable boundary |
The helper processes are architectural isolation points. Plug-ins, by contrast, are not isolated: they execute in the main process and can affect its address space, UI thread, and stability.
| Path | Contents |
|---|---|
src/ |
Main executable implementation; large subsystems are split across related translation units |
src/common/ |
Native window/dialog framework, containers, memory, strings, handle wrappers, tracing, graphics, regular expressions |
src/plugins/shared/ |
Public plug-in SDK and shared data structures |
src/plugins/<name>/ |
Individual plug-in implementations and resources |
src/reglib/ |
Registry/configuration support |
src/salmon/, src/salopen/, src/salspawn/ |
Helper executables |
src/shellext/ |
32-bit and 64-bit Explorer integrations |
src/tserver/, src/translator/, src/sfx7zip/ |
Diagnostics, localization, and packaging tools |
src/vcxproj/ |
Solution, project files, and shared MSBuild properties |
tests/FileManager.UiTests/ |
NUnit/FlaUI executable-level UI regression suite |
.github/workflows/ |
Pull-request and release automation |
The executable deliberately installs crash support before the normal C runtime entry. /ENTRY:MyEntryPoint is declared in src/app_entry.cpp:50; MyEntryPoint invokes SalmonInit() and then transfers control to WinMainCRTStartup (src/app_entry.cpp:92). WinMain wraps WinMainBody in structured exception handling and terminates the process if the protected body escapes unexpectedly (src/app_entry.cpp:4185).
WinMainBody (src/app_entry.cpp:2967) is the composition root. Its order matters:
- Establish process error mode, thread bookkeeping, globals, OLE, dynamically used system APIs, OS characteristics, and integrity level.
- Initialize the internal WinLib framework and the cross-process configuration mutex.
- Discover, validate, and load the selected
.slglanguage module asHLanguage. - Parse command-line options and optional
config.regimport. - Locate/migrate registry configuration and enforce the configured single-instance policy.
- Initialize shell/common controls and application services: packs, resources, check/find services, menus, disk cache, graphics, viewer support, worker support, icon pool, shell integration, overlays, and associations.
- Construct
CMainWindow, which creates the panels and top-level controls duringWM_CREATE. - Load configuration, restore UI state, populate and refresh panels, and apply startup paths/options.
- Discover/load plug-ins through
CPlugins::LoadAll(src/plugins_interface.cpp:3056). - Register the process with the task-list/single-instance service and enter the message loop.
This is an explicit initialization graph rather than dependency-injection composition. Global objects are constructed statically, then activated in a carefully sequenced startup procedure.
The loop in src/app_entry.cpp:3818 is a conventional Win32 dispatcher with application-specific routing:
GetMessagesupplies input and posted work.- Menu tracking and modeless-dialog routing can consume a message before normal dispatch.
TranslateAccelerator,TranslateMessage, andDispatchMessageprovide keyboard and window-message dispatch.CMainWindow::OnEnterIdleperforms deferred work when the foreground queue becomes idle.- Commands forwarded by another application instance, delayed plug-in commands, and deferred plug-in unloads are drained at controlled points in the UI loop.
The message queue is therefore both the UI transport and a coordination mechanism between background work, plug-ins, modeless windows, and the main shell.
sequenceDiagram
participant OS as Windows loader
participant Entry as MyEntryPoint / WinMainBody
participant Salmon as salmon.exe
participant Config as Registry and language modules
participant Main as CMainWindow
participant Plugins as CPlugins
OS->>Entry: Start process
Entry->>Salmon: Initialize crash channel
Entry->>Entry: Initialize OLE, WinLib, shell, workers
Entry->>Config: Select language and load/migrate settings
Entry->>Main: Create top-level window and two panels
Main->>Config: Restore layout, histories, paths
Entry->>Plugins: Discover and LoadAll
Entry->>Main: Run message and idle loop
Main-->>Entry: Close/shutdown accepted
Entry->>Plugins: Unload/release
Entry->>Entry: Stop queues, threads, cache, WinLib, OLE
Shutdown begins in CMainWindow::HandleShutdown (src/mainwnd_shutdown.cpp:96). It coordinates confirmation, persistence, plug-in and child-window closure, and Windows session-ending rules. Final teardown in src/app_entry.cpp:4119 waits for dependent windows and stops the safe-wait, viewer, icon, operation, find, check, graphics, and shell subsystems before releasing language resources and OLE.
The dominant lifecycle convention is paired Initialize/Release or Create/Destroy calls. Reverse-order teardown is essential because many services hold raw pointers, HWNDs, thread handles, or callbacks into services initialized earlier.
The internal framework in src/common/winlib.h wraps Win32 without hiding the HWND/message model:
CWindowsObjectprovides the common object identity and lifetime base.CWindowrepresents an attached or newly created HWND.CDialogadds modal/modeless dialog creation and data transfer.CWindowsManagermaps HWND values to C++ objects.CWindowQueuetracks sets of modeless windows so owners can enumerate or close them safely.
CWindow::Create and AttachToWindow register objects with the manager. The shared window procedure resolves the object, attaches it during creation, and dispatches to the virtual WindowProc (src/common/winlib.cpp:242). On WM_DESTROY, it detaches and may delete the wrapper according to its ObjectOrigin. This is a thin object façade around native message dispatch, not a retained-mode UI framework.
CDialog uses two overridable hooks:
Validate(CTransferInfo&)verifies user input.Transfer(CTransferInfo&)moves values between dialog controls and application state.
That protocol is used by configuration and operation dialogs and is a concrete Template Method pattern.
CMainWindow (src/mainwnd.h:373) owns the shell-level state:
LeftPanel,RightPanel, and the currently active panel;- menu bars, rebar/toolbars, drive bars, directory lines, status lines, and command line;
- histories, associations, viewer masks, and configuration-dialog state;
- detached plug-in filesystems and modeless viewer/find window queues;
- queued change notifications and deferred refresh/configuration flags;
- shutdown, activation, taskbar, and single-instance coordination state.
Its implementation is partitioned by concern even though it remains one large class:
| Translation unit | Main concern |
|---|---|
mainwnd_init.cpp |
Construction, window creation, initial layout |
mainwnd_messages.cpp |
WindowProc and operating-system messages |
mainwnd_commands.cpp |
Resource-ID command dispatch and user actions |
mainwnd_config.cpp |
Settings load/save/migration |
mainwnd_panels.cpp |
Active/other panel coordination |
mainwnd_help.cpp |
Help integration |
mainwnd_shutdown.cpp |
Close/session-end lifecycle |
During WM_CREATE, the main window creates the menu/rebar, both CFilesWindow instances, the command line, and toolbars (src/mainwnd_messages.cpp:714). HandleWmCommand (src/mainwnd_commands.cpp:2125) is the central dispatcher for menu, toolbar, accelerator, and control notifications. It translates stable resource/control IDs into method calls on the main window or active panel.
CFilesWindow is the principal panel controller. Its base, CFilesWindowAncestor (src/fileswnd.h:475), carries the current path and source type. CPanelType makes the three operating modes explicit:
ptDisk: a normal filesystem directory;ptZIPArchive: a path inside an archive exposed by an archiver plug-in;ptPluginFS: a virtual filesystem instance supplied by a plug-in.
The derived class adds presentation and interaction state: columns and view templates, list/status controls, sort/filter settings, quick search, focus and selection, drag-and-drop, history, icon/thumbnail caches, refresh flags, and worker synchronization.
Important navigation and refresh methods include:
| Method | Responsibility |
|---|---|
ReadDirectory (fileswindow_navigation.cpp:113) |
Enumerate and classify a disk directory |
ChangeDir (fileswindow_navigation.cpp:2035) |
Full path transition with validation and history behavior |
ChangeDirLite (fileswindow_navigation.cpp:2622) |
Lighter-weight transition for known/special cases |
ChangePathToArchive (fileswindow_execute.cpp:1997) |
Replace the panel source with archive contents |
ChangePathToPluginFS (fileswindow_execute.cpp:2692) |
Attach a plug-in filesystem and open its path |
CommonRefresh (fileswindow_init.cpp:2322) |
Coordinate a panel refresh while preserving UI state |
RefreshDirectory (fileswindow_quicksearch.cpp:2252) |
Re-read current source and reconcile selection/focus |
RefreshListBox (fileswindow_execute.cpp:3522) |
Update the displayed virtual list |
Execute (fileswindow_execute.cpp:54) |
Open a directory/archive/file according to source type |
ViewFile / EditFile (fileswindow_file_actions.cpp:703, :1230) |
Route content to internal, plug-in, associated, or external tools |
CFileListBox and CFileListHeader in src/filesbox.h implement the owner-drawn virtual item view. The control reads panel-owned arrays instead of owning duplicate item objects. It supports Brief, Detailed, Icons, Thumbnails, and Tiles modes, plus column resizing, keyboard/mouse navigation, quick search, selection, drag/drop, and custom drawing.
This model/view separation is pragmatic rather than formal: the list box knows about panel state, while the panel owns the item model, selection semantics, and operations.
CFileData (src/plugins/shared/spl_com.h:203) is the shared file-entry representation used by both the host and plug-ins. It carries the name, extension position, size, attributes, timestamps, and plug-in-specific data. Keeping it in the shared SDK lets archive and filesystem plug-ins populate entries directly, but also makes its layout part of the binary contract.
Panels store directories and files in CFilesArray collections. Disk listings allocate names from CStringArena, reducing many small allocations and allowing the whole listing's strings to be released together. Archive and plug-in listings may attach a CPluginDataInterfaceAbstract implementation to interpret or release per-item data.
CQuadWord is the SDK's portable 64-bit integer abstraction for sizes and counters. It appears throughout progress, archive, and file metadata interfaces.
The codebase predates modern C++ ownership idioms and deliberately uses its own containers:
TDirectArray<T>stores objects inline.TIndirectArray<T>stores and owns pointers.- specialized arrays add domain-specific search, sorting, or cleanup behavior.
- raw
new/delete, Windows allocation APIs, handles, and explicit release calls express most ownership.
The important reading rule is that type, origin flag, and cleanup path jointly define ownership. A pointer in a collection is not necessarily borrowed merely because it is raw. CWindow's ObjectOrigin, plug-in-data release callbacks, disk-cache locks, and TIndirectArray deletion behavior are examples of ownership encoded outside the C++ type system.
Much of the historical core represents paths and text as narrow strings, generally UTF-8-aware application strings, then calls explicit wide-character wrappers at Windows boundaries. Full UTF-16 internal path support is provided through CPathW (in src/common/wide_path.h) and wide procedural utilities (SalPath*W, CommonPrefixLengthW, CutDirectoryW, SlashesToBackslashesAndRemoveDupsW), allowing paths to be represented and manipulated directly in native UTF-16 without MAX_PATH truncation limits while handling extended-length syntax (\\?\). Language text is stored in resource-only .slg modules and accessed through the selected HLanguage. Code that crosses among application text, Windows UTF-16, archive formats, and remote filesystem encodings must preserve that conversion boundary.
Subsystem managers such as Plugins, Configuration, DiskCache, WindowsManager, TaskList, and MainWindow are globally reachable. They form an implicit service locator initialized by WinMainBody. This makes call sites concise and suits the message-oriented architecture, but dependencies and valid-use windows are established by convention and startup order rather than constructors.
The navigation module turns a path plus source kind into a sorted, filtered panel listing. For disk panels it uses Win32 filesystem enumeration. For archives it navigates a plug-in-provided archive directory tree. For virtual filesystems it calls the mounted CPluginFSInterfaceAbstract. All three converge on the same file-list control and common focus/selection/status behavior.
Panel-to-panel methods on CMainWindow supply operations with the active source and target. This twin-panel contract is foundational: many commands interpret one panel as the selected source and the other as the default destination.
src/worker.h defines an operation instruction set. COperation describes one action—copy/move/delete a file, create/move/delete a directory, change attributes, count size, convert content, or preserve directory time. COperations is both the instruction list and the shared job state: totals, target capabilities, source/target paths, network characteristics, speed meters, errors, and synchronization.
The high-level panel code plans work before mutating the filesystem:
- A panel action validates selection, source kind, target path, and plug-in capabilities.
BuildScriptMainorBuildScriptMain2(src/fileswindow_operations.cpp:1123,:514) walks the selection and emitsCOperationrecords.StartProgressDialog(src/dialogs_file_ops.cpp:350) creates the operation UI on a dedicated dialog thread.CProgressDialog::StartWorkerstarts the worker (src/operations_core.cpp:1366).ThreadWorkerBody(src/operations_core.cpp:921) interprets opcodes and invokesDoCopyFile,DoMoveFile,DoCreateDir,DoDeleteFile, attribute/conversion, or count-size handlers.- Completion updates panels and posts path-change notifications.
src/async_copy.cpp contains the lower-level copy engine: synchronous/overlapped decisions, buffer sizing, alternate data streams, compression/encryption/security metadata, overwrite and retry policy, cancellation, and progress updates. A native copy reaches its durable commit point only after the output was opened with write-through where supported, FlushFileBuffers and CloseHandle both succeed, and the closed destination can be reopened with its file metadata and size verified. An overwrite then uses the write-through replace/rename commit; a cross-volume move may delete its source only after this boundary. Each COperations instance owns an interlocked lifecycle (planned, running, cancel-requested, stopping, completed, or failed) and a manual-reset cancellation event. The dialog and worker make idempotent requests and state transitions through this owner; debug builds stop on invalid transitions. COperationsQueue tracks disk operation windows and their paused/running state.
The operation planner treats every directory reparse point as a hard operation boundary. Copy, move, count, convert, and recursive attribute work do not enumerate a junction, symbolic link, mount point, cloud directory, or unknown directory tag. This prevents a target outside the selected root, a changed target, and an in-tree cycle from becoming part of the script. Reparse files are likewise not opened while planning non-delete work, so a cloud placeholder remains offline rather than being hydrated by an incidental metadata or size read.
Delete uses a separate handle-first path with FILE_FLAG_OPEN_REPARSE_POINT and the captured file identity. Directory deletion accepts only the mount-point and symbolic-link tags (a junction is a mount-point tag); an unknown tag fails with ERROR_REPARSE_TAG_MISMATCH instead of deleting or resolving its destination. The current conservative copy/move policy skips reparse entries rather than recreating them: preserving a link is a future explicit feature, not a reason to follow its target.
The UI integration suite builds disposable junction topologies with a target outside the selected root, a changed target, and a cycle. It verifies that copying preserves ordinary in-root content without materializing either target, and that deleting a junction deletes only the link. The native regression test also ratchets the no-hydration and unknown-tag rules; provider-owned cloud placeholders and volume mount points require an appropriate host/provider for live end-to-end setup.
The engine distinguishes three preservation levels. Required metadata must pass the operation's durable-copy/identity checks before a cross-volume move can delete its source. Best effort metadata is copied or repaired when the target and current privilege allow it; a verified failure is recorded. Unsupported metadata is not represented by the target or is not copied by this engine; it is recorded as a known loss. The contract applies to native disk copy and move operations only; archive and virtual-filesystem plug-ins retain their own capability contracts.
| Operation and target | Required | Best effort | Unsupported |
|---|---|---|---|
| Copy to NTFS | Default data stream | Last-write time; displayed attributes when requested; owner/group/DACL when requested and permitted; ADS; NTFS compression/EFS | Creation and last-access times |
| Copy to ReFS | Default data stream | Last-write time; displayed attributes when requested; owner/group/DACL when requested and permitted; ADS | Creation and last-access times; NTFS compression/EFS |
| Copy to FAT/FAT32/exFAT | Default data stream | Last-write time and the supported attribute subset | Creation and last-access times; owner/group/DACL; ADS; NTFS compression/EFS |
| Copy to SMB | Default data stream | Last-write time, attributes, owner/group/DACL, ADS, and compression/EFS subject to server/share policy | Creation and last-access times |
| Same-volume move on NTFS/ReFS/FAT | The same filesystem object is renamed, so its data and supported metadata remain attached to that object | None | None introduced by the move |
| Same-volume move on SMB | Default data stream | All other metadata, subject to server/share rename semantics | None introduced by the engine |
| Cross-volume move | The target must pass the copy durable-commit check before source deletion | The target's corresponding copy row | The target's corresponding copy row |
For a cross-volume move, the worker records planned losses accepted while building the script (for example ADS or ACL loss), plus actual ADS, timestamp, attribute, ACL, and compression/EFS losses. Immediately before every source-file or source-directory deletion it displays any unacknowledged losses. The default No retains the source and lets the remaining move continue as a copy; only an explicit Yes allows source deletion. The account is held in CProgressDlgData::MetadataLosses, so a directory cleanup path cannot bypass a loss already found while copying a child.
Security preservation is best effort on NTFS, ReFS, and SMB, and unsupported on FAT-family targets. The copy engine reads the complete owner/group/DACL descriptor before changing the target. It preserves the DACL protection bit (and therefore whether inheritance is enabled) and compares every explicit ACE after the write; this includes explicit deny ACEs. Inherited ACEs may differ when the target has a different parent, but they must remain inherited rather than becoming new explicit permissions.
| Source/target descriptor and token capability | Owner and group | DACL, inheritance, and explicit allow/deny ACEs | Result under the metadata contract |
|---|---|---|---|
Descriptor readable; SeRestorePrivilege enabled |
Apply and then verify | Apply and then verify | Success only after all components verify; otherwise restore the target snapshot and report a security metadata loss. |
| Descriptor readable; no restore privilege; target owner/group already match | Leave unchanged | Apply and then verify | Success only after DACL protection and explicit ACEs verify; a failure restores the prior DACL and reports a security metadata loss. |
| Descriptor readable; no restore privilege; owner or group differs | Do not change | Do not change | Do not take temporary ownership or install a permissive DACL. Report a security metadata loss and use the normal copy-permissions warning. |
| Source or target descriptor inaccessible | Do not change | Do not change | Report a security metadata loss; the normal warning can cancel, retry, or continue the copy without claiming ACL preservation. |
| FAT/FAT32/exFAT target | Unsupported | Unsupported | Record the known loss during planning; a cross-volume move requires the explicit metadata-loss confirmation before source deletion. |
An ACL write is never considered successful merely because SetNamedSecurityInfoW returns success. The post-write comparison prevents a partial component update from being represented as preserved. If restoration itself fails, the operation reports that error instead of silently treating the descriptor as copied.
sequenceDiagram
actor User
participant Cmd as Main-window command dispatcher
participant Panel as CFilesWindow
participant Plan as COperations script
participant Dialog as CProgressDialog thread
participant Worker as ThreadWorkerBody
participant FS as Filesystem or plug-in
User->>Cmd: Copy, move, or delete
Cmd->>Panel: FilesAction / operation method
Panel->>Panel: Validate selection, target, capabilities
Panel->>Plan: BuildScriptMain(2)
Panel->>Dialog: StartProgressDialog
Dialog->>Worker: StartWorker
loop Each COperation
Worker->>FS: Execute opcode
Worker-->>Dialog: Progress, question, or error message
Dialog-->>Worker: Continue, retry, skip, pause, or cancel
end
Worker-->>Panel: Completion and change notifications
Panel->>Panel: Refresh affected paths
The recycle-bin branch is deliberately different: supported disk deletions can call SHFileOperationW with FO_DELETE; permanent or unsupported deletions use the native operation script.
CPlugins is the registry of known plug-ins. CPluginData represents one module and its configuration, menu, icon, capability, and load state. Encapsulation classes mediate calls so the host can attach plug-in identity, validate state, normalize error handling, and guard unload behavior.
Loading (CPluginData::InitDLL, src/plugins_loading.cpp:2142) follows this protocol:
- Resolve the module path and load the DLL.
- Obtain
SalamanderPluginEntry,SalamanderPluginGetReqVer, and optional SDK-version exports. - Reject incompatible required versions (the current SDK constant is version 103).
- Create the host-side
CSalamanderPluginEntrygateway and call the plug-in entry point. - Obtain
CPluginInterfaceAbstract, query capability subinterfaces, load configuration, and invokeConnect. - Register menus, file masks, archive formats, viewer associations, filesystem names, thumbnails, and icons through
CSalamanderConnectAbstract.
CPluginInterfaceAbstract (src/plugins/shared/spl_base.h:469) is the root contract. Capability flags and getters expose optional archive, viewer, menu-extension, filesystem, and thumbnail-loader interfaces. Common callbacks cover configuration, lifecycle events, history clearing, password-manager events, and path changes.
The two largest capability families are:
- Archive (
spl_arc.h): list an archive; unpack one, selected, or all items; pack; delete entries; approve archive closure; state cache policy. - Filesystem (
spl_fs.h): open/close a filesystem; navigate/list paths; expose supported services; view, rename, delete, copy, move, create directories, change attributes, show properties/context menus, search, and convert external/internal path forms.
Plug-ins use host gateway interfaces instead of directly reaching every internal object. These supply general services, GUI helpers, safe file operations, language loading, viewer integration, and filesystem-name registration. The SDK is still a C++ ABI: compiler/runtime compatibility, object lifetime, virtual-table layout, and exception behavior are part of the practical contract.
Representative plug-in families in the solution are:
- archive/compression: 7-Zip, ZIP, TAR, PAK, ARJ, CAB, CHM, FAT, ISO, LHA, MIME, OLE, and RAR readers/writers;
- virtual filesystems: FTP, Network Neighborhood, Folders, Registry Editor, portable devices, and Windows Mobile;
- viewers/analyzers: database, browser, multimedia, PE, image, disk-map, and file-comparison tools;
- utilities: automation, checksum, version check, renamer, and split/combine;
- SDK demonstrations: archive/menu/viewer/sample plug-ins.
The host retains the user-facing archive state while an archiver plug-in supplies format logic. src/zip.h and related files define host-side archive directory structures, selection enumeration, extraction targets, cache integration, and progress services. The archive plug-in returns a listing and implements mutations; the panel presents that tree using normal list controls.
Opening a file inside an archive illustrates the boundary: the plug-in unpacks the item through UnpackOneFile, the host assigns a temporary path in CDiskCache, and the viewer/editor/association layer opens the materialized disk file. Cache ownership records which plug-in must participate in cleanup.
CFilesWindow::Execute first classifies the focused item. Directories change panel path; archive files may enter archive mode; other files follow associations or execution templates. ViewFile resolves configured viewer masks and Plugins.FindViewEdit to choose among the internal viewer, a plug-in viewer, an external viewer command, or a Windows association. EditFile similarly resolves the configured editor.
For archive and virtual-filesystem sources, content is normally materialized into CDiskCache. Cache locks keep it alive while a viewer/editor is active, and change tracking can upload or copy modified content back when the source capability supports it.
CConfiguration (src/cfgdlg.h:176) is the central settings aggregate. CMainWindow::LoadConfig and SaveConfig (src/mainwnd_config.cpp) serialize application state to the registry, apply defaults, and migrate older ConfigVersion layouts. Plug-ins receive private registry subkeys and use their own LoadConfiguration/SaveConfiguration callbacks.
Persistence has several coordination mechanisms:
CLoadSaveToRegistryMutexserializes configuration work across processes.CRegistryWorkerThreadperforms registry work while the UI side continues pumping messages.ScheduleConfigSavedebounces ordinary changes by 250 ms.SaveConfigprevents reentrant saves and remembers when another save is requested during the current one.SaveConfigwrites the full snapshot to the inactive one of twoConfiguration Generations, verifies a checksum and completion marker, flushes it, then switches the root'sActive GenerationDWORD. That one value write is the commit point; the previous verified generation remains available until the next successful startup.- Startup validates the selected generation before exposing it to the existing host and plug-in readers, and falls back to the other verified generation if the selected one is incomplete or has a checksum mismatch. Legacy direct trees remain readable and are migrated on their next save.
- Each transactional generation has a schema version. Startup runs the idempotent metadata migration, then validates required sections, supported configuration/schema versions, value ranges, and cross-field invariants before
LoadConfigapplies the profile. A rejected active generation falls back to the other verified profile; if neither profile is valid, the application uses defaults and reports the recovery to the user. - The legacy
Save In Progressand backupCopy Is OKmarkers remain only for recognising/recovering pre-transactional configuration trees. - a
config.regbeside the application can seed/import configuration.
Configuration is therefore a versioned state snapshot, not a collection of independent repositories. Changes to the aggregate, UI transfer code, defaults, migration, and persistence keys must remain synchronized.
Disk panels use directory-snooper threads based on FindFirstChangeNotification. Shell notifications and explicit operation notifications also enter the application. CMainWindow::PostChangeOnPathNotification queues/coalesces changes and fans them out to both disk panels and interested plug-in filesystems.
BeginStopRefresh/EndStopRefresh form a nesting protocol that suspends disruptive refreshes during compound operations. Deferred changes are processed after the outermost scope ends. Debug builds retain call-stack information to detect mismatched pairs. Refresh tries to preserve focus, selection, scroll position, and quick-search state while reconciling the new listing.
| Service | Role and coordination |
|---|---|
CDiskCache (src/cache.cpp) |
Allocates temporary materialized files, tracks locks and plug-in cleanup ownership, removes stale data at shutdown |
Directory snoopers (src/snooper.cpp) |
Wait for filesystem change events and post refresh work to the UI |
CIconThreadPool / panel icon thread |
Extract icons and thumbnails away from direct paint handling; posts results back to panels |
| Find/viewer queues | Track modeless windows and make owner shutdown deterministic |
TaskList control thread |
Implements single-instance forwarding and shell copy/paste integration; protects command parameters with a critical section |
| Registry worker | Proxies potentially blocking registry operations while UI messages remain serviced |
| Progress-dialog and operation workers | Execute long file actions and marshal questions/progress/cancellation through messages and events |
Salmon client |
Signals a separate crash process and waits for dump/report handoff |
- Bootstrap loads the selected language and locates the registry configuration.
CMainWindowand both panels are created before saved panel state is applied.LoadConfigrestores layout, view modes, histories, filters, and left/right paths.- Each panel reads its source and populates
CFilesArraycollections. - Command-line paths and switches override applicable restored state.
- Plug-ins load; archive and virtual-filesystem registrations become available.
- Panels are shown/refreshed and the message loop begins.
- Keyboard, mouse, history, drive bar, or command line requests a path.
ChangeDirnormalizes and validates it, handling unavailable media and error UI.ReadDirectoryenumerates entries into directory/file arrays and string storage.- Filtering and sort order are applied; parent entry and special items are synthesized where required.
RefreshListBoxupdates the virtual view and status line.- History, focus, selection, icon work, snooper registration, and dependent toolbars are updated.
Executerecognizes an archive through registered masks/format handlers.- The selected plug-in lists the archive into a host archive tree.
ChangePathToArchiveswitches panel state from disk to archive and renders the relevant directory.- Executing a contained file asks the plug-in to unpack it to a disk-cache path.
- Viewer/editor/association dispatch opens the cached file.
- Cache locks and viewer lifecycle decide when the temporary file can be removed or propagated back.
- A drive-bar item, path, menu command, or plug-in action selects a registered filesystem name.
- The host calls the plug-in's
OpenFSand wraps the resulting interface in host encapsulation. ChangePathToPluginFSasks the filesystem to normalize/change path and list entries.- The panel renders returned
CFileDataitems and consults supported-service flags to enable commands. - File actions delegate to the filesystem when supported; operations requiring a local file use cache/materialization helpers.
- Leaving the filesystem either closes it or places it in the detached-filesystem collection when the contract permits reuse.
- The active panel supplies selected source items; the opposite panel supplies the default target.
- The command determines the source/target combination: disk, archive, or plug-in filesystem.
- Capability and path checks select native scripting or the relevant plug-in transfer method.
- Native transfers build a
COperationsscript and start progress/worker threads. - The worker processes items, marshaling overwrite, retry, skip, pause, cancellation, and error decisions to the progress UI.
- Speed and byte/item totals are protected by
StatusCSand displayed by the progress dialog. - A native copy reports success only after its durable commit point; affected paths then receive notifications and visible panels refresh.
- A configuration dialog transfers current values into controls.
- On acceptance,
Validatechecks input andTransferwrites the new values toCConfigurationor a feature-owned object. - The affected subsystem is updated immediately where appropriate.
ScheduleConfigSavecoalesces repeated mutations.- The registry worker obtains the cross-process mutex and writes the versioned host snapshot plus plug-in configuration into the inactive generation.
- The host computes and validates the staged generation's checksum, writes its completion marker, flushes it, and atomically changes
Active Generation. An interrupted stage never becomes visible; the prior generation is retained until a successful startup.
- Startup parses paths/options before deciding whether this process should remain alive.
CheckOnlyOneInstancelocates the existing instance through the task-list/shared-state mechanism.- Parsed command parameters are copied into synchronized shared/control-thread state.
- The running instance is notified and activates itself as appropriate.
- Its UI thread consumes the forwarded request during an idle-safe point and calls
ApplyCommandLineParams. - The new process exits without constructing a second full application instance.
MyEntryPointestablishes the Salmon channel before entering the CRT/application body.- The main process and
salmon.exeshare process metadata, language selection, mappings, and events. - A fatal failure signals the crash event; the external process can inspect the failed process even when its UI/runtime state is compromised.
- Salmon produces a minidump/report and later presents or uploads it according to configuration.
- The protected
WinMainboundary terminates the damaged process after handoff.
The UI thread owns nearly all HWND manipulation, panel state transitions, menus, and plug-in commands. Background threads perform waits, enumeration-adjacent work, registry I/O, icons/thumbnails, crash monitoring, and file operations. Communication is primarily by posted/sent Windows messages, Win32 events, queues, and critical sections.
Important concurrency invariants are:
- An HWND and its wrapper may disappear after a posted message; queues and shutdown flags must validate lifetime.
- Panel listing arrays must not be replaced while paint/input/icon work still references them.
- Worker threads do not directly run arbitrary UI logic; questions and progress are marshaled to a dialog/window.
COperations::StatusCSguards mutable progress measurements shared with the dialog.- Task-list command parameters are protected until the UI thread consumes them.
- Refresh suspension is reference-counted/nested; each begin must have a matching end.
- Plug-in unload is deferred until active callbacks, windows, filesystems, and plug-in-owned data are no longer in use.
- Shutdown stops producers before releasing the global objects and handles they use.
New or migrated shared CRITICAL_SECTION acquisitions use the ranked CScopedCriticalSection form and must progress from a lower rank to a higher rank. Re-entering the same critical section is permitted; acquiring a different critical section at the same or lower rank is not. Do not hold a ranked lock across a cross-thread SendMessage, modal UI, or an operation that can synchronously call back into the UI.
| Rank | Lock family | Examples and rule |
|---|---|---|
| 10 | Process lifetime | Startup/shutdown state; acquire before every other ranked lock. |
| 20 | Configuration | Registry/configuration state. |
| 30 | Plug-in registry | Plug-in registration and lifetime bookkeeping. |
| 40 | Worker queue | Background queue and dispatcher state. |
| 50 | Operation state | Copy/move/delete and progress state. |
| 60 | UI state | Panel/window state; never use while making a synchronous cross-thread UI call. |
| 70 | External broker | CParserBrokerClient::Lock; it serializes one broker request at a time and is the first migrated ranked lock. |
In Debug builds, LockOrderEnter maintains a per-thread rank stack and asserts if a non-recursive acquisition does not increase the rank. It first attempts the critical section without blocking; after ten seconds of contention it writes the requested lock name/rank, waiter thread ID, current owner, recursion count, and already-held rank to the debugger before preserving the existing blocking acquisition behavior. This deliberately keeps Release locking and legacy unranked call sites compatible while each owner is migrated at its subsystem boundary.
The Nightly Lock Stress workflow runs the repeated UI lifecycle suite on an isolated desktop runner with Application Verifier's Locks layer enabled for salamand.exe. The runner removes the verifier's process-persistent settings in a finally block and uploads AppVerifier logs, so a failed lane cannot affect later jobs on that machine.
The design does not use a single scheduler or futures abstraction. Each subsystem owns its thread/event/message protocol, so callers must learn that protocol before changing lifecycle or error handling.
| Pattern | Where it appears | Consequence |
|---|---|---|
| Modular monolith | Most shell, UI, panel, config, and operation code in salamand.exe |
Simple in-process calls; broad compile/runtime coupling |
| Microkernel / plug-in architecture | CPlugins, .spl modules, SDK interfaces |
Features can be added by capability; ABI and in-process trust must be maintained |
| Capability interfaces | Archive/viewer/menu/FS/thumbnail flags and getters | Host enables commands based on supported services rather than plug-in type names |
| Façade/adapter | Plug-in encapsulation and host gateway classes | Central place for identity, validation, compatibility, and unload checks |
| Message-driven architecture | Window procedures, posted refresh/progress/control messages | Natural Win32 integration; payload and object lifetime are protocol obligations |
| Command dispatcher | WM_COMMAND and CMainWindow::HandleWmCommand |
Menu/toolbar/accelerator IDs converge on one routing surface |
| Strategy/state by source kind | CPanelType, archive and plug-in-FS branches |
One panel UI presents several backing stores; methods must handle all modes |
| Operation script + interpreter | COperations and ThreadWorkerBody |
Planning is separated from execution and progress can be calculated up front |
| Template Method | CWindow::WindowProc, CDialog::Validate/Transfer |
Native lifecycle remains centralized while derived windows customize behavior |
| Observer/event notification | Shell changes, snooper events, path notifications | Multiple panels/filesystems can react to mutations without direct caller knowledge |
| Producer/consumer | Operation, icon, registry, and trace queues | Blocking work leaves the UI thread but requires explicit shutdown/backpressure rules |
| Service locator / global managers | Plugins, Configuration, DiskCache, TaskList, MainWindow |
Convenient access with implicit dependency and initialization contracts |
| Coarse aggregate state | CConfiguration, CFilesWindow, COperations |
Closely related behavior is colocated, but safe changes often cross many fields/methods |
| Resource-based localization | Executable resources and .slg language modules |
UI text is externalized; resource IDs and translated modules are compatibility assets |
| Version negotiation | plug-in required/current SDK versions, config versions | Old data/modules can be rejected or migrated explicitly |
| Explicit lifecycle pairs | init/release, attach/detach, lock/unlock, begin/end refresh | Deterministic native cleanup, with correctness enforced mostly by convention |
| Defensive native diagnostics | TRACE, call-stack messages, handle tracking, last-error propagation |
Debug builds detect protocol violations without changing Release behavior |
There is no strict Clean Architecture layering: panels call Win32, shell, configuration, and plug-in services directly, while global managers are accessible across translation units. The strongest architectural boundaries are executable/DLL boundaries, SDK interfaces, HWND/message protocols, and the operation-script boundary.
Most native APIs return BOOL, handles, or error codes. The code typically captures GetLastError, emits TRACE, shows a localized dialog at a user boundary, and selects retry/skip/cancel or an alternate path. Exceptions are not the general control-flow mechanism; the outer SEH boundary exists for crash capture rather than ordinary recovery.
Built-in diagnostic facilities include:
TRACEandCALL_STACK_MESSAGEinstrumentation;- a fixed, allocation-free release diagnostic ring of sanitized operation transitions, waits, retries, and plug-in DLL leaf names; it is written into the locally viewable crash report and therefore enters an archive only when the user elects to send that report;
- Debug handle wrappers and
HANDLES_ENABLEleak/misuse tracking; - custom allocation-failure handling and heap diagnostics;
tserver.exefor cross-process trace collection;- Salmon minidump/report generation;
- configuration in-progress/backup markers;
- worker progress, cancellation, retry, and error-dialog protocols;
- plug-in SDK/version validation before calling capability methods.
The repository's prioritized stability recommendations are documented separately in refactoring.md; this document describes the current mechanisms and contracts rather than proposing replacements.
The checked-in automated test suite is executable-level rather than native unit-level. tests/FileManager.UiTests uses NUnit and FlaUI UIA3 against a real built salamand.exe. Tests are non-parallel because they manipulate shared desktop and per-user application state.
The suite covers roughly 118 scenarios in eight families, including:
- main-window startup and basic commands;
- accessibility/control discovery;
- configuration cancel versus commit;
- persistence within the running process and after restart;
- FTP bookmark persistence.
- native create/copy/rename/move/delete commands for files and nested directory trees, including cancelled dialogs and expected failure paths.
Stable command/control IDs are the automation seam. FILEMANAGER_UI_ISOLATED=1 is required so test launches use an isolated configuration context, and teardown kills only processes launched by the fixture. These tests validate user-visible integration across the native executable, registry persistence, and UI Automation tree.
There are currently no checked-in native unit-test projects for panel algorithms, operation scripts, plug-in ABI contracts, or worker failure paths. CI build coverage and executable UI scenarios are therefore the principal automated safety net; manual and plug-in-specific testing remain important for crash consistency, storage fault injection, and unusual OS integrations.
- Allocate/reuse the correct resource and accelerator IDs.
- Add routing in
CMainWindow::HandleWmCommandor the appropriate control notification path. - Put source-specific behavior on
CFilesWindowwhen it depends on disk/archive/plug-in-FS state. - Update command enable/check state in the relevant menu/toolbar refresh path.
- Add localized strings/resources to language projects.
- Add an executable-level scenario when a stable UI Automation seam exists.
Avoid assuming only disk panels: commands exposed in the common shell must either work for all CPanelType values or be disabled using capability information.
- Add the field and default to the appropriate configuration/feature owner.
- Add load/save registry logic and increment or extend version migration when necessary.
- Bind the dialog through
CTransferInfo, including validation. - Apply the change to the live subsystem and schedule persistence.
- Check cancel, accept, current-process behavior, and restart persistence.
- Decide whether the key belongs to host configuration or a plug-in-private registry subtree.
- Define which thread owns input, mutable state, callbacks, and completion.
- Use a message/event payload with explicit allocation and cleanup responsibility.
- Marshal HWND/UI changes to the owning UI thread.
- Add cancellation and a shutdown join/termination protocol.
- Prevent callbacks after owner destruction and validate posted-message targets.
- Expose progress/errors without blocking the main message loop.
- Implement the smallest existing SDK interface that represents the feature.
- Advertise the corresponding capability and supported-service flags.
- Register masks, menus, filesystem names, viewers, thumbnails, or icons during
Connect. - Honor host ownership and release callbacks for returned data.
- Keep callbacks on documented threads and avoid retaining host pointers beyond their lifetime.
- If the SDK must change, use version negotiation and preserve binary layout for old modules or explicitly reject them.
- Supply and validate the plug-in's language module.
- Determine whether the change belongs in planning (
BuildScriptMain*) or execution (ThreadWorkerBody/Do*). - Preserve aggregate size/item counts and operation flags used by progress UI.
- Cover cancellation, pause, overwrite, retry, skip, low-space, partial-file, metadata, and cleanup paths.
- Maintain refresh suppression and final path notifications.
- Check disk-to-disk and any affected archive/virtual-filesystem delegation paths.
- Verify source and target semantics for both left/right active-panel orientations.
The following rules are implicit in the current implementation and should be treated as architectural contracts:
WinMainBodyowns subsystem initialization order; do not call a global service before its initializer or after its release.- UI objects and HWNDs are UI-thread-owned unless a subsystem explicitly documents a cross-thread message protocol.
- A panel source is a tagged state. Keep
CPanelType, archive state, and plug-in-FS state synchronized during transitions. - Plug-in interfaces remain valid only while the owning
CPluginDatais loaded and its active references/windows/filesystems are retained. CFileDataand SDK virtual interfaces are ABI surfaces; field or virtual-method changes require compatibility analysis.- File-operation planning owns instruction payloads until transferred to the progress/worker lifecycle; error exits must release partially built scripts.
- Cached files cannot be removed while viewer/editor/plug-in locks exist.
- Every refresh-suspension begin, critical-section entry, event/handle acquisition, object attachment, and window-queue registration needs its matching release path.
- Configuration mutations must account for defaults, migration, dialog transfer, persistence, and restart behavior together.
- Resource and command IDs are shared contracts among code, menus, dialogs, language modules, and UI automation.
| Question | Start here |
|---|---|
| How does the process start or stop? | src/app_entry.cpp, src/mainwnd_shutdown.cpp, src/salmoncl.cpp |
| Where is a menu/shortcut command handled? | src/mainwnd_commands.cpp, resource/accelerator definitions |
| How does a panel navigate or refresh? | src/fileswnd.h, src/fileswindow_navigation.cpp, src/fileswindow_init.cpp |
| How is the file list drawn and controlled? | src/filesbox.h, src/filesbox_*.cpp |
| How is a copy/move/delete performed? | src/fileswindow_operations.cpp, src/fileswindow_delete.cpp, src/worker.h, src/operations_core.cpp, src/async_copy.cpp |
| How is a file viewed or edited? | src/fileswindow_file_actions.cpp, viewer code, src/cache.cpp |
| How is a plug-in loaded? | src/plugins_loading.cpp, src/plugins_interface.cpp, src/plugins/shared/spl_base.h |
| What can archive plug-ins do? | src/plugins/shared/spl_arc.h, src/zip.h |
| What can virtual filesystems do? | src/plugins/shared/spl_fs.h |
| Where are settings stored? | src/cfgdlg.h, src/mainwnd_config.cpp, src/regwork.cpp, src/reglib/ |
| How do filesystem changes reach panels? | src/snooper.cpp, main-window change-notification methods, panel refresh methods |
| How are native windows wrapped? | src/common/winlib.h, src/common/winlib.cpp |
| How is shipping verified? | .github/workflows/, installer scripts, tests/FileManager.UiTests/ |
Open Salamander's architecture is optimized around a responsive native twin-panel shell with long-lived compatibility. Its central design decisions are:
- keep the core close to Win32 and its message/handle model;
- present disk directories, archives, and remote/virtual stores through one panel interaction model;
- move extensible formats and tools behind a versioned plug-in SDK;
- plan multi-file mutations as scripts and execute them with progress/error coordination;
- isolate crash reporting and selected shell/process duties in helper binaries;
- persist a versioned registry snapshot and load UI text from language modules;
- use explicit ownership, lifecycle pairs, and message/event synchronization instead of modern C++ framework abstractions.
For maintainers, the most consequential boundaries are not folder names. They are the plug-in ABI, the UI-thread/HWND boundary, the panel's tagged source state, the operation-plan/worker boundary, the configuration snapshot, and the ordered lifecycle rooted in WinMainBody. Changes that preserve those contracts can remain local even in large classes; changes that cross them require end-to-end validation across startup, UI, workers, plug-ins, persistence, and shutdown.