Changelog

All notable changes to this project are documented here. The format is based on Keep a Changelog.

1.8.1 (2026-08-19)

Added

  • docs/user_guide.rst: document --auto-update, and add a new “Configuration Files” section listing every persisted config file and its default location per platform

Fixed

  • docs/user_guide.rst: --config-dir’s documented macOS default was wrong (~/Library/Application Support/pbrenamer); it’s actually ~/Library/Preferences/pbrenamer (matches platform/dirs.py:_MacDirs.config_home)

  • --auto-update crashed the PyInstaller executable with ModuleNotFoundError: No module named 'http' — the Linux/Windows build excluded the stdlib http package, which urllib.request needs. Removed http from pbrenamer.spec’s excludes

  • --auto-update then failed every HTTPS request with unknown url type: https on the Linux build — PyInstaller’s binary scan resolved libssl.so.3/libcrypto.so.3 to the system copy (missing symbols the bundled _ssl/_hashlib extensions need) instead of the conda env’s own matching copy. pbrenamer.spec now forces the env’s own OpenSSL libraries back into the bundle, and adds ssl to hiddenimports since urllib.request only imports it inside a try/except that PyInstaller’s static analysis misses

  • README: “Internationalised” feature bullet claimed only English and French were included; it now lists all 8 bundled languages (English, German, Spanish, French, Italian, Russian, Vietnamese, Chinese)

1.8.0 (2026-08-19)

