All notable changes to this project will be documented in this file.
- Molten Works redesign — cast-iron stations with chrome pipes, forge-amber / cyan / magenta / lime / purple palette, and embedded Instrument Serif / Oswald / JetBrains Mono typography. 11 hand-drawn forge-themed SVG icons (pit, mold, die, chisel, brand, sieve, rack, mill, crucible, press, loupe). Rebuilt top bar (FLOW·FORGE logo, pipeline path, STN/PIPE stats, IGNITE / QUENCH / DRY RUN buttons), operations library, inspector, and bottom console to match the concept.
- Traveling mercury beads —
MwMercuryDropletcustom control renders four staggered beads riding each pipe's cubic bezier while a pipeline is running. Uses the same midpoint formula as the pipe so the bead tracks the rendered curve by construction. - Custom pipe shape —
MwPipeConnectionreplaces Nodify'sConnectionso the pipe and droplets share the exact same geometry. Five layered strokes for the chrome look (shadow / outer / core / inner channel / liquid). - Running-state visuals — category-colored aura behind each station, pulsing heat glow inside the iron body, pulsing LIVE dot, animated gauge strip (opacity + color change), and mercury inner-dot + glow on active ports.
- Canvas backdrop — radial forge glow, cyan HUD wash, magenta corner brackets, 40×40 / 200×200 grid overlay, animated cyan scanline, 14 staggered ember particles rising from the bottom, system HUD with lat/mem/err stats.
- DEMO button — toggles the running-state visuals on every station and pipe without executing a real pipeline. Disabled while an actual run is in flight.
- Shared pipe / bead geometry helper — new
MwGeometry.IsFinitevalidators are wired into theStyledPropertyregistrations on bothMwPipeConnectionandMwMercuryDroplet, so the two ends of the shared cubic reject the same class of non-finite input at the property boundary.
- Dark-only theme — light theme support removed. Placeholder
LightResourceDictionaryand runtime toggle dropped;App.axamllocksRequestedThemeVariant="Dark". Toolbar theme-toggle button,ToggleThemecommand,IsDarkTheme/ThemeIconproperties, and theActualThemeVariantChangedsubscription removed. Breaking change for anyone who was using the light theme. - Screenshots refreshed — three new shots matching the redesigned UI (empty state hero, populated editor, running stations). Old pre-redesign screenshots deleted.
PathGuard.EnsureWithinDirectorytrailing-separator bug — a user-typed trailing backslash on the output folder, or a drive-root / UNC-share selected as the allowed root (C:\,\\server\share\), doubled the separator in the prefix check and rejected every valid child as "resolves outside".Path.TrimEndingDirectorySeparatornormalization plus a newPathGuard.NormalizedRootPrefixhelper that preserves a root-anchored trailing separator rather than doubling it. Same fix routed throughFolderInputNodeenumeration.- Empty-value validation —
FolderInput,FolderOutput,RenamePattern,RenameRegex, andImageConvertnow throw a friendlyNodeConfigurationExceptionnaming the station when a required field is empty or whitespace, instead of letting rawArgumentException/ IO failures fan out per file. - Bool / int config silent corruption —
JsonElement.GetRawText()returned lowercase"true"/"false"butBoolStringConverter.ConvertBackemits"True"/"False". Case mismatch on the first CheckBox round-trip re-firedOnValueChanged, pushed another UndoRedo entry, rebuilt Fields, produced another mismatched VM, and looped until the UI thread starved.ConfigFieldViewModelnow normalizes bool values in its constructor, and an unparseable bool or int string no longer lands in a typed slot whereGetBoolean/GetInt32would throw on next load. - Inspector blanking after CheckBox toggle —
OnUndoRedoStateChangedcalledRefreshPropertiesPanelsynchronously inside a binding setter's event dispatch, detaching the very CheckBox whose event was still on the stack. Refresh now deferred viaDispatcher.UIThread.Post. - PropertiesView empty-state layout — DockPanel default-Left docking made the empty-state prompt a narrow strip while the Fields
ScrollViewertookLastChildFill. Wrapped both in aGridsoIsVisiblealone controls which shows. - Execution progress bar scale —
ProgressBar.Maximumwas1while the VM'sProgressproperty scales to0..100, so the bar clamped to full at roughly 1 % on every real run. Maximum is now100. - Mercury droplet layout side effects — positioning via
Canvas.SetLeft/SetTophappened insideMeasureOverride, which writes layout-affecting attached properties during the measure pass. Moved toArrangeOverridewithAffectsArrangeonly; render output is position-independent. - Mercury droplet progress overshoot — beads briefly flew off the pipe at animation keyframe boundaries.
ComputeBezierPointclampsProgressto[0, 1], and theStyledPropertyvalidators on Source, Target, Progress, and the pipe's offsets reject NaN / ±Infinity so the cubic geometry cannot be silently poisoned. - Running-state thread safety —
ExecutePipelineAsync'sfinallyflippedExecutionLog.IsRunningon whatever threadRunAsyncresumed on (typically a thread-pool thread), then mutated the node / connectionObservableCollectionfrom off-thread.UpdateRunningVisualnow guards withDispatcher.UIThread.CheckAccessand re-posts when off-thread; thefinallyawaitsDispatcher.UIThread.InvokeAsync(...)so the next execute call sees the flipped flag at its guard. - Running-state on graph mutations — stations and pipes added after the canvas was already in the "forge lit" state (template load, undo of a delete, bulk
Clear + Add) stayed visually idle.OnEditorRunningCollectionChangednow seedsIsRunningonNewItemsand re-syncs the whole canvas onReset. - 30+ regression tests — trailing-separator enumeration across drive-root / UNC / user-typed paths, empty-value configs, bool / int unparseable-input rejection, bool round-trip idempotency, running-state + demo propagation, every registered TypeKey has a bespoke MwOpsMap entry whose category bucket matches its
NodeRegistry.GetCategoryForTypeKey.
- Node connections broken since v1.4.0 — dragging a connection wire from any connector was offset and unusable; caused by
RotateTransform(45°)applied directly to the Nodify Connector control, which rotated Nodify's internal coordinate calculations; moved rotation into a custom ControlTemplate Border so the diamond shape is preserved without affecting drag math - Atomic writes for image nodes — ImageConvertNode and ImageResizeNode now use temp-file-then-rename pattern, preventing data loss on I/O errors or cancellation (previously wrote directly to target/original path)
- Path traversal hardening — PathGuard now validates backup paths in FolderOutputNode; RenameRegexNode fullpath scope uses PathGuard instead of hand-rolled check; FolderInputNode filters out symlinked files outside source root; CLI validates --input/--output directories exist
- FilterNode thread safety — removed mutable
_dryRuninstance field; parameter is now passed directly to helper methods - Semaphore disposal race — PipelineRunner now fully drains in-flight tasks before disposing the semaphore
- Buffered transform double-counting — SortNode jobs were counted as both Skipped and Failed when flush failed; runner now skips early disposition for IBufferedTransformNode
- ImageResizeNode missing ErrorMessage — file-too-large failure now sets
job.ErrorMessage(was null in UI) - FilterNode inconsistent date defaults — missing-file paths now return
DateTime.MinValueconsistently (wasstring.Emptyfor live runs) - SortNode stale buffer — buffer is cleared in Configure to prevent cancelled-run jobs bleeding into next run
- NodeLibrary theme refresh — brushes now update on theme toggle; search filter reapplied after refresh
- Runner log diagnostics — failure log messages now include
job.ErrorMessage
- Full codebase audit — 2-round review across 6 dimensions (security, leaks, performance, architecture, bugs, coverage) with 45+ issues resolved
- Performance — MetadataExtractNode reads EXIF once per file (was once per key); FilterNode uses single FileInfo per file; SortByKey uses index-based sorting; HashSet for SupportedFormats and InvalidFileNameChars lookups; static ValidFormats in ImageConvertNode
- RecentPipelines validation — AppSettings.Validate() now filters entries with null bytes, excessive length, relative paths, or whitespace
- 342 tests — 20 new tests for FilterNode operators, date fields, RenamePattern conflict resolution, SortNode date sorting, RenameRegex fullpath I/O, null deserialization, RecentPipelines validation; cross-platform temp paths replacing hardcoded
/tmp/; removed 2 redundant tests; strengthened assertions on FolderOutput content and SortNode ordering - CommunityToolkit.Mvvm 8.4.1 → 8.4.2
- Meziantou.Analyzer — added as a second analyzer alongside StyleCop for string correctness, regex safety, collection abstraction, and method complexity checks
- Stricter editorconfig — re-enabled SA1503 (braces required), SA1402 (one type per file), SA1649 (filename matches type), SA1518 (trailing newline); upgraded
varand accessibility modifier rules from suggestion to warning; added IDE0005 (unused usings) and CA1001 (IDisposable) enforcement - CA2007 enforcement in Core — ConfigureAwait(false) now enforced by analyzer for all Core async methods
- Per-project editorconfigs — Core enforces CA2007, UI suppresses MA0004/CA2007 (needs sync context), CLI suppresses MA0047 (top-level statements), Tests relax MA0002/MA0005
- One type per file — split 12 types into separate files across Core (NodeCategory, ExecutionPhase, PhaseChanged, FilesDiscovered, FileProcessed, FileJobStatus, ConfigFieldType, NodeDefinition, Connection, CanvasPosition, PipelineTemplate) and UI (RecentPipelineItem)
- Collection abstractions — interface and model return types changed from
List<T>/Dictionary<K,V>toIReadOnlyList<T>/IList<T>/IDictionary<K,V>throughout Core and consuming projects - String correctness — added
StringComparer.Ordinalto all dictionary/hashset constructors,string.EqualswithStringComparisonfor all string comparisons,CultureInfo.InvariantCulturefor allTryParsecalls - Regex safety — added
RegexOptions.ExplicitCaptureto FilterNode and RenamePatternNode regexes; added timeout to RenamePatternNode - Method complexity — extracted helpers from PipelineRunner (6 methods) and CLI Program (7 methods) to stay under 80-line threshold
- CLI structure — refactored monolithic 228-line handler into focused methods: ConfigureLogging, LoadAndConfigurePipelineAsync, ApplyNodeOverride, CreateProgressReporter, PrintFileResult, PrintSummary, ToExitCode
- MetadataExtractor 2.9.0 → 2.9.2, FluentAssertions 8.8.0 → 8.9.0, coverlet.collector 8.0.0 → 8.0.1, CommunityToolkit.Mvvm 8.4.0 → 8.4.1
- Critical crash on bool config fields — ToggleSwitch crashes with
PART_MovingKnobsKeyNotFoundException when Molten Forge theme is active; replaced with CheckBox - Memory leaks — PipelineNodeViewModel and PipelineConnectorViewModel event subscriptions to
ActualThemeVariantChangedandPropertyChangedwere never unsubscribed, preventing GC; addedDetach()cleanup on node removal - Path traversal — crafted filenames could escape output directories via RenamePatternNode, RenameAddAffixNode, RenameRegexNode (filename mode), and FolderOutputNode; added
PathGuard.EnsureWithinDirectory()checks - ReDoS vulnerability — RenameRegexNode compiled user-supplied regex with no timeout; added 2-second timeout matching FilterNode
- Dry-run file I/O — MetadataExtractNode, FilterNode, and SortNode performed disk reads during dry-run; now return defaults without file access
- ImageCompressNode data loss — overwrote original file in-place; now saves to temp file and swaps atomically
- Output node error isolation — first output node failure no longer prevents subsequent outputs from running
- Silently dropped jobs — transforms returning empty with Processing status are now counted as skipped
- CTS race condition —
Cancel()andDispose()on_ctscould race; fixed withInterlocked.Exchange - Predictable temp files — PipelineSerializer and AppSettingsManager used
.tmpsuffix; now use random GUID suffix - Serializer TOCTOU — removed redundant
File.Existscheck beforeReadAllTextAsync - Sync File.Exists on UI thread — removed blocking call from
OpenRecentAsync
- Path traversal protection —
PathGuard.EnsureWithinDirectory()helper used by all rename nodes and FolderOutputNode - Decompression bomb guard — 500 MB file size check and
MaxFrames = 1decoder option on all image nodes - ImageResize dimension bounds — width/height validated to 1-32768 in
Configure() - Crash log handler — unhandled exceptions write to
crash.login app directory - 13 new tests — cancellation, dry-run, path traversal, ReDoS timeout, bounds validation, serializer edge cases (322 total)
- ConfigureAwait(false) — added to all Core async methods to avoid unnecessary UI thread marshaling
- SortNode performance — pre-compute sort keys to eliminate O(n log n) filesystem calls
- FileJob property caching —
Extension,FileName,DirectoryNamecached with lazy invalidation - Streaming serialization — PipelineSerializer and AppSettingsManager stream to FileStream instead of string buffer
- Execution log batching — buffer FileProcessed events with 50ms DispatcherTimer flush to reduce UI layout thrashing
- FilterNode normalization — operator/field strings lowercased at configure time instead of per-file
- ImageConvertNode encoder caching — encoder created once in
Configure()instead of per file - DRY refactoring — extracted
ThemeHelper,NodeIconMap, sharedConfigHelpertest helper - Named event handlers — anonymous lambdas replaced with named methods in EditorViewModel and MainWindowViewModel
- NodeLibrary filtering — reuses group VMs with
ApplyFilter()instead of recreating per keystroke - Microsoft.Extensions.* 10.0.3 → 10.0.5, System.CommandLine 2.0.3 → 2.0.5
- Molten Forge theme — complete visual overhaul with warm amber accent, category-colored nodes (blue/green/amber), gradient tinted backgrounds, and diamond-shaped connectors
- Light/dark theme toggle — toolbar button to switch between dark and light variants at runtime; nodes, connectors, and all panels update dynamically
- Custom node template — rounded corners, emoji icons, config preview text, and category-colored headers replacing Nodify built-in node control
- Node library icons — colored icon boxes with category headers in the sidebar
- Properties badge — category-colored badge on the properties panel header
- Theme resource keys renamed from
Midnight*toForge*across all views - Node brushes rebuild dynamically on theme change via
ActualThemeVariantChanged - Hardcoded dark-mode colors replaced with theme resource lookups throughout XAML
- Updated screenshots for Molten Forge theme (editor-overview, node-pipeline, node-library, properties-panel)
- Undo/redo system — full command-pattern undo/redo for all editor actions: add node, delete node(s), move node, connect, disconnect, and config changes
- UndoRedoManager — linked-list-backed stack with 25-entry cap,
StateChangedevent, andPushOrCoalescefor keystroke coalescing - 6 undoable commands —
AddNodeCommand,RemoveNodesCommand,MoveNodeCommand,ConnectCommand,DisconnectCommand,ChangeConfigCommand, plusCompositeCommandfor batch operations - Keyboard shortcuts — Ctrl+Z (undo) and Ctrl+Y (redo) wired in the editor
- Real-time progress reporting —
PipelineProgressEventdiscriminated union (PhaseChanged,FilesDiscovered,FileProcessed) with live UI and CLI updates - Progress phases — Enumerating → Processing → Complete with file discovery count throttled every 100 files
- CLI progress output — live scanning count and per-file status with lock-based thread safety
- Execution log cap — output, error, and warning tabs capped at 5,000 entries to prevent memory growth on large pipelines
- 309 tests — 66 new tests covering undo/redo manager, all 6 commands, progress reporting, execution log view model, and editor undo/redo integration
- SemaphoreSlim disposal — proper await of in-flight tasks before disposing semaphore under cancellation
- Command loss on exception — undo/redo executes the operation before modifying the stack, preserving commands on failure
- IsConnected recalculation —
RemoveNodesCommandsplits removal and recalculation into two passes for correct multi-connection handling - Selection state restoration —
RemoveNodesCommand.Undo()restores node selection state from before deletion - Properties panel sync — undo/redo refreshes the properties panel via
StateChangedsubscription instead of per-command callbacks - Pipeline Complete event — only reported on successful completion, not on cancellation or failure
- Dependency injection — full DI container (
Microsoft.Extensions.DependencyInjection) across Core, UI, and CLI projects - Structured logging — replaced static
Serilog.Log.Loggerwith dependency-injectedILogger<T>viaMicrosoft.Extensions.Logging; Serilog remains as the provider behind the abstraction - Core DI registration — new
AddFlowForgeCore()extension method as single source of truth for service registration (NodeRegistry, PipelineRunner, AppSettingsManager) - Node logger injection — all 11 nodes receive typed
ILogger<T>viaILoggerFactoryinNodeRegistry.CreateDefault() - Test logging — migrated all tests from Serilog boilerplate to
NullLogger<T>.Instance - App startup safety — guarded
App.Servicesproperty throws descriptive error on DI failure instead of NRE - Shutdown resilience — disposal wrapped in try-catch with
Log.CloseAndFlush()fallback - Constructor guards —
ArgumentNullException.ThrowIfNullon all node, runner, and view model constructors - Diagnostic logging —
LogDebugin Configure methods andLogWarningon error paths for all nodes - DI cleanup —
IDialogServiceandIServiceProviderinjected via constructor instead of service locator - Captive dependency fix —
EditorViewModelregistered as singleton to match its actual lifetime in singletonMainWindowViewModel - Error handling — replaced bare
catch {}inAppSettingsManager.SaveAsync, narrowed CLI catch scope for DI vs pipeline errors - 243 tests — 7 new DI registration tests verifying service resolution and lifetimes
- File browser dialogs — native OS open/save dialogs for pipeline files
- Recent pipelines menu — MRU list persisted across sessions with clear option
- Backup before overwrite — FolderOutput can create
.bak(or custom suffix) backups of destination files before overwriting - Zoom-to-fit — toolbar button to fit the entire graph into the viewport
- Keyboard shortcuts help — dialog showing all available keyboard shortcuts
- JSON CLI output —
--format jsonflag on CLI runner for machine-readable output - Config field tooltips — hover descriptions on all node configuration fields
- Sample pipelines — 4 ready-to-run
.ffpipefiles insamples/directory - 236 tests — expanded coverage for new features and edge cases
- Backup suffix validation — reject lone
"."suffix that causes data loss on NTFS (trailing dots stripped) - Stale recent path removal — case-insensitive matching and persist removal to settings file
- Serilog stdout contamination — route log output to stderr in
--format jsonmode - Init race condition — gate settings writes until initial load completes
- MenuItem event handler leaks — unsubscribe Click handlers before rebuilding recent menu
- Business logic in code-behind — moved
Path.GetFileNamefrom ToolbarView into ViewModel - Explicit CancellationToken — pass
CancellationToken.Noneintentionally on UI-initiated loads - Silent test pass — sample pipeline tests now log skips and guard against false-green in CI
- Event handler leaks — prevent accumulation in CanvasView and ToolbarView on DataContext changes
- Path traversal — block
backupSuffixvalues containing path separators or traversal sequences - AppSettings validation — input validation and safe defaults for all settings
- MetadataExtract — accept string keys, remove async void, preserve graph name on load
- Dead code removal — cleaned up unused code across the codebase
- Microsoft.NET.Test.Sdk 18.0.1 → 18.3.0
- Visual Node Editor — drag-and-drop canvas with pan, zoom, wire connections, and rubber-band selection (Nodify.Avalonia)
- 11 built-in nodes:
- Input: Folder Input (recursive, glob patterns)
- Process: Rename Pattern, Rename Regex, Rename Add Affix, Filter, Sort, Image Resize, Image Convert, Image Compress, Metadata Extract
- Output: Folder Output (copy/move with timestamp preservation)
- Pipeline engine — topological sort execution, async concurrency, dry-run mode, cancellation support
- Pipeline serialization —
.ffpipeJSON format with atomic writes - Node library sidebar — categorized (Input/Process/Save To) with search
- Properties panel — auto-generated config forms from node schemas (text, number, boolean, file/folder picker, dropdown)
- VS-style output panel — tabbed Output/Errors/Warnings with badge counts, resizable via GridSplitter
- Pipeline templates — one-click starters: Photo Import, Batch Rename, Web Export, Compress
- Midnight theme — custom dark theme with GitHub Dark-inspired color palette
- CLI runner —
flowforge run <pipeline.ffpipe>with--input,--output,--dry-run,--verboseflags - App settings — cross-platform JSON persistence with sensible defaults
- Structured logging — Serilog with file and console sinks
- 202 tests — full coverage across all nodes, runner, serializer, registry, templates, settings
- Static analysis — StyleCop + TreatWarningsAsErrors + EditorConfig enforced
- CI/CD — GitHub Actions for build/test/format, release automation, Dependabot