Added

  • --auto-update downloads the latest GitHub release matching the running platform/architecture and replaces the current executable with it, then exits. Only available in PyInstaller-built executables (sys.frozen) — rejected with an error for source checkouts and pip/PyPI installs, which have no single running binary to replace. New platform/auto_update.py

  • Headless mode: --dry-run/--no-dry-run flag prints the equivalent mv commands to stdout instead of renaming files. Output is a valid POSIX shell script (paths quoted with shlex.quote, conflicts reported as # comments) that can be piped to sh to actually perform the rename. Takes precedence over --confirm — no interactive prompt is shown

  • Headless mode: --select GLOB restricts the rename to entries matching GLOB, mirroring GUI row selection (clicking a file). Repeatable to accumulate matches (equivalent of Ctrl+click). With --sidecar-mode, selecting a base file also selects its sidecars and vice versa, using the same grouping and ambiguity handling as the GUI (core/sidecar.build_sidecar_groups()). New __main__._resolve_selection()

Fixed

  • make dist always resolved the release version to dev, even on a clean, exactly-tagged commit — PY_SOURCES in the Makefile was built from unsorted find output, so pybabel extract (via make translate, a dist prerequisite) reordered .po catalogue entries on every run despite no actual string changes, dirtying the working tree right before tools/git_version.sh checked it. find output is now sorted, making make translate idempotent

  • Sidecar mode: a sidecar file ambiguous between multiple base files (e.g. two candidate base files sharing a suffix) no longer disables the Rename button for the whole file list — the error now only blocks rename when one of the files involved in the ambiguity is actually selected, matching how every other field-resolution error is already scoped to the current selection

  • Sidecar mode: re-clicking (plain click, no modifier) any file belonging to an already fully-selected sidecar group no longer deselects the whole group. Only a genuine Ctrl+click now toggles a single item against the existing selection; a plain click or Shift range-select always replaces the selection with the clicked file’s whole group, consistent with clicking an ungrouped file staying selected

1.7.0 (2026-08-18)

Added

  • Drag’n’drop of files and directories between PBRenamer and external applications, and between the directory tree and the file list: copy or move (negotiated via the OS/Qt drag modifier keys), with a batched Overwrite/Skip/Cancel confirmation when a destination already exists. New core/filetools.copy_path()/move_path() functions back the operation; only moves are recorded to the undo history (copies are not undoable). Implemented by the new ui/dnd_widgets.py (DirectoryTreeView, FileListWidget)

  • Custom, HiDPI-aware drag cursors (copy / move / alias / not-allowed), sourced from PBIcons’ new cursors/ icon set and synced via tools/update_icons.py (now also covers the fixed _CURSOR_FILES list and searches the cursors PBIcons subdirectory); implemented by the new ui/dnd_cursors.py

  • README badges: latest GitHub release, CI status, test coverage (Codecov), and PyPI version

  • CI now uploads coverage.xml to Codecov (codecov/codecov-action) after the test suite runs; pytest gained a --cov-report=xml output alongside the existing terminal report

  • Sidecar file support: a file associated with a base file by a name suffix (e.g. img.jpg + img.xmp) can now be grouped, colored, selected, and renamed together with its base file. New Edit → Sidecar Files… dialog (ui/sidecar_dialog.py), with one tab per base-file category (Images / Video / Audio / Other) plus a Common tab, configures which extensions count as that category’s base files and which suffixes identify its sidecars, each list independently restorable to its default; persisted to sidecar_config.json. A new toolbar “Sidecar mode” checkbox (enabled only for the Keep/Lowercase/Uppercase extension modes) drives the behaviour: sidecar rows are colored distinctly in the file list, selecting a base or sidecar auto-selects the whole group, a sidecar with more than one candidate base file is flagged as an explicit, unrenamable ambiguity error, and renaming a group applies the pattern to the base file’s stem only, reusing it verbatim (own suffix unchanged) for every sidecar. New pure grouping algorithm in core/sidecar.py (SidecarConfig, build_sidecar_groups()). Mirrored in headless mode via --sidecar-mode/--no-sidecar-mode (__main__.py)

Changed

  • About dialog restyled to match the PBIcons-family apps (e.g. PBRegisterActivity): icon in its own column on the left, application name/version/author/description/details framed in a raised panel on the right, centered Close button

Fixed

  • _on_rename() could use a row’s raw error text (e.g. an ambiguous-sidecar message) as a literal rename target if invoked while the Rename button’s disabled state was bypassed; it now skips any row flagged with a field/grouping error

  • Adding a base-file extension already assigned to a different category (e.g. jpg to both Images and Video) silently created a duplicate that made category lookup ambiguous; the Sidecar Files dialog now rejects it with an explicit warning naming the conflicting category

  • The Sidecar Files dialog let a sidecar suffix (per-category or Common) be added even when it was already a base extension in one of the categories (e.g. adding jpg as a Video sidecar suffix while it is an Images base extension), creating the same category-lookup ambiguity as the base-extension case; it is now rejected with the same warning

  • In sidecar mode, Ctrl+click-toggling off one member (base or sidecar) of an already-fully-selected group left the whole group selected instead of deselecting it: _link_group_selection() only ever looked at the current selection to decide which groups to expand, so the group’s other still-selected members immediately re-pulled the toggled-off file back in. It now also tracks the previous selection and, when part of a group is toggled off, deselects the whole group — mirroring the existing behaviour where selecting any member selects the whole group. Toggling off an unrelated file (outside any group, or the sole selected member of a solo file) is unaffected

1.6.0 (2026-08-13)

Added

  • Five editorial {vi:…} video metadata fields, read from the MediaInfo General track: performer, copyright, comment, description, genre

  • Extension normalization table, managed from its own tab in the Settings dialog (Edit → Settings… → Extension Normalization): user-editable from → to extension mappings (e.g. jpeg → jpg, yml → yaml), persisted to extension_normalization.json, used by the new “Normalize extension” mode; changes apply immediately regardless of whether the dialog is later confirmed or cancelled

Changed

  • Menu actions (Open folder, Information, History, Settings, Edit shortcuts, About, Quit) and the search/replace toolbar buttons (add-to-history “+”, pattern help “?”) now use bundled SVG icons (resources/*.svg) instead of plain text / programmatically-painted icons; pattern_help.make_add_icon()/make_help_icon() removed

  • The “Keep extension” checkbox is now a 5-way combobox: Keep extension (unchanged behaviour), Extension in lowercase, Extension in uppercase, Normalize extension (via the new normalization table), Modify extension (unchanged behaviour of the former unchecked state). Existing saved preferences (keep_extension: true/false, in toolbar state and named saves) are migrated transparently to keep/modify.

  • CLI: --keep-ext/--no-keep-ext replaced by --ext-mode {keep,lower,upper,normalize,modify} (default keep), consistent with the existing string-choices style of --case/--sep. This is a breaking change for scripts invoking --keep-ext directly.

Fixed

  • make pypi-build now removes dist/pypi/ before rebuilding, so publish-pypi / publish-testpypi no longer pick up wheel/sdist artifacts left over from previous versions alongside the current one

  • Navigating via a shortcut/bookmark (_on_shortcut) triggered _reload_files() twice — _navigate_to()’s setCurrentIndex() synchronously fired _on_directory_selected() before _current_dir had been updated, so its same-path guard did not catch the redundant call — causing the file list (and, with auto-preview on, the preview column) to be cleared and repopulated a second time right after the first; fixed by setting _current_dir before calling _navigate_to(), matching the pattern already used in _on_open()

  • Preview/file list still flickered once per directory change even after the above fix: QFileSystemWatcher can emit a spurious directoryChanged signal shortly after a directory starts being watched (e.g. a desktop indexer or thumbnailer touching directory metadata with no actual file added/removed/renamed), which _on_fs_change treated as a real change and rebuilt the whole file list for. _on_fs_change now compares the on-disk listing against what’s currently displayed (_listing_unchanged()) and skips the reload when nothing actually changed.

1.5.1 (2026-08-09)

Added

  • make verify-testpypi / make verify-pypi: install the latest pbrenamer from TestPyPI / the real PyPI into a throwaway venv, run pbrenamer $(ARGS) (default --version, override e.g. make verify-testpypi ARGS="--help-search") as a smoke test, then remove the venv — cleanup always runs even if the install or the smoke test fails, and the target still exits non-zero on failure

Fixed

  • Mixed-type replacement templates (e.g. {im:…}{vi:…}) on a file that matches none of the namespaces used in the template still flag the file as an error (unchanged), but the message is now explicit about which namespaces were tried instead of misleadingly naming only the first field — e.g. ⚠ no matching type (im/vi) instead of ⚠ im:DateTimeDigitized unavailable, which wrongly suggested only that one field was the problem.

1.5.0 (2026-08-09)

Added

  • make update-icons (new Makefile target, backed by the new tools/update_icons.py) syncs pbrenamer.svg and the new pbrenamer.ico/pbrenamer.icns byte-for-byte from the PBIcons reference project (local checkout or GitHub)

  • PyInstaller build now embeds a native icon (pbrenamer.ico on Windows, pbrenamer.icns on macOS via BUNDLE), synced from PBIcons

  • --install-desktop-entry / --uninstall-desktop-entry CLI flags (platform/desktop.py): install/remove a user-level .desktop menu entry and icon on Linux, working around pip’s lack of a post-install hook for pip install pbrenamer

  • The generated .desktop entry’s Comment= is now localized (Comment[fr]=, Comment[de]=, …) for every language with a compiled catalogue, reusing the existing translated “A graphical batch file renaming utility.” string from the About dialog; Name=PBRenamer is intentionally left unlocalized

  • make pypi-build / make publish-testpypi / make publish-pypi (new Makefile targets): build the wheel + sdist into dist/pypi/ and upload to TestPyPI or the real PyPI via twine; publish-pypi asks for an explicit yes confirmation before the irreversible upload. Adds build/twine as dev dependencies

Fixed

  • Picking an entry from the search pattern history no longer overwrites the replace field with the first entry of the replace history (and vice versa); each history combo box now only refreshes its own list

  • PyPI wheel now ships compiled .mo translation catalogues: a new hatchling build hook (hatch_build.py) compiles the committed .po sources at wheel-build time and force-includes the result, since .mo files are gitignored generated artifacts and were previously silently dropped from the wheel — the installed app had no languages available (i18n.available_languages() globs for .mo)

  • license metadata switched from the legacy { file = "LICENSE" } form (which duplicated the full GPLv3 text into the License: metadata field) to the SPDX expression GPL-3.0-only with license-files = ["LICENSE"], and dropped the now-redundant License :: classifier

1.4.4 (2026-08-05)

Added

  • Documentation: the app icon (src/pbrenamer/resources/pbrenamer.svg) is now used as the Sphinx logo and favicon, copied to docs/_static/pbrenamer.svg at build time so the artwork has a single source of truth.

Changed

  • Development environment management migrated from conda to [pixi](https://pixi.sh): environment.yml is replaced by [tool.pixi.*] sections in pyproject.toml and a committed pixi.lock. make venv now bootstraps pixi itself if missing; the Makefile’s CONDA_RUN wrapper now resolves to pixi run -- instead of conda run -n pbrenamer. CI provisions pixi via prefix-dev/setup-pixi instead of conda-incubator/setup-miniconda in the test, hooks, docs, and build jobs. No application code changed.

Fixed

  • docs/conf.py: the ## [Unreleased] section of CHANGELOG.md was dropped by the RST changelog generator except for its orphaned ### Added/### Changed/… subheadings, which were emitted without content and at an incorrect nesting level. [Unreleased] is now recognised like a dated release and included only when it has actual content.

  • Makefile: the hooks target was missing from .PHONY and silently shadowed by the hooks/ directory (holding the PyInstaller runtime font hook), so make hooks was a no-op instead of running pre-commit.

  • CI: the Windows build job’s PyInstaller step used a bash-style multi-line command with \ line continuations, which is not valid under PowerShell (the default shell on windows-latest); it now runs as a single line.

1.4.3 (2026-06-24)

Fixed

  • Linux bundle — font selection: _load_bundled_fonts() now explicitly sets Ubuntu as the application font after registering the bundled .ttf files. The libfontconfig.so bundled by PyInstaller has its default config path hardcoded to the build machine’s conda prefix; on any other machine that path is absent and fontconfig fails silently, leaving Qt with no valid system font and an unpredictable default. Forcing Ubuntu bypasses fontconfig for font selection while the runtime hook (pyi_rth_fonts.py) still provides a portable fonts.conf so rendering settings (anti-aliasing, hinting…) are inherited from the system /etc/fonts/fonts.conf.

1.4.2 (2026-06-24)

Fixed

  • Linux bundle: fonts and font size are now identical between local and CI builds. The fonts.conf embedded by PyInstaller contained absolute paths to the build machine’s conda environment; on any other machine those paths are missing and Qt fell back to system fonts only, at a different size. Fix: conda fonts (fonts-conda-ecosystem: Ubuntu, DejaVu, Inconsolata, SourceCodePro) are included in the bundle via pbrenamer.spec for glyph coverage; a runtime hook (hooks/pyi_rth_fonts.py) generates a portable fonts.conf at startup that includes the system /etc/fonts/fonts.conf and calls FcFini()/FcInit() to force fontconfig re-initialisation with the correct config — the system font is then resolved normally by Qt, with the same size as in the local run. (Closes [#2](https://github.com/ppoilbarbe/PBRenamer/issues/2))

  • ReadTheDocs build: removed the pre_build step that attempted to run pyside6-uic on *.ui files — no such files exist (*_ui.py are committed hand-written sources); the stale step caused the RTD build to fail on every tag since 1.4.1

1.4.1 (2026-06-18)

Fixed

  • audio_meta.read_field now returns None for video container files (MP4, MKV, MOV, …); previously read_field called mutagen directly without going through can_read, so mutagen — which can read MP4 tags — returned audio field values (title, duration, …) for video files; {au:…} tokens in a replacement template could therefore resolve on video files as if they were pure-audio files

## [1.4.0] - 2026-06-18 [YANKED]

> Regression: audio_meta.read_field returns audio metadata for video > containers (MP4, MKV, …) because it bypasses can_read. Replaced by 1.4.1.

Added

  • settings.configure(config_dir) — new public function that overrides the configuration directory used by all settings and i18n functions; a single call redirects settings, i18n, and PatternPresets config I/O simultaneously; pass None to restore the platform default; intended for testing

  • --config-dir DIR CLI option (both GUI and headless modes): overrides the configuration directory at startup before any settings access; intended for testing (noted in --help)

  • Window geometry persistence for all windows and dialogs (except About): MainWindow, FileInfoWindow, HistoryDialog, SettingsDialog, ShortcutsDialog, and PatternHelpDialog save/restore size and position across sessions via WindowState.save_geometry / load_geometry

  • LRU management for search, replace, and named-saves dropdowns: the most recently used entry is promoted to position 0 on each use; history is capped at 20 entries

  • Mixed file-type replacement templates: when {im:…}, {vi:…}, or {au:…} tokens from more than one namespace appear in the same template, substitute() probes each namespace with can_read() at runtime — the applicable namespace follows strict behaviour (absent field without default → FieldResolutionError), non-applicable namespaces silently contribute "" without requiring a default

  • {vi:…} field resolution now rejects files that have no video track even when MediaInfo returns a General track (e.g. JPEG files parsed by pymediainfo)

  • UI layouts converted from Qt Designer .ui files to hand-written Python source (*_ui.py): main_window_ui.py, history_dialog_ui.py, settings_dialog_ui.py, about_dialog_ui.py — eliminates the Qt Designer file-parsing step at build time

Changed

  • Test configuration isolation: conftest.py provides a config_dir autouse fixture that redirects all settings I/O to a per-test temporary directory via settings.configure(); the real ~/.config/pbrenamer is never touched during test runs; tests that need direct file access can request the config_dir fixture to obtain the path

  • Named saves storage format migrated from a flat JSON dict (no ordering guarantee) to an ordered JSON list [{"name": …, …config…}, …]; legacy dict files are read transparently and upgraded on next write

  • patternHelp.PatternHelpDialog: geometry now restored in showEvent (first paint) rather than in the constructor, matching the behaviour of all other secondary windows

Fixed

  • audio_meta.can_read now returns False for video container files (MP4, MKV, MOV, …) that carry no audio-only track; previously a General track from pymediainfo could satisfy the read check, causing {au:…} fields to attempt — and silently fail — audio tag extraction on video files

  • Main window geometry was not restored at startup: restoreGeometry() was called in __init__ before show(), so the window manager ignored it; moved to showEvent (fires once via _geometry_restored flag)

  • Named saves combo list order reverted to sorted order on a subsequent save: Qt’s QCompleter (auto-attached to the editable QComboBox) received textChanged events from the embedded QLineEdit even while combo.blockSignals(True) was active, and could queue a deferred activated callback that called use_save with a wrong index; fixed by blocking the QLineEdit signals separately and using setCurrentIndex instead of setCurrentText during _populate_named_saves

  • Preview pane appeared and immediately disappeared on the first click in the directory tree: _on_directory_selected was called twice for the initial path, the second call with the same path clearing the just-populated preview; fixed with an early-return guard when the path has not changed

Removed

  • Qt Designer .ui files (about_dialog.ui, history_dialog.ui, main_window.ui, settings_dialog.ui) — replaced by *_ui.py Python sources

  • tools/extract_ui_strings.py — no longer needed; translatable strings are extracted directly from Python source via pybabel

1.3.0 (2026-06-15)

Added

  • Undo button displays the number of available undo levels — text changes to “Undo (N)” and the tooltip explains how many rename batches can be reverted

  • Case-transform modifier in replacement field tokens: = (unchanged), - (lower), + (upper), ! (capitalise first char), * (Title Case)

  • Centre-align (^) in replacement field tokens

  • Toolbar-state and preview-delay persistence in Settings

  • tools/po_check.py: PO file inspection tool using the babel library (statistics, untranslated entries, regex search, language diff) — replaces ad-hoc grep/msgfmt usage that breaks on multi-line entries

  • src/pbrenamer/argparse_qt.py: add_qt_arguments() declares Qt CLI flags (--style, --platform, …) as a proper argparse argument group; collected in args.qt_args as single-dash tokens ready for QApplication; Qt options are hidden from the usage: line to reduce noise while remaining fully visible in the --help output

  • tools/fix_po_files.py: utility to normalise PO file formatting in bulk

  • Comprehensive test suite covering all source modules at 100%; includes the full main window (_on_preview, _refresh_conflicts, context menus, undo, conflict detection, drag-and-drop, keyboard shortcuts, settings dialog, file-info window, pattern-help dialog, presets, bookmarks, i18n bootstrap, and all core modules)

  • Headless Qt test execution via QT_QPA_PLATFORM=offscreen in conftest.py; QMenu.exec patched at module level so no blocking dialog appears during CI or local make test runs

Changed

  • main() now uses parse_args() instead of parse_known_args(); unknown flags are rejected natively by argparse — _validate_qt_argv removed

Fixed

  • {L} search-pattern token now matches Unicode letters; accented Latin (é, ê, ü, …), Cyrillic, Greek and all other Unicode letter categories are captured correctly — was limited to ASCII [a-zA-Z], now uses [^\W\d_] which covers all \w characters that are neither digits nor underscores

  • Qt built-in translations are now loaded alongside the application catalogue so dialog buttons (OK, Cancel, Yes, No, …) are correctly translated when a non-English language is selected

  • Pattern help dialog: HTML-escaped {1:&lt;12} so the browser no longer interprets <12> as an HTML tag

  • Fifteen # pragma: no cover guards on reachable logic replaced by targeted tests: case-insensitive no-match in rename_using_plain_text, zero-width regex guard, {newnum} CLI branch (start value, invalid start, no-match, field error, collision k+=1), rename failure exit path, main() headless dispatch, _parse_exif_datetime on malformed input, _read_iptc unknown key, _apply_case unknown modifier

1.2.0 (2026-06-09)

Added

  • Shortcuts menu (Go → Shortcuts): navigate instantly to system directories (Home, Desktop, Documents, Downloads, Pictures, Music, Videos on all platforms; GTK bookmarks on Linux) and user-defined shortcuts

  • Right-click on any directory in the tree to Add as shortcut; shortcuts are persisted to shortcuts.json in the user config directory

  • Edit Shortcuts dialog (Go → Edit Shortcuts): reorder (Move up / Move down) and remove user-defined shortcuts

  • File information window (View → File information): non-modal window that shows the actual values of all replacement fields ({date}, {mdatetime}, {dir}, {im:…}, {au:…}, {vi:…}, …) for the currently selected file; updates live as the selection changes

  • Restore last opened directory setting in the new Behaviour group of the Settings dialog; when enabled, the app reopens the last accessed directory at startup (CLI-provided directory always takes priority; default: disabled)

  • make run now accepts an ARGS variable to forward arguments and options to the program (e.g. make run ARGS="--debug /some/dir")

Fixed

  • email.utils was incorrectly listed in PyInstaller excludes, causing importlib.metadata calls in the About dialog to fail in packaged builds

  • CI test job now compiles .mo catalogues before running the test suite, fixing four test_i18n.py failures (available_languages() globs .mo files which are gitignored and were absent on the runner)

Changed

  • Executable names are now lowercase in the PyInstaller spec and CI artifact globs (e.g. pbrenamer-1.2.0-linux-x86_64 instead of PBRenamer-…)

  • pybabel extract now uses --no-location to omit source-file/line-number comments from .po files; make translate is idempotent on an unchanged source tree, fixing make dist always producing a dev build instead of using the git tag version

1.1.0 (2026-06-01)

Added

  • srcdist Makefile target: produces a source archive dist/pbrenamer-<ver>-src.tar.gz via git archive

  • tools/git_version.sh: derives the version string from the exact Git tag when HEAD is tagged and the working tree is clean, falls back to dev; used by both dist and srcdist targets

Changed

  • i18n toolchain replaced: xgettext/msgmerge/msgfmt/msginit removed in favour of pybabel extract/update/compile/init; babel.cfg mapping file added; gettext dependency replaced by babel in environment.yml

Removed

  • .mo compiled catalogues removed from the repository (now gitignored); the CI regenerates them via pybabel compile before each PyInstaller build

1.0.0 (2026-05-03)

Added

  • Translations for six new languages: German (de), Spanish (es), Italian (it), Russian (ru), Vietnamese (vi), and Simplified Chinese (zh_CN)

  • All 272 translatable strings covered in each new locale; .mo files compiled and committed alongside the .po sources

0.3.2 (2026-04-25)

Fixed

  • docs/conf.py: read release and version dynamically from pbrenamer.__version__ instead of a hard-coded string, so the Sphinx build always reflects the current version without a manual update

0.3.1 (2026-04-25)

Fixed

  • README: remove insert/delete at position and manual rename from feature list (functions exist in filetools.py but are not wired to the GUI)

0.3.0 (2026-04-25)

Added

  • Complete Sphinx documentation: user guide (GUI usage, all search modes, replacement fields reference, headless CLI reference) and API reference (autodoc for all modules)

  • docs/conf.py generates changelog.rst from CHANGELOG.md at build time; the generated file is gitignored

0.2.0 (2026-04-24)

Added

  • Non-modal help dialogs for search and replace patterns, with geometry persistence (position and size saved across sessions)

  • Help dialog content fully translated (English and French); field-name tokens left in code syntax, all descriptions and headers localised

  • Log-level preference in the Settings dialog (DEBUG / INFO / WARNING / ERROR / CRITICAL, default INFO); applied at startup and immediately on change

  • CLI flags --debug / -d, --verbose / -v, --quiet / -q to override the saved log-level preference at launch

  • Debug traces throughout the codebase via Python logging (file listing, per-file preview, rename operations, metadata reads, template parsing)

  • .ui files for the About, Settings and History dialogs; all dialogs now follow the Designer → pyside6-uic workflow

  • WindowState.save_geometry / load_geometry for named secondary-window geometry slots

Fixed

  • CI “Compile Qt UI files” step now compiles all *.ui files instead of only main_window.ui, fixing ModuleNotFoundError for the new dialog modules

0.1.0 (2026-04-23)

Added

  • Live-preview rename table with per-row conflict highlighting; Rename button disabled until all conflicts are resolved

  • Flexible renaming rules: pattern search/replace, text substitution, case transforms, insert/delete at position, counter, date stamps, parent-folder name, and manual rename

  • Keep-extension option so transforms apply to the stem only

  • Multi-selection — apply rules to selected files only

  • Recursive directory traversal

  • Directory entries shown in a distinct colour

  • One-click undo of the last rename batch

  • Pattern preset save/restore (persisted per user)

  • Case-aware conflict detection honouring the filesystem’s actual case-sensitivity (probed at runtime)

  • Language settings dialog; language preference persisted across sessions

  • Internationalisation via gettext — English and French bundled

  • Platform abstraction layer (platform/) covering Linux (XDG), macOS, and Windows for config/data directories, filesystem probing, and locale detection

  • Standalone executable builds via PyInstaller for Linux, macOS, and Windows

  • CI/CD pipeline: test → pre-commit hooks → 3-platform build → GitHub release on semver tag

  • make bump-patch / bump-minor / bump-major / bump-set targets backed by tools/bump_version.py for atomic version increments

  • NOCONDA=1 Makefile flag to bypass conda wrapping when tools are on PATH