{"owner":"qarmin","repo":"czkawka","hasSkills":true,"totalSkillsCount":12,"totalTokensCount":17960,"categories":["root-instruction","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","CLAUDE.md","cedinia/AGENTS.md","cedinia/CLAUDE.md","czkawka_cli/AGENTS.md","czkawka_cli/CLAUDE.md","czkawka_core/AGENTS.md","czkawka_core/CLAUDE.md","czkawka_gui/AGENTS.md","czkawka_gui/CLAUDE.md","krokiet/AGENTS.md","krokiet/CLAUDE.md"],"skills":{"AGENTS.md":"# Czkawka – Codebase Guide\n\n## Language\n\nAll code, comments, commit messages, and documentation must be written in **English**.\n\n---\n\n## AI-authored commits & merge/pull requests\n\nAlways, without exception: every commit created by an AI assistant must start its title (the\nfirst line of the commit message) with `[AI] `, e.g. `[AI] Fix crash when scanning empty\ndirectories`. If the commit message has a body, the body does not need the prefix again - the\ntitle alone marks it.\n\nThe same applies to any merge request (GitLab) or pull request (GitHub) opened by an AI\nassistant: both the title and the body/description must start with `[AI] `, e.g. title\n`[AI] Add duplicate detection for symlinked folders` and body starting with\n`[AI] This MR adds ...`. This must stay visible to reviewers at a glance in both GitLab and\nGitHub - never omit it, even for small or automatic changes.\n\n---\n\n## Rust Style\n\nApplies to every Rust crate in the workspace (`czkawka_core`, `czkawka_cli`, `czkawka_gui`,\n`krokiet`, `cedinia`). Program-specific deviations or elaborations live in that program's own\n`AGENTS.md`.\n\n**Formatting & lints**\n- Format with `cargo +nightly fmt`, then `cargo fmt` for stable compatibility\n  (`.rustfmt.toml`: `max_width = 120`, granular import grouping `group_imports = \"StdExternalCrate\"`).\n- Code must compile without clippy warnings. Suppress a lint locally only with a comment\n  explaining why.\n\n**Errors & panics**\n- Prefer `?`, `ok_or_else`, `map_err` over panic-based flows.\n- `unwrap()` only in tests.\n- `expect()` in production only when BOTH hold: there is no correct recovery path, and\n  continuing would likely hide a serious bug - always with a precise, actionable message. This\n  is visible throughout Krokiet/Cedinia callback code: a Slint weak reference is upgraded with\n  `expect()` because if the window is already gone there is nothing meaningful to do except crash.\n- Never silently swallow errors with `let _ = ...` unless the failure is genuinely irrelevant.\n- Model domain errors with explicit types (`thiserror`/enums), not raw strings.\n- Wrap I/O errors with context (e.g. file path) instead of letting std's bare \"Permission\n  denied\" propagate - without the path it's hard to find the source.\n- Don't ignore `Result`; handle it explicitly.\n\n**Code shape**\n- Keep functions focused; once one grows past ~30-50 lines, extract its logical steps into\n  separate, well-named functions. Never carve a long function into sections with comment\n  headers or bare `{ ... }` scope blocks - a `// validate input` comment over a block is the\n  signal to pull it out into `validate_input(...)`, not to fence it off.\n- Name things by intent, not by container/type (`user_count`, not `vec_users_len`).\n- Make invalid states unrepresentable: enums over strings, `Option<T>` over sentinel values\n  (`-1`, empty string).\n- Prefer iterators and collection methods over manual index-based loops.\n- Prefer `#[derive(...)]` over manual impls when behavior is standard.\n- Avoid unnecessary copies and allocations; prefer references and `Cow` where it helps.\n- Prefer external crates over reimplementing them.\n\n**Comments**\nKeep comments short and minimal - a single terse line, not a paragraph. Code should be\nself-documenting through clear naming. Add a comment only when the _why_ is not easily inferred\nfrom reading the code - algorithmic choices, non-obvious constraints, workarounds for external\nlibrary bugs, etc. Do not restate what the code already says. Never use the `—`/`–` dash\ncharacters in code, comments, or commit messages - plain `-` only (ASCII box-drawing diagrams are\nthe only exception).\n\nException: `czkawka_core` is a library - every frontend in this workspace depends on it, and so do\nexternal consumers who only read its public API, not its internals. Its `pub fn`/`pub struct`\nsurface warrants more explanation than the rest of the workspace; see `czkawka_core/AGENTS.md`.\n\n**Tests, fuzzers, benchmarks**\n- Cover as much code as possible with tests; keep them readable with explicit `assert_eq!` and\n  input data close to the assertions.\n- Add fuzzers/examples/benchmarks where it helps show usage, find bugs, or measure the\n  performance impact of a change.\n\n**`unsafe`**\nAvoid whenever possible. Every `unsafe` block documents its invariants and why the safety\nrequirements hold (see cedinia's `#[unsafe(no_mangle)] android_main`).\n\n**Files & logging**\n- Keep source files at or below 500 lines; split large files into modules.\n- Always log via `log` (`handsome_logger`); reach for `tracing` only if actually needed.\n\n**Auto-vectorization**\nLLVM vectorizes a loop when it can prove: no loop-carried dependency, no aliasing, no bounds\nchecks, and a favorable cost model.\n- Enable it: `.iter()`/`.iter_mut()` + `.zip()` instead of index loops (drops bounds checks);\n  `.chunks_exact(N)`/`.chunks_exact_mut(N)` + `.remainder()` for aligned SIMD chunk processing;\n  `(&[T], &mut [T])` over `(&mut [T], &mut [T])` so LLVM can assume no aliasing; keep the loop\n  body uniform (no per-element `if`/`match`, no early returns); use a reduction (`.sum()`/\n  `.fold()`) instead of a running accumulator that depends on its own previous value.\n- Kills it: `slice[i]` index loops, value-based `continue` inside an element loop, mutable\n  state shared across iterations.\n- Verify: inspect asm on godbolt.org for `vmulps`/`vpaddb`/`vpsubusb` vs scalar `mulss`/`movss`;\n  measure the delta with `RUSTFLAGS=\"-C opt-level=3 -C no-vectorize-loops -C no-vectorize-slp\"`;\n  build with `RUSTFLAGS=\"-C target-cpu=native\"` to unlock AVX2/AVX-512 on the host CPU.\n- When LLVM won't vectorize, reach for explicit SIMD: `std::arch` (stable, arch-specific),\n  `std::simd`/`portable_simd` (nightly, portable), or the stable `wide`/`pulp` crates.\n\n**Performance micro-hints**\n- Pre-allocate with `Vec::with_capacity(n)` / `HashMap::with_capacity(n)` when the count is\n  roughly known - avoids the realloc ladder.\n- Reuse buffers across calls (`buf.clear()`) instead of reallocating.\n- For almost-always-short collections, consider `SmallVec`/`ArrayVec` (stack-allocated until\n  they spill).\n- Use the smallest enum discriminant that fits (`#[repr(u8)]`/`#[repr(u16)]`) for tighter\n  packing and better cache density.\n- Wrap Criterion bench inputs/outputs in `std::hint::black_box(...)` to stop dead-code\n  elimination from invalidating the measurement.\n\n---\n\n## Project Goals\n\nTwo properties are non-negotiable across every sub-project:\n\n1. **Performance first** – scanning and file operations must be fast. Parallelism via `rayon`,\n   efficient algorithms (e.g. perceptual hashing, blake3), and careful memory use are the norm.\n   Avoid unnecessary allocations or copies in hot paths.\n\n2. **Minimal non-Rust dependencies** – every additional C/C++ library (or any other non-Rust\n   language) makes cross-compilation harder, narrows the set of supported targets, and increases\n   build complexity for contributors. Prefer pure-Rust crates. If a native library is truly\n   necessary (e.g. `libheif`, `libraw`), gate it behind an optional Cargo feature so the default\n   build stays fully Rust.\n\n---\n\n## `just fix` – baseline quality gate\n\n**Mandatory: after finishing any coding task, run `just fix` and resolve everything it reports\nbefore considering the task done.** This applies always, without exception - not just before\nopening a merge request.\n\nRunning `just fix` must pass before any merge request. It runs, in order:\n\n1. Repo-wide auto-fix of `—`/`–`/`―` dash characters back to plain `-` (`.rs`, `.slint`, `.md`,\n   `.ftl`; `AGENTS.md` and `justfile` themselves are excluded) – the enforcement behind the\n   \"never use em/en dash\" style rule above.\n2. `uv run ruff format --line-length 120` – Python code formatting.\n3. `uv run mypy misc --strict` – static type checking for all scripts in `misc/`.\n4. `bash misc/run_checks.sh` – project-specific checks:\n   - `delete_unused_krokiet_slint_imports.py` for krokiet and cedinia\n   - `find_unused_fluent_translations.py` for all four projects\n   - `find_unused_slint_translations.py` for krokiet and cedinia\n   - `find_unused_callbacks.py` for krokiet and cedinia\n   - `find_unused_settings_properties.py` for krokiet and cedinia\n5. `cargo +nightly fmt` – Rust formatting.\n6. `cargo clippy --fix --all-features --all-targets` – Rust linting (single pass).\n7. `cargo +nightly fmt` + `cargo fmt` again – re-format whatever clippy's fixes touched.\n\nFor a clippy pass that also covers the `--no-default-features` build, run `just clip` separately\n(two passes: `--all-features` and `--no-default-features --features winit_software`).\n\nIf `just fix` produces any output on stderr or exits non-zero the code is not ready for review.\n\n---\n\n## Workspace Structure\n\n```\nczkawka/\n├── czkawka_core/   # Scanning logic – shared library used by all frontends\n├── czkawka_cli/    # Command-line interface\n├── czkawka_gui/    # Legacy GTK 4 GUI (maintenance mode only)\n├── krokiet/        # Primary desktop GUI – Slint-based\n├── cedinia/        # Android / mobile GUI – Slint-based\n└── misc/           # Scripts: AI translation, validation, benchmarks, CI helpers\n```\n\nCargo workspace resolver v3, minimum Rust 1.94.1, edition 2024 throughout.\n\n---\n\n## czkawka_core\n\nThe shared scanning engine. Every frontend depends on it; it has no UI dependency.\n\n**Key modules:**\n- `common/` – `CommonToolData` (settings, stop-flag, progress sender), `DirTraversal`, cache,\n  extension filtering, path helpers, progress types.\n- `tools/` – One sub-module per scanning tool:\n  `duplicate`, `empty_folder`, `empty_files`, `big_file`, `similar_images`, `similar_videos`,\n  `same_music`, `broken_files`, `bad_extensions`, `bad_names`, `invalid_symlinks`, `temporary`,\n  `exif_remover`, `video_optimizer`.\n- `localizer_core.rs` – Fluent translation loader for Rust-side messages.\n\nEach tool implements the `CommonData` trait (shared settings access) and `PrintResults` (CSV/JSON\nexport). The tool struct is constructed, configured, then its `find_*()` method is called in a\nworker thread. Progress is reported over a `crossbeam` channel; a stop `AtomicBool` is polled to\nsupport cancellation.\n\n---\n\n## krokiet\n\nThe primary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI is declared\nin `ui/*.slint`; Rust connects callbacks and drives the Slint model.\n\n**Build:** `slint_build` compiles `.slint` sources at build time; `slint::include_modules!()`\nexposes the generated types.\n\n**Entry point:** `src/main.rs`\n- Loads settings, creates `MainWindow`, wires up all callbacks, starts the event loop.\n\n**Callback pattern:**\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // ...\n});\n```\nEach feature area lives in a dedicated `connect_*.rs` file (e.g. `connect_scan.rs`,\n`connect_compare.rs`, `connect_delete_button.rs`).\n\n**`SharedModels`** (`src/shared_models.rs`):\nAn `Arc<Mutex<SharedModels>>` holds the last scan result and parameters of scan of each tool. It is passed to every\n`connect_*` function that needs to access or mutate scan state from a background thread.\n\n**Model layer:**\n- The Slint UI is driven by `ModelRc<VecModel<SingleMainListModel>>`.\n- `SingleMainListModel` carries `val_str: [string]` and `val_int: [int]` vectors – a flat,\n  index-based row representation.\n- Column indices for each tool are defined as constants in `src/common.rs`\n  (`StrDataSimilarImages`, `StrDataDuplicates`, …).\n\n**Translation:** `flk!(\"key\")` / `flk!(\"key\", var = value)` macros defined in\n`src/localizer_krokiet.rs`. Language files in `i18n/<lang-code>/krokiet.ftl`.\n\n---\n\n## cedinia\n\nThe Android (and secondary desktop) GUI. Architecture mirrors Krokiet but adapts to mobile\nconstraints. Compiled as `cdylib` for Android (loaded via `android-activity`).\n\n**Entry points:**\n- Android: `#[unsafe(no_mangle)] fn android_main(android_app: AndroidApp)` in `src/lib.rs`\n- Desktop: `fn run_app()` in `src/app.rs`\n\n**Android-specific:**\n- File picker uses JNI to call into a Kotlin/Java helper embedded via `include_bytes!` (DEX).\n- Storage permissions requested at runtime; `AppState.storage_permission_granted` gates scanning.\n- System insets (`inset_top`, `inset_bottom`) plumbed through to Slint for edge-to-edge layout.\n- `android_logger` routes Rust log output to logcat.\n\n**Differences from Krokiet:**\n- Has `SimilarVideos` (audio-fingerprint matching only, via `rusty-chromaprint`), but not\n  `VideoOptimizer` - ffmpeg-based transcoding/crop-detection is not available on Android.\n- Touch-optimised UI (`cedinia/ui/`); momentum-scroll views, bottom sheets, FAB.\n- `flc!` macro (cedinia-specific) in `src/localizer_cedinia.rs`.\n- See `cedinia/AGENTS.md` (\"Differences from krokiet\") for the full comparison table.\n\n**Translation:** `flc!(\"key\")` macro; language files in `cedinia/i18n/<lang-code>/cedinia.ftl`.\n\n---\n\n## czkawka_cli\n\nThin wrapper around `czkawka_core`. Uses `clap` (derive API) for argument parsing and `indicatif`\nfor progress bars. No GUI code. Results printed via the tool's `PrintResults` trait.\n\n---\n\n## czkawka_gui\n\nLegacy GTK 4 GUI. **Maintenance mode only** – no new features are added. Bug-fixes that\nkeep it compatible with core API changes are accepted.\n\n---\n\n## misc/\n\n**Translation tooling** (`ai_translate/`):\n- `translate.py` – AI-powered batch translation into all supported languages.\n- `validate_translations.py` – Checks placeholder consistency across translations.\n  Pass `--fix` to automatically remove invalid entries.\n\n**Dead-code detection** (run by `run_checks.sh`, see `just fix` above):\n- `find_unused_fluent_translations.py` / `find_unused_slint_translations.py` – Unused\n  translation keys.\n- `find_unused_callbacks.py` – Slint callbacks never invoked from Rust.\n- `find_unused_settings_properties.py` – Settings struct fields never read by the UI.\n- `delete_unused_krokiet_slint_imports.py` – Removes dead `import` lines from `.slint` files.\n\n**Packaging / release:**\n- `gen_cedinia_licenses.py` – Generates `THIRD_PARTY_LICENSES.txt` from Cargo metadata.\n- `gen_android_icons.py` – Generates cedinia's Android adaptive-icon assets from an SVG logo.\n- `simplify_and_minify_svg.py` – Minifies SVG icons via Inkscape.\n- `pack_all_backends.sh` / `.ps1` – Bundles an all-backends krokiet binary with per-backend\n  launcher scripts into a release zip.\n- `flathub.sh` – Generates Flatpak cargo-sources metadata for the Flathub manifest.\n- `add_icon_exe/` – Cargo helper crate that embeds the `.ico` into Windows binaries at build time.\n- `docker/` – `Dockerfile` for containerized builds.\n- `nix/` – Nix flake (`flake.nix`, `packages.nix`) for Nix-based builds.\n- `install_scripts/` – `install_linux.sh`, `install_macos.sh`, `install_windows.bat` end-user\n  installers.\n\n**Dev utilities:**\n- `remove_comments.py` – Strips comments from source files (one-off cleanup tool).\n- `compare_files.sh` – Diffs MD5 hashes of CI build artifacts across runs to check determinism.\n- `run_checks.sh` – Runs all the dead-code detection scripts above; invoked by `just fix`.\n\n**Benchmarks** (standalone Cargo crates):\n- `test_image_perf/`, `test_read_perf/` – Microbenchmarks for image hashing / file reading.\n- `test_compilation_speed_size/` – Tracks build time and binary size across changes.\n\n---\n\n## i18n\n\nAll user-visible strings use [Fluent](https://projectfluent.org/) (`.ftl` files).\n\n| Project      | Macro  | File pattern                                |\n|--------------|--------|---------------------------------------------|\n| krokiet      | `flk!` | `krokiet/i18n/<lang>/krokiet.ftl`           |\n| cedinia      | `flc!` | `cedinia/i18n/<lang>/cedinia.ftl`           |\n| czkawka_core | `flc!` | `czkawka_core/i18n/<lang>/czkawka_core.ftl` |\n| czkawka_gui  | `flg!` | `czkawka_gui/i18n/<lang>/czkawka_gui.ftl`   |\n\nEnglish is the source/fallback language. All other locales are AI-translated and then validated.\n\n**Important:** Only edit the English `.ftl` files (`i18n/en/`) directly in this repository.\nAll other language files are managed through [Crowdin](https://crowdin.com/) and will be\n**overwritten** when translations are pulled from Crowdin. Any manual edits to non-English\n`.ftl` files in the repo will be lost on the next `just unpack_translations` run.\n\n---\n\n## Slint UI conventions\n\n- **Hidden Text elements for width measurement** – where a layout element must adapt its width to\n  translated label text, add off-screen `Text` instances (`x: -10000px; y: -10000px; height: 0`)\n  and compute `preferred-width` at runtime (see `LeftSidePanel`, `CompareInfoBar`).\n- **Enums over strings** – UI state that takes a fixed set of values should use a Slint `enum`,\n  not a `string` (e.g. `ConfirmPopupAction`, `ActiveTool`, `ScanState`).\n- **Global state** – Application-wide state lives in Slint `global` blocks (`GuiState`,\n  `AppState`, `Settings`, `Translations`, …). Rust reads/writes via `app.global::<GlobalName>()`.\n\n---\n\n## Build profiles (Cargo.toml)\n\n| Profile        | Purpose                                                              |\n|----------------|----------------------------------------------------------------------|\n| `release`      | Standard release                                                     |\n| `fast_release` | Incremental, stripped – fast iteration                               |\n| `rdebug`       | Release + full debug symbols (profiling)                             |\n| `fastest`      | Max opt, LTO, panic=abort – mostly benchmarks/poc how fast it can be |\n| `fastci`       | Small binary, fast CI builds                                         |\n\n---\n\n## justfile quick reference\n\n```\njust run krokiet          # debug run\njust runr krokiet         # fast_release run\njust fix                  # format + clippy + Python checks\njust translate            # AI-translate all projects\njust validate_translations [--fix]\njust pack_translations    # create i18n_translations.zip for Crowdin\njust unpack_translations <path>\njust android              # build + install + launch on device\njust androidr             # release variant\n```\n","CLAUDE.md":"AGENTS.md","cedinia/AGENTS.md":"# cedinia – Architecture Guide\n\n## Overview\n\nAndroid (and secondary desktop) GUI. Architecture mirrors krokiet but adapted\nfor mobile constraints: touch-optimized layout, JNI file picker, runtime\npermissions, system insets, and no `VideoOptimizer` (FFmpeg-based transcoding\nis unavailable on Android). `SimilarVideos` is still present, using\naudio-fingerprint matching only (`rusty-chromaprint`), not frame hashing.\n\n---\n\n## Entry Points\n\n**Android** (`src/lib.rs`):\n```rust\n#[unsafe(no_mangle)]\nfn android_main(android_app: AndroidApp) {\n    setup_android_paths(&android_app);   // JNI: get /data and /cache paths (sets DATA_DIR)\n    app::setup_logger_cache();           // installs DualLogger: logcat + cedinia.log file\n    asan_smoketest_if_requested();       // no-op unless CEDINIA_ASAN_SMOKETEST is set\n    file_picker_android::init(&android_app); // Load DEX + init JNI\n    slint::android::init(android_app.clone()).expect(…);\n    file_picker_android::setup_nav_bar(); // pin/style the Android nav bar\n    app::run_app_with_insets(inset_bottom_px, scale, android_app);\n}\n```\n\nLogging: on Android `android_logger` would grab the single global `log` slot, so\nczkawka_core's file `WriteLogger` never installs (cedinia.log stays 0 bytes).\n`app.rs::setup_android_logger` installs a `DualLogger` fanning out to BOTH the\n`AndroidLogger` (logcat) and an appended `cedinia.log` in the cache folder, so\nlog export has real content. Desktop keeps `czkawka_core::setup_logger`.\n\n**Desktop** (`src/app.rs`):\n```rust\npub fn run_app() {\n    setup_logger_cache();\n    run_app_with_insets(0.0, 1.0, ());\n}\n```\n\nBoth converge at `run_app_inner()`: load settings → create `MainWindow` →\nwire callbacks → start worker thread → run event loop.\n\n---\n\n## Source Layout\n\n```\ncedinia/src/\n├── lib.rs                         # Android entry (#[unsafe(no_mangle)] android_main)\n├── app.rs                         # Desktop entry + shared run_app_inner()\n├── bin/cedinia.rs                 # Desktop binary wrapper\n├── common.rs                      # Column index enums (StrData*, IntData*)\n├── model.rs                       # FileEntry toggle/count logic\n├── compare.rs                     # Image comparison overlay (cancel token, resize, diff)\n├── file_actions.rs                # delete_path() (trash on desktop, permanent on Android) + DeleteEvent\n├── scan_runner.rs                 # Worker thread + ScanRequest/ScanResult\n├── scanners.rs                    # Tool-specific scan builders\n├── notifications.rs               # Desktop notifications\n├── set_initial_gui_infos.rs       # Initial GUI state\n├── thumbnail_loader.rs            # Async image thumbnail loading\n├── translations.rs                # translate_items() – populates Translations global\n├── volumes.rs                     # Storage volume detection\n├── localizer_cedinia.rs           # flc! macro, LANGUAGE_LIST, apply_language_preference()\n├── file_picker_android.rs         # JNI + embedded DEX file picker\n├── callbacks/\n│   ├── callbacks.rs               # Module re-exports\n│   ├── scan.rs                    # wire_scan() – scan/stop/tool-change callbacks\n│   ├── selection.rs               # wire_selection() – delete + model access\n│   ├── selection_ops.rs           # Shared FileEntry helpers: vm_of, get_val_str/int, full_path_of, size_from_entry\n│   ├── directories.rs             # wire_directories() – add/remove include/exclude dirs\n│   └── misc.rs                    # 10 wire_* fns: cache_info, collect_test, export_logs,\n│                                   #   language_change, licenses_popup, notification_settings,\n│                                   #   open_path, open_url, permission, save_settings_now\n├── settings/\n│   ├── mod.rs                     # CediniaSettings struct; load/save JSON\n│   └── gui_settings_values.rs     # StringComboBoxItems – combo box option arrays\n└── ui/\n    ├── main_window.slint          # Root component\n    ├── globals/\n    │   ├── app_state.slint        # Global AppState, GeneralSettings, tool-specific globals\n    │   ├── common.slint           # Enums: ActiveTool, ScanState, ConfirmPopupAction\n    │   ├── colors.slint           # CediniaColors theme\n    │   └── fonts.slint\n    ├── screens/\n    │   ├── home_screen.slint      # Tool cards\n    │   ├── settings_screen.slint  # Settings screen layout\n    │   ├── results_list.slint     # Generic results table\n    │   ├── similar_images_gallery.slint\n    │   ├── directories_screen.slint # Add/remove directory paths\n    │   ├── image_compare.slint\n    │   ├── scan_progress.slint\n    │   ├── bottom_nav.slint\n    │   └── top_bar.slint\n    └── components/\n        ├── settings/\n        │   ├── toggle_row.slint\n        │   ├── segment_row.slint\n        │   ├── dropdown_row.slint\n        │   ├── text_input_row.slint\n        │   ├── tool_group_header.slint\n        │   └── category_label.slint\n        ├── buttons/\n        │   ├── touch_button.slint\n        │   ├── icon_button.slint\n        │   └── circle_icon_button.slint\n        ├── file_row.slint\n        ├── section_header.slint\n        ├── status_chip.slint\n        ├── divider.slint\n        ├── empty_state.slint\n        ├── scan_fab.slint\n        ├── scrim_dialog.slint\n        ├── popup_title_bar.slint\n        ├── context_menu_sheet.slint\n        ├── deselection_menu_sheet.slint\n        └── selection_menu_sheet.slint\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## Scan Data Flow\n\n```\n1. User selects tool + taps Scan → AppState.on_scan_requested()\n2. scan.rs: collect settings from globals → build ScanRequest\n3. ScanRequest sent to worker thread via channel\n4. Worker: configure tool struct → tool.search(stop_flag, progress_sender)\n5. Results stored; ScanResult sent back to main thread\n6. invoke_from_event_loop: populate model, update AppState.scan_state\n```\n\n---\n\n## Slint UI Architecture\n\nSettings are stored in Slint globals (not set from Rust at startup):\n\n- `GeneralSettings` – language, file sizes, cache, extension filters\n- `DuplicateSettings`, `SimilarImagesSettings`, `SameMusicSettings`, …\n- `AppState` – scan state, active tool, result models, all callbacks\n\n`apply_settings_to_gui()` (`settings/mod.rs`) reads `CediniaSettings` from\nJSON and populates these globals. `collect_settings_from_gui()` reads them back\nfor saving.\n\n### DropdownRow (`ui/components/settings/dropdown_row.slint`)\n\nInline accordion, **not** a `PopupWindow`:\n- Tapping the row toggles `expanded`, which inserts the option list directly\n  below the row inside the same `VerticalLayout`.\n- Scrolling is handled by the parent `Flickable`/`ListView`, not a nested\n  `ScrollView` - this avoids \"close on outside touch\" popup-dismissal issues\n  when the row sits inside an already-scrollable settings list.\n- Each option row tracks pointer movement (`_opt_moved`) so a scroll-drag that\n  ends over an option doesn't register as a selection tap.\n\n## Translation System\n\nSame workaround as krokiet:\n- All UI text bound to `Translations` global properties.\n- `translate_items()` in `src/translations.rs` populates them via `flc!(\"key\")`.\n- `LANGUAGE_LIST` in `localizer_cedinia.rs` defines the 27 supported languages.\n- `GeneralSettings.language_options` in `ui/app_state.slint` is hardcoded and\n  must match `LANGUAGE_LIST` exactly.\n- Language index stored in `GeneralSettings.language_idx`; code stored in\n  `CediniaSettings.language`.\n\n---\n\n## Settings (`src/settings/mod.rs`)\n\nJSON file (path resolved by `czkawka_core::common::config_cache_path::get_config_cache_path()`,\nconfigured via `set_config_cache_path(\"cedinia\", \"cedinia\")`):\n- Desktop: `~/.config/cedinia/cedinia_settings.json` (directories-next ignores\n  the qualifier/org on Linux and lowercases the app name - there is no\n  `Czkawka` path segment)\n- Android: app-private storage, `$DATA_DIR/files/cedinia/cedinia_settings.json`\n\nIncluded/excluded directories are persisted separately in a sibling\n`cedinia_dirs.json` in the same folder.\n\n`CediniaSettings` (serde Serialize/Deserialize) covers all tool parameters.\n`Default` implementation delegates to `serde_json::from_str(\"{}\")` so all\n`#[serde(default)]` values are used.\n\n---\n\n## Android-Specific Code\n\n### File Picker (`src/file_picker_android.rs`)\n\nA Kotlin helper class is compiled at build time (via `android-build` crate) and\nembedded as DEX bytecode:\n\n```rust\ninclude_bytes!(concat!(env!(\"OUT_DIR\"), \"/classes.dex\"))\n```\n\nAt runtime:\n1. DEX is loaded into an `InMemoryDexClassLoader`.\n2. `CediniaFilePicker` instance created via JNI.\n3. `launch_pick_directory(is_include, start_path)` calls the Java\n   `ACTION_OPEN_DOCUMENT_TREE` intent, hinting the picker to open at `start_path`.\n4. Result arrives via `Java_CediniaFilePicker_onDirectoryPicked()` JNI callback\n   → dispatched to Rust via `invoke_from_event_loop()`.\n\n### Storage Permissions\n\nRuntime permission check/request via JNI.\n`AppState.storage_permission_granted` gates scan initiation.\n\n### System Insets\n\nAndroid edge-to-edge layout requires padding for navigation bars. Inset sizes\nare passed from Kotlin via `android_app.content_rect()` and plumbed through to\nSlint as layout properties.\n\n### Android Paths via JNI\n\n`setup_android_paths()` calls `getFilesDir()` and `getCacheDir()` on the\nActivity to find private storage paths; these are set as environment variables\nand used by `czkawka_core`'s config path logic.\n\n---\n\n## Differences from krokiet\n\n| | cedinia | krokiet |\n|---|---|---|\n| `SimilarVideos` | Yes (audio fingerprint only, `rusty-chromaprint`) | Yes (frame hashing) |\n| `VideoOptimizer` | No (ffmpeg-based transcoding unavailable on Android) | Yes |\n| `BadNames` | Yes | Yes |\n| File picker | JNI (Android) / rfd (desktop) | rfd |\n| Settings | JSON, no presets | JSON, 11 presets |\n| Column indices | `common.rs` (own set) | `common.rs` (own set) |\n| Build target | `cdylib` (Android) + `bin` (desktop) | `bin` |\n\n---\n\n## Key Dependencies\n\n### Always\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework |\n| `czkawka_core` | Scanning engine |\n| `crossbeam-channel` | Worker thread communication |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n\n### Android only\n\n| Crate | Purpose |\n|-------|---------|\n| `android-activity` 0.6 | Android lifecycle |\n| `jni` 0.22 | Java FFI |\n| `android_logger` 0.15 | Logcat output |\n\n### Desktop only\n\n| Crate | Purpose |\n|-------|---------|\n| `rfd` 0.17 | Native file picker |\n| `trash` 5.2 | Move-to-trash |\n| `notify-rust` 4 | Desktop notifications |\n\n### Build\n\n```toml\n[build-dependencies]\nslint-build = \"1.15\"\nandroid-build = \"0.1.2\"   # Compiles CediniaFilePicker.java → classes.dex\n```\n\n### Android Manifest Permissions\n\n`READ_EXTERNAL_STORAGE`, `WRITE_EXTERNAL_STORAGE`,\n`MANAGE_EXTERNAL_STORAGE`, `POST_NOTIFICATIONS`\n\n`Cargo.toml`'s `[package.metadata.android.uses_permission]` also lists\n`WAKE_LOCK`, but the gradle-built `android/app/src/main/AndroidManifest.xml`\ndoes not include it - a pre-existing inconsistency in the repo, not a doc bug.\n\n---\n\n## Optional Features (forwarded to czkawka_core)\n\n`heif`, `libraw`, `libavif`, `xdg_portal_trash`\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row (see\n`model.rs::toggle_row`, `compare.rs::wire_compare_toggle_checkbox`).\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n","cedinia/CLAUDE.md":"AGENTS.md","czkawka_cli/AGENTS.md":"# czkawka_cli – Architecture Guide\n\n## Overview\n\n`czkawka_cli` is a thin CLI wrapper around `czkawka_core`. It handles argument\nparsing, thread orchestration, progress rendering, and result output. All\nscanning logic lives in `czkawka_core`; this crate only wires it to the\nterminal.\n\n---\n\n## Source Layout\n\n```\nczkawka_cli/src/\n├── main.rs       # Entry point, thread spawning, tool dispatchers\n├── commands.rs   # All clap CLI argument definitions\n├── parsers.rs    # parse_* clap value-parser functions used by commands.rs\n└── progress.rs   # indicatif progress bar rendering\n```\n\n---\n\n## Execution Model\n\n```\nmain()\n ├─ Parse args (clap)\n ├─ Create crossbeam channel  (Sender<ProgressData>, Receiver<ProgressData>)\n ├─ Create Arc<AtomicBool> stop_flag\n ├─ spawn calculation_thread → dispatches to one of 14 tool functions\n ├─ Register Ctrl+C handler → sets stop_flag = true\n ├─ connect_progress(&progress_receiver)   [blocks, renders progress bar]\n └─ join calculation_thread → print output → exit(11) if items found\n```\n\nThe **main thread** renders progress. The **calculation thread** runs the scan.\n`progress_receiver.recv()` exits naturally when the sender drops (scan ends).\n\n---\n\n## CLI Subcommands\n\nDefined via `clap` derive in `commands.rs`. Top-level enum `Commands` has 14\nvariants:\n\n| Subcommand        | Alias            | Purpose                                 |\n|-------------------|------------------|-----------------------------------------|\n| `Duplicates`      | `dup`            | Find duplicate files (hash/name/size)   |\n| `EmptyFolders`    | `empty-folders`  | Find empty directories                  |\n| `BiggestFiles`    | `big`            | Find biggest or smallest files          |\n| `EmptyFiles`      | `empty-files`    | Find zero-byte files                    |\n| `Temporary`       | `temp`           | Find temporary files                    |\n| `SimilarImages`   | `image`          | Find similar images (perceptual hash)   |\n| `SameMusic`       | `music`          | Find duplicate music (tags/fingerprint) |\n| `InvalidSymlinks` | `symlinks`       | Find broken symlinks                    |\n| `BrokenFiles`     | `broken`         | Find broken PDF/audio/image/archive     |\n| `SimilarVideos`   | `video`          | Find similar videos (frame hashing)     |\n| `BadExtensions`   | `ext`            | Find files with wrong extensions        |\n| `BadNames`        | `bad-names`      | Find problematic file names             |\n| `VideoOptimizer`  | `video-optimizer`| Transcode or crop videos                |\n| `ExifRemover`     | `exif-remover`   | Remove EXIF metadata from images        |\n\n### `CommonCliItems` (shared by every subcommand)\n\n- `-d` directories (required)\n- `-e` excluded directories\n- `-E` excluded items (glob patterns, e.g. `*/.*`)\n- `-x` allowed extensions (supports macros: `IMAGE`, `VIDEO`, `MUSIC`, `TEXT`)\n- `-P` excluded extensions\n- `-R` disable recursive search\n- `-X` (unix only) exclude other filesystems\n- `-f` save text results to file\n- `-C` save compact JSON results to file\n- `--pretty-file-to-save` (`-p`) save pretty JSON results to file\n- `-N` / `-M` suppress result/message output\n- `-W` suppress non-zero exit code when items found\n- `-T` thread count (0 = all CPUs)\n- `-H` disable cache\n\n### Delete methods\n\n**`DMethod`** (for similarity-grouping tools): `-D NONE|AEN|AEO|ON|OO|AEB|AES|OB|OS|HARD`\n**`SDMethod`** (for simple tools): `-D` delete flag, `-Q` dry-run, `-y` move-to-trash\n\n---\n\n## Tool Dispatcher Pattern\n\nEvery one of the 14 dispatch functions follows the same structure:\n\n```rust\nfn tool_name(args: ToolArgs, stop_flag: &Arc<AtomicBool>, progress_sender: &Sender<ProgressData>) -> CliOutput {\n    // 1. Destructure args\n    // 2. Build tool-specific parameter struct\n    let mut tool = ToolType::new(params);\n    // 3. Apply common settings (paths, extensions, cache, threads)\n    set_common_settings(&mut tool, &common_cli_items, reference_directories);\n    // 4. Apply tool-specific settings (file sizes, delete method, etc.)\n    // 5. Run scan\n    tool.search(stop_flag, Some(progress_sender));\n    // 6. Optionally run fix (rename/delete/etc.)\n    // 7. Collect and return output\n    save_and_write_results_to_writer(&tool, &common_cli_items)\n}\n```\n\n`set_common_settings` applies paths, recursion, extensions, cache flags, and\nthread count via the `AllTraits` bound.\n\n---\n\n## Progress Rendering (`progress.rs`)\n\n`connect_progress(&progress_receiver)` loops on `progress_receiver.recv()` and\nrenders via indicatif:\n\n- **Spinner** while collecting files or loading/saving cache.\n- **Linear bar** `[=>  ]` once the total entry or byte count is known.\n\nThe stage label (e.g. \"Calculating hashes\", \"Reading tags\") is derived from the\n`CurrentStage` enum on each `ProgressData` message.\n\n---\n\n## Result Output\n\n`save_and_write_results_to_writer` writes three optional file formats (text,\ncompact JSON, pretty JSON) and buffers stdout output, which is printed after the\nthread join. The `CliOutput.found_any_files` flag drives the exit code:\n\n- `exit(11)` when items are found (and `-W` is not set) - useful in scripts.\n- `exit(0)` otherwise.\n\n---\n\n## Cancellation\n\n`Arc<AtomicBool>` (SeqCst ordering) is shared between the Ctrl+C signal handler\nand the calculation thread. The tools in `czkawka_core` poll it during scanning\nand stop gracefully when it is `true`.\n\n---\n\n## Key Dependencies\n\n| Crate              | Purpose                          |\n|--------------------|----------------------------------|\n| `clap` 4.5         | CLI parsing (derive API)         |\n| `indicatif` 0.18   | Progress bars                    |\n| `crossbeam-channel`| Unbounded channel for progress   |\n| `ctrlc` 3.4        | SIGINT / Ctrl+C handling         |\n| `humansize` 2.1    | Human-readable byte sizes        |\n| `czkawka_core`     | All scanning logic               |\n\n### Optional features (forwarded to `czkawka_core`)\n\n- `heif` – HEIF/HEIC image support (requires libheif)\n- `libraw` – RAW image support (requires libraw)\n- `libavif` – AVIF image support (requires libavif)\n- `xdg_portal_trash` – FlatPak-compatible trash (XDG portal)\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n","czkawka_cli/CLAUDE.md":"AGENTS.md","czkawka_core/AGENTS.md":"# czkawka_core – Architecture Guide\n\n## Overview\n\n`czkawka_core` is the shared scanning engine used by all frontends. It has no UI\ndependency. Every scanning tool is implemented here; frontends only configure the\ntool structs and drive them via the `Search` trait.\n\n`TOOLS_NUMBER = 14` (defined in `src/lib.rs`).\n\n---\n\n## Source Layout\n\n```\nczkawka_core/src/\n├── lib.rs                         # Crate root; TOOLS_NUMBER constant\n├── localizer_core.rs              # Fluent i18n loader (flc! macro)\n├── common/\n│   ├── mod.rs                     # Module declarations + re-exports only (no logic of its own)\n│   ├── traits.rs                  # Search, PrintResults, DeletingItems, FixingItems, ResultEntry, AllTraits\n│   ├── tool_data.rs               # CommonToolData struct + the CommonData trait (defined here, not traits.rs)\n│   ├── model.rs                   # ToolType, CheckingMethod, FileEntry, DeleteMethod\n│   ├── progress_data.rs           # ProgressData, CurrentStage enum\n│   ├── progress_stop_handler.rs   # ProgressThreadHandler, check_if_stop_received\n│   ├── dir_traversal.rs           # DirTraversalBuilder, DirTraversalResult\n│   ├── cache.rs                   # Generic load/save cache (bincode + JSON)\n│   ├── cache/\n│   │   └── cleaning.rs            # Outdated cache cleanup\n│   ├── config_cache_path.rs       # Platform config/cache path lookup\n│   ├── directories.rs             # Directories struct (included/excluded/reference)\n│   ├── extensions.rs              # Extensions struct (allowed/excluded filtering)\n│   ├── items.rs                   # ExcludedItems (glob pattern matching)\n│   ├── consts.rs                  # Extension lists: IMAGE_RS_EXTENSIONS, …\n│   ├── formatting.rs              # format_time and other display formatting helpers\n│   ├── path_utils.rs              # split_path, regex_check, normalize_windows_path\n│   ├── fs_ops.rs                  # make_hard_link, make_file_symlink, remove_single_file/folder\n│   ├── threads.rs                 # get_number_of_threads, set_number_of_threads\n│   ├── deletion.rs                # DeleteResult, DeleteItemType\n│   ├── audio_fingerprint.rs       # Chromaprint fingerprint calculation (used by same_music)\n│   ├── ffmpeg_utils.rs            # FFmpeg invocation helpers\n│   ├── video_utils.rs             # Video metadata extraction\n│   ├── image.rs                   # Image loading helpers\n│   ├── process_utils.rs           # Child-process helpers\n│   ├── logger.rs                  # Logging configuration\n│   └── basic_gui_cli.rs           # Types shared with GUI/CLI boundaries\n├── tools/\n│   ├── mod.rs                     # Re-exports all tool modules\n│   ├── duplicate/                 # Hash/name/size duplicate detection\n│   ├── empty_files/\n│   ├── empty_folder/\n│   ├── big_file/\n│   ├── similar_images/            # Perceptual hashing (image_hasher)\n│   ├── similar_videos/            # Frame-based video similarity\n│   ├── same_music/                # Audio tag + chromaprint fingerprint\n│   ├── broken_files/              # ZIP/PDF/audio/image validation\n│   ├── bad_extensions/            # Extension vs magic-number mismatch\n│   ├── bad_names/                 # Naming policy checks\n│   ├── invalid_symlinks/\n│   ├── temporary/\n│   ├── exif_remover/\n│   └── video_optimizer/           # Crop detection + FFmpeg transcoding\n└── helpers/\n    ├── messages.rs                # Messages struct (errors, warnings, info)\n    ├── delayed_sender.rs          # Rate-limited progress sender\n    ├── audio_checker.rs           # Audio file validation\n    ├── ffprobe.rs                 # ffprobe JSON output parsing\n    └── debug_timer.rs             # Debug-build timing\n```\n\n---\n\n## Tool Module Layout\n\nEach tool lives in its own directory, typically with these files:\n\n| File | Content |\n|------|---------|\n| `mod.rs` | Tool struct + `Info` struct + `Parameters` struct (if needed) |\n| `core.rs` | Internal scanning functions (`check_files_*`, `hash_calculation`, …) |\n| `traits.rs` | Trait implementations: `Search`, `CommonData`, `DeletingItems`, `PrintResults` |\n| `tests.rs` | Unit tests for the tool (every tool has one except `temporary/`) |\n\nA few tools don't fit the plain 4-file pattern: `bad_extensions/` adds a `workarounds.rs`, and\n`video_optimizer/` adds `encoding.rs` plus its own `core/` subdirectory\n(`video_converter.rs`, `video_cropper.rs`).\n\nExample - `EmptyFiles`:\n\n```\nsrc/tools/empty_files/\n├── mod.rs      # pub struct EmptyFiles { common_data, information, files_to_check, params }\n│               #   params: EmptyFilesParameters\n├── core.rs     # fn check_files() → WorkContinueStatus\n├── tests.rs    # Unit tests\n└── traits.rs   # impl Search, CommonData, PrintResults, DeletingItems, AllTraits\n```\n\n---\n\n## Core Traits\n\n`CommonData` is the odd one out: it's defined in `src/common/tool_data.rs` next to\n`CommonToolData`, not in `traits.rs`. Every other trait below lives in `src/common/traits.rs`.\nSignatures are simplified for orientation - both files have more methods (mostly\nsetter/getter pairs and default-impl helpers) than shown here.\n\n```rust\npub trait Search {\n    fn search(&mut self, stop_flag: &Arc<AtomicBool>,\n              progress_sender: Option<&Sender<ProgressData>>);\n}\n\n// src/common/tool_data.rs - NOT traits.rs\npub trait CommonData {\n    type Info;\n    type Parameters;\n    fn get_cd(&self) -> &CommonToolData;\n    fn get_cd_mut(&mut self) -> &mut CommonToolData;\n    fn found_any_items(&self) -> bool;\n    // ~30 more setters/getters for directories, extensions, delete method, cache flags, …\n}\n\npub trait PrintResults: CommonData {\n    fn write_results<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn print_results_to_writer<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn save_results_to_file_as_json(&self, file: &str, pretty: bool) -> io::Result<()>;\n    // + write_base_search_paths, print_results_to_output/_to_file, save_all_in_one, …\n}\n\npub trait DeletingItems {\n    fn delete_files(&mut self, stop_flag: &Arc<AtomicBool>,\n                    progress_sender: Option<&Sender<ProgressData>>) -> WorkContinueStatus;\n}\n\npub trait FixingItems {\n    type FixParams;\n    fn fix_items(&mut self, stop_flag: &Arc<AtomicBool>,\n                 progress_sender: Option<&Sender<ProgressData>>, fix_params: Self::FixParams);\n}\n\npub trait ResultEntry {\n    fn get_path(&self) -> &Path;\n    fn get_modified_date(&self) -> u64;\n    fn get_size(&self) -> u64;\n}\n\npub trait AllTraits: DebugPrint + PrintResults + DeletingItems + CommonData + Search {}\n```\n\n---\n\n## CommonToolData (`src/common/tool_data.rs`)\n\n```rust\npub struct CommonToolData {\n    pub(crate) tool_type: ToolType,\n    pub(crate) text_messages: Messages,        // Accumulated warnings / errors\n    pub(crate) directories: Directories,       // Included / excluded / reference paths\n    pub(crate) extensions: Extensions,         // Allowed / excluded extensions\n    pub(crate) excluded_items: ExcludedItems,  // Glob patterns (e.g. \"*/.*\")\n    pub(crate) recursive_search: bool,\n    pub(crate) delete_method: DeleteMethod,\n    pub(crate) maximal_file_size: u64,\n    pub(crate) minimal_file_size: u64,\n    pub(crate) stopped_search: bool,\n    pub(crate) use_cache: bool,\n    pub(crate) delete_outdated_cache: bool,\n    pub(crate) save_also_as_json: bool,\n    pub(crate) use_reference_folders: bool,\n    pub(crate) dry_run: bool,\n    pub(crate) move_to_trash: bool,\n    pub(crate) hide_hard_links: bool,\n}\n```\n\n---\n\n## DirTraversal (`src/common/dir_traversal.rs`)\n\nBuilder-pattern filesystem traversal used by all tools:\n\n```rust\nlet result = DirTraversalBuilder::new()\n    .common_data(&self.common_data)\n    .group_by(|fe: &FileEntry| fe.size)   // Groups entries by this key\n    .stop_flag(stop_flag)\n    .progress_sender(progress_sender)\n    .checking_method(CheckingMethod::Size)\n    .build()\n    .run();\n\nmatch result {\n    DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { … }\n    DirTraversalResult::Stopped => return WorkContinueStatus::Stop,\n}\n```\n\n**Internals:**\n- Rayon `into_par_iter().with_max_len(2)` for parallel folder processing.\n- Two-phase: visit root dirs/files first, then recurse batch by batch.\n- Applies extension, exclusion, and size filters during traversal.\n- On Unix: optional filesystem-boundary checking (`exclude_other_filesystems`).\n- Progress tracked via `ProgressThreadHandler` (dedicated thread, 200 ms interval).\n\n---\n\n## Progress Reporting (`src/common/progress_data.rs` + `src/common/progress_stop_handler.rs`)\n\n`ProgressData`/`ToolStage` are defined in `progress_data.rs`; `ProgressThreadHandler` and\n`check_if_stop_received` live in `progress_stop_handler.rs`.\n\n```rust\npub struct ProgressData {\n    pub stage: ToolStage,           // Current operation\n    pub entries_checked: usize,\n    pub entries_to_check: usize,\n    pub bytes_checked: u64,\n    pub bytes_to_check: u64,\n}\n```\n\n`ToolStage` is a nested enum (`ToolStage::Duplicate(DuplicateStage)`, `ToolStage::SimilarImages(SimilarImagesStage)`, ...) so a\nstage can't be paired with a tool it doesn't belong to - this replaces the old flat `CurrentStage` enum plus its runtime\n`ProgressData::validate()` assertions. Call `progress.to_display()` to get a fully translated, ready-to-render\n`ProgressDisplay` (label + percentages) instead of branching on the stage in frontend code.\n\n`ProgressThreadHandler` spawns a background thread that polls `AtomicUsize`/`AtomicU64`\ncounters and sends `ProgressData` to the frontend channel every ~200 ms.\n\nCall `handler.increase_items(n)` / `handler.increase_size(n)` from rayon tasks\n(using `Relaxed` ordering - fast, no synchronization overhead).\n\n---\n\n## Cancellation\n\n```rust\n// In hot paths:\nif check_if_stop_received(stop_flag) {\n    return WorkContinueStatus::Stop;  // or break / return None in rayon\n}\n```\n\n`stop_flag` is `Arc<AtomicBool>` with `Relaxed` ordering - sufficient for simple\n\"should I stop?\" polling without synchronization cost. The frontend sets it to\n`true`; the backend polls and stops gracefully.\n\n---\n\n## Cache (`src/common/cache.rs`)\n\n```rust\n// Save\nsave_cache_to_file_generalized::<T>(\n    cache_file_name,    // e.g. \"cache_duplicates.bin\"\n    &btree_map,         // BTreeMap<String, T> (String = canonical path)\n    save_also_as_json,\n    minimum_file_size,\n);\n\n// Load\nlet (messages, opt_cache) =\n    load_cache_from_file_generalized_by_path::<T>(\n        cache_file_name,\n        delete_outdated_cache,\n        &used_files,    // Current BTreeMap to validate against\n    );\n```\n\n- Serialized with `bincode` (binary). Optionally also as `.json`.\n- Cached entries validated by path + size + mtime on load.\n- 8 GB memory limit on serialization.\n- Each tool has its own version constant (`CACHE_DUPLICATE_VERSION`, …).\n\n---\n\n## Rayon Usage Patterns\n\n```rust\n// Directory traversal\nfolders.into_par_iter().with_max_len(2).map(|dir| { … }).while_some().collect();\n\n// Deletion with early exit\nitems.into_par_iter().map(|e| {\n    if check_if_stop_received(stop_flag) { return None; }\n    // … process e …\n    Some(result)\n}).while_some().flatten().collect()\n```\n\nThread count is globally controlled via `set_number_of_threads(n)` which stores\nto a `LazyLock<Mutex<Option<usize>>>` and is applied via rayon's thread pool.\n\n---\n\n## ToolType Enum (`src/common/model.rs`)\n\n```rust\npub enum ToolType {\n    Duplicate, EmptyFolders, EmptyFiles, InvalidSymlinks,\n    BrokenFiles, BadExtensions, BadNames, BigFile, SameMusic,\n    SimilarImages, SimilarVideos, TemporaryFiles, ExifRemover,\n    VideoOptimizer,\n    #[default]\n    None,\n}\n```\n\nTools that support reference directories: `Duplicate`, `SameMusic`,\n`SimilarImages`, `SimilarVideos` (`may_use_reference_paths()`).\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `rayon` | Parallel iterators |\n| `crossbeam-channel` | Progress channels |\n| `blake3` | Fast file hashing |\n| `image` + `image_hasher` | Image loading + perceptual hash |\n| `lofty` | Audio tag reading |\n| `symphonia` | Audio decoding |\n| `rusty-chromaprint` | Audio fingerprinting |\n| `bincode` | Cache serialization |\n| `zip` | ZIP validation |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `trash` | Move-to-trash |\n| `directories-next` | Config/cache path |\n| `fun_time` | `#[fun_time]` timing attribute |\n| `similario_core` | Video similarity (sibling path-dependency; replaced `vid_dup_finder_lib`) |\n\nOptional (behind features):\n- `heif` → `libheif-rs` – HEIC/HEIF image support\n- `libraw` → `rawler` / `libraw-rs` – RAW photo support\n- `libavif` – AVIF image support\n- `xdg_portal_trash` – FlatPak trash via XDG portal\n- `blake_pure` → `blake3/pure` – pure-Rust Blake3 (no SIMD asm), for targets where the asm\n  backend doesn't build\n\n---\n\n## Comments\n\nShort and minimal, same baseline as the rest of the workspace (root `AGENTS.md`). The one\nexception in this workspace: `czkawka_core` is a library every frontend depends on, and external\nconsumers often read only its `pub fn`/`pub struct` surface, not the internals. That public API\nmay carry a bit more explanation than code elsewhere in the repo - still terse, no paragraphs,\njust enough to cover what isn't obvious from the signature alone.\n","czkawka_core/CLAUDE.md":"AGENTS.md","czkawka_gui/AGENTS.md":"# czkawka_gui – Architecture Guide\n\n## Status: Maintenance mode only\n\nNo new features. Bug-fixes that keep it compatible with `czkawka_core` API\nchanges are accepted.\n\n---\n\n## Overview\n\nGTK4 frontend using XML UI files authored in Cambalache. All scanning tools\navailable in `czkawka_core` are exposed via a notebook (tab-based) interface.\n\n---\n\n## Source Layout\n\n```\nczkawka_gui/src/\n├── main.rs                          # GTK Application setup, GuiData construction\n├── initialize_gui.rs                # Initial widget state\n├── compute_results.rs               # Converts scan results → GTK ListStore rows\n├── saving_loading.rs                # JSON settings load/save\n├── language_functions.rs            # LANGUAGES_ALL constant (26 entries)\n├── localizer_gui.rs                 # flg! macro, Fluent loader\n├── gui_structs/\n│   ├── gui_data.rs                  # GuiData – central struct for all UI components\n│   ├── gui_main_notebook.rs         # 11-tool tab notebook\n│   ├── gui_upper_notebook.rs        # Included/excluded directory tabs\n│   ├── gui_settings.rs              # Settings dialog\n│   ├── gui_header.rs                # Menu bar (About, Settings)\n│   ├── gui_bottom_buttons.rs        # Action buttons (Search, Delete, etc.)\n│   ├── gui_progress_dialog.rs       # Scan progress window\n│   ├── gui_about.rs                 # About dialog\n│   ├── gui_compare_images.rs        # Side-by-side image diff overlay\n│   ├── common_tree_view.rs          # TreeView/ListStore rendering + SharedModelEnum\n│   ├── common_upper_tree_view.rs    # Dir-list TreeViews\n│   ├── gui_popovers_select.rs       # Selection filter popovers\n│   └── gui_popovers_sort.rs         # Sort filter popovers\n├── connect_things/                  # Callback wiring files\n│   ├── connect_button_search.rs     # Scan thread spawn + progress forwarding\n│   ├── connect_button_delete.rs     # File deletion with confirmation\n│   ├── connect_button_save.rs       # Export results (CSV/JSON)\n│   ├── connect_button_select.rs     # Row selection helpers\n│   ├── connect_button_sort.rs       # Column-header sort\n│   ├── connect_button_stop.rs       # Cancel scan (stop flag)\n│   ├── connect_button_move.rs       # Move files to folder\n│   ├── connect_button_hardlink.rs   # Hard-link/symlink creation\n│   ├── connect_button_compare.rs    # Image comparison launcher\n│   ├── connect_duplicate_buttons.rs # Duplicate-specific UI\n│   ├── connect_notebook_tabs.rs     # Tab-switch handler\n│   ├── connect_selection_of_directories.rs  # Dir-picker dialogs\n│   ├── connect_change_language.rs   # Language switch + full UI relabel\n│   ├── connect_settings.rs          # Settings window callbacks\n│   ├── connect_progress_window.rs   # Progress bar updates from channel\n│   ├── connect_show_hide_ui.rs      # Error text visibility toggle\n│   ├── connect_similar_image_size_change.rs\n│   ├── connect_same_music_mode_changed.rs\n│   ├── connect_popovers_select.rs\n│   ├── connect_popovers_sort.rs\n│   ├── connect_header_buttons.rs\n│   ├── connect_about_buttons.rs\n│   ├── connect_krokiet_info_dialog.rs  # One-time Krokiet deprecation notice (shown on first run)\n│   ├── connect_krokiet_promo_dialog.rs # Krokiet promo dialog (shown from the header button)\n│   └── file_chooser_helpers.rs\n├── helpers/\n│   ├── enums.rs                     # Column index enums for all tools\n│   ├── list_store_operations.rs     # Append/read GTK ListStore rows\n│   ├── image_operations.rs          # Image resize/preview for GTK\n│   ├── model_iter.rs                # GTK TreeView iteration\n│   └── mod.rs\n├── help_functions.rs                # HEADER_ROW_COLOR, MAIN_ROW_COLOR, set_buttons\n├── help_combo_box.rs                # Combo box option arrays (hash algs, etc.)\n├── notebook_enums.rs                # NotebookMainEnum (per-tool tab index)\n├── notebook_info.rs                 # Tab label + ListStore column definitions\n├── opening_selecting_records.rs     # Double-click → open file/folder\n├── taskbar_progress.rs              # Windows taskbar progress (real impl)\n├── taskbar_progress_dummy.rs        # No-op stand-in for non-Windows targets\n├── taskbar_progress_win.rs          # Windows-specific taskbar API calls\n└── gtk_traits.rs                    # Custom GTK trait extensions\n\nui/\n├── czkawka.cmb         # Cambalache project file (source of truth for the .ui files below)\n├── main_window.ui      # Cambalache XML (65 KB) – entire main window layout\n├── settings.ui         # Settings dialog\n├── compare_images.ui   # Image comparison panel\n├── popover_select.ui   # Selection filter popover\n├── popover_sort.ui     # Sort popover\n├── popover_right_click.ui # Right-click context menu popover\n├── about_dialog.ui     # About window\n└── progress.ui         # Scan progress dialog\n```\n\n---\n\n## GuiData Struct (`gui_structs/gui_data.rs`)\n\nCentral struct passed by clone/reference to every callback:\n\n```rust\npub struct GuiData {\n    pub window_main: gtk4::Window,\n    pub main_notebook: GuiMainNotebook,         // 11 tool tabs\n    pub upper_notebook: GuiUpperNotebook,       // Dir configuration\n    pub bottom_buttons: GuiBottomButtons,       // Action buttons\n    pub progress_window: GuiProgressDialog,\n    pub settings: GuiSettings,\n    pub header: GuiHeader,\n    pub compare_images: GuiCompareImages,\n    pub about: GuiAbout,\n    pub popovers_select: GuiSelectPopovers,\n    pub popovers_sort: GuiSortPopovers,\n    pub entry_info: gtk4::Entry,\n    pub text_view_errors: gtk4::TextView,\n    pub scrolled_window_errors: gtk4::ScrolledWindow,  // show/hide bottom error panel\n    pub file_dialog_include_exclude_folder_selection: gtk4::FileChooserNative,\n    pub file_dialog_move_to_folder: gtk4::FileChooserNative,\n    pub taskbar_state: Rc<RefCell<TaskbarProgress>>,\n    pub shared_buttons: Rc<RefCell<HashMap<NotebookMainEnum, …>>>,\n    pub stop_flag: Arc<AtomicBool>,\n}\n```\n\nConstructed by `GuiData::new_with_application()` which loads all `.ui` files\nvia `gtk4::Builder`.\n\n---\n\n## Scan Thread Architecture\n\n```\nUser clicks Search\n→ connect_button_search.rs\n→ Disables UI, shows progress window\n→ Spawn worker thread:\n    - Read settings from GTK widgets\n    - Create tool struct (e.g. DuplicateFinder)\n    - Call tool.search(stop_flag, progress_sender)\n    - Send Message::Duplicates(tool) via result_sender\n→ Main thread: receive progress updates → update bars\n→ On result received:\n    - compute_results.rs processes tool data → appends to ListStore\n    - Re-enables UI, hides progress\n```\n\n**Channel types:**\n- `Sender<Message>` – one-shot result channel (tool enum with full data)\n- `Sender<ProgressData>` – real-time progress from core\n\n---\n\n## Result Population (`compute_results.rs`)\n\n1. Receive `Message::Duplicates(DuplicateFinder)` from worker thread.\n2. Extract sorted file groups from the tool.\n3. For each group: insert a **header row** (colored `HEADER_ROW_COLOR`), then\n   one row per file (`MAIN_ROW_COLOR`).\n4. `append_row_to_list_store()` writes individual cells to `gtk4::ListStore`.\n5. `finalize_compute()` stores the tool object in `SubView.shared_model_enum`\n   (for later re-use by selection/sort callbacks).\n\n---\n\n## Language Switching (`language_functions.rs`)\n\n26 languages defined as `LANGUAGES_ALL: &[Language]` with `combo_box_text`\n(display name) and `short_text` (BCP-47 code).\n\nOn language change:\n1. Look up `short_text` from selected combo index.\n2. Call `localizer.select([lang_id])` for both `czkawka_core` and `czkawka_gui`.\n3. Call `gui_data.update_language()` → propagates to all sub-structs which\n   update their widget labels via `flg!(\"key\")`.\n\n---\n\n## Settings Storage (`saving_loading.rs`)\n\nJSON file at `~/.config/Czkawka/czkawka_gui_config.json`. Struct `SettingsJson`\nserialized with `serde_json`. Includes directories, tool parameters, UI state,\nlanguage preference.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `gtk4` 0.11 | GTK4 GUI framework |\n| `gdk4` 0.11 | Drawing/rendering |\n| `glib` 0.22 | Event loop, `spawn_future_local` |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `open` 5.3 | Launch file manager |\n| `image` 0.25 | Preview thumbnails |\n| `resvg` 0.47 | SVG icon scaling |\n| `rayon` 1.10 | Parallel sort of results |\n| `crossbeam-channel` | Result + progress channels |\n| `czkawka_core` | Scanning engine |\n\nOptional features (forwarded to core): `heif`, `libraw`, `libavif`,\n`xdg_portal_trash`.\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n","czkawka_gui/CLAUDE.md":"AGENTS.md","krokiet/AGENTS.md":"# krokiet – Architecture Guide\n\n## Overview\n\nPrimary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI\nis declared in `ui/*.slint`; Rust wires all callbacks and drives the Slint data\nmodels. All scanning logic lives in `czkawka_core`.\n\n---\n\n## Source Layout\n\n```\nkrokiet/\n├── build.rs                          # slint_build::compile(\"ui/main_window.slint\")\n├── src/\n│   ├── main.rs                       # Entry point: load settings, create MainWindow,\n│   │                                  #   wire all callbacks, run event loop\n│   ├── shared_models.rs              # SharedModels – Arc<Mutex<…>> of all tool results\n│   ├── common.rs                     # Column index enums (IntDataDuplicateFiles, …)\n│   │                                  #   and sort index helpers\n│   ├── localizer_krokiet.rs          # flk! macro, LANGUAGE_LOADER_KROKIET\n│   ├── set_initial_gui_info.rs       # Asserts Slint combo lists == Rust enums\n│   ├── set_initial_scroll_list_data_indexes.rs\n│   ├── simpler_model.rs              # Minimal serializable row struct for threading\n│   ├── audio_player.rs               # Play sound on scan completion\n│   ├── notification_manager.rs       # Desktop notifications\n│   ├── create_calculate_task_size.rs # Background total-size calculator\n│   ├── clear_outdated_video_thumbnails.rs\n│   ├── connect_scan.rs               # Routes scan to per-tool functions\n│   ├── connect_scan/                 # One file per tool (duplicate.rs, …)\n│   ├── connect_progress_receiver.rs  # Receives ProgressData → updates UI bar\n│   ├── connect_row_selection/        # Row checkbox logic, group handling\n│   │   ├── mod.rs\n│   │   ├── checker.rs\n│   │   ├── clipboard.rs\n│   │   ├── context_menu.rs\n│   │   ├── opener.rs\n│   │   └── selection.rs\n│   ├── connect_sort.rs               # Column-header sort (string + int indices)\n│   ├── connect_translation.rs        # LANGUAGE_LIST, change_language(), translate_items()\n│   ├── connect_compare.rs            # Image comparison overlay\n│   ├── connect_show_preview.rs       # File preview / thumbnail\n│   ├── connect_open.rs               # Open file/folder in system app\n│   ├── connect_save.rs               # Export results via PrintResults\n│   ├── connect_show_confirmation.rs  # Confirmation popup before destructive action\n│   ├── connect_directories_changes.rs\n│   ├── connect_clean_cache.rs\n│   ├── connect_tab_changed.rs\n│   ├── connect_stop.rs               # Sets stop_flag = true\n│   ├── connect_rfd.rs                # File picker (rfd)\n│   ├── connect_select/\n│   │   ├── mod.rs\n│   │   └── custom_select.rs          # Advanced filter popup\n│   ├── file_actions/\n│   │   ├── connect_delete.rs\n│   │   ├── connect_move.rs\n│   │   ├── connect_rename.rs\n│   │   ├── connect_hardlink.rs\n│   │   ├── connect_symlink.rs\n│   │   ├── connect_optimize_video.rs\n│   │   └── connect_clean_exif.rs\n│   ├── model_operations/\n│   │   ├── mod.rs                    # Slint model ↔ thread-safe model conversion\n│   │   └── model_processor.rs        # Parallel item processing framework\n│   └── settings/\n│       ├── mod.rs                    # Load/save JSON settings (11 presets)\n│       ├── model.rs                  # BasicSettings, SettingsCustom structs\n│       └── combo_box.rs              # StringComboBoxItems + regenerate_items()\n└── ui/\n    ├── main_window.slint             # Root component; imports everything; exports globals\n    ├── globals/\n    │   ├── common.slint              # Enums + structs: ActiveTab, SingleMainListModel, …\n    │   ├── gui_state.slint           # Global GuiState (transient runtime state)\n    │   ├── settings.slint            # Global Settings (persisted)\n    │   ├── translations.slint        # Global Translations (all UI strings)\n    │   ├── callabler.slint           # Global Callabler (all Rust to UI callbacks)\n    │   ├── fonts.slint               # FontSizes global\n    │   ├── color_palette.slint       # Dark-theme color definitions\n    │   └── text_size.slint\n    ├── screens/\n    │   ├── left_side_panel.slint     # Tool selector + tool settings sub-panel\n    │   ├── main_lists.slint          # Results table (bound to model)\n    │   ├── action_buttons.slint      # Scan / Stop / Select / Delete / … buttons\n    │   ├── bottom_panel.slint        # Progress bar + error text + status\n    │   ├── progress.slint            # Real-time scan progress widget\n    │   ├── image_compare.slint       # Side-by-side image diff overlay\n    │   ├── settings_list.slint       # Settings tab form\n    │   ├── tool_settings.slint       # Per-tool options (hash alg, threshold, …)\n    │   ├── about.slint                # About tab\n    │   └── included_paths.slint\n    ├── components/\n    │   ├── preview.slint             # File preview / thumbnail side panel\n    │   ├── selectable_tree_view.slint # Custom tree widget for path lists\n    │   ├── hint_text.slint\n    │   ├── popup_base.slint\n    │   ├── popup_centered_text.slint\n    │   └── popup_context_menu.slint\n    └── popups/                       # 18 modal dialogs: popup_delete, popup_save,\n                                       #   popup_optimize, popup_crop_video,\n                                       #   popup_custom_select, popup_rename_*, …\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## SharedModels (`src/shared_models.rs`)\n\n```rust\npub struct SharedModels {\n    pub shared_duplication_state:       Option<DuplicateFinder>,\n    pub shared_empty_folders_state:     Option<EmptyFolder>,\n    // … one field per tool (14 total)\n}\n```\n\nCreated once in `main.rs` as `Arc<Mutex<SharedModels>>`, cloned and passed to\nevery callback that needs scan results (preview, compare, save, delete, …).\n\nWorker threads lock exclusively to **write** results after a scan finishes.\nUI callbacks lock briefly to **read** results for previews / exports.\n\n`VideoOptimizer` is one of the 14 tools but doesn't fit the scan/select/delete\nshape of the others: it has its own `connect_scan/video_optimizer.rs` entry\npoint plus a dedicated `file_actions/connect_optimize_video.rs` action, and\ntwo popups (`popup_optimize.slint` for transcode options,\n`popup_crop_video.slint` for crop-region preview) instead of reusing the\ngeneric delete/save popups.\n\n---\n\n## Scan Data Flow\n\n```\n1. User clicks Scan → on_scan_starting(active_tab)\n2. connect_scan.rs: collect_settings() → ScanData → route to scan_<tool>()\n3. scan_<tool>(): spawn worker thread\n       → create and configure tool struct\n       → tool.search(stop_flag, progress_sender)\n       → store tool in shared_models.lock()\n       → populate Slint VecModel<SingleMainListModel>\n       → app.upgrade_in_event_loop(|app| app.set_<tool>_model(model))\n4. UI re-renders with new model\n```\n\n---\n\n## Slint Data Model\n\n```rust\n// ui/common.slint\nexport struct SingleMainListModel {\n    checked: bool,           // row selected by user\n    header_row: bool,        // group header\n    filled_header_row: bool, // header with formatted summary\n    focused_row: bool,      // UI highlight\n    val_str: [string],       // text columns\n    val_int: [int],          // numeric columns (raw values for sorting)\n}\n```\n\nColumn indices are constants in `src/common.rs`:\n\n```rust\npub enum StrDataDuplicateFiles { Size, Name, Path, ModificationDate }\npub enum IntDataDuplicateFiles { ModificationDatePart1, ModificationDatePart2,\n                                  SizePart1, SizePart2 }\n```\n\nSlint cannot store `i64`, so dates and file sizes are split into two `i32` fields.\n\n---\n\n## Translation System\n\nSlint has no native Fluent support. Workaround:\n1. All UI text is bound to properties in the `Translations` global\n   (`ui/translations.slint`), not hardcoded in `.slint`.\n2. `translate_items()` in `connect_translation.rs` (~440 lines, file is ~750 lines total)\n   sets every property via `flk!(\"key\")` after language changes.\n3. Language list defined as `LANGUAGE_LIST: &[Language]` in\n   `connect_translation.rs`.\n4. Hardcoded combo-box lists in `.slint` files (e.g. `Settings.languages_list`)\n   are verified at startup against Rust enums via `assert_eq!` in\n   `set_initial_gui_info.rs` (workaround for [slint#7632](https://github.com/slint-ui/slint/issues/7632)).\n\n---\n\n## Settings (`src/settings/`)\n\nTwo-tier JSON system:\n\n| File | Content |\n|------|---------|\n| `~/.config/krokiet/config_general.json` | `BasicSettings` – default preset index, preset names, theme, window size |\n| `~/.config/krokiet/config_preset_N.json` | `SettingsCustom` – paths, extensions, tool parameters |\n\n(`directories-next` resolves the config dir from the lowercased app name only - the\nqualifier/organization passed to `set_config_cache_path` are ignored on Linux.)\n\n11 preset slots (indices 0–10). Slot 10 is reserved for CLI-mode overrides.\n`StringComboBoxItems::regenerate_items()` builds all combo box option arrays\nfrom Rust enums; used both for settings serialization and UI initialization.\n\n---\n\n## Callback Registration Pattern\n\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // use app …\n});\n```\n\n`expect()` is correct here: if the window is gone, no callback should fire.\nCross-thread updates use `weak.upgrade_in_event_loop(|app| { … })`.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework (GPL-3.0) |\n| `czkawka_core` | Scanning engine |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `crossbeam-channel` | Progress + result channels |\n| `rayon` | Parallel result sorting |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n| `rfd` 0.17 | File picker (xdg-portal backend) |\n| `notify-rust` | Desktop notifications |\n| `rodio` | Scan-complete sound (optional `audio` feature) |\n| `fontique` | Font discovery (dlopen, avoids C dep) |\n\n### Optional features\n\n- `audio` – scan-complete sound via `rodio`\n- Renderer features: `winit_femtovg` (default), `winit_software` (default),\n  `skia_opengl`, `skia_vulkan`, etc. – select GPU backend at compile time\n- `heif`, `libraw`, `libavif`, `xdg_portal_trash` – forwarded to `czkawka_core`\n\n### Build\n\n`build.rs` compiles `ui/main_window.slint` with style `fluent-dark` (overridable\nvia `SLINT_STYLE` env var).\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row.\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n","krokiet/CLAUDE.md":"AGENTS.md"},"files":{"AGENTS.md":"# Czkawka – Codebase Guide\n\n## Language\n\nAll code, comments, commit messages, and documentation must be written in **English**.\n\n---\n\n## AI-authored commits & merge/pull requests\n\nAlways, without exception: every commit created by an AI assistant must start its title (the\nfirst line of the commit message) with `[AI] `, e.g. `[AI] Fix crash when scanning empty\ndirectories`. If the commit message has a body, the body does not need the prefix again - the\ntitle alone marks it.\n\nThe same applies to any merge request (GitLab) or pull request (GitHub) opened by an AI\nassistant: both the title and the body/description must start with `[AI] `, e.g. title\n`[AI] Add duplicate detection for symlinked folders` and body starting with\n`[AI] This MR adds ...`. This must stay visible to reviewers at a glance in both GitLab and\nGitHub - never omit it, even for small or automatic changes.\n\n---\n\n## Rust Style\n\nApplies to every Rust crate in the workspace (`czkawka_core`, `czkawka_cli`, `czkawka_gui`,\n`krokiet`, `cedinia`). Program-specific deviations or elaborations live in that program's own\n`AGENTS.md`.\n\n**Formatting & lints**\n- Format with `cargo +nightly fmt`, then `cargo fmt` for stable compatibility\n  (`.rustfmt.toml`: `max_width = 120`, granular import grouping `group_imports = \"StdExternalCrate\"`).\n- Code must compile without clippy warnings. Suppress a lint locally only with a comment\n  explaining why.\n\n**Errors & panics**\n- Prefer `?`, `ok_or_else`, `map_err` over panic-based flows.\n- `unwrap()` only in tests.\n- `expect()` in production only when BOTH hold: there is no correct recovery path, and\n  continuing would likely hide a serious bug - always with a precise, actionable message. This\n  is visible throughout Krokiet/Cedinia callback code: a Slint weak reference is upgraded with\n  `expect()` because if the window is already gone there is nothing meaningful to do except crash.\n- Never silently swallow errors with `let _ = ...` unless the failure is genuinely irrelevant.\n- Model domain errors with explicit types (`thiserror`/enums), not raw strings.\n- Wrap I/O errors with context (e.g. file path) instead of letting std's bare \"Permission\n  denied\" propagate - without the path it's hard to find the source.\n- Don't ignore `Result`; handle it explicitly.\n\n**Code shape**\n- Keep functions focused; once one grows past ~30-50 lines, extract its logical steps into\n  separate, well-named functions. Never carve a long function into sections with comment\n  headers or bare `{ ... }` scope blocks - a `// validate input` comment over a block is the\n  signal to pull it out into `validate_input(...)`, not to fence it off.\n- Name things by intent, not by container/type (`user_count`, not `vec_users_len`).\n- Make invalid states unrepresentable: enums over strings, `Option<T>` over sentinel values\n  (`-1`, empty string).\n- Prefer iterators and collection methods over manual index-based loops.\n- Prefer `#[derive(...)]` over manual impls when behavior is standard.\n- Avoid unnecessary copies and allocations; prefer references and `Cow` where it helps.\n- Prefer external crates over reimplementing them.\n\n**Comments**\nKeep comments short and minimal - a single terse line, not a paragraph. Code should be\nself-documenting through clear naming. Add a comment only when the _why_ is not easily inferred\nfrom reading the code - algorithmic choices, non-obvious constraints, workarounds for external\nlibrary bugs, etc. Do not restate what the code already says. Never use the `—`/`–` dash\ncharacters in code, comments, or commit messages - plain `-` only (ASCII box-drawing diagrams are\nthe only exception).\n\nException: `czkawka_core` is a library - every frontend in this workspace depends on it, and so do\nexternal consumers who only read its public API, not its internals. Its `pub fn`/`pub struct`\nsurface warrants more explanation than the rest of the workspace; see `czkawka_core/AGENTS.md`.\n\n**Tests, fuzzers, benchmarks**\n- Cover as much code as possible with tests; keep them readable with explicit `assert_eq!` and\n  input data close to the assertions.\n- Add fuzzers/examples/benchmarks where it helps show usage, find bugs, or measure the\n  performance impact of a change.\n\n**`unsafe`**\nAvoid whenever possible. Every `unsafe` block documents its invariants and why the safety\nrequirements hold (see cedinia's `#[unsafe(no_mangle)] android_main`).\n\n**Files & logging**\n- Keep source files at or below 500 lines; split large files into modules.\n- Always log via `log` (`handsome_logger`); reach for `tracing` only if actually needed.\n\n**Auto-vectorization**\nLLVM vectorizes a loop when it can prove: no loop-carried dependency, no aliasing, no bounds\nchecks, and a favorable cost model.\n- Enable it: `.iter()`/`.iter_mut()` + `.zip()` instead of index loops (drops bounds checks);\n  `.chunks_exact(N)`/`.chunks_exact_mut(N)` + `.remainder()` for aligned SIMD chunk processing;\n  `(&[T], &mut [T])` over `(&mut [T], &mut [T])` so LLVM can assume no aliasing; keep the loop\n  body uniform (no per-element `if`/`match`, no early returns); use a reduction (`.sum()`/\n  `.fold()`) instead of a running accumulator that depends on its own previous value.\n- Kills it: `slice[i]` index loops, value-based `continue` inside an element loop, mutable\n  state shared across iterations.\n- Verify: inspect asm on godbolt.org for `vmulps`/`vpaddb`/`vpsubusb` vs scalar `mulss`/`movss`;\n  measure the delta with `RUSTFLAGS=\"-C opt-level=3 -C no-vectorize-loops -C no-vectorize-slp\"`;\n  build with `RUSTFLAGS=\"-C target-cpu=native\"` to unlock AVX2/AVX-512 on the host CPU.\n- When LLVM won't vectorize, reach for explicit SIMD: `std::arch` (stable, arch-specific),\n  `std::simd`/`portable_simd` (nightly, portable), or the stable `wide`/`pulp` crates.\n\n**Performance micro-hints**\n- Pre-allocate with `Vec::with_capacity(n)` / `HashMap::with_capacity(n)` when the count is\n  roughly known - avoids the realloc ladder.\n- Reuse buffers across calls (`buf.clear()`) instead of reallocating.\n- For almost-always-short collections, consider `SmallVec`/`ArrayVec` (stack-allocated until\n  they spill).\n- Use the smallest enum discriminant that fits (`#[repr(u8)]`/`#[repr(u16)]`) for tighter\n  packing and better cache density.\n- Wrap Criterion bench inputs/outputs in `std::hint::black_box(...)` to stop dead-code\n  elimination from invalidating the measurement.\n\n---\n\n## Project Goals\n\nTwo properties are non-negotiable across every sub-project:\n\n1. **Performance first** – scanning and file operations must be fast. Parallelism via `rayon`,\n   efficient algorithms (e.g. perceptual hashing, blake3), and careful memory use are the norm.\n   Avoid unnecessary allocations or copies in hot paths.\n\n2. **Minimal non-Rust dependencies** – every additional C/C++ library (or any other non-Rust\n   language) makes cross-compilation harder, narrows the set of supported targets, and increases\n   build complexity for contributors. Prefer pure-Rust crates. If a native library is truly\n   necessary (e.g. `libheif`, `libraw`), gate it behind an optional Cargo feature so the default\n   build stays fully Rust.\n\n---\n\n## `just fix` – baseline quality gate\n\n**Mandatory: after finishing any coding task, run `just fix` and resolve everything it reports\nbefore considering the task done.** This applies always, without exception - not just before\nopening a merge request.\n\nRunning `just fix` must pass before any merge request. It runs, in order:\n\n1. Repo-wide auto-fix of `—`/`–`/`―` dash characters back to plain `-` (`.rs`, `.slint`, `.md`,\n   `.ftl`; `AGENTS.md` and `justfile` themselves are excluded) – the enforcement behind the\n   \"never use em/en dash\" style rule above.\n2. `uv run ruff format --line-length 120` – Python code formatting.\n3. `uv run mypy misc --strict` – static type checking for all scripts in `misc/`.\n4. `bash misc/run_checks.sh` – project-specific checks:\n   - `delete_unused_krokiet_slint_imports.py` for krokiet and cedinia\n   - `find_unused_fluent_translations.py` for all four projects\n   - `find_unused_slint_translations.py` for krokiet and cedinia\n   - `find_unused_callbacks.py` for krokiet and cedinia\n   - `find_unused_settings_properties.py` for krokiet and cedinia\n5. `cargo +nightly fmt` – Rust formatting.\n6. `cargo clippy --fix --all-features --all-targets` – Rust linting (single pass).\n7. `cargo +nightly fmt` + `cargo fmt` again – re-format whatever clippy's fixes touched.\n\nFor a clippy pass that also covers the `--no-default-features` build, run `just clip` separately\n(two passes: `--all-features` and `--no-default-features --features winit_software`).\n\nIf `just fix` produces any output on stderr or exits non-zero the code is not ready for review.\n\n---\n\n## Workspace Structure\n\n```\nczkawka/\n├── czkawka_core/   # Scanning logic – shared library used by all frontends\n├── czkawka_cli/    # Command-line interface\n├── czkawka_gui/    # Legacy GTK 4 GUI (maintenance mode only)\n├── krokiet/        # Primary desktop GUI – Slint-based\n├── cedinia/        # Android / mobile GUI – Slint-based\n└── misc/           # Scripts: AI translation, validation, benchmarks, CI helpers\n```\n\nCargo workspace resolver v3, minimum Rust 1.94.1, edition 2024 throughout.\n\n---\n\n## czkawka_core\n\nThe shared scanning engine. Every frontend depends on it; it has no UI dependency.\n\n**Key modules:**\n- `common/` – `CommonToolData` (settings, stop-flag, progress sender), `DirTraversal`, cache,\n  extension filtering, path helpers, progress types.\n- `tools/` – One sub-module per scanning tool:\n  `duplicate`, `empty_folder`, `empty_files`, `big_file`, `similar_images`, `similar_videos`,\n  `same_music`, `broken_files`, `bad_extensions`, `bad_names`, `invalid_symlinks`, `temporary`,\n  `exif_remover`, `video_optimizer`.\n- `localizer_core.rs` – Fluent translation loader for Rust-side messages.\n\nEach tool implements the `CommonData` trait (shared settings access) and `PrintResults` (CSV/JSON\nexport). The tool struct is constructed, configured, then its `find_*()` method is called in a\nworker thread. Progress is reported over a `crossbeam` channel; a stop `AtomicBool` is polled to\nsupport cancellation.\n\n---\n\n## krokiet\n\nThe primary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI is declared\nin `ui/*.slint`; Rust connects callbacks and drives the Slint model.\n\n**Build:** `slint_build` compiles `.slint` sources at build time; `slint::include_modules!()`\nexposes the generated types.\n\n**Entry point:** `src/main.rs`\n- Loads settings, creates `MainWindow`, wires up all callbacks, starts the event loop.\n\n**Callback pattern:**\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // ...\n});\n```\nEach feature area lives in a dedicated `connect_*.rs` file (e.g. `connect_scan.rs`,\n`connect_compare.rs`, `connect_delete_button.rs`).\n\n**`SharedModels`** (`src/shared_models.rs`):\nAn `Arc<Mutex<SharedModels>>` holds the last scan result and parameters of scan of each tool. It is passed to every\n`connect_*` function that needs to access or mutate scan state from a background thread.\n\n**Model layer:**\n- The Slint UI is driven by `ModelRc<VecModel<SingleMainListModel>>`.\n- `SingleMainListModel` carries `val_str: [string]` and `val_int: [int]` vectors – a flat,\n  index-based row representation.\n- Column indices for each tool are defined as constants in `src/common.rs`\n  (`StrDataSimilarImages`, `StrDataDuplicates`, …).\n\n**Translation:** `flk!(\"key\")` / `flk!(\"key\", var = value)` macros defined in\n`src/localizer_krokiet.rs`. Language files in `i18n/<lang-code>/krokiet.ftl`.\n\n---\n\n## cedinia\n\nThe Android (and secondary desktop) GUI. Architecture mirrors Krokiet but adapts to mobile\nconstraints. Compiled as `cdylib` for Android (loaded via `android-activity`).\n\n**Entry points:**\n- Android: `#[unsafe(no_mangle)] fn android_main(android_app: AndroidApp)` in `src/lib.rs`\n- Desktop: `fn run_app()` in `src/app.rs`\n\n**Android-specific:**\n- File picker uses JNI to call into a Kotlin/Java helper embedded via `include_bytes!` (DEX).\n- Storage permissions requested at runtime; `AppState.storage_permission_granted` gates scanning.\n- System insets (`inset_top`, `inset_bottom`) plumbed through to Slint for edge-to-edge layout.\n- `android_logger` routes Rust log output to logcat.\n\n**Differences from Krokiet:**\n- Has `SimilarVideos` (audio-fingerprint matching only, via `rusty-chromaprint`), but not\n  `VideoOptimizer` - ffmpeg-based transcoding/crop-detection is not available on Android.\n- Touch-optimised UI (`cedinia/ui/`); momentum-scroll views, bottom sheets, FAB.\n- `flc!` macro (cedinia-specific) in `src/localizer_cedinia.rs`.\n- See `cedinia/AGENTS.md` (\"Differences from krokiet\") for the full comparison table.\n\n**Translation:** `flc!(\"key\")` macro; language files in `cedinia/i18n/<lang-code>/cedinia.ftl`.\n\n---\n\n## czkawka_cli\n\nThin wrapper around `czkawka_core`. Uses `clap` (derive API) for argument parsing and `indicatif`\nfor progress bars. No GUI code. Results printed via the tool's `PrintResults` trait.\n\n---\n\n## czkawka_gui\n\nLegacy GTK 4 GUI. **Maintenance mode only** – no new features are added. Bug-fixes that\nkeep it compatible with core API changes are accepted.\n\n---\n\n## misc/\n\n**Translation tooling** (`ai_translate/`):\n- `translate.py` – AI-powered batch translation into all supported languages.\n- `validate_translations.py` – Checks placeholder consistency across translations.\n  Pass `--fix` to automatically remove invalid entries.\n\n**Dead-code detection** (run by `run_checks.sh`, see `just fix` above):\n- `find_unused_fluent_translations.py` / `find_unused_slint_translations.py` – Unused\n  translation keys.\n- `find_unused_callbacks.py` – Slint callbacks never invoked from Rust.\n- `find_unused_settings_properties.py` – Settings struct fields never read by the UI.\n- `delete_unused_krokiet_slint_imports.py` – Removes dead `import` lines from `.slint` files.\n\n**Packaging / release:**\n- `gen_cedinia_licenses.py` – Generates `THIRD_PARTY_LICENSES.txt` from Cargo metadata.\n- `gen_android_icons.py` – Generates cedinia's Android adaptive-icon assets from an SVG logo.\n- `simplify_and_minify_svg.py` – Minifies SVG icons via Inkscape.\n- `pack_all_backends.sh` / `.ps1` – Bundles an all-backends krokiet binary with per-backend\n  launcher scripts into a release zip.\n- `flathub.sh` – Generates Flatpak cargo-sources metadata for the Flathub manifest.\n- `add_icon_exe/` – Cargo helper crate that embeds the `.ico` into Windows binaries at build time.\n- `docker/` – `Dockerfile` for containerized builds.\n- `nix/` – Nix flake (`flake.nix`, `packages.nix`) for Nix-based builds.\n- `install_scripts/` – `install_linux.sh`, `install_macos.sh`, `install_windows.bat` end-user\n  installers.\n\n**Dev utilities:**\n- `remove_comments.py` – Strips comments from source files (one-off cleanup tool).\n- `compare_files.sh` – Diffs MD5 hashes of CI build artifacts across runs to check determinism.\n- `run_checks.sh` – Runs all the dead-code detection scripts above; invoked by `just fix`.\n\n**Benchmarks** (standalone Cargo crates):\n- `test_image_perf/`, `test_read_perf/` – Microbenchmarks for image hashing / file reading.\n- `test_compilation_speed_size/` – Tracks build time and binary size across changes.\n\n---\n\n## i18n\n\nAll user-visible strings use [Fluent](https://projectfluent.org/) (`.ftl` files).\n\n| Project      | Macro  | File pattern                                |\n|--------------|--------|---------------------------------------------|\n| krokiet      | `flk!` | `krokiet/i18n/<lang>/krokiet.ftl`           |\n| cedinia      | `flc!` | `cedinia/i18n/<lang>/cedinia.ftl`           |\n| czkawka_core | `flc!` | `czkawka_core/i18n/<lang>/czkawka_core.ftl` |\n| czkawka_gui  | `flg!` | `czkawka_gui/i18n/<lang>/czkawka_gui.ftl`   |\n\nEnglish is the source/fallback language. All other locales are AI-translated and then validated.\n\n**Important:** Only edit the English `.ftl` files (`i18n/en/`) directly in this repository.\nAll other language files are managed through [Crowdin](https://crowdin.com/) and will be\n**overwritten** when translations are pulled from Crowdin. Any manual edits to non-English\n`.ftl` files in the repo will be lost on the next `just unpack_translations` run.\n\n---\n\n## Slint UI conventions\n\n- **Hidden Text elements for width measurement** – where a layout element must adapt its width to\n  translated label text, add off-screen `Text` instances (`x: -10000px; y: -10000px; height: 0`)\n  and compute `preferred-width` at runtime (see `LeftSidePanel`, `CompareInfoBar`).\n- **Enums over strings** – UI state that takes a fixed set of values should use a Slint `enum`,\n  not a `string` (e.g. `ConfirmPopupAction`, `ActiveTool`, `ScanState`).\n- **Global state** – Application-wide state lives in Slint `global` blocks (`GuiState`,\n  `AppState`, `Settings`, `Translations`, …). Rust reads/writes via `app.global::<GlobalName>()`.\n\n---\n\n## Build profiles (Cargo.toml)\n\n| Profile        | Purpose                                                              |\n|----------------|----------------------------------------------------------------------|\n| `release`      | Standard release                                                     |\n| `fast_release` | Incremental, stripped – fast iteration                               |\n| `rdebug`       | Release + full debug symbols (profiling)                             |\n| `fastest`      | Max opt, LTO, panic=abort – mostly benchmarks/poc how fast it can be |\n| `fastci`       | Small binary, fast CI builds                                         |\n\n---\n\n## justfile quick reference\n\n```\njust run krokiet          # debug run\njust runr krokiet         # fast_release run\njust fix                  # format + clippy + Python checks\njust translate            # AI-translate all projects\njust validate_translations [--fix]\njust pack_translations    # create i18n_translations.zip for Crowdin\njust unpack_translations <path>\njust android              # build + install + launch on device\njust androidr             # release variant\n```\n","CLAUDE.md":"AGENTS.md","cedinia/AGENTS.md":"# cedinia – Architecture Guide\n\n## Overview\n\nAndroid (and secondary desktop) GUI. Architecture mirrors krokiet but adapted\nfor mobile constraints: touch-optimized layout, JNI file picker, runtime\npermissions, system insets, and no `VideoOptimizer` (FFmpeg-based transcoding\nis unavailable on Android). `SimilarVideos` is still present, using\naudio-fingerprint matching only (`rusty-chromaprint`), not frame hashing.\n\n---\n\n## Entry Points\n\n**Android** (`src/lib.rs`):\n```rust\n#[unsafe(no_mangle)]\nfn android_main(android_app: AndroidApp) {\n    setup_android_paths(&android_app);   // JNI: get /data and /cache paths (sets DATA_DIR)\n    app::setup_logger_cache();           // installs DualLogger: logcat + cedinia.log file\n    asan_smoketest_if_requested();       // no-op unless CEDINIA_ASAN_SMOKETEST is set\n    file_picker_android::init(&android_app); // Load DEX + init JNI\n    slint::android::init(android_app.clone()).expect(…);\n    file_picker_android::setup_nav_bar(); // pin/style the Android nav bar\n    app::run_app_with_insets(inset_bottom_px, scale, android_app);\n}\n```\n\nLogging: on Android `android_logger` would grab the single global `log` slot, so\nczkawka_core's file `WriteLogger` never installs (cedinia.log stays 0 bytes).\n`app.rs::setup_android_logger` installs a `DualLogger` fanning out to BOTH the\n`AndroidLogger` (logcat) and an appended `cedinia.log` in the cache folder, so\nlog export has real content. Desktop keeps `czkawka_core::setup_logger`.\n\n**Desktop** (`src/app.rs`):\n```rust\npub fn run_app() {\n    setup_logger_cache();\n    run_app_with_insets(0.0, 1.0, ());\n}\n```\n\nBoth converge at `run_app_inner()`: load settings → create `MainWindow` →\nwire callbacks → start worker thread → run event loop.\n\n---\n\n## Source Layout\n\n```\ncedinia/src/\n├── lib.rs                         # Android entry (#[unsafe(no_mangle)] android_main)\n├── app.rs                         # Desktop entry + shared run_app_inner()\n├── bin/cedinia.rs                 # Desktop binary wrapper\n├── common.rs                      # Column index enums (StrData*, IntData*)\n├── model.rs                       # FileEntry toggle/count logic\n├── compare.rs                     # Image comparison overlay (cancel token, resize, diff)\n├── file_actions.rs                # delete_path() (trash on desktop, permanent on Android) + DeleteEvent\n├── scan_runner.rs                 # Worker thread + ScanRequest/ScanResult\n├── scanners.rs                    # Tool-specific scan builders\n├── notifications.rs               # Desktop notifications\n├── set_initial_gui_infos.rs       # Initial GUI state\n├── thumbnail_loader.rs            # Async image thumbnail loading\n├── translations.rs                # translate_items() – populates Translations global\n├── volumes.rs                     # Storage volume detection\n├── localizer_cedinia.rs           # flc! macro, LANGUAGE_LIST, apply_language_preference()\n├── file_picker_android.rs         # JNI + embedded DEX file picker\n├── callbacks/\n│   ├── callbacks.rs               # Module re-exports\n│   ├── scan.rs                    # wire_scan() – scan/stop/tool-change callbacks\n│   ├── selection.rs               # wire_selection() – delete + model access\n│   ├── selection_ops.rs           # Shared FileEntry helpers: vm_of, get_val_str/int, full_path_of, size_from_entry\n│   ├── directories.rs             # wire_directories() – add/remove include/exclude dirs\n│   └── misc.rs                    # 10 wire_* fns: cache_info, collect_test, export_logs,\n│                                   #   language_change, licenses_popup, notification_settings,\n│                                   #   open_path, open_url, permission, save_settings_now\n├── settings/\n│   ├── mod.rs                     # CediniaSettings struct; load/save JSON\n│   └── gui_settings_values.rs     # StringComboBoxItems – combo box option arrays\n└── ui/\n    ├── main_window.slint          # Root component\n    ├── globals/\n    │   ├── app_state.slint        # Global AppState, GeneralSettings, tool-specific globals\n    │   ├── common.slint           # Enums: ActiveTool, ScanState, ConfirmPopupAction\n    │   ├── colors.slint           # CediniaColors theme\n    │   └── fonts.slint\n    ├── screens/\n    │   ├── home_screen.slint      # Tool cards\n    │   ├── settings_screen.slint  # Settings screen layout\n    │   ├── results_list.slint     # Generic results table\n    │   ├── similar_images_gallery.slint\n    │   ├── directories_screen.slint # Add/remove directory paths\n    │   ├── image_compare.slint\n    │   ├── scan_progress.slint\n    │   ├── bottom_nav.slint\n    │   └── top_bar.slint\n    └── components/\n        ├── settings/\n        │   ├── toggle_row.slint\n        │   ├── segment_row.slint\n        │   ├── dropdown_row.slint\n        │   ├── text_input_row.slint\n        │   ├── tool_group_header.slint\n        │   └── category_label.slint\n        ├── buttons/\n        │   ├── touch_button.slint\n        │   ├── icon_button.slint\n        │   └── circle_icon_button.slint\n        ├── file_row.slint\n        ├── section_header.slint\n        ├── status_chip.slint\n        ├── divider.slint\n        ├── empty_state.slint\n        ├── scan_fab.slint\n        ├── scrim_dialog.slint\n        ├── popup_title_bar.slint\n        ├── context_menu_sheet.slint\n        ├── deselection_menu_sheet.slint\n        └── selection_menu_sheet.slint\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## Scan Data Flow\n\n```\n1. User selects tool + taps Scan → AppState.on_scan_requested()\n2. scan.rs: collect settings from globals → build ScanRequest\n3. ScanRequest sent to worker thread via channel\n4. Worker: configure tool struct → tool.search(stop_flag, progress_sender)\n5. Results stored; ScanResult sent back to main thread\n6. invoke_from_event_loop: populate model, update AppState.scan_state\n```\n\n---\n\n## Slint UI Architecture\n\nSettings are stored in Slint globals (not set from Rust at startup):\n\n- `GeneralSettings` – language, file sizes, cache, extension filters\n- `DuplicateSettings`, `SimilarImagesSettings`, `SameMusicSettings`, …\n- `AppState` – scan state, active tool, result models, all callbacks\n\n`apply_settings_to_gui()` (`settings/mod.rs`) reads `CediniaSettings` from\nJSON and populates these globals. `collect_settings_from_gui()` reads them back\nfor saving.\n\n### DropdownRow (`ui/components/settings/dropdown_row.slint`)\n\nInline accordion, **not** a `PopupWindow`:\n- Tapping the row toggles `expanded`, which inserts the option list directly\n  below the row inside the same `VerticalLayout`.\n- Scrolling is handled by the parent `Flickable`/`ListView`, not a nested\n  `ScrollView` - this avoids \"close on outside touch\" popup-dismissal issues\n  when the row sits inside an already-scrollable settings list.\n- Each option row tracks pointer movement (`_opt_moved`) so a scroll-drag that\n  ends over an option doesn't register as a selection tap.\n\n## Translation System\n\nSame workaround as krokiet:\n- All UI text bound to `Translations` global properties.\n- `translate_items()` in `src/translations.rs` populates them via `flc!(\"key\")`.\n- `LANGUAGE_LIST` in `localizer_cedinia.rs` defines the 27 supported languages.\n- `GeneralSettings.language_options` in `ui/app_state.slint` is hardcoded and\n  must match `LANGUAGE_LIST` exactly.\n- Language index stored in `GeneralSettings.language_idx`; code stored in\n  `CediniaSettings.language`.\n\n---\n\n## Settings (`src/settings/mod.rs`)\n\nJSON file (path resolved by `czkawka_core::common::config_cache_path::get_config_cache_path()`,\nconfigured via `set_config_cache_path(\"cedinia\", \"cedinia\")`):\n- Desktop: `~/.config/cedinia/cedinia_settings.json` (directories-next ignores\n  the qualifier/org on Linux and lowercases the app name - there is no\n  `Czkawka` path segment)\n- Android: app-private storage, `$DATA_DIR/files/cedinia/cedinia_settings.json`\n\nIncluded/excluded directories are persisted separately in a sibling\n`cedinia_dirs.json` in the same folder.\n\n`CediniaSettings` (serde Serialize/Deserialize) covers all tool parameters.\n`Default` implementation delegates to `serde_json::from_str(\"{}\")` so all\n`#[serde(default)]` values are used.\n\n---\n\n## Android-Specific Code\n\n### File Picker (`src/file_picker_android.rs`)\n\nA Kotlin helper class is compiled at build time (via `android-build` crate) and\nembedded as DEX bytecode:\n\n```rust\ninclude_bytes!(concat!(env!(\"OUT_DIR\"), \"/classes.dex\"))\n```\n\nAt runtime:\n1. DEX is loaded into an `InMemoryDexClassLoader`.\n2. `CediniaFilePicker` instance created via JNI.\n3. `launch_pick_directory(is_include, start_path)` calls the Java\n   `ACTION_OPEN_DOCUMENT_TREE` intent, hinting the picker to open at `start_path`.\n4. Result arrives via `Java_CediniaFilePicker_onDirectoryPicked()` JNI callback\n   → dispatched to Rust via `invoke_from_event_loop()`.\n\n### Storage Permissions\n\nRuntime permission check/request via JNI.\n`AppState.storage_permission_granted` gates scan initiation.\n\n### System Insets\n\nAndroid edge-to-edge layout requires padding for navigation bars. Inset sizes\nare passed from Kotlin via `android_app.content_rect()` and plumbed through to\nSlint as layout properties.\n\n### Android Paths via JNI\n\n`setup_android_paths()` calls `getFilesDir()` and `getCacheDir()` on the\nActivity to find private storage paths; these are set as environment variables\nand used by `czkawka_core`'s config path logic.\n\n---\n\n## Differences from krokiet\n\n| | cedinia | krokiet |\n|---|---|---|\n| `SimilarVideos` | Yes (audio fingerprint only, `rusty-chromaprint`) | Yes (frame hashing) |\n| `VideoOptimizer` | No (ffmpeg-based transcoding unavailable on Android) | Yes |\n| `BadNames` | Yes | Yes |\n| File picker | JNI (Android) / rfd (desktop) | rfd |\n| Settings | JSON, no presets | JSON, 11 presets |\n| Column indices | `common.rs` (own set) | `common.rs` (own set) |\n| Build target | `cdylib` (Android) + `bin` (desktop) | `bin` |\n\n---\n\n## Key Dependencies\n\n### Always\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework |\n| `czkawka_core` | Scanning engine |\n| `crossbeam-channel` | Worker thread communication |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n\n### Android only\n\n| Crate | Purpose |\n|-------|---------|\n| `android-activity` 0.6 | Android lifecycle |\n| `jni` 0.22 | Java FFI |\n| `android_logger` 0.15 | Logcat output |\n\n### Desktop only\n\n| Crate | Purpose |\n|-------|---------|\n| `rfd` 0.17 | Native file picker |\n| `trash` 5.2 | Move-to-trash |\n| `notify-rust` 4 | Desktop notifications |\n\n### Build\n\n```toml\n[build-dependencies]\nslint-build = \"1.15\"\nandroid-build = \"0.1.2\"   # Compiles CediniaFilePicker.java → classes.dex\n```\n\n### Android Manifest Permissions\n\n`READ_EXTERNAL_STORAGE`, `WRITE_EXTERNAL_STORAGE`,\n`MANAGE_EXTERNAL_STORAGE`, `POST_NOTIFICATIONS`\n\n`Cargo.toml`'s `[package.metadata.android.uses_permission]` also lists\n`WAKE_LOCK`, but the gradle-built `android/app/src/main/AndroidManifest.xml`\ndoes not include it - a pre-existing inconsistency in the repo, not a doc bug.\n\n---\n\n## Optional Features (forwarded to czkawka_core)\n\n`heif`, `libraw`, `libavif`, `xdg_portal_trash`\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row (see\n`model.rs::toggle_row`, `compare.rs::wire_compare_toggle_checkbox`).\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n","cedinia/CLAUDE.md":"AGENTS.md","czkawka_cli/AGENTS.md":"# czkawka_cli – Architecture Guide\n\n## Overview\n\n`czkawka_cli` is a thin CLI wrapper around `czkawka_core`. It handles argument\nparsing, thread orchestration, progress rendering, and result output. All\nscanning logic lives in `czkawka_core`; this crate only wires it to the\nterminal.\n\n---\n\n## Source Layout\n\n```\nczkawka_cli/src/\n├── main.rs       # Entry point, thread spawning, tool dispatchers\n├── commands.rs   # All clap CLI argument definitions\n├── parsers.rs    # parse_* clap value-parser functions used by commands.rs\n└── progress.rs   # indicatif progress bar rendering\n```\n\n---\n\n## Execution Model\n\n```\nmain()\n ├─ Parse args (clap)\n ├─ Create crossbeam channel  (Sender<ProgressData>, Receiver<ProgressData>)\n ├─ Create Arc<AtomicBool> stop_flag\n ├─ spawn calculation_thread → dispatches to one of 14 tool functions\n ├─ Register Ctrl+C handler → sets stop_flag = true\n ├─ connect_progress(&progress_receiver)   [blocks, renders progress bar]\n └─ join calculation_thread → print output → exit(11) if items found\n```\n\nThe **main thread** renders progress. The **calculation thread** runs the scan.\n`progress_receiver.recv()` exits naturally when the sender drops (scan ends).\n\n---\n\n## CLI Subcommands\n\nDefined via `clap` derive in `commands.rs`. Top-level enum `Commands` has 14\nvariants:\n\n| Subcommand        | Alias            | Purpose                                 |\n|-------------------|------------------|-----------------------------------------|\n| `Duplicates`      | `dup`            | Find duplicate files (hash/name/size)   |\n| `EmptyFolders`    | `empty-folders`  | Find empty directories                  |\n| `BiggestFiles`    | `big`            | Find biggest or smallest files          |\n| `EmptyFiles`      | `empty-files`    | Find zero-byte files                    |\n| `Temporary`       | `temp`           | Find temporary files                    |\n| `SimilarImages`   | `image`          | Find similar images (perceptual hash)   |\n| `SameMusic`       | `music`          | Find duplicate music (tags/fingerprint) |\n| `InvalidSymlinks` | `symlinks`       | Find broken symlinks                    |\n| `BrokenFiles`     | `broken`         | Find broken PDF/audio/image/archive     |\n| `SimilarVideos`   | `video`          | Find similar videos (frame hashing)     |\n| `BadExtensions`   | `ext`            | Find files with wrong extensions        |\n| `BadNames`        | `bad-names`      | Find problematic file names             |\n| `VideoOptimizer`  | `video-optimizer`| Transcode or crop videos                |\n| `ExifRemover`     | `exif-remover`   | Remove EXIF metadata from images        |\n\n### `CommonCliItems` (shared by every subcommand)\n\n- `-d` directories (required)\n- `-e` excluded directories\n- `-E` excluded items (glob patterns, e.g. `*/.*`)\n- `-x` allowed extensions (supports macros: `IMAGE`, `VIDEO`, `MUSIC`, `TEXT`)\n- `-P` excluded extensions\n- `-R` disable recursive search\n- `-X` (unix only) exclude other filesystems\n- `-f` save text results to file\n- `-C` save compact JSON results to file\n- `--pretty-file-to-save` (`-p`) save pretty JSON results to file\n- `-N` / `-M` suppress result/message output\n- `-W` suppress non-zero exit code when items found\n- `-T` thread count (0 = all CPUs)\n- `-H` disable cache\n\n### Delete methods\n\n**`DMethod`** (for similarity-grouping tools): `-D NONE|AEN|AEO|ON|OO|AEB|AES|OB|OS|HARD`\n**`SDMethod`** (for simple tools): `-D` delete flag, `-Q` dry-run, `-y` move-to-trash\n\n---\n\n## Tool Dispatcher Pattern\n\nEvery one of the 14 dispatch functions follows the same structure:\n\n```rust\nfn tool_name(args: ToolArgs, stop_flag: &Arc<AtomicBool>, progress_sender: &Sender<ProgressData>) -> CliOutput {\n    // 1. Destructure args\n    // 2. Build tool-specific parameter struct\n    let mut tool = ToolType::new(params);\n    // 3. Apply common settings (paths, extensions, cache, threads)\n    set_common_settings(&mut tool, &common_cli_items, reference_directories);\n    // 4. Apply tool-specific settings (file sizes, delete method, etc.)\n    // 5. Run scan\n    tool.search(stop_flag, Some(progress_sender));\n    // 6. Optionally run fix (rename/delete/etc.)\n    // 7. Collect and return output\n    save_and_write_results_to_writer(&tool, &common_cli_items)\n}\n```\n\n`set_common_settings` applies paths, recursion, extensions, cache flags, and\nthread count via the `AllTraits` bound.\n\n---\n\n## Progress Rendering (`progress.rs`)\n\n`connect_progress(&progress_receiver)` loops on `progress_receiver.recv()` and\nrenders via indicatif:\n\n- **Spinner** while collecting files or loading/saving cache.\n- **Linear bar** `[=>  ]` once the total entry or byte count is known.\n\nThe stage label (e.g. \"Calculating hashes\", \"Reading tags\") is derived from the\n`CurrentStage` enum on each `ProgressData` message.\n\n---\n\n## Result Output\n\n`save_and_write_results_to_writer` writes three optional file formats (text,\ncompact JSON, pretty JSON) and buffers stdout output, which is printed after the\nthread join. The `CliOutput.found_any_files` flag drives the exit code:\n\n- `exit(11)` when items are found (and `-W` is not set) - useful in scripts.\n- `exit(0)` otherwise.\n\n---\n\n## Cancellation\n\n`Arc<AtomicBool>` (SeqCst ordering) is shared between the Ctrl+C signal handler\nand the calculation thread. The tools in `czkawka_core` poll it during scanning\nand stop gracefully when it is `true`.\n\n---\n\n## Key Dependencies\n\n| Crate              | Purpose                          |\n|--------------------|----------------------------------|\n| `clap` 4.5         | CLI parsing (derive API)         |\n| `indicatif` 0.18   | Progress bars                    |\n| `crossbeam-channel`| Unbounded channel for progress   |\n| `ctrlc` 3.4        | SIGINT / Ctrl+C handling         |\n| `humansize` 2.1    | Human-readable byte sizes        |\n| `czkawka_core`     | All scanning logic               |\n\n### Optional features (forwarded to `czkawka_core`)\n\n- `heif` – HEIF/HEIC image support (requires libheif)\n- `libraw` – RAW image support (requires libraw)\n- `libavif` – AVIF image support (requires libavif)\n- `xdg_portal_trash` – FlatPak-compatible trash (XDG portal)\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n","czkawka_cli/CLAUDE.md":"AGENTS.md","czkawka_core/AGENTS.md":"# czkawka_core – Architecture Guide\n\n## Overview\n\n`czkawka_core` is the shared scanning engine used by all frontends. It has no UI\ndependency. Every scanning tool is implemented here; frontends only configure the\ntool structs and drive them via the `Search` trait.\n\n`TOOLS_NUMBER = 14` (defined in `src/lib.rs`).\n\n---\n\n## Source Layout\n\n```\nczkawka_core/src/\n├── lib.rs                         # Crate root; TOOLS_NUMBER constant\n├── localizer_core.rs              # Fluent i18n loader (flc! macro)\n├── common/\n│   ├── mod.rs                     # Module declarations + re-exports only (no logic of its own)\n│   ├── traits.rs                  # Search, PrintResults, DeletingItems, FixingItems, ResultEntry, AllTraits\n│   ├── tool_data.rs               # CommonToolData struct + the CommonData trait (defined here, not traits.rs)\n│   ├── model.rs                   # ToolType, CheckingMethod, FileEntry, DeleteMethod\n│   ├── progress_data.rs           # ProgressData, CurrentStage enum\n│   ├── progress_stop_handler.rs   # ProgressThreadHandler, check_if_stop_received\n│   ├── dir_traversal.rs           # DirTraversalBuilder, DirTraversalResult\n│   ├── cache.rs                   # Generic load/save cache (bincode + JSON)\n│   ├── cache/\n│   │   └── cleaning.rs            # Outdated cache cleanup\n│   ├── config_cache_path.rs       # Platform config/cache path lookup\n│   ├── directories.rs             # Directories struct (included/excluded/reference)\n│   ├── extensions.rs              # Extensions struct (allowed/excluded filtering)\n│   ├── items.rs                   # ExcludedItems (glob pattern matching)\n│   ├── consts.rs                  # Extension lists: IMAGE_RS_EXTENSIONS, …\n│   ├── formatting.rs              # format_time and other display formatting helpers\n│   ├── path_utils.rs              # split_path, regex_check, normalize_windows_path\n│   ├── fs_ops.rs                  # make_hard_link, make_file_symlink, remove_single_file/folder\n│   ├── threads.rs                 # get_number_of_threads, set_number_of_threads\n│   ├── deletion.rs                # DeleteResult, DeleteItemType\n│   ├── audio_fingerprint.rs       # Chromaprint fingerprint calculation (used by same_music)\n│   ├── ffmpeg_utils.rs            # FFmpeg invocation helpers\n│   ├── video_utils.rs             # Video metadata extraction\n│   ├── image.rs                   # Image loading helpers\n│   ├── process_utils.rs           # Child-process helpers\n│   ├── logger.rs                  # Logging configuration\n│   └── basic_gui_cli.rs           # Types shared with GUI/CLI boundaries\n├── tools/\n│   ├── mod.rs                     # Re-exports all tool modules\n│   ├── duplicate/                 # Hash/name/size duplicate detection\n│   ├── empty_files/\n│   ├── empty_folder/\n│   ├── big_file/\n│   ├── similar_images/            # Perceptual hashing (image_hasher)\n│   ├── similar_videos/            # Frame-based video similarity\n│   ├── same_music/                # Audio tag + chromaprint fingerprint\n│   ├── broken_files/              # ZIP/PDF/audio/image validation\n│   ├── bad_extensions/            # Extension vs magic-number mismatch\n│   ├── bad_names/                 # Naming policy checks\n│   ├── invalid_symlinks/\n│   ├── temporary/\n│   ├── exif_remover/\n│   └── video_optimizer/           # Crop detection + FFmpeg transcoding\n└── helpers/\n    ├── messages.rs                # Messages struct (errors, warnings, info)\n    ├── delayed_sender.rs          # Rate-limited progress sender\n    ├── audio_checker.rs           # Audio file validation\n    ├── ffprobe.rs                 # ffprobe JSON output parsing\n    └── debug_timer.rs             # Debug-build timing\n```\n\n---\n\n## Tool Module Layout\n\nEach tool lives in its own directory, typically with these files:\n\n| File | Content |\n|------|---------|\n| `mod.rs` | Tool struct + `Info` struct + `Parameters` struct (if needed) |\n| `core.rs` | Internal scanning functions (`check_files_*`, `hash_calculation`, …) |\n| `traits.rs` | Trait implementations: `Search`, `CommonData`, `DeletingItems`, `PrintResults` |\n| `tests.rs` | Unit tests for the tool (every tool has one except `temporary/`) |\n\nA few tools don't fit the plain 4-file pattern: `bad_extensions/` adds a `workarounds.rs`, and\n`video_optimizer/` adds `encoding.rs` plus its own `core/` subdirectory\n(`video_converter.rs`, `video_cropper.rs`).\n\nExample - `EmptyFiles`:\n\n```\nsrc/tools/empty_files/\n├── mod.rs      # pub struct EmptyFiles { common_data, information, files_to_check, params }\n│               #   params: EmptyFilesParameters\n├── core.rs     # fn check_files() → WorkContinueStatus\n├── tests.rs    # Unit tests\n└── traits.rs   # impl Search, CommonData, PrintResults, DeletingItems, AllTraits\n```\n\n---\n\n## Core Traits\n\n`CommonData` is the odd one out: it's defined in `src/common/tool_data.rs` next to\n`CommonToolData`, not in `traits.rs`. Every other trait below lives in `src/common/traits.rs`.\nSignatures are simplified for orientation - both files have more methods (mostly\nsetter/getter pairs and default-impl helpers) than shown here.\n\n```rust\npub trait Search {\n    fn search(&mut self, stop_flag: &Arc<AtomicBool>,\n              progress_sender: Option<&Sender<ProgressData>>);\n}\n\n// src/common/tool_data.rs - NOT traits.rs\npub trait CommonData {\n    type Info;\n    type Parameters;\n    fn get_cd(&self) -> &CommonToolData;\n    fn get_cd_mut(&mut self) -> &mut CommonToolData;\n    fn found_any_items(&self) -> bool;\n    // ~30 more setters/getters for directories, extensions, delete method, cache flags, …\n}\n\npub trait PrintResults: CommonData {\n    fn write_results<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn print_results_to_writer<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn save_results_to_file_as_json(&self, file: &str, pretty: bool) -> io::Result<()>;\n    // + write_base_search_paths, print_results_to_output/_to_file, save_all_in_one, …\n}\n\npub trait DeletingItems {\n    fn delete_files(&mut self, stop_flag: &Arc<AtomicBool>,\n                    progress_sender: Option<&Sender<ProgressData>>) -> WorkContinueStatus;\n}\n\npub trait FixingItems {\n    type FixParams;\n    fn fix_items(&mut self, stop_flag: &Arc<AtomicBool>,\n                 progress_sender: Option<&Sender<ProgressData>>, fix_params: Self::FixParams);\n}\n\npub trait ResultEntry {\n    fn get_path(&self) -> &Path;\n    fn get_modified_date(&self) -> u64;\n    fn get_size(&self) -> u64;\n}\n\npub trait AllTraits: DebugPrint + PrintResults + DeletingItems + CommonData + Search {}\n```\n\n---\n\n## CommonToolData (`src/common/tool_data.rs`)\n\n```rust\npub struct CommonToolData {\n    pub(crate) tool_type: ToolType,\n    pub(crate) text_messages: Messages,        // Accumulated warnings / errors\n    pub(crate) directories: Directories,       // Included / excluded / reference paths\n    pub(crate) extensions: Extensions,         // Allowed / excluded extensions\n    pub(crate) excluded_items: ExcludedItems,  // Glob patterns (e.g. \"*/.*\")\n    pub(crate) recursive_search: bool,\n    pub(crate) delete_method: DeleteMethod,\n    pub(crate) maximal_file_size: u64,\n    pub(crate) minimal_file_size: u64,\n    pub(crate) stopped_search: bool,\n    pub(crate) use_cache: bool,\n    pub(crate) delete_outdated_cache: bool,\n    pub(crate) save_also_as_json: bool,\n    pub(crate) use_reference_folders: bool,\n    pub(crate) dry_run: bool,\n    pub(crate) move_to_trash: bool,\n    pub(crate) hide_hard_links: bool,\n}\n```\n\n---\n\n## DirTraversal (`src/common/dir_traversal.rs`)\n\nBuilder-pattern filesystem traversal used by all tools:\n\n```rust\nlet result = DirTraversalBuilder::new()\n    .common_data(&self.common_data)\n    .group_by(|fe: &FileEntry| fe.size)   // Groups entries by this key\n    .stop_flag(stop_flag)\n    .progress_sender(progress_sender)\n    .checking_method(CheckingMethod::Size)\n    .build()\n    .run();\n\nmatch result {\n    DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { … }\n    DirTraversalResult::Stopped => return WorkContinueStatus::Stop,\n}\n```\n\n**Internals:**\n- Rayon `into_par_iter().with_max_len(2)` for parallel folder processing.\n- Two-phase: visit root dirs/files first, then recurse batch by batch.\n- Applies extension, exclusion, and size filters during traversal.\n- On Unix: optional filesystem-boundary checking (`exclude_other_filesystems`).\n- Progress tracked via `ProgressThreadHandler` (dedicated thread, 200 ms interval).\n\n---\n\n## Progress Reporting (`src/common/progress_data.rs` + `src/common/progress_stop_handler.rs`)\n\n`ProgressData`/`ToolStage` are defined in `progress_data.rs`; `ProgressThreadHandler` and\n`check_if_stop_received` live in `progress_stop_handler.rs`.\n\n```rust\npub struct ProgressData {\n    pub stage: ToolStage,           // Current operation\n    pub entries_checked: usize,\n    pub entries_to_check: usize,\n    pub bytes_checked: u64,\n    pub bytes_to_check: u64,\n}\n```\n\n`ToolStage` is a nested enum (`ToolStage::Duplicate(DuplicateStage)`, `ToolStage::SimilarImages(SimilarImagesStage)`, ...) so a\nstage can't be paired with a tool it doesn't belong to - this replaces the old flat `CurrentStage` enum plus its runtime\n`ProgressData::validate()` assertions. Call `progress.to_display()` to get a fully translated, ready-to-render\n`ProgressDisplay` (label + percentages) instead of branching on the stage in frontend code.\n\n`ProgressThreadHandler` spawns a background thread that polls `AtomicUsize`/`AtomicU64`\ncounters and sends `ProgressData` to the frontend channel every ~200 ms.\n\nCall `handler.increase_items(n)` / `handler.increase_size(n)` from rayon tasks\n(using `Relaxed` ordering - fast, no synchronization overhead).\n\n---\n\n## Cancellation\n\n```rust\n// In hot paths:\nif check_if_stop_received(stop_flag) {\n    return WorkContinueStatus::Stop;  // or break / return None in rayon\n}\n```\n\n`stop_flag` is `Arc<AtomicBool>` with `Relaxed` ordering - sufficient for simple\n\"should I stop?\" polling without synchronization cost. The frontend sets it to\n`true`; the backend polls and stops gracefully.\n\n---\n\n## Cache (`src/common/cache.rs`)\n\n```rust\n// Save\nsave_cache_to_file_generalized::<T>(\n    cache_file_name,    // e.g. \"cache_duplicates.bin\"\n    &btree_map,         // BTreeMap<String, T> (String = canonical path)\n    save_also_as_json,\n    minimum_file_size,\n);\n\n// Load\nlet (messages, opt_cache) =\n    load_cache_from_file_generalized_by_path::<T>(\n        cache_file_name,\n        delete_outdated_cache,\n        &used_files,    // Current BTreeMap to validate against\n    );\n```\n\n- Serialized with `bincode` (binary). Optionally also as `.json`.\n- Cached entries validated by path + size + mtime on load.\n- 8 GB memory limit on serialization.\n- Each tool has its own version constant (`CACHE_DUPLICATE_VERSION`, …).\n\n---\n\n## Rayon Usage Patterns\n\n```rust\n// Directory traversal\nfolders.into_par_iter().with_max_len(2).map(|dir| { … }).while_some().collect();\n\n// Deletion with early exit\nitems.into_par_iter().map(|e| {\n    if check_if_stop_received(stop_flag) { return None; }\n    // … process e …\n    Some(result)\n}).while_some().flatten().collect()\n```\n\nThread count is globally controlled via `set_number_of_threads(n)` which stores\nto a `LazyLock<Mutex<Option<usize>>>` and is applied via rayon's thread pool.\n\n---\n\n## ToolType Enum (`src/common/model.rs`)\n\n```rust\npub enum ToolType {\n    Duplicate, EmptyFolders, EmptyFiles, InvalidSymlinks,\n    BrokenFiles, BadExtensions, BadNames, BigFile, SameMusic,\n    SimilarImages, SimilarVideos, TemporaryFiles, ExifRemover,\n    VideoOptimizer,\n    #[default]\n    None,\n}\n```\n\nTools that support reference directories: `Duplicate`, `SameMusic`,\n`SimilarImages`, `SimilarVideos` (`may_use_reference_paths()`).\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `rayon` | Parallel iterators |\n| `crossbeam-channel` | Progress channels |\n| `blake3` | Fast file hashing |\n| `image` + `image_hasher` | Image loading + perceptual hash |\n| `lofty` | Audio tag reading |\n| `symphonia` | Audio decoding |\n| `rusty-chromaprint` | Audio fingerprinting |\n| `bincode` | Cache serialization |\n| `zip` | ZIP validation |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `trash` | Move-to-trash |\n| `directories-next` | Config/cache path |\n| `fun_time` | `#[fun_time]` timing attribute |\n| `similario_core` | Video similarity (sibling path-dependency; replaced `vid_dup_finder_lib`) |\n\nOptional (behind features):\n- `heif` → `libheif-rs` – HEIC/HEIF image support\n- `libraw` → `rawler` / `libraw-rs` – RAW photo support\n- `libavif` – AVIF image support\n- `xdg_portal_trash` – FlatPak trash via XDG portal\n- `blake_pure` → `blake3/pure` – pure-Rust Blake3 (no SIMD asm), for targets where the asm\n  backend doesn't build\n\n---\n\n## Comments\n\nShort and minimal, same baseline as the rest of the workspace (root `AGENTS.md`). The one\nexception in this workspace: `czkawka_core` is a library every frontend depends on, and external\nconsumers often read only its `pub fn`/`pub struct` surface, not the internals. That public API\nmay carry a bit more explanation than code elsewhere in the repo - still terse, no paragraphs,\njust enough to cover what isn't obvious from the signature alone.\n","czkawka_core/CLAUDE.md":"AGENTS.md","czkawka_gui/AGENTS.md":"# czkawka_gui – Architecture Guide\n\n## Status: Maintenance mode only\n\nNo new features. Bug-fixes that keep it compatible with `czkawka_core` API\nchanges are accepted.\n\n---\n\n## Overview\n\nGTK4 frontend using XML UI files authored in Cambalache. All scanning tools\navailable in `czkawka_core` are exposed via a notebook (tab-based) interface.\n\n---\n\n## Source Layout\n\n```\nczkawka_gui/src/\n├── main.rs                          # GTK Application setup, GuiData construction\n├── initialize_gui.rs                # Initial widget state\n├── compute_results.rs               # Converts scan results → GTK ListStore rows\n├── saving_loading.rs                # JSON settings load/save\n├── language_functions.rs            # LANGUAGES_ALL constant (26 entries)\n├── localizer_gui.rs                 # flg! macro, Fluent loader\n├── gui_structs/\n│   ├── gui_data.rs                  # GuiData – central struct for all UI components\n│   ├── gui_main_notebook.rs         # 11-tool tab notebook\n│   ├── gui_upper_notebook.rs        # Included/excluded directory tabs\n│   ├── gui_settings.rs              # Settings dialog\n│   ├── gui_header.rs                # Menu bar (About, Settings)\n│   ├── gui_bottom_buttons.rs        # Action buttons (Search, Delete, etc.)\n│   ├── gui_progress_dialog.rs       # Scan progress window\n│   ├── gui_about.rs                 # About dialog\n│   ├── gui_compare_images.rs        # Side-by-side image diff overlay\n│   ├── common_tree_view.rs          # TreeView/ListStore rendering + SharedModelEnum\n│   ├── common_upper_tree_view.rs    # Dir-list TreeViews\n│   ├── gui_popovers_select.rs       # Selection filter popovers\n│   └── gui_popovers_sort.rs         # Sort filter popovers\n├── connect_things/                  # Callback wiring files\n│   ├── connect_button_search.rs     # Scan thread spawn + progress forwarding\n│   ├── connect_button_delete.rs     # File deletion with confirmation\n│   ├── connect_button_save.rs       # Export results (CSV/JSON)\n│   ├── connect_button_select.rs     # Row selection helpers\n│   ├── connect_button_sort.rs       # Column-header sort\n│   ├── connect_button_stop.rs       # Cancel scan (stop flag)\n│   ├── connect_button_move.rs       # Move files to folder\n│   ├── connect_button_hardlink.rs   # Hard-link/symlink creation\n│   ├── connect_button_compare.rs    # Image comparison launcher\n│   ├── connect_duplicate_buttons.rs # Duplicate-specific UI\n│   ├── connect_notebook_tabs.rs     # Tab-switch handler\n│   ├── connect_selection_of_directories.rs  # Dir-picker dialogs\n│   ├── connect_change_language.rs   # Language switch + full UI relabel\n│   ├── connect_settings.rs          # Settings window callbacks\n│   ├── connect_progress_window.rs   # Progress bar updates from channel\n│   ├── connect_show_hide_ui.rs      # Error text visibility toggle\n│   ├── connect_similar_image_size_change.rs\n│   ├── connect_same_music_mode_changed.rs\n│   ├── connect_popovers_select.rs\n│   ├── connect_popovers_sort.rs\n│   ├── connect_header_buttons.rs\n│   ├── connect_about_buttons.rs\n│   ├── connect_krokiet_info_dialog.rs  # One-time Krokiet deprecation notice (shown on first run)\n│   ├── connect_krokiet_promo_dialog.rs # Krokiet promo dialog (shown from the header button)\n│   └── file_chooser_helpers.rs\n├── helpers/\n│   ├── enums.rs                     # Column index enums for all tools\n│   ├── list_store_operations.rs     # Append/read GTK ListStore rows\n│   ├── image_operations.rs          # Image resize/preview for GTK\n│   ├── model_iter.rs                # GTK TreeView iteration\n│   └── mod.rs\n├── help_functions.rs                # HEADER_ROW_COLOR, MAIN_ROW_COLOR, set_buttons\n├── help_combo_box.rs                # Combo box option arrays (hash algs, etc.)\n├── notebook_enums.rs                # NotebookMainEnum (per-tool tab index)\n├── notebook_info.rs                 # Tab label + ListStore column definitions\n├── opening_selecting_records.rs     # Double-click → open file/folder\n├── taskbar_progress.rs              # Windows taskbar progress (real impl)\n├── taskbar_progress_dummy.rs        # No-op stand-in for non-Windows targets\n├── taskbar_progress_win.rs          # Windows-specific taskbar API calls\n└── gtk_traits.rs                    # Custom GTK trait extensions\n\nui/\n├── czkawka.cmb         # Cambalache project file (source of truth for the .ui files below)\n├── main_window.ui      # Cambalache XML (65 KB) – entire main window layout\n├── settings.ui         # Settings dialog\n├── compare_images.ui   # Image comparison panel\n├── popover_select.ui   # Selection filter popover\n├── popover_sort.ui     # Sort popover\n├── popover_right_click.ui # Right-click context menu popover\n├── about_dialog.ui     # About window\n└── progress.ui         # Scan progress dialog\n```\n\n---\n\n## GuiData Struct (`gui_structs/gui_data.rs`)\n\nCentral struct passed by clone/reference to every callback:\n\n```rust\npub struct GuiData {\n    pub window_main: gtk4::Window,\n    pub main_notebook: GuiMainNotebook,         // 11 tool tabs\n    pub upper_notebook: GuiUpperNotebook,       // Dir configuration\n    pub bottom_buttons: GuiBottomButtons,       // Action buttons\n    pub progress_window: GuiProgressDialog,\n    pub settings: GuiSettings,\n    pub header: GuiHeader,\n    pub compare_images: GuiCompareImages,\n    pub about: GuiAbout,\n    pub popovers_select: GuiSelectPopovers,\n    pub popovers_sort: GuiSortPopovers,\n    pub entry_info: gtk4::Entry,\n    pub text_view_errors: gtk4::TextView,\n    pub scrolled_window_errors: gtk4::ScrolledWindow,  // show/hide bottom error panel\n    pub file_dialog_include_exclude_folder_selection: gtk4::FileChooserNative,\n    pub file_dialog_move_to_folder: gtk4::FileChooserNative,\n    pub taskbar_state: Rc<RefCell<TaskbarProgress>>,\n    pub shared_buttons: Rc<RefCell<HashMap<NotebookMainEnum, …>>>,\n    pub stop_flag: Arc<AtomicBool>,\n}\n```\n\nConstructed by `GuiData::new_with_application()` which loads all `.ui` files\nvia `gtk4::Builder`.\n\n---\n\n## Scan Thread Architecture\n\n```\nUser clicks Search\n→ connect_button_search.rs\n→ Disables UI, shows progress window\n→ Spawn worker thread:\n    - Read settings from GTK widgets\n    - Create tool struct (e.g. DuplicateFinder)\n    - Call tool.search(stop_flag, progress_sender)\n    - Send Message::Duplicates(tool) via result_sender\n→ Main thread: receive progress updates → update bars\n→ On result received:\n    - compute_results.rs processes tool data → appends to ListStore\n    - Re-enables UI, hides progress\n```\n\n**Channel types:**\n- `Sender<Message>` – one-shot result channel (tool enum with full data)\n- `Sender<ProgressData>` – real-time progress from core\n\n---\n\n## Result Population (`compute_results.rs`)\n\n1. Receive `Message::Duplicates(DuplicateFinder)` from worker thread.\n2. Extract sorted file groups from the tool.\n3. For each group: insert a **header row** (colored `HEADER_ROW_COLOR`), then\n   one row per file (`MAIN_ROW_COLOR`).\n4. `append_row_to_list_store()` writes individual cells to `gtk4::ListStore`.\n5. `finalize_compute()` stores the tool object in `SubView.shared_model_enum`\n   (for later re-use by selection/sort callbacks).\n\n---\n\n## Language Switching (`language_functions.rs`)\n\n26 languages defined as `LANGUAGES_ALL: &[Language]` with `combo_box_text`\n(display name) and `short_text` (BCP-47 code).\n\nOn language change:\n1. Look up `short_text` from selected combo index.\n2. Call `localizer.select([lang_id])` for both `czkawka_core` and `czkawka_gui`.\n3. Call `gui_data.update_language()` → propagates to all sub-structs which\n   update their widget labels via `flg!(\"key\")`.\n\n---\n\n## Settings Storage (`saving_loading.rs`)\n\nJSON file at `~/.config/Czkawka/czkawka_gui_config.json`. Struct `SettingsJson`\nserialized with `serde_json`. Includes directories, tool parameters, UI state,\nlanguage preference.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `gtk4` 0.11 | GTK4 GUI framework |\n| `gdk4` 0.11 | Drawing/rendering |\n| `glib` 0.22 | Event loop, `spawn_future_local` |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `open` 5.3 | Launch file manager |\n| `image` 0.25 | Preview thumbnails |\n| `resvg` 0.47 | SVG icon scaling |\n| `rayon` 1.10 | Parallel sort of results |\n| `crossbeam-channel` | Result + progress channels |\n| `czkawka_core` | Scanning engine |\n\nOptional features (forwarded to core): `heif`, `libraw`, `libavif`,\n`xdg_portal_trash`.\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n","czkawka_gui/CLAUDE.md":"AGENTS.md","krokiet/AGENTS.md":"# krokiet – Architecture Guide\n\n## Overview\n\nPrimary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI\nis declared in `ui/*.slint`; Rust wires all callbacks and drives the Slint data\nmodels. All scanning logic lives in `czkawka_core`.\n\n---\n\n## Source Layout\n\n```\nkrokiet/\n├── build.rs                          # slint_build::compile(\"ui/main_window.slint\")\n├── src/\n│   ├── main.rs                       # Entry point: load settings, create MainWindow,\n│   │                                  #   wire all callbacks, run event loop\n│   ├── shared_models.rs              # SharedModels – Arc<Mutex<…>> of all tool results\n│   ├── common.rs                     # Column index enums (IntDataDuplicateFiles, …)\n│   │                                  #   and sort index helpers\n│   ├── localizer_krokiet.rs          # flk! macro, LANGUAGE_LOADER_KROKIET\n│   ├── set_initial_gui_info.rs       # Asserts Slint combo lists == Rust enums\n│   ├── set_initial_scroll_list_data_indexes.rs\n│   ├── simpler_model.rs              # Minimal serializable row struct for threading\n│   ├── audio_player.rs               # Play sound on scan completion\n│   ├── notification_manager.rs       # Desktop notifications\n│   ├── create_calculate_task_size.rs # Background total-size calculator\n│   ├── clear_outdated_video_thumbnails.rs\n│   ├── connect_scan.rs               # Routes scan to per-tool functions\n│   ├── connect_scan/                 # One file per tool (duplicate.rs, …)\n│   ├── connect_progress_receiver.rs  # Receives ProgressData → updates UI bar\n│   ├── connect_row_selection/        # Row checkbox logic, group handling\n│   │   ├── mod.rs\n│   │   ├── checker.rs\n│   │   ├── clipboard.rs\n│   │   ├── context_menu.rs\n│   │   ├── opener.rs\n│   │   └── selection.rs\n│   ├── connect_sort.rs               # Column-header sort (string + int indices)\n│   ├── connect_translation.rs        # LANGUAGE_LIST, change_language(), translate_items()\n│   ├── connect_compare.rs            # Image comparison overlay\n│   ├── connect_show_preview.rs       # File preview / thumbnail\n│   ├── connect_open.rs               # Open file/folder in system app\n│   ├── connect_save.rs               # Export results via PrintResults\n│   ├── connect_show_confirmation.rs  # Confirmation popup before destructive action\n│   ├── connect_directories_changes.rs\n│   ├── connect_clean_cache.rs\n│   ├── connect_tab_changed.rs\n│   ├── connect_stop.rs               # Sets stop_flag = true\n│   ├── connect_rfd.rs                # File picker (rfd)\n│   ├── connect_select/\n│   │   ├── mod.rs\n│   │   └── custom_select.rs          # Advanced filter popup\n│   ├── file_actions/\n│   │   ├── connect_delete.rs\n│   │   ├── connect_move.rs\n│   │   ├── connect_rename.rs\n│   │   ├── connect_hardlink.rs\n│   │   ├── connect_symlink.rs\n│   │   ├── connect_optimize_video.rs\n│   │   └── connect_clean_exif.rs\n│   ├── model_operations/\n│   │   ├── mod.rs                    # Slint model ↔ thread-safe model conversion\n│   │   └── model_processor.rs        # Parallel item processing framework\n│   └── settings/\n│       ├── mod.rs                    # Load/save JSON settings (11 presets)\n│       ├── model.rs                  # BasicSettings, SettingsCustom structs\n│       └── combo_box.rs              # StringComboBoxItems + regenerate_items()\n└── ui/\n    ├── main_window.slint             # Root component; imports everything; exports globals\n    ├── globals/\n    │   ├── common.slint              # Enums + structs: ActiveTab, SingleMainListModel, …\n    │   ├── gui_state.slint           # Global GuiState (transient runtime state)\n    │   ├── settings.slint            # Global Settings (persisted)\n    │   ├── translations.slint        # Global Translations (all UI strings)\n    │   ├── callabler.slint           # Global Callabler (all Rust to UI callbacks)\n    │   ├── fonts.slint               # FontSizes global\n    │   ├── color_palette.slint       # Dark-theme color definitions\n    │   └── text_size.slint\n    ├── screens/\n    │   ├── left_side_panel.slint     # Tool selector + tool settings sub-panel\n    │   ├── main_lists.slint          # Results table (bound to model)\n    │   ├── action_buttons.slint      # Scan / Stop / Select / Delete / … buttons\n    │   ├── bottom_panel.slint        # Progress bar + error text + status\n    │   ├── progress.slint            # Real-time scan progress widget\n    │   ├── image_compare.slint       # Side-by-side image diff overlay\n    │   ├── settings_list.slint       # Settings tab form\n    │   ├── tool_settings.slint       # Per-tool options (hash alg, threshold, …)\n    │   ├── about.slint                # About tab\n    │   └── included_paths.slint\n    ├── components/\n    │   ├── preview.slint             # File preview / thumbnail side panel\n    │   ├── selectable_tree_view.slint # Custom tree widget for path lists\n    │   ├── hint_text.slint\n    │   ├── popup_base.slint\n    │   ├── popup_centered_text.slint\n    │   └── popup_context_menu.slint\n    └── popups/                       # 18 modal dialogs: popup_delete, popup_save,\n                                       #   popup_optimize, popup_crop_video,\n                                       #   popup_custom_select, popup_rename_*, …\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## SharedModels (`src/shared_models.rs`)\n\n```rust\npub struct SharedModels {\n    pub shared_duplication_state:       Option<DuplicateFinder>,\n    pub shared_empty_folders_state:     Option<EmptyFolder>,\n    // … one field per tool (14 total)\n}\n```\n\nCreated once in `main.rs` as `Arc<Mutex<SharedModels>>`, cloned and passed to\nevery callback that needs scan results (preview, compare, save, delete, …).\n\nWorker threads lock exclusively to **write** results after a scan finishes.\nUI callbacks lock briefly to **read** results for previews / exports.\n\n`VideoOptimizer` is one of the 14 tools but doesn't fit the scan/select/delete\nshape of the others: it has its own `connect_scan/video_optimizer.rs` entry\npoint plus a dedicated `file_actions/connect_optimize_video.rs` action, and\ntwo popups (`popup_optimize.slint` for transcode options,\n`popup_crop_video.slint` for crop-region preview) instead of reusing the\ngeneric delete/save popups.\n\n---\n\n## Scan Data Flow\n\n```\n1. User clicks Scan → on_scan_starting(active_tab)\n2. connect_scan.rs: collect_settings() → ScanData → route to scan_<tool>()\n3. scan_<tool>(): spawn worker thread\n       → create and configure tool struct\n       → tool.search(stop_flag, progress_sender)\n       → store tool in shared_models.lock()\n       → populate Slint VecModel<SingleMainListModel>\n       → app.upgrade_in_event_loop(|app| app.set_<tool>_model(model))\n4. UI re-renders with new model\n```\n\n---\n\n## Slint Data Model\n\n```rust\n// ui/common.slint\nexport struct SingleMainListModel {\n    checked: bool,           // row selected by user\n    header_row: bool,        // group header\n    filled_header_row: bool, // header with formatted summary\n    focused_row: bool,      // UI highlight\n    val_str: [string],       // text columns\n    val_int: [int],          // numeric columns (raw values for sorting)\n}\n```\n\nColumn indices are constants in `src/common.rs`:\n\n```rust\npub enum StrDataDuplicateFiles { Size, Name, Path, ModificationDate }\npub enum IntDataDuplicateFiles { ModificationDatePart1, ModificationDatePart2,\n                                  SizePart1, SizePart2 }\n```\n\nSlint cannot store `i64`, so dates and file sizes are split into two `i32` fields.\n\n---\n\n## Translation System\n\nSlint has no native Fluent support. Workaround:\n1. All UI text is bound to properties in the `Translations` global\n   (`ui/translations.slint`), not hardcoded in `.slint`.\n2. `translate_items()` in `connect_translation.rs` (~440 lines, file is ~750 lines total)\n   sets every property via `flk!(\"key\")` after language changes.\n3. Language list defined as `LANGUAGE_LIST: &[Language]` in\n   `connect_translation.rs`.\n4. Hardcoded combo-box lists in `.slint` files (e.g. `Settings.languages_list`)\n   are verified at startup against Rust enums via `assert_eq!` in\n   `set_initial_gui_info.rs` (workaround for [slint#7632](https://github.com/slint-ui/slint/issues/7632)).\n\n---\n\n## Settings (`src/settings/`)\n\nTwo-tier JSON system:\n\n| File | Content |\n|------|---------|\n| `~/.config/krokiet/config_general.json` | `BasicSettings` – default preset index, preset names, theme, window size |\n| `~/.config/krokiet/config_preset_N.json` | `SettingsCustom` – paths, extensions, tool parameters |\n\n(`directories-next` resolves the config dir from the lowercased app name only - the\nqualifier/organization passed to `set_config_cache_path` are ignored on Linux.)\n\n11 preset slots (indices 0–10). Slot 10 is reserved for CLI-mode overrides.\n`StringComboBoxItems::regenerate_items()` builds all combo box option arrays\nfrom Rust enums; used both for settings serialization and UI initialization.\n\n---\n\n## Callback Registration Pattern\n\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // use app …\n});\n```\n\n`expect()` is correct here: if the window is gone, no callback should fire.\nCross-thread updates use `weak.upgrade_in_event_loop(|app| { … })`.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework (GPL-3.0) |\n| `czkawka_core` | Scanning engine |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `crossbeam-channel` | Progress + result channels |\n| `rayon` | Parallel result sorting |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n| `rfd` 0.17 | File picker (xdg-portal backend) |\n| `notify-rust` | Desktop notifications |\n| `rodio` | Scan-complete sound (optional `audio` feature) |\n| `fontique` | Font discovery (dlopen, avoids C dep) |\n\n### Optional features\n\n- `audio` – scan-complete sound via `rodio`\n- Renderer features: `winit_femtovg` (default), `winit_software` (default),\n  `skia_opengl`, `skia_vulkan`, etc. – select GPU backend at compile time\n- `heif`, `libraw`, `libavif`, `xdg_portal_trash` – forwarded to `czkawka_core`\n\n### Build\n\n`build.rs` compiles `ui/main_window.slint` with style `fluent-dark` (overridable\nvia `SLINT_STYLE` env var).\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row.\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n","krokiet/CLAUDE.md":"AGENTS.md"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Czkawka – Codebase Guide\n\n## Language\n\nAll code, comments, commit messages, and documentation must be written in **English**.\n\n---\n\n## AI-authored commits & merge/pull requests\n\nAlways, without exception: every commit created by an AI assistant must start its title (the\nfirst line of the commit message) with `[AI] `, e.g. `[AI] Fix crash when scanning empty\ndirectories`. If the commit message has a body, the body does not need the prefix again - the\ntitle alone marks it.\n\nThe same applies to any merge request (GitLab) or pull request (GitHub) opened by an AI\nassistant: both the title and the body/description must start with `[AI] `, e.g. title\n`[AI] Add duplicate detection for symlinked folders` and body starting with\n`[AI] This MR adds ...`. This must stay visible to reviewers at a glance in both GitLab and\nGitHub - never omit it, even for small or automatic changes.\n\n---\n\n## Rust Style\n\nApplies to every Rust crate in the workspace (`czkawka_core`, `czkawka_cli`, `czkawka_gui`,\n`krokiet`, `cedinia`). Program-specific deviations or elaborations live in that program's own\n`AGENTS.md`.\n\n**Formatting & lints**\n- Format with `cargo +nightly fmt`, then `cargo fmt` for stable compatibility\n  (`.rustfmt.toml`: `max_width = 120`, granular import grouping `group_imports = \"StdExternalCrate\"`).\n- Code must compile without clippy warnings. Suppress a lint locally only with a comment\n  explaining why.\n\n**Errors & panics**\n- Prefer `?`, `ok_or_else`, `map_err` over panic-based flows.\n- `unwrap()` only in tests.\n- `expect()` in production only when BOTH hold: there is no correct recovery path, and\n  continuing would likely hide a serious bug - always with a precise, actionable message. This\n  is visible throughout Krokiet/Cedinia callback code: a Slint weak reference is upgraded with\n  `expect()` because if the window is already gone there is nothing meaningful to do except crash.\n- Never silently swallow errors with `let _ = ...` unless the failure is genuinely irrelevant.\n- Model domain errors with explicit types (`thiserror`/enums), not raw strings.\n- Wrap I/O errors with context (e.g. file path) instead of letting std's bare \"Permission\n  denied\" propagate - without the path it's hard to find the source.\n- Don't ignore `Result`; handle it explicitly.\n\n**Code shape**\n- Keep functions focused; once one grows past ~30-50 lines, extract its logical steps into\n  separate, well-named functions. Never carve a long function into sections with comment\n  headers or bare `{ ... }` scope blocks - a `// validate input` comment over a block is the\n  signal to pull it out into `validate_input(...)`, not to fence it off.\n- Name things by intent, not by container/type (`user_count`, not `vec_users_len`).\n- Make invalid states unrepresentable: enums over strings, `Option<T>` over sentinel values\n  (`-1`, empty string).\n- Prefer iterators and collection methods over manual index-based loops.\n- Prefer `#[derive(...)]` over manual impls when behavior is standard.\n- Avoid unnecessary copies and allocations; prefer references and `Cow` where it helps.\n- Prefer external crates over reimplementing them.\n\n**Comments**\nKeep comments short and minimal - a single terse line, not a paragraph. Code should be\nself-documenting through clear naming. Add a comment only when the _why_ is not easily inferred\nfrom reading the code - algorithmic choices, non-obvious constraints, workarounds for external\nlibrary bugs, etc. Do not restate what the code already says. Never use the `—`/`–` dash\ncharacters in code, comments, or commit messages - plain `-` only (ASCII box-drawing diagrams are\nthe only exception).\n\nException: `czkawka_core` is a library - every frontend in this workspace depends on it, and so do\nexternal consumers who only read its public API, not its internals. Its `pub fn`/`pub struct`\nsurface warrants more explanation than the rest of the workspace; see `czkawka_core/AGENTS.md`.\n\n**Tests, fuzzers, benchmarks**\n- Cover as much code as possible with tests; keep them readable with explicit `assert_eq!` and\n  input data close to the assertions.\n- Add fuzzers/examples/benchmarks where it helps show usage, find bugs, or measure the\n  performance impact of a change.\n\n**`unsafe`**\nAvoid whenever possible. Every `unsafe` block documents its invariants and why the safety\nrequirements hold (see cedinia's `#[unsafe(no_mangle)] android_main`).\n\n**Files & logging**\n- Keep source files at or below 500 lines; split large files into modules.\n- Always log via `log` (`handsome_logger`); reach for `tracing` only if actually needed.\n\n**Auto-vectorization**\nLLVM vectorizes a loop when it can prove: no loop-carried dependency, no aliasing, no bounds\nchecks, and a favorable cost model.\n- Enable it: `.iter()`/`.iter_mut()` + `.zip()` instead of index loops (drops bounds checks);\n  `.chunks_exact(N)`/`.chunks_exact_mut(N)` + `.remainder()` for aligned SIMD chunk processing;\n  `(&[T], &mut [T])` over `(&mut [T], &mut [T])` so LLVM can assume no aliasing; keep the loop\n  body uniform (no per-element `if`/`match`, no early returns); use a reduction (`.sum()`/\n  `.fold()`) instead of a running accumulator that depends on its own previous value.\n- Kills it: `slice[i]` index loops, value-based `continue` inside an element loop, mutable\n  state shared across iterations.\n- Verify: inspect asm on godbolt.org for `vmulps`/`vpaddb`/`vpsubusb` vs scalar `mulss`/`movss`;\n  measure the delta with `RUSTFLAGS=\"-C opt-level=3 -C no-vectorize-loops -C no-vectorize-slp\"`;\n  build with `RUSTFLAGS=\"-C target-cpu=native\"` to unlock AVX2/AVX-512 on the host CPU.\n- When LLVM won't vectorize, reach for explicit SIMD: `std::arch` (stable, arch-specific),\n  `std::simd`/`portable_simd` (nightly, portable), or the stable `wide`/`pulp` crates.\n\n**Performance micro-hints**\n- Pre-allocate with `Vec::with_capacity(n)` / `HashMap::with_capacity(n)` when the count is\n  roughly known - avoids the realloc ladder.\n- Reuse buffers across calls (`buf.clear()`) instead of reallocating.\n- For almost-always-short collections, consider `SmallVec`/`ArrayVec` (stack-allocated until\n  they spill).\n- Use the smallest enum discriminant that fits (`#[repr(u8)]`/`#[repr(u16)]`) for tighter\n  packing and better cache density.\n- Wrap Criterion bench inputs/outputs in `std::hint::black_box(...)` to stop dead-code\n  elimination from invalidating the measurement.\n\n---\n\n## Project Goals\n\nTwo properties are non-negotiable across every sub-project:\n\n1. **Performance first** – scanning and file operations must be fast. Parallelism via `rayon`,\n   efficient algorithms (e.g. perceptual hashing, blake3), and careful memory use are the norm.\n   Avoid unnecessary allocations or copies in hot paths.\n\n2. **Minimal non-Rust dependencies** – every additional C/C++ library (or any other non-Rust\n   language) makes cross-compilation harder, narrows the set of supported targets, and increases\n   build complexity for contributors. Prefer pure-Rust crates. If a native library is truly\n   necessary (e.g. `libheif`, `libraw`), gate it behind an optional Cargo feature so the default\n   build stays fully Rust.\n\n---\n\n## `just fix` – baseline quality gate\n\n**Mandatory: after finishing any coding task, run `just fix` and resolve everything it reports\nbefore considering the task done.** This applies always, without exception - not just before\nopening a merge request.\n\nRunning `just fix` must pass before any merge request. It runs, in order:\n\n1. Repo-wide auto-fix of `—`/`–`/`―` dash characters back to plain `-` (`.rs`, `.slint`, `.md`,\n   `.ftl`; `AGENTS.md` and `justfile` themselves are excluded) – the enforcement behind the\n   \"never use em/en dash\" style rule above.\n2. `uv run ruff format --line-length 120` – Python code formatting.\n3. `uv run mypy misc --strict` – static type checking for all scripts in `misc/`.\n4. `bash misc/run_checks.sh` – project-specific checks:\n   - `delete_unused_krokiet_slint_imports.py` for krokiet and cedinia\n   - `find_unused_fluent_translations.py` for all four projects\n   - `find_unused_slint_translations.py` for krokiet and cedinia\n   - `find_unused_callbacks.py` for krokiet and cedinia\n   - `find_unused_settings_properties.py` for krokiet and cedinia\n5. `cargo +nightly fmt` – Rust formatting.\n6. `cargo clippy --fix --all-features --all-targets` – Rust linting (single pass).\n7. `cargo +nightly fmt` + `cargo fmt` again – re-format whatever clippy's fixes touched.\n\nFor a clippy pass that also covers the `--no-default-features` build, run `just clip` separately\n(two passes: `--all-features` and `--no-default-features --features winit_software`).\n\nIf `just fix` produces any output on stderr or exits non-zero the code is not ready for review.\n\n---\n\n## Workspace Structure\n\n```\nczkawka/\n├── czkawka_core/   # Scanning logic – shared library used by all frontends\n├── czkawka_cli/    # Command-line interface\n├── czkawka_gui/    # Legacy GTK 4 GUI (maintenance mode only)\n├── krokiet/        # Primary desktop GUI – Slint-based\n├── cedinia/        # Android / mobile GUI – Slint-based\n└── misc/           # Scripts: AI translation, validation, benchmarks, CI helpers\n```\n\nCargo workspace resolver v3, minimum Rust 1.94.1, edition 2024 throughout.\n\n---\n\n## czkawka_core\n\nThe shared scanning engine. Every frontend depends on it; it has no UI dependency.\n\n**Key modules:**\n- `common/` – `CommonToolData` (settings, stop-flag, progress sender), `DirTraversal`, cache,\n  extension filtering, path helpers, progress types.\n- `tools/` – One sub-module per scanning tool:\n  `duplicate`, `empty_folder`, `empty_files`, `big_file`, `similar_images`, `similar_videos`,\n  `same_music`, `broken_files`, `bad_extensions`, `bad_names`, `invalid_symlinks`, `temporary`,\n  `exif_remover`, `video_optimizer`.\n- `localizer_core.rs` – Fluent translation loader for Rust-side messages.\n\nEach tool implements the `CommonData` trait (shared settings access) and `PrintResults` (CSV/JSON\nexport). The tool struct is constructed, configured, then its `find_*()` method is called in a\nworker thread. Progress is reported over a `crossbeam` channel; a stop `AtomicBool` is polled to\nsupport cancellation.\n\n---\n\n## krokiet\n\nThe primary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI is declared\nin `ui/*.slint`; Rust connects callbacks and drives the Slint model.\n\n**Build:** `slint_build` compiles `.slint` sources at build time; `slint::include_modules!()`\nexposes the generated types.\n\n**Entry point:** `src/main.rs`\n- Loads settings, creates `MainWindow`, wires up all callbacks, starts the event loop.\n\n**Callback pattern:**\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // ...\n});\n```\nEach feature area lives in a dedicated `connect_*.rs` file (e.g. `connect_scan.rs`,\n`connect_compare.rs`, `connect_delete_button.rs`).\n\n**`SharedModels`** (`src/shared_models.rs`):\nAn `Arc<Mutex<SharedModels>>` holds the last scan result and parameters of scan of each tool. It is passed to every\n`connect_*` function that needs to access or mutate scan state from a background thread.\n\n**Model layer:**\n- The Slint UI is driven by `ModelRc<VecModel<SingleMainListModel>>`.\n- `SingleMainListModel` carries `val_str: [string]` and `val_int: [int]` vectors – a flat,\n  index-based row representation.\n- Column indices for each tool are defined as constants in `src/common.rs`\n  (`StrDataSimilarImages`, `StrDataDuplicates`, …).\n\n**Translation:** `flk!(\"key\")` / `flk!(\"key\", var = value)` macros defined in\n`src/localizer_krokiet.rs`. Language files in `i18n/<lang-code>/krokiet.ftl`.\n\n---\n\n## cedinia\n\nThe Android (and secondary desktop) GUI. Architecture mirrors Krokiet but adapts to mobile\nconstraints. Compiled as `cdylib` for Android (loaded via `android-activity`).\n\n**Entry points:**\n- Android: `#[unsafe(no_mangle)] fn android_main(android_app: AndroidApp)` in `src/lib.rs`\n- Desktop: `fn run_app()` in `src/app.rs`\n\n**Android-specific:**\n- File picker uses JNI to call into a Kotlin/Java helper embedded via `include_bytes!` (DEX).\n- Storage permissions requested at runtime; `AppState.storage_permission_granted` gates scanning.\n- System insets (`inset_top`, `inset_bottom`) plumbed through to Slint for edge-to-edge layout.\n- `android_logger` routes Rust log output to logcat.\n\n**Differences from Krokiet:**\n- Has `SimilarVideos` (audio-fingerprint matching only, via `rusty-chromaprint`), but not\n  `VideoOptimizer` - ffmpeg-based transcoding/crop-detection is not available on Android.\n- Touch-optimised UI (`cedinia/ui/`); momentum-scroll views, bottom sheets, FAB.\n- `flc!` macro (cedinia-specific) in `src/localizer_cedinia.rs`.\n- See `cedinia/AGENTS.md` (\"Differences from krokiet\") for the full comparison table.\n\n**Translation:** `flc!(\"key\")` macro; language files in `cedinia/i18n/<lang-code>/cedinia.ftl`.\n\n---\n\n## czkawka_cli\n\nThin wrapper around `czkawka_core`. Uses `clap` (derive API) for argument parsing and `indicatif`\nfor progress bars. No GUI code. Results printed via the tool's `PrintResults` trait.\n\n---\n\n## czkawka_gui\n\nLegacy GTK 4 GUI. **Maintenance mode only** – no new features are added. Bug-fixes that\nkeep it compatible with core API changes are accepted.\n\n---\n\n## misc/\n\n**Translation tooling** (`ai_translate/`):\n- `translate.py` – AI-powered batch translation into all supported languages.\n- `validate_translations.py` – Checks placeholder consistency across translations.\n  Pass `--fix` to automatically remove invalid entries.\n\n**Dead-code detection** (run by `run_checks.sh`, see `just fix` above):\n- `find_unused_fluent_translations.py` / `find_unused_slint_translations.py` – Unused\n  translation keys.\n- `find_unused_callbacks.py` – Slint callbacks never invoked from Rust.\n- `find_unused_settings_properties.py` – Settings struct fields never read by the UI.\n- `delete_unused_krokiet_slint_imports.py` – Removes dead `import` lines from `.slint` files.\n\n**Packaging / release:**\n- `gen_cedinia_licenses.py` – Generates `THIRD_PARTY_LICENSES.txt` from Cargo metadata.\n- `gen_android_icons.py` – Generates cedinia's Android adaptive-icon assets from an SVG logo.\n- `simplify_and_minify_svg.py` – Minifies SVG icons via Inkscape.\n- `pack_all_backends.sh` / `.ps1` – Bundles an all-backends krokiet binary with per-backend\n  launcher scripts into a release zip.\n- `flathub.sh` – Generates Flatpak cargo-sources metadata for the Flathub manifest.\n- `add_icon_exe/` – Cargo helper crate that embeds the `.ico` into Windows binaries at build time.\n- `docker/` – `Dockerfile` for containerized builds.\n- `nix/` – Nix flake (`flake.nix`, `packages.nix`) for Nix-based builds.\n- `install_scripts/` – `install_linux.sh`, `install_macos.sh`, `install_windows.bat` end-user\n  installers.\n\n**Dev utilities:**\n- `remove_comments.py` – Strips comments from source files (one-off cleanup tool).\n- `compare_files.sh` – Diffs MD5 hashes of CI build artifacts across runs to check determinism.\n- `run_checks.sh` – Runs all the dead-code detection scripts above; invoked by `just fix`.\n\n**Benchmarks** (standalone Cargo crates):\n- `test_image_perf/`, `test_read_perf/` – Microbenchmarks for image hashing / file reading.\n- `test_compilation_speed_size/` – Tracks build time and binary size across changes.\n\n---\n\n## i18n\n\nAll user-visible strings use [Fluent](https://projectfluent.org/) (`.ftl` files).\n\n| Project      | Macro  | File pattern                                |\n|--------------|--------|---------------------------------------------|\n| krokiet      | `flk!` | `krokiet/i18n/<lang>/krokiet.ftl`           |\n| cedinia      | `flc!` | `cedinia/i18n/<lang>/cedinia.ftl`           |\n| czkawka_core | `flc!` | `czkawka_core/i18n/<lang>/czkawka_core.ftl` |\n| czkawka_gui  | `flg!` | `czkawka_gui/i18n/<lang>/czkawka_gui.ftl`   |\n\nEnglish is the source/fallback language. All other locales are AI-translated and then validated.\n\n**Important:** Only edit the English `.ftl` files (`i18n/en/`) directly in this repository.\nAll other language files are managed through [Crowdin](https://crowdin.com/) and will be\n**overwritten** when translations are pulled from Crowdin. Any manual edits to non-English\n`.ftl` files in the repo will be lost on the next `just unpack_translations` run.\n\n---\n\n## Slint UI conventions\n\n- **Hidden Text elements for width measurement** – where a layout element must adapt its width to\n  translated label text, add off-screen `Text` instances (`x: -10000px; y: -10000px; height: 0`)\n  and compute `preferred-width` at runtime (see `LeftSidePanel`, `CompareInfoBar`).\n- **Enums over strings** – UI state that takes a fixed set of values should use a Slint `enum`,\n  not a `string` (e.g. `ConfirmPopupAction`, `ActiveTool`, `ScanState`).\n- **Global state** – Application-wide state lives in Slint `global` blocks (`GuiState`,\n  `AppState`, `Settings`, `Translations`, …). Rust reads/writes via `app.global::<GlobalName>()`.\n\n---\n\n## Build profiles (Cargo.toml)\n\n| Profile        | Purpose                                                              |\n|----------------|----------------------------------------------------------------------|\n| `release`      | Standard release                                                     |\n| `fast_release` | Incremental, stripped – fast iteration                               |\n| `rdebug`       | Release + full debug symbols (profiling)                             |\n| `fastest`      | Max opt, LTO, panic=abort – mostly benchmarks/poc how fast it can be |\n| `fastci`       | Small binary, fast CI builds                                         |\n\n---\n\n## justfile quick reference\n\n```\njust run krokiet          # debug run\njust runr krokiet         # fast_release run\njust fix                  # format + clippy + Python checks\njust translate            # AI-translate all projects\njust validate_translations [--fix]\njust pack_translations    # create i18n_translations.zip for Crowdin\njust unpack_translations <path>\njust android              # build + install + launch on device\njust androidr             # release variant\n```\n","isInternal":false,"tokens":4566,"sizeBytes":18245},{"name":"AGENTS.md","path":"cedinia/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/cedinia/AGENTS.md","title":"cedinia - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# cedinia – Architecture Guide\n\n## Overview\n\nAndroid (and secondary desktop) GUI. Architecture mirrors krokiet but adapted\nfor mobile constraints: touch-optimized layout, JNI file picker, runtime\npermissions, system insets, and no `VideoOptimizer` (FFmpeg-based transcoding\nis unavailable on Android). `SimilarVideos` is still present, using\naudio-fingerprint matching only (`rusty-chromaprint`), not frame hashing.\n\n---\n\n## Entry Points\n\n**Android** (`src/lib.rs`):\n```rust\n#[unsafe(no_mangle)]\nfn android_main(android_app: AndroidApp) {\n    setup_android_paths(&android_app);   // JNI: get /data and /cache paths (sets DATA_DIR)\n    app::setup_logger_cache();           // installs DualLogger: logcat + cedinia.log file\n    asan_smoketest_if_requested();       // no-op unless CEDINIA_ASAN_SMOKETEST is set\n    file_picker_android::init(&android_app); // Load DEX + init JNI\n    slint::android::init(android_app.clone()).expect(…);\n    file_picker_android::setup_nav_bar(); // pin/style the Android nav bar\n    app::run_app_with_insets(inset_bottom_px, scale, android_app);\n}\n```\n\nLogging: on Android `android_logger` would grab the single global `log` slot, so\nczkawka_core's file `WriteLogger` never installs (cedinia.log stays 0 bytes).\n`app.rs::setup_android_logger` installs a `DualLogger` fanning out to BOTH the\n`AndroidLogger` (logcat) and an appended `cedinia.log` in the cache folder, so\nlog export has real content. Desktop keeps `czkawka_core::setup_logger`.\n\n**Desktop** (`src/app.rs`):\n```rust\npub fn run_app() {\n    setup_logger_cache();\n    run_app_with_insets(0.0, 1.0, ());\n}\n```\n\nBoth converge at `run_app_inner()`: load settings → create `MainWindow` →\nwire callbacks → start worker thread → run event loop.\n\n---\n\n## Source Layout\n\n```\ncedinia/src/\n├── lib.rs                         # Android entry (#[unsafe(no_mangle)] android_main)\n├── app.rs                         # Desktop entry + shared run_app_inner()\n├── bin/cedinia.rs                 # Desktop binary wrapper\n├── common.rs                      # Column index enums (StrData*, IntData*)\n├── model.rs                       # FileEntry toggle/count logic\n├── compare.rs                     # Image comparison overlay (cancel token, resize, diff)\n├── file_actions.rs                # delete_path() (trash on desktop, permanent on Android) + DeleteEvent\n├── scan_runner.rs                 # Worker thread + ScanRequest/ScanResult\n├── scanners.rs                    # Tool-specific scan builders\n├── notifications.rs               # Desktop notifications\n├── set_initial_gui_infos.rs       # Initial GUI state\n├── thumbnail_loader.rs            # Async image thumbnail loading\n├── translations.rs                # translate_items() – populates Translations global\n├── volumes.rs                     # Storage volume detection\n├── localizer_cedinia.rs           # flc! macro, LANGUAGE_LIST, apply_language_preference()\n├── file_picker_android.rs         # JNI + embedded DEX file picker\n├── callbacks/\n│   ├── callbacks.rs               # Module re-exports\n│   ├── scan.rs                    # wire_scan() – scan/stop/tool-change callbacks\n│   ├── selection.rs               # wire_selection() – delete + model access\n│   ├── selection_ops.rs           # Shared FileEntry helpers: vm_of, get_val_str/int, full_path_of, size_from_entry\n│   ├── directories.rs             # wire_directories() – add/remove include/exclude dirs\n│   └── misc.rs                    # 10 wire_* fns: cache_info, collect_test, export_logs,\n│                                   #   language_change, licenses_popup, notification_settings,\n│                                   #   open_path, open_url, permission, save_settings_now\n├── settings/\n│   ├── mod.rs                     # CediniaSettings struct; load/save JSON\n│   └── gui_settings_values.rs     # StringComboBoxItems – combo box option arrays\n└── ui/\n    ├── main_window.slint          # Root component\n    ├── globals/\n    │   ├── app_state.slint        # Global AppState, GeneralSettings, tool-specific globals\n    │   ├── common.slint           # Enums: ActiveTool, ScanState, ConfirmPopupAction\n    │   ├── colors.slint           # CediniaColors theme\n    │   └── fonts.slint\n    ├── screens/\n    │   ├── home_screen.slint      # Tool cards\n    │   ├── settings_screen.slint  # Settings screen layout\n    │   ├── results_list.slint     # Generic results table\n    │   ├── similar_images_gallery.slint\n    │   ├── directories_screen.slint # Add/remove directory paths\n    │   ├── image_compare.slint\n    │   ├── scan_progress.slint\n    │   ├── bottom_nav.slint\n    │   └── top_bar.slint\n    └── components/\n        ├── settings/\n        │   ├── toggle_row.slint\n        │   ├── segment_row.slint\n        │   ├── dropdown_row.slint\n        │   ├── text_input_row.slint\n        │   ├── tool_group_header.slint\n        │   └── category_label.slint\n        ├── buttons/\n        │   ├── touch_button.slint\n        │   ├── icon_button.slint\n        │   └── circle_icon_button.slint\n        ├── file_row.slint\n        ├── section_header.slint\n        ├── status_chip.slint\n        ├── divider.slint\n        ├── empty_state.slint\n        ├── scan_fab.slint\n        ├── scrim_dialog.slint\n        ├── popup_title_bar.slint\n        ├── context_menu_sheet.slint\n        ├── deselection_menu_sheet.slint\n        └── selection_menu_sheet.slint\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## Scan Data Flow\n\n```\n1. User selects tool + taps Scan → AppState.on_scan_requested()\n2. scan.rs: collect settings from globals → build ScanRequest\n3. ScanRequest sent to worker thread via channel\n4. Worker: configure tool struct → tool.search(stop_flag, progress_sender)\n5. Results stored; ScanResult sent back to main thread\n6. invoke_from_event_loop: populate model, update AppState.scan_state\n```\n\n---\n\n## Slint UI Architecture\n\nSettings are stored in Slint globals (not set from Rust at startup):\n\n- `GeneralSettings` – language, file sizes, cache, extension filters\n- `DuplicateSettings`, `SimilarImagesSettings`, `SameMusicSettings`, …\n- `AppState` – scan state, active tool, result models, all callbacks\n\n`apply_settings_to_gui()` (`settings/mod.rs`) reads `CediniaSettings` from\nJSON and populates these globals. `collect_settings_from_gui()` reads them back\nfor saving.\n\n### DropdownRow (`ui/components/settings/dropdown_row.slint`)\n\nInline accordion, **not** a `PopupWindow`:\n- Tapping the row toggles `expanded`, which inserts the option list directly\n  below the row inside the same `VerticalLayout`.\n- Scrolling is handled by the parent `Flickable`/`ListView`, not a nested\n  `ScrollView` - this avoids \"close on outside touch\" popup-dismissal issues\n  when the row sits inside an already-scrollable settings list.\n- Each option row tracks pointer movement (`_opt_moved`) so a scroll-drag that\n  ends over an option doesn't register as a selection tap.\n\n## Translation System\n\nSame workaround as krokiet:\n- All UI text bound to `Translations` global properties.\n- `translate_items()` in `src/translations.rs` populates them via `flc!(\"key\")`.\n- `LANGUAGE_LIST` in `localizer_cedinia.rs` defines the 27 supported languages.\n- `GeneralSettings.language_options` in `ui/app_state.slint` is hardcoded and\n  must match `LANGUAGE_LIST` exactly.\n- Language index stored in `GeneralSettings.language_idx`; code stored in\n  `CediniaSettings.language`.\n\n---\n\n## Settings (`src/settings/mod.rs`)\n\nJSON file (path resolved by `czkawka_core::common::config_cache_path::get_config_cache_path()`,\nconfigured via `set_config_cache_path(\"cedinia\", \"cedinia\")`):\n- Desktop: `~/.config/cedinia/cedinia_settings.json` (directories-next ignores\n  the qualifier/org on Linux and lowercases the app name - there is no\n  `Czkawka` path segment)\n- Android: app-private storage, `$DATA_DIR/files/cedinia/cedinia_settings.json`\n\nIncluded/excluded directories are persisted separately in a sibling\n`cedinia_dirs.json` in the same folder.\n\n`CediniaSettings` (serde Serialize/Deserialize) covers all tool parameters.\n`Default` implementation delegates to `serde_json::from_str(\"{}\")` so all\n`#[serde(default)]` values are used.\n\n---\n\n## Android-Specific Code\n\n### File Picker (`src/file_picker_android.rs`)\n\nA Kotlin helper class is compiled at build time (via `android-build` crate) and\nembedded as DEX bytecode:\n\n```rust\ninclude_bytes!(concat!(env!(\"OUT_DIR\"), \"/classes.dex\"))\n```\n\nAt runtime:\n1. DEX is loaded into an `InMemoryDexClassLoader`.\n2. `CediniaFilePicker` instance created via JNI.\n3. `launch_pick_directory(is_include, start_path)` calls the Java\n   `ACTION_OPEN_DOCUMENT_TREE` intent, hinting the picker to open at `start_path`.\n4. Result arrives via `Java_CediniaFilePicker_onDirectoryPicked()` JNI callback\n   → dispatched to Rust via `invoke_from_event_loop()`.\n\n### Storage Permissions\n\nRuntime permission check/request via JNI.\n`AppState.storage_permission_granted` gates scan initiation.\n\n### System Insets\n\nAndroid edge-to-edge layout requires padding for navigation bars. Inset sizes\nare passed from Kotlin via `android_app.content_rect()` and plumbed through to\nSlint as layout properties.\n\n### Android Paths via JNI\n\n`setup_android_paths()` calls `getFilesDir()` and `getCacheDir()` on the\nActivity to find private storage paths; these are set as environment variables\nand used by `czkawka_core`'s config path logic.\n\n---\n\n## Differences from krokiet\n\n| | cedinia | krokiet |\n|---|---|---|\n| `SimilarVideos` | Yes (audio fingerprint only, `rusty-chromaprint`) | Yes (frame hashing) |\n| `VideoOptimizer` | No (ffmpeg-based transcoding unavailable on Android) | Yes |\n| `BadNames` | Yes | Yes |\n| File picker | JNI (Android) / rfd (desktop) | rfd |\n| Settings | JSON, no presets | JSON, 11 presets |\n| Column indices | `common.rs` (own set) | `common.rs` (own set) |\n| Build target | `cdylib` (Android) + `bin` (desktop) | `bin` |\n\n---\n\n## Key Dependencies\n\n### Always\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework |\n| `czkawka_core` | Scanning engine |\n| `crossbeam-channel` | Worker thread communication |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n\n### Android only\n\n| Crate | Purpose |\n|-------|---------|\n| `android-activity` 0.6 | Android lifecycle |\n| `jni` 0.22 | Java FFI |\n| `android_logger` 0.15 | Logcat output |\n\n### Desktop only\n\n| Crate | Purpose |\n|-------|---------|\n| `rfd` 0.17 | Native file picker |\n| `trash` 5.2 | Move-to-trash |\n| `notify-rust` 4 | Desktop notifications |\n\n### Build\n\n```toml\n[build-dependencies]\nslint-build = \"1.15\"\nandroid-build = \"0.1.2\"   # Compiles CediniaFilePicker.java → classes.dex\n```\n\n### Android Manifest Permissions\n\n`READ_EXTERNAL_STORAGE`, `WRITE_EXTERNAL_STORAGE`,\n`MANAGE_EXTERNAL_STORAGE`, `POST_NOTIFICATIONS`\n\n`Cargo.toml`'s `[package.metadata.android.uses_permission]` also lists\n`WAKE_LOCK`, but the gradle-built `android/app/src/main/AndroidManifest.xml`\ndoes not include it - a pre-existing inconsistency in the repo, not a doc bug.\n\n---\n\n## Optional Features (forwarded to czkawka_core)\n\n`heif`, `libraw`, `libavif`, `xdg_portal_trash`\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row (see\n`model.rs::toggle_row`, `compare.rs::wire_compare_toggle_checkbox`).\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n","isInternal":false,"tokens":3346,"sizeBytes":14027},{"name":"AGENTS.md","path":"czkawka_cli/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/czkawka_cli/AGENTS.md","title":"czkawka_cli - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# czkawka_cli – Architecture Guide\n\n## Overview\n\n`czkawka_cli` is a thin CLI wrapper around `czkawka_core`. It handles argument\nparsing, thread orchestration, progress rendering, and result output. All\nscanning logic lives in `czkawka_core`; this crate only wires it to the\nterminal.\n\n---\n\n## Source Layout\n\n```\nczkawka_cli/src/\n├── main.rs       # Entry point, thread spawning, tool dispatchers\n├── commands.rs   # All clap CLI argument definitions\n├── parsers.rs    # parse_* clap value-parser functions used by commands.rs\n└── progress.rs   # indicatif progress bar rendering\n```\n\n---\n\n## Execution Model\n\n```\nmain()\n ├─ Parse args (clap)\n ├─ Create crossbeam channel  (Sender<ProgressData>, Receiver<ProgressData>)\n ├─ Create Arc<AtomicBool> stop_flag\n ├─ spawn calculation_thread → dispatches to one of 14 tool functions\n ├─ Register Ctrl+C handler → sets stop_flag = true\n ├─ connect_progress(&progress_receiver)   [blocks, renders progress bar]\n └─ join calculation_thread → print output → exit(11) if items found\n```\n\nThe **main thread** renders progress. The **calculation thread** runs the scan.\n`progress_receiver.recv()` exits naturally when the sender drops (scan ends).\n\n---\n\n## CLI Subcommands\n\nDefined via `clap` derive in `commands.rs`. Top-level enum `Commands` has 14\nvariants:\n\n| Subcommand        | Alias            | Purpose                                 |\n|-------------------|------------------|-----------------------------------------|\n| `Duplicates`      | `dup`            | Find duplicate files (hash/name/size)   |\n| `EmptyFolders`    | `empty-folders`  | Find empty directories                  |\n| `BiggestFiles`    | `big`            | Find biggest or smallest files          |\n| `EmptyFiles`      | `empty-files`    | Find zero-byte files                    |\n| `Temporary`       | `temp`           | Find temporary files                    |\n| `SimilarImages`   | `image`          | Find similar images (perceptual hash)   |\n| `SameMusic`       | `music`          | Find duplicate music (tags/fingerprint) |\n| `InvalidSymlinks` | `symlinks`       | Find broken symlinks                    |\n| `BrokenFiles`     | `broken`         | Find broken PDF/audio/image/archive     |\n| `SimilarVideos`   | `video`          | Find similar videos (frame hashing)     |\n| `BadExtensions`   | `ext`            | Find files with wrong extensions        |\n| `BadNames`        | `bad-names`      | Find problematic file names             |\n| `VideoOptimizer`  | `video-optimizer`| Transcode or crop videos                |\n| `ExifRemover`     | `exif-remover`   | Remove EXIF metadata from images        |\n\n### `CommonCliItems` (shared by every subcommand)\n\n- `-d` directories (required)\n- `-e` excluded directories\n- `-E` excluded items (glob patterns, e.g. `*/.*`)\n- `-x` allowed extensions (supports macros: `IMAGE`, `VIDEO`, `MUSIC`, `TEXT`)\n- `-P` excluded extensions\n- `-R` disable recursive search\n- `-X` (unix only) exclude other filesystems\n- `-f` save text results to file\n- `-C` save compact JSON results to file\n- `--pretty-file-to-save` (`-p`) save pretty JSON results to file\n- `-N` / `-M` suppress result/message output\n- `-W` suppress non-zero exit code when items found\n- `-T` thread count (0 = all CPUs)\n- `-H` disable cache\n\n### Delete methods\n\n**`DMethod`** (for similarity-grouping tools): `-D NONE|AEN|AEO|ON|OO|AEB|AES|OB|OS|HARD`\n**`SDMethod`** (for simple tools): `-D` delete flag, `-Q` dry-run, `-y` move-to-trash\n\n---\n\n## Tool Dispatcher Pattern\n\nEvery one of the 14 dispatch functions follows the same structure:\n\n```rust\nfn tool_name(args: ToolArgs, stop_flag: &Arc<AtomicBool>, progress_sender: &Sender<ProgressData>) -> CliOutput {\n    // 1. Destructure args\n    // 2. Build tool-specific parameter struct\n    let mut tool = ToolType::new(params);\n    // 3. Apply common settings (paths, extensions, cache, threads)\n    set_common_settings(&mut tool, &common_cli_items, reference_directories);\n    // 4. Apply tool-specific settings (file sizes, delete method, etc.)\n    // 5. Run scan\n    tool.search(stop_flag, Some(progress_sender));\n    // 6. Optionally run fix (rename/delete/etc.)\n    // 7. Collect and return output\n    save_and_write_results_to_writer(&tool, &common_cli_items)\n}\n```\n\n`set_common_settings` applies paths, recursion, extensions, cache flags, and\nthread count via the `AllTraits` bound.\n\n---\n\n## Progress Rendering (`progress.rs`)\n\n`connect_progress(&progress_receiver)` loops on `progress_receiver.recv()` and\nrenders via indicatif:\n\n- **Spinner** while collecting files or loading/saving cache.\n- **Linear bar** `[=>  ]` once the total entry or byte count is known.\n\nThe stage label (e.g. \"Calculating hashes\", \"Reading tags\") is derived from the\n`CurrentStage` enum on each `ProgressData` message.\n\n---\n\n## Result Output\n\n`save_and_write_results_to_writer` writes three optional file formats (text,\ncompact JSON, pretty JSON) and buffers stdout output, which is printed after the\nthread join. The `CliOutput.found_any_files` flag drives the exit code:\n\n- `exit(11)` when items are found (and `-W` is not set) - useful in scripts.\n- `exit(0)` otherwise.\n\n---\n\n## Cancellation\n\n`Arc<AtomicBool>` (SeqCst ordering) is shared between the Ctrl+C signal handler\nand the calculation thread. The tools in `czkawka_core` poll it during scanning\nand stop gracefully when it is `true`.\n\n---\n\n## Key Dependencies\n\n| Crate              | Purpose                          |\n|--------------------|----------------------------------|\n| `clap` 4.5         | CLI parsing (derive API)         |\n| `indicatif` 0.18   | Progress bars                    |\n| `crossbeam-channel`| Unbounded channel for progress   |\n| `ctrlc` 3.4        | SIGINT / Ctrl+C handling         |\n| `humansize` 2.1    | Human-readable byte sizes        |\n| `czkawka_core`     | All scanning logic               |\n\n### Optional features (forwarded to `czkawka_core`)\n\n- `heif` – HEIF/HEIC image support (requires libheif)\n- `libraw` – RAW image support (requires libraw)\n- `libavif` – AVIF image support (requires libavif)\n- `xdg_portal_trash` – FlatPak-compatible trash (XDG portal)\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n","isInternal":false,"tokens":1609,"sizeBytes":6432},{"name":"AGENTS.md","path":"czkawka_core/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/czkawka_core/AGENTS.md","title":"czkawka_core - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# czkawka_core – Architecture Guide\n\n## Overview\n\n`czkawka_core` is the shared scanning engine used by all frontends. It has no UI\ndependency. Every scanning tool is implemented here; frontends only configure the\ntool structs and drive them via the `Search` trait.\n\n`TOOLS_NUMBER = 14` (defined in `src/lib.rs`).\n\n---\n\n## Source Layout\n\n```\nczkawka_core/src/\n├── lib.rs                         # Crate root; TOOLS_NUMBER constant\n├── localizer_core.rs              # Fluent i18n loader (flc! macro)\n├── common/\n│   ├── mod.rs                     # Module declarations + re-exports only (no logic of its own)\n│   ├── traits.rs                  # Search, PrintResults, DeletingItems, FixingItems, ResultEntry, AllTraits\n│   ├── tool_data.rs               # CommonToolData struct + the CommonData trait (defined here, not traits.rs)\n│   ├── model.rs                   # ToolType, CheckingMethod, FileEntry, DeleteMethod\n│   ├── progress_data.rs           # ProgressData, CurrentStage enum\n│   ├── progress_stop_handler.rs   # ProgressThreadHandler, check_if_stop_received\n│   ├── dir_traversal.rs           # DirTraversalBuilder, DirTraversalResult\n│   ├── cache.rs                   # Generic load/save cache (bincode + JSON)\n│   ├── cache/\n│   │   └── cleaning.rs            # Outdated cache cleanup\n│   ├── config_cache_path.rs       # Platform config/cache path lookup\n│   ├── directories.rs             # Directories struct (included/excluded/reference)\n│   ├── extensions.rs              # Extensions struct (allowed/excluded filtering)\n│   ├── items.rs                   # ExcludedItems (glob pattern matching)\n│   ├── consts.rs                  # Extension lists: IMAGE_RS_EXTENSIONS, …\n│   ├── formatting.rs              # format_time and other display formatting helpers\n│   ├── path_utils.rs              # split_path, regex_check, normalize_windows_path\n│   ├── fs_ops.rs                  # make_hard_link, make_file_symlink, remove_single_file/folder\n│   ├── threads.rs                 # get_number_of_threads, set_number_of_threads\n│   ├── deletion.rs                # DeleteResult, DeleteItemType\n│   ├── audio_fingerprint.rs       # Chromaprint fingerprint calculation (used by same_music)\n│   ├── ffmpeg_utils.rs            # FFmpeg invocation helpers\n│   ├── video_utils.rs             # Video metadata extraction\n│   ├── image.rs                   # Image loading helpers\n│   ├── process_utils.rs           # Child-process helpers\n│   ├── logger.rs                  # Logging configuration\n│   └── basic_gui_cli.rs           # Types shared with GUI/CLI boundaries\n├── tools/\n│   ├── mod.rs                     # Re-exports all tool modules\n│   ├── duplicate/                 # Hash/name/size duplicate detection\n│   ├── empty_files/\n│   ├── empty_folder/\n│   ├── big_file/\n│   ├── similar_images/            # Perceptual hashing (image_hasher)\n│   ├── similar_videos/            # Frame-based video similarity\n│   ├── same_music/                # Audio tag + chromaprint fingerprint\n│   ├── broken_files/              # ZIP/PDF/audio/image validation\n│   ├── bad_extensions/            # Extension vs magic-number mismatch\n│   ├── bad_names/                 # Naming policy checks\n│   ├── invalid_symlinks/\n│   ├── temporary/\n│   ├── exif_remover/\n│   └── video_optimizer/           # Crop detection + FFmpeg transcoding\n└── helpers/\n    ├── messages.rs                # Messages struct (errors, warnings, info)\n    ├── delayed_sender.rs          # Rate-limited progress sender\n    ├── audio_checker.rs           # Audio file validation\n    ├── ffprobe.rs                 # ffprobe JSON output parsing\n    └── debug_timer.rs             # Debug-build timing\n```\n\n---\n\n## Tool Module Layout\n\nEach tool lives in its own directory, typically with these files:\n\n| File | Content |\n|------|---------|\n| `mod.rs` | Tool struct + `Info` struct + `Parameters` struct (if needed) |\n| `core.rs` | Internal scanning functions (`check_files_*`, `hash_calculation`, …) |\n| `traits.rs` | Trait implementations: `Search`, `CommonData`, `DeletingItems`, `PrintResults` |\n| `tests.rs` | Unit tests for the tool (every tool has one except `temporary/`) |\n\nA few tools don't fit the plain 4-file pattern: `bad_extensions/` adds a `workarounds.rs`, and\n`video_optimizer/` adds `encoding.rs` plus its own `core/` subdirectory\n(`video_converter.rs`, `video_cropper.rs`).\n\nExample - `EmptyFiles`:\n\n```\nsrc/tools/empty_files/\n├── mod.rs      # pub struct EmptyFiles { common_data, information, files_to_check, params }\n│               #   params: EmptyFilesParameters\n├── core.rs     # fn check_files() → WorkContinueStatus\n├── tests.rs    # Unit tests\n└── traits.rs   # impl Search, CommonData, PrintResults, DeletingItems, AllTraits\n```\n\n---\n\n## Core Traits\n\n`CommonData` is the odd one out: it's defined in `src/common/tool_data.rs` next to\n`CommonToolData`, not in `traits.rs`. Every other trait below lives in `src/common/traits.rs`.\nSignatures are simplified for orientation - both files have more methods (mostly\nsetter/getter pairs and default-impl helpers) than shown here.\n\n```rust\npub trait Search {\n    fn search(&mut self, stop_flag: &Arc<AtomicBool>,\n              progress_sender: Option<&Sender<ProgressData>>);\n}\n\n// src/common/tool_data.rs - NOT traits.rs\npub trait CommonData {\n    type Info;\n    type Parameters;\n    fn get_cd(&self) -> &CommonToolData;\n    fn get_cd_mut(&mut self) -> &mut CommonToolData;\n    fn found_any_items(&self) -> bool;\n    // ~30 more setters/getters for directories, extensions, delete method, cache flags, …\n}\n\npub trait PrintResults: CommonData {\n    fn write_results<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn print_results_to_writer<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn save_results_to_file_as_json(&self, file: &str, pretty: bool) -> io::Result<()>;\n    // + write_base_search_paths, print_results_to_output/_to_file, save_all_in_one, …\n}\n\npub trait DeletingItems {\n    fn delete_files(&mut self, stop_flag: &Arc<AtomicBool>,\n                    progress_sender: Option<&Sender<ProgressData>>) -> WorkContinueStatus;\n}\n\npub trait FixingItems {\n    type FixParams;\n    fn fix_items(&mut self, stop_flag: &Arc<AtomicBool>,\n                 progress_sender: Option<&Sender<ProgressData>>, fix_params: Self::FixParams);\n}\n\npub trait ResultEntry {\n    fn get_path(&self) -> &Path;\n    fn get_modified_date(&self) -> u64;\n    fn get_size(&self) -> u64;\n}\n\npub trait AllTraits: DebugPrint + PrintResults + DeletingItems + CommonData + Search {}\n```\n\n---\n\n## CommonToolData (`src/common/tool_data.rs`)\n\n```rust\npub struct CommonToolData {\n    pub(crate) tool_type: ToolType,\n    pub(crate) text_messages: Messages,        // Accumulated warnings / errors\n    pub(crate) directories: Directories,       // Included / excluded / reference paths\n    pub(crate) extensions: Extensions,         // Allowed / excluded extensions\n    pub(crate) excluded_items: ExcludedItems,  // Glob patterns (e.g. \"*/.*\")\n    pub(crate) recursive_search: bool,\n    pub(crate) delete_method: DeleteMethod,\n    pub(crate) maximal_file_size: u64,\n    pub(crate) minimal_file_size: u64,\n    pub(crate) stopped_search: bool,\n    pub(crate) use_cache: bool,\n    pub(crate) delete_outdated_cache: bool,\n    pub(crate) save_also_as_json: bool,\n    pub(crate) use_reference_folders: bool,\n    pub(crate) dry_run: bool,\n    pub(crate) move_to_trash: bool,\n    pub(crate) hide_hard_links: bool,\n}\n```\n\n---\n\n## DirTraversal (`src/common/dir_traversal.rs`)\n\nBuilder-pattern filesystem traversal used by all tools:\n\n```rust\nlet result = DirTraversalBuilder::new()\n    .common_data(&self.common_data)\n    .group_by(|fe: &FileEntry| fe.size)   // Groups entries by this key\n    .stop_flag(stop_flag)\n    .progress_sender(progress_sender)\n    .checking_method(CheckingMethod::Size)\n    .build()\n    .run();\n\nmatch result {\n    DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { … }\n    DirTraversalResult::Stopped => return WorkContinueStatus::Stop,\n}\n```\n\n**Internals:**\n- Rayon `into_par_iter().with_max_len(2)` for parallel folder processing.\n- Two-phase: visit root dirs/files first, then recurse batch by batch.\n- Applies extension, exclusion, and size filters during traversal.\n- On Unix: optional filesystem-boundary checking (`exclude_other_filesystems`).\n- Progress tracked via `ProgressThreadHandler` (dedicated thread, 200 ms interval).\n\n---\n\n## Progress Reporting (`src/common/progress_data.rs` + `src/common/progress_stop_handler.rs`)\n\n`ProgressData`/`ToolStage` are defined in `progress_data.rs`; `ProgressThreadHandler` and\n`check_if_stop_received` live in `progress_stop_handler.rs`.\n\n```rust\npub struct ProgressData {\n    pub stage: ToolStage,           // Current operation\n    pub entries_checked: usize,\n    pub entries_to_check: usize,\n    pub bytes_checked: u64,\n    pub bytes_to_check: u64,\n}\n```\n\n`ToolStage` is a nested enum (`ToolStage::Duplicate(DuplicateStage)`, `ToolStage::SimilarImages(SimilarImagesStage)`, ...) so a\nstage can't be paired with a tool it doesn't belong to - this replaces the old flat `CurrentStage` enum plus its runtime\n`ProgressData::validate()` assertions. Call `progress.to_display()` to get a fully translated, ready-to-render\n`ProgressDisplay` (label + percentages) instead of branching on the stage in frontend code.\n\n`ProgressThreadHandler` spawns a background thread that polls `AtomicUsize`/`AtomicU64`\ncounters and sends `ProgressData` to the frontend channel every ~200 ms.\n\nCall `handler.increase_items(n)` / `handler.increase_size(n)` from rayon tasks\n(using `Relaxed` ordering - fast, no synchronization overhead).\n\n---\n\n## Cancellation\n\n```rust\n// In hot paths:\nif check_if_stop_received(stop_flag) {\n    return WorkContinueStatus::Stop;  // or break / return None in rayon\n}\n```\n\n`stop_flag` is `Arc<AtomicBool>` with `Relaxed` ordering - sufficient for simple\n\"should I stop?\" polling without synchronization cost. The frontend sets it to\n`true`; the backend polls and stops gracefully.\n\n---\n\n## Cache (`src/common/cache.rs`)\n\n```rust\n// Save\nsave_cache_to_file_generalized::<T>(\n    cache_file_name,    // e.g. \"cache_duplicates.bin\"\n    &btree_map,         // BTreeMap<String, T> (String = canonical path)\n    save_also_as_json,\n    minimum_file_size,\n);\n\n// Load\nlet (messages, opt_cache) =\n    load_cache_from_file_generalized_by_path::<T>(\n        cache_file_name,\n        delete_outdated_cache,\n        &used_files,    // Current BTreeMap to validate against\n    );\n```\n\n- Serialized with `bincode` (binary). Optionally also as `.json`.\n- Cached entries validated by path + size + mtime on load.\n- 8 GB memory limit on serialization.\n- Each tool has its own version constant (`CACHE_DUPLICATE_VERSION`, …).\n\n---\n\n## Rayon Usage Patterns\n\n```rust\n// Directory traversal\nfolders.into_par_iter().with_max_len(2).map(|dir| { … }).while_some().collect();\n\n// Deletion with early exit\nitems.into_par_iter().map(|e| {\n    if check_if_stop_received(stop_flag) { return None; }\n    // … process e …\n    Some(result)\n}).while_some().flatten().collect()\n```\n\nThread count is globally controlled via `set_number_of_threads(n)` which stores\nto a `LazyLock<Mutex<Option<usize>>>` and is applied via rayon's thread pool.\n\n---\n\n## ToolType Enum (`src/common/model.rs`)\n\n```rust\npub enum ToolType {\n    Duplicate, EmptyFolders, EmptyFiles, InvalidSymlinks,\n    BrokenFiles, BadExtensions, BadNames, BigFile, SameMusic,\n    SimilarImages, SimilarVideos, TemporaryFiles, ExifRemover,\n    VideoOptimizer,\n    #[default]\n    None,\n}\n```\n\nTools that support reference directories: `Duplicate`, `SameMusic`,\n`SimilarImages`, `SimilarVideos` (`may_use_reference_paths()`).\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `rayon` | Parallel iterators |\n| `crossbeam-channel` | Progress channels |\n| `blake3` | Fast file hashing |\n| `image` + `image_hasher` | Image loading + perceptual hash |\n| `lofty` | Audio tag reading |\n| `symphonia` | Audio decoding |\n| `rusty-chromaprint` | Audio fingerprinting |\n| `bincode` | Cache serialization |\n| `zip` | ZIP validation |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `trash` | Move-to-trash |\n| `directories-next` | Config/cache path |\n| `fun_time` | `#[fun_time]` timing attribute |\n| `similario_core` | Video similarity (sibling path-dependency; replaced `vid_dup_finder_lib`) |\n\nOptional (behind features):\n- `heif` → `libheif-rs` – HEIC/HEIF image support\n- `libraw` → `rawler` / `libraw-rs` – RAW photo support\n- `libavif` – AVIF image support\n- `xdg_portal_trash` – FlatPak trash via XDG portal\n- `blake_pure` → `blake3/pure` – pure-Rust Blake3 (no SIMD asm), for targets where the asm\n  backend doesn't build\n\n---\n\n## Comments\n\nShort and minimal, same baseline as the rest of the workspace (root `AGENTS.md`). The one\nexception in this workspace: `czkawka_core` is a library every frontend depends on, and external\nconsumers often read only its `pub fn`/`pub struct` surface, not the internals. That public API\nmay carry a bit more explanation than code elsewhere in the repo - still terse, no paragraphs,\njust enough to cover what isn't obvious from the signature alone.\n","isInternal":false,"tokens":3227,"sizeBytes":13616},{"name":"AGENTS.md","path":"czkawka_gui/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/czkawka_gui/AGENTS.md","title":"czkawka_gui - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# czkawka_gui – Architecture Guide\n\n## Status: Maintenance mode only\n\nNo new features. Bug-fixes that keep it compatible with `czkawka_core` API\nchanges are accepted.\n\n---\n\n## Overview\n\nGTK4 frontend using XML UI files authored in Cambalache. All scanning tools\navailable in `czkawka_core` are exposed via a notebook (tab-based) interface.\n\n---\n\n## Source Layout\n\n```\nczkawka_gui/src/\n├── main.rs                          # GTK Application setup, GuiData construction\n├── initialize_gui.rs                # Initial widget state\n├── compute_results.rs               # Converts scan results → GTK ListStore rows\n├── saving_loading.rs                # JSON settings load/save\n├── language_functions.rs            # LANGUAGES_ALL constant (26 entries)\n├── localizer_gui.rs                 # flg! macro, Fluent loader\n├── gui_structs/\n│   ├── gui_data.rs                  # GuiData – central struct for all UI components\n│   ├── gui_main_notebook.rs         # 11-tool tab notebook\n│   ├── gui_upper_notebook.rs        # Included/excluded directory tabs\n│   ├── gui_settings.rs              # Settings dialog\n│   ├── gui_header.rs                # Menu bar (About, Settings)\n│   ├── gui_bottom_buttons.rs        # Action buttons (Search, Delete, etc.)\n│   ├── gui_progress_dialog.rs       # Scan progress window\n│   ├── gui_about.rs                 # About dialog\n│   ├── gui_compare_images.rs        # Side-by-side image diff overlay\n│   ├── common_tree_view.rs          # TreeView/ListStore rendering + SharedModelEnum\n│   ├── common_upper_tree_view.rs    # Dir-list TreeViews\n│   ├── gui_popovers_select.rs       # Selection filter popovers\n│   └── gui_popovers_sort.rs         # Sort filter popovers\n├── connect_things/                  # Callback wiring files\n│   ├── connect_button_search.rs     # Scan thread spawn + progress forwarding\n│   ├── connect_button_delete.rs     # File deletion with confirmation\n│   ├── connect_button_save.rs       # Export results (CSV/JSON)\n│   ├── connect_button_select.rs     # Row selection helpers\n│   ├── connect_button_sort.rs       # Column-header sort\n│   ├── connect_button_stop.rs       # Cancel scan (stop flag)\n│   ├── connect_button_move.rs       # Move files to folder\n│   ├── connect_button_hardlink.rs   # Hard-link/symlink creation\n│   ├── connect_button_compare.rs    # Image comparison launcher\n│   ├── connect_duplicate_buttons.rs # Duplicate-specific UI\n│   ├── connect_notebook_tabs.rs     # Tab-switch handler\n│   ├── connect_selection_of_directories.rs  # Dir-picker dialogs\n│   ├── connect_change_language.rs   # Language switch + full UI relabel\n│   ├── connect_settings.rs          # Settings window callbacks\n│   ├── connect_progress_window.rs   # Progress bar updates from channel\n│   ├── connect_show_hide_ui.rs      # Error text visibility toggle\n│   ├── connect_similar_image_size_change.rs\n│   ├── connect_same_music_mode_changed.rs\n│   ├── connect_popovers_select.rs\n│   ├── connect_popovers_sort.rs\n│   ├── connect_header_buttons.rs\n│   ├── connect_about_buttons.rs\n│   ├── connect_krokiet_info_dialog.rs  # One-time Krokiet deprecation notice (shown on first run)\n│   ├── connect_krokiet_promo_dialog.rs # Krokiet promo dialog (shown from the header button)\n│   └── file_chooser_helpers.rs\n├── helpers/\n│   ├── enums.rs                     # Column index enums for all tools\n│   ├── list_store_operations.rs     # Append/read GTK ListStore rows\n│   ├── image_operations.rs          # Image resize/preview for GTK\n│   ├── model_iter.rs                # GTK TreeView iteration\n│   └── mod.rs\n├── help_functions.rs                # HEADER_ROW_COLOR, MAIN_ROW_COLOR, set_buttons\n├── help_combo_box.rs                # Combo box option arrays (hash algs, etc.)\n├── notebook_enums.rs                # NotebookMainEnum (per-tool tab index)\n├── notebook_info.rs                 # Tab label + ListStore column definitions\n├── opening_selecting_records.rs     # Double-click → open file/folder\n├── taskbar_progress.rs              # Windows taskbar progress (real impl)\n├── taskbar_progress_dummy.rs        # No-op stand-in for non-Windows targets\n├── taskbar_progress_win.rs          # Windows-specific taskbar API calls\n└── gtk_traits.rs                    # Custom GTK trait extensions\n\nui/\n├── czkawka.cmb         # Cambalache project file (source of truth for the .ui files below)\n├── main_window.ui      # Cambalache XML (65 KB) – entire main window layout\n├── settings.ui         # Settings dialog\n├── compare_images.ui   # Image comparison panel\n├── popover_select.ui   # Selection filter popover\n├── popover_sort.ui     # Sort popover\n├── popover_right_click.ui # Right-click context menu popover\n├── about_dialog.ui     # About window\n└── progress.ui         # Scan progress dialog\n```\n\n---\n\n## GuiData Struct (`gui_structs/gui_data.rs`)\n\nCentral struct passed by clone/reference to every callback:\n\n```rust\npub struct GuiData {\n    pub window_main: gtk4::Window,\n    pub main_notebook: GuiMainNotebook,         // 11 tool tabs\n    pub upper_notebook: GuiUpperNotebook,       // Dir configuration\n    pub bottom_buttons: GuiBottomButtons,       // Action buttons\n    pub progress_window: GuiProgressDialog,\n    pub settings: GuiSettings,\n    pub header: GuiHeader,\n    pub compare_images: GuiCompareImages,\n    pub about: GuiAbout,\n    pub popovers_select: GuiSelectPopovers,\n    pub popovers_sort: GuiSortPopovers,\n    pub entry_info: gtk4::Entry,\n    pub text_view_errors: gtk4::TextView,\n    pub scrolled_window_errors: gtk4::ScrolledWindow,  // show/hide bottom error panel\n    pub file_dialog_include_exclude_folder_selection: gtk4::FileChooserNative,\n    pub file_dialog_move_to_folder: gtk4::FileChooserNative,\n    pub taskbar_state: Rc<RefCell<TaskbarProgress>>,\n    pub shared_buttons: Rc<RefCell<HashMap<NotebookMainEnum, …>>>,\n    pub stop_flag: Arc<AtomicBool>,\n}\n```\n\nConstructed by `GuiData::new_with_application()` which loads all `.ui` files\nvia `gtk4::Builder`.\n\n---\n\n## Scan Thread Architecture\n\n```\nUser clicks Search\n→ connect_button_search.rs\n→ Disables UI, shows progress window\n→ Spawn worker thread:\n    - Read settings from GTK widgets\n    - Create tool struct (e.g. DuplicateFinder)\n    - Call tool.search(stop_flag, progress_sender)\n    - Send Message::Duplicates(tool) via result_sender\n→ Main thread: receive progress updates → update bars\n→ On result received:\n    - compute_results.rs processes tool data → appends to ListStore\n    - Re-enables UI, hides progress\n```\n\n**Channel types:**\n- `Sender<Message>` – one-shot result channel (tool enum with full data)\n- `Sender<ProgressData>` – real-time progress from core\n\n---\n\n## Result Population (`compute_results.rs`)\n\n1. Receive `Message::Duplicates(DuplicateFinder)` from worker thread.\n2. Extract sorted file groups from the tool.\n3. For each group: insert a **header row** (colored `HEADER_ROW_COLOR`), then\n   one row per file (`MAIN_ROW_COLOR`).\n4. `append_row_to_list_store()` writes individual cells to `gtk4::ListStore`.\n5. `finalize_compute()` stores the tool object in `SubView.shared_model_enum`\n   (for later re-use by selection/sort callbacks).\n\n---\n\n## Language Switching (`language_functions.rs`)\n\n26 languages defined as `LANGUAGES_ALL: &[Language]` with `combo_box_text`\n(display name) and `short_text` (BCP-47 code).\n\nOn language change:\n1. Look up `short_text` from selected combo index.\n2. Call `localizer.select([lang_id])` for both `czkawka_core` and `czkawka_gui`.\n3. Call `gui_data.update_language()` → propagates to all sub-structs which\n   update their widget labels via `flg!(\"key\")`.\n\n---\n\n## Settings Storage (`saving_loading.rs`)\n\nJSON file at `~/.config/Czkawka/czkawka_gui_config.json`. Struct `SettingsJson`\nserialized with `serde_json`. Includes directories, tool parameters, UI state,\nlanguage preference.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `gtk4` 0.11 | GTK4 GUI framework |\n| `gdk4` 0.11 | Drawing/rendering |\n| `glib` 0.22 | Event loop, `spawn_future_local` |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `open` 5.3 | Launch file manager |\n| `image` 0.25 | Preview thumbnails |\n| `resvg` 0.47 | SVG icon scaling |\n| `rayon` 1.10 | Parallel sort of results |\n| `crossbeam-channel` | Result + progress channels |\n| `czkawka_core` | Scanning engine |\n\nOptional features (forwarded to core): `heif`, `libraw`, `libavif`,\n`xdg_portal_trash`.\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n","isInternal":false,"tokens":2106,"sizeBytes":9157},{"name":"AGENTS.md","path":"krokiet/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/krokiet/AGENTS.md","title":"krokiet - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# krokiet – Architecture Guide\n\n## Overview\n\nPrimary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI\nis declared in `ui/*.slint`; Rust wires all callbacks and drives the Slint data\nmodels. All scanning logic lives in `czkawka_core`.\n\n---\n\n## Source Layout\n\n```\nkrokiet/\n├── build.rs                          # slint_build::compile(\"ui/main_window.slint\")\n├── src/\n│   ├── main.rs                       # Entry point: load settings, create MainWindow,\n│   │                                  #   wire all callbacks, run event loop\n│   ├── shared_models.rs              # SharedModels – Arc<Mutex<…>> of all tool results\n│   ├── common.rs                     # Column index enums (IntDataDuplicateFiles, …)\n│   │                                  #   and sort index helpers\n│   ├── localizer_krokiet.rs          # flk! macro, LANGUAGE_LOADER_KROKIET\n│   ├── set_initial_gui_info.rs       # Asserts Slint combo lists == Rust enums\n│   ├── set_initial_scroll_list_data_indexes.rs\n│   ├── simpler_model.rs              # Minimal serializable row struct for threading\n│   ├── audio_player.rs               # Play sound on scan completion\n│   ├── notification_manager.rs       # Desktop notifications\n│   ├── create_calculate_task_size.rs # Background total-size calculator\n│   ├── clear_outdated_video_thumbnails.rs\n│   ├── connect_scan.rs               # Routes scan to per-tool functions\n│   ├── connect_scan/                 # One file per tool (duplicate.rs, …)\n│   ├── connect_progress_receiver.rs  # Receives ProgressData → updates UI bar\n│   ├── connect_row_selection/        # Row checkbox logic, group handling\n│   │   ├── mod.rs\n│   │   ├── checker.rs\n│   │   ├── clipboard.rs\n│   │   ├── context_menu.rs\n│   │   ├── opener.rs\n│   │   └── selection.rs\n│   ├── connect_sort.rs               # Column-header sort (string + int indices)\n│   ├── connect_translation.rs        # LANGUAGE_LIST, change_language(), translate_items()\n│   ├── connect_compare.rs            # Image comparison overlay\n│   ├── connect_show_preview.rs       # File preview / thumbnail\n│   ├── connect_open.rs               # Open file/folder in system app\n│   ├── connect_save.rs               # Export results via PrintResults\n│   ├── connect_show_confirmation.rs  # Confirmation popup before destructive action\n│   ├── connect_directories_changes.rs\n│   ├── connect_clean_cache.rs\n│   ├── connect_tab_changed.rs\n│   ├── connect_stop.rs               # Sets stop_flag = true\n│   ├── connect_rfd.rs                # File picker (rfd)\n│   ├── connect_select/\n│   │   ├── mod.rs\n│   │   └── custom_select.rs          # Advanced filter popup\n│   ├── file_actions/\n│   │   ├── connect_delete.rs\n│   │   ├── connect_move.rs\n│   │   ├── connect_rename.rs\n│   │   ├── connect_hardlink.rs\n│   │   ├── connect_symlink.rs\n│   │   ├── connect_optimize_video.rs\n│   │   └── connect_clean_exif.rs\n│   ├── model_operations/\n│   │   ├── mod.rs                    # Slint model ↔ thread-safe model conversion\n│   │   └── model_processor.rs        # Parallel item processing framework\n│   └── settings/\n│       ├── mod.rs                    # Load/save JSON settings (11 presets)\n│       ├── model.rs                  # BasicSettings, SettingsCustom structs\n│       └── combo_box.rs              # StringComboBoxItems + regenerate_items()\n└── ui/\n    ├── main_window.slint             # Root component; imports everything; exports globals\n    ├── globals/\n    │   ├── common.slint              # Enums + structs: ActiveTab, SingleMainListModel, …\n    │   ├── gui_state.slint           # Global GuiState (transient runtime state)\n    │   ├── settings.slint            # Global Settings (persisted)\n    │   ├── translations.slint        # Global Translations (all UI strings)\n    │   ├── callabler.slint           # Global Callabler (all Rust to UI callbacks)\n    │   ├── fonts.slint               # FontSizes global\n    │   ├── color_palette.slint       # Dark-theme color definitions\n    │   └── text_size.slint\n    ├── screens/\n    │   ├── left_side_panel.slint     # Tool selector + tool settings sub-panel\n    │   ├── main_lists.slint          # Results table (bound to model)\n    │   ├── action_buttons.slint      # Scan / Stop / Select / Delete / … buttons\n    │   ├── bottom_panel.slint        # Progress bar + error text + status\n    │   ├── progress.slint            # Real-time scan progress widget\n    │   ├── image_compare.slint       # Side-by-side image diff overlay\n    │   ├── settings_list.slint       # Settings tab form\n    │   ├── tool_settings.slint       # Per-tool options (hash alg, threshold, …)\n    │   ├── about.slint                # About tab\n    │   └── included_paths.slint\n    ├── components/\n    │   ├── preview.slint             # File preview / thumbnail side panel\n    │   ├── selectable_tree_view.slint # Custom tree widget for path lists\n    │   ├── hint_text.slint\n    │   ├── popup_base.slint\n    │   ├── popup_centered_text.slint\n    │   └── popup_context_menu.slint\n    └── popups/                       # 18 modal dialogs: popup_delete, popup_save,\n                                       #   popup_optimize, popup_crop_video,\n                                       #   popup_custom_select, popup_rename_*, …\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## SharedModels (`src/shared_models.rs`)\n\n```rust\npub struct SharedModels {\n    pub shared_duplication_state:       Option<DuplicateFinder>,\n    pub shared_empty_folders_state:     Option<EmptyFolder>,\n    // … one field per tool (14 total)\n}\n```\n\nCreated once in `main.rs` as `Arc<Mutex<SharedModels>>`, cloned and passed to\nevery callback that needs scan results (preview, compare, save, delete, …).\n\nWorker threads lock exclusively to **write** results after a scan finishes.\nUI callbacks lock briefly to **read** results for previews / exports.\n\n`VideoOptimizer` is one of the 14 tools but doesn't fit the scan/select/delete\nshape of the others: it has its own `connect_scan/video_optimizer.rs` entry\npoint plus a dedicated `file_actions/connect_optimize_video.rs` action, and\ntwo popups (`popup_optimize.slint` for transcode options,\n`popup_crop_video.slint` for crop-region preview) instead of reusing the\ngeneric delete/save popups.\n\n---\n\n## Scan Data Flow\n\n```\n1. User clicks Scan → on_scan_starting(active_tab)\n2. connect_scan.rs: collect_settings() → ScanData → route to scan_<tool>()\n3. scan_<tool>(): spawn worker thread\n       → create and configure tool struct\n       → tool.search(stop_flag, progress_sender)\n       → store tool in shared_models.lock()\n       → populate Slint VecModel<SingleMainListModel>\n       → app.upgrade_in_event_loop(|app| app.set_<tool>_model(model))\n4. UI re-renders with new model\n```\n\n---\n\n## Slint Data Model\n\n```rust\n// ui/common.slint\nexport struct SingleMainListModel {\n    checked: bool,           // row selected by user\n    header_row: bool,        // group header\n    filled_header_row: bool, // header with formatted summary\n    focused_row: bool,      // UI highlight\n    val_str: [string],       // text columns\n    val_int: [int],          // numeric columns (raw values for sorting)\n}\n```\n\nColumn indices are constants in `src/common.rs`:\n\n```rust\npub enum StrDataDuplicateFiles { Size, Name, Path, ModificationDate }\npub enum IntDataDuplicateFiles { ModificationDatePart1, ModificationDatePart2,\n                                  SizePart1, SizePart2 }\n```\n\nSlint cannot store `i64`, so dates and file sizes are split into two `i32` fields.\n\n---\n\n## Translation System\n\nSlint has no native Fluent support. Workaround:\n1. All UI text is bound to properties in the `Translations` global\n   (`ui/translations.slint`), not hardcoded in `.slint`.\n2. `translate_items()` in `connect_translation.rs` (~440 lines, file is ~750 lines total)\n   sets every property via `flk!(\"key\")` after language changes.\n3. Language list defined as `LANGUAGE_LIST: &[Language]` in\n   `connect_translation.rs`.\n4. Hardcoded combo-box lists in `.slint` files (e.g. `Settings.languages_list`)\n   are verified at startup against Rust enums via `assert_eq!` in\n   `set_initial_gui_info.rs` (workaround for [slint#7632](https://github.com/slint-ui/slint/issues/7632)).\n\n---\n\n## Settings (`src/settings/`)\n\nTwo-tier JSON system:\n\n| File | Content |\n|------|---------|\n| `~/.config/krokiet/config_general.json` | `BasicSettings` – default preset index, preset names, theme, window size |\n| `~/.config/krokiet/config_preset_N.json` | `SettingsCustom` – paths, extensions, tool parameters |\n\n(`directories-next` resolves the config dir from the lowercased app name only - the\nqualifier/organization passed to `set_config_cache_path` are ignored on Linux.)\n\n11 preset slots (indices 0–10). Slot 10 is reserved for CLI-mode overrides.\n`StringComboBoxItems::regenerate_items()` builds all combo box option arrays\nfrom Rust enums; used both for settings serialization and UI initialization.\n\n---\n\n## Callback Registration Pattern\n\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // use app …\n});\n```\n\n`expect()` is correct here: if the window is gone, no callback should fire.\nCross-thread updates use `weak.upgrade_in_event_loop(|app| { … })`.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework (GPL-3.0) |\n| `czkawka_core` | Scanning engine |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `crossbeam-channel` | Progress + result channels |\n| `rayon` | Parallel result sorting |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n| `rfd` 0.17 | File picker (xdg-portal backend) |\n| `notify-rust` | Desktop notifications |\n| `rodio` | Scan-complete sound (optional `audio` feature) |\n| `fontique` | Font discovery (dlopen, avoids C dep) |\n\n### Optional features\n\n- `audio` – scan-complete sound via `rodio`\n- Renderer features: `winit_femtovg` (default), `winit_software` (default),\n  `skia_opengl`, `skia_vulkan`, etc. – select GPU backend at compile time\n- `heif`, `libraw`, `libavif`, `xdg_portal_trash` – forwarded to `czkawka_core`\n\n### Build\n\n`build.rs` compiles `ui/main_window.slint` with style `fluent-dark` (overridable\nvia `SLINT_STYLE` env var).\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row.\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n","isInternal":false,"tokens":3088,"sizeBytes":13351},{"name":"CLAUDE.md","path":"cedinia/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/cedinia/CLAUDE.md","title":"cedinia - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"CLAUDE.md","path":"czkawka_cli/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/czkawka_cli/CLAUDE.md","title":"czkawka_cli - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"CLAUDE.md","path":"czkawka_core/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/czkawka_core/CLAUDE.md","title":"czkawka_core - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"CLAUDE.md","path":"czkawka_gui/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/czkawka_gui/CLAUDE.md","title":"czkawka_gui - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"CLAUDE.md","path":"krokiet/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/qarmin/czkawka/HEAD/krokiet/CLAUDE.md","title":"krokiet - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9}],"systemPromptSnippet":"<yakaai_skills repo=\"qarmin/czkawka\">\n<!-- File: AGENTS.md (Tokens: ~4566 | Category: root-instruction) -->\n# Czkawka – Codebase Guide\n\n## Language\n\nAll code, comments, commit messages, and documentation must be written in **English**.\n\n---\n\n## AI-authored commits & merge/pull requests\n\nAlways, without exception: every commit created by an AI assistant must start its title (the\nfirst line of the commit message) with `[AI] `, e.g. `[AI] Fix crash when scanning empty\ndirectories`. If the commit message has a body, the body does not need the prefix again - the\ntitle alone marks it.\n\nThe same applies to any merge request (GitLab) or pull request (GitHub) opened by an AI\nassistant: both the title and the body/description must start with `[AI] `, e.g. title\n`[AI] Add duplicate detection for symlinked folders` and body starting with\n`[AI] This MR adds ...`. This must stay visible to reviewers at a glance in both GitLab and\nGitHub - never omit it, even for small or automatic changes.\n\n---\n\n## Rust Style\n\nApplies to every Rust crate in the workspace (`czkawka_core`, `czkawka_cli`, `czkawka_gui`,\n`krokiet`, `cedinia`). Program-specific deviations or elaborations live in that program's own\n`AGENTS.md`.\n\n**Formatting & lints**\n- Format with `cargo +nightly fmt`, then `cargo fmt` for stable compatibility\n  (`.rustfmt.toml`: `max_width = 120`, granular import grouping `group_imports = \"StdExternalCrate\"`).\n- Code must compile without clippy warnings. Suppress a lint locally only with a comment\n  explaining why.\n\n**Errors & panics**\n- Prefer `?`, `ok_or_else`, `map_err` over panic-based flows.\n- `unwrap()` only in tests.\n- `expect()` in production only when BOTH hold: there is no correct recovery path, and\n  continuing would likely hide a serious bug - always with a precise, actionable message. This\n  is visible throughout Krokiet/Cedinia callback code: a Slint weak reference is upgraded with\n  `expect()` because if the window is already gone there is nothing meaningful to do except crash.\n- Never silently swallow errors with `let _ = ...` unless the failure is genuinely irrelevant.\n- Model domain errors with explicit types (`thiserror`/enums), not raw strings.\n- Wrap I/O errors with context (e.g. file path) instead of letting std's bare \"Permission\n  denied\" propagate - without the path it's hard to find the source.\n- Don't ignore `Result`; handle it explicitly.\n\n**Code shape**\n- Keep functions focused; once one grows past ~30-50 lines, extract its logical steps into\n  separate, well-named functions. Never carve a long function into sections with comment\n  headers or bare `{ ... }` scope blocks - a `// validate input` comment over a block is the\n  signal to pull it out into `validate_input(...)`, not to fence it off.\n- Name things by intent, not by container/type (`user_count`, not `vec_users_len`).\n- Make invalid states unrepresentable: enums over strings, `Option<T>` over sentinel values\n  (`-1`, empty string).\n- Prefer iterators and collection methods over manual index-based loops.\n- Prefer `#[derive(...)]` over manual impls when behavior is standard.\n- Avoid unnecessary copies and allocations; prefer references and `Cow` where it helps.\n- Prefer external crates over reimplementing them.\n\n**Comments**\nKeep comments short and minimal - a single terse line, not a paragraph. Code should be\nself-documenting through clear naming. Add a comment only when the _why_ is not easily inferred\nfrom reading the code - algorithmic choices, non-obvious constraints, workarounds for external\nlibrary bugs, etc. Do not restate what the code already says. Never use the `—`/`–` dash\ncharacters in code, comments, or commit messages - plain `-` only (ASCII box-drawing diagrams are\nthe only exception).\n\nException: `czkawka_core` is a library - every frontend in this workspace depends on it, and so do\nexternal consumers who only read its public API, not its internals. Its `pub fn`/`pub struct`\nsurface warrants more explanation than the rest of the workspace; see `czkawka_core/AGENTS.md`.\n\n**Tests, fuzzers, benchmarks**\n- Cover as much code as possible with tests; keep them readable with explicit `assert_eq!` and\n  input data close to the assertions.\n- Add fuzzers/examples/benchmarks where it helps show usage, find bugs, or measure the\n  performance impact of a change.\n\n**`unsafe`**\nAvoid whenever possible. Every `unsafe` block documents its invariants and why the safety\nrequirements hold (see cedinia's `#[unsafe(no_mangle)] android_main`).\n\n**Files & logging**\n- Keep source files at or below 500 lines; split large files into modules.\n- Always log via `log` (`handsome_logger`); reach for `tracing` only if actually needed.\n\n**Auto-vectorization**\nLLVM vectorizes a loop when it can prove: no loop-carried dependency, no aliasing, no bounds\nchecks, and a favorable cost model.\n- Enable it: `.iter()`/`.iter_mut()` + `.zip()` instead of index loops (drops bounds checks);\n  `.chunks_exact(N)`/`.chunks_exact_mut(N)` + `.remainder()` for aligned SIMD chunk processing;\n  `(&[T], &mut [T])` over `(&mut [T], &mut [T])` so LLVM can assume no aliasing; keep the loop\n  body uniform (no per-element `if`/`match`, no early returns); use a reduction (`.sum()`/\n  `.fold()`) instead of a running accumulator that depends on its own previous value.\n- Kills it: `slice[i]` index loops, value-based `continue` inside an element loop, mutable\n  state shared across iterations.\n- Verify: inspect asm on godbolt.org for `vmulps`/`vpaddb`/`vpsubusb` vs scalar `mulss`/`movss`;\n  measure the delta with `RUSTFLAGS=\"-C opt-level=3 -C no-vectorize-loops -C no-vectorize-slp\"`;\n  build with `RUSTFLAGS=\"-C target-cpu=native\"` to unlock AVX2/AVX-512 on the host CPU.\n- When LLVM won't vectorize, reach for explicit SIMD: `std::arch` (stable, arch-specific),\n  `std::simd`/`portable_simd` (nightly, portable), or the stable `wide`/`pulp` crates.\n\n**Performance micro-hints**\n- Pre-allocate with `Vec::with_capacity(n)` / `HashMap::with_capacity(n)` when the count is\n  roughly known - avoids the realloc ladder.\n- Reuse buffers across calls (`buf.clear()`) instead of reallocating.\n- For almost-always-short collections, consider `SmallVec`/`ArrayVec` (stack-allocated until\n  they spill).\n- Use the smallest enum discriminant that fits (`#[repr(u8)]`/`#[repr(u16)]`) for tighter\n  packing and better cache density.\n- Wrap Criterion bench inputs/outputs in `std::hint::black_box(...)` to stop dead-code\n  elimination from invalidating the measurement.\n\n---\n\n## Project Goals\n\nTwo properties are non-negotiable across every sub-project:\n\n1. **Performance first** – scanning and file operations must be fast. Parallelism via `rayon`,\n   efficient algorithms (e.g. perceptual hashing, blake3), and careful memory use are the norm.\n   Avoid unnecessary allocations or copies in hot paths.\n\n2. **Minimal non-Rust dependencies** – every additional C/C++ library (or any other non-Rust\n   language) makes cross-compilation harder, narrows the set of supported targets, and increases\n   build complexity for contributors. Prefer pure-Rust crates. If a native library is truly\n   necessary (e.g. `libheif`, `libraw`), gate it behind an optional Cargo feature so the default\n   build stays fully Rust.\n\n---\n\n## `just fix` – baseline quality gate\n\n**Mandatory: after finishing any coding task, run `just fix` and resolve everything it reports\nbefore considering the task done.** This applies always, without exception - not just before\nopening a merge request.\n\nRunning `just fix` must pass before any merge request. It runs, in order:\n\n1. Repo-wide auto-fix of `—`/`–`/`―` dash characters back to plain `-` (`.rs`, `.slint`, `.md`,\n   `.ftl`; `AGENTS.md` and `justfile` themselves are excluded) – the enforcement behind the\n   \"never use em/en dash\" style rule above.\n2. `uv run ruff format --line-length 120` – Python code formatting.\n3. `uv run mypy misc --strict` – static type checking for all scripts in `misc/`.\n4. `bash misc/run_checks.sh` – project-specific checks:\n   - `delete_unused_krokiet_slint_imports.py` for krokiet and cedinia\n   - `find_unused_fluent_translations.py` for all four projects\n   - `find_unused_slint_translations.py` for krokiet and cedinia\n   - `find_unused_callbacks.py` for krokiet and cedinia\n   - `find_unused_settings_properties.py` for krokiet and cedinia\n5. `cargo +nightly fmt` – Rust formatting.\n6. `cargo clippy --fix --all-features --all-targets` – Rust linting (single pass).\n7. `cargo +nightly fmt` + `cargo fmt` again – re-format whatever clippy's fixes touched.\n\nFor a clippy pass that also covers the `--no-default-features` build, run `just clip` separately\n(two passes: `--all-features` and `--no-default-features --features winit_software`).\n\nIf `just fix` produces any output on stderr or exits non-zero the code is not ready for review.\n\n---\n\n## Workspace Structure\n\n```\nczkawka/\n├── czkawka_core/   # Scanning logic – shared library used by all frontends\n├── czkawka_cli/    # Command-line interface\n├── czkawka_gui/    # Legacy GTK 4 GUI (maintenance mode only)\n├── krokiet/        # Primary desktop GUI – Slint-based\n├── cedinia/        # Android / mobile GUI – Slint-based\n└── misc/           # Scripts: AI translation, validation, benchmarks, CI helpers\n```\n\nCargo workspace resolver v3, minimum Rust 1.94.1, edition 2024 throughout.\n\n---\n\n## czkawka_core\n\nThe shared scanning engine. Every frontend depends on it; it has no UI dependency.\n\n**Key modules:**\n- `common/` – `CommonToolData` (settings, stop-flag, progress sender), `DirTraversal`, cache,\n  extension filtering, path helpers, progress types.\n- `tools/` – One sub-module per scanning tool:\n  `duplicate`, `empty_folder`, `empty_files`, `big_file`, `similar_images`, `similar_videos`,\n  `same_music`, `broken_files`, `bad_extensions`, `bad_names`, `invalid_symlinks`, `temporary`,\n  `exif_remover`, `video_optimizer`.\n- `localizer_core.rs` – Fluent translation loader for Rust-side messages.\n\nEach tool implements the `CommonData` trait (shared settings access) and `PrintResults` (CSV/JSON\nexport). The tool struct is constructed, configured, then its `find_*()` method is called in a\nworker thread. Progress is reported over a `crossbeam` channel; a stop `AtomicBool` is polled to\nsupport cancellation.\n\n---\n\n## krokiet\n\nThe primary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI is declared\nin `ui/*.slint`; Rust connects callbacks and drives the Slint model.\n\n**Build:** `slint_build` compiles `.slint` sources at build time; `slint::include_modules!()`\nexposes the generated types.\n\n**Entry point:** `src/main.rs`\n- Loads settings, creates `MainWindow`, wires up all callbacks, starts the event loop.\n\n**Callback pattern:**\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // ...\n});\n```\nEach feature area lives in a dedicated `connect_*.rs` file (e.g. `connect_scan.rs`,\n`connect_compare.rs`, `connect_delete_button.rs`).\n\n**`SharedModels`** (`src/shared_models.rs`):\nAn `Arc<Mutex<SharedModels>>` holds the last scan result and parameters of scan of each tool. It is passed to every\n`connect_*` function that needs to access or mutate scan state from a background thread.\n\n**Model layer:**\n- The Slint UI is driven by `ModelRc<VecModel<SingleMainListModel>>`.\n- `SingleMainListModel` carries `val_str: [string]` and `val_int: [int]` vectors – a flat,\n  index-based row representation.\n- Column indices for each tool are defined as constants in `src/common.rs`\n  (`StrDataSimilarImages`, `StrDataDuplicates`, …).\n\n**Translation:** `flk!(\"key\")` / `flk!(\"key\", var = value)` macros defined in\n`src/localizer_krokiet.rs`. Language files in `i18n/<lang-code>/krokiet.ftl`.\n\n---\n\n## cedinia\n\nThe Android (and secondary desktop) GUI. Architecture mirrors Krokiet but adapts to mobile\nconstraints. Compiled as `cdylib` for Android (loaded via `android-activity`).\n\n**Entry points:**\n- Android: `#[unsafe(no_mangle)] fn android_main(android_app: AndroidApp)` in `src/lib.rs`\n- Desktop: `fn run_app()` in `src/app.rs`\n\n**Android-specific:**\n- File picker uses JNI to call into a Kotlin/Java helper embedded via `include_bytes!` (DEX).\n- Storage permissions requested at runtime; `AppState.storage_permission_granted` gates scanning.\n- System insets (`inset_top`, `inset_bottom`) plumbed through to Slint for edge-to-edge layout.\n- `android_logger` routes Rust log output to logcat.\n\n**Differences from Krokiet:**\n- Has `SimilarVideos` (audio-fingerprint matching only, via `rusty-chromaprint`), but not\n  `VideoOptimizer` - ffmpeg-based transcoding/crop-detection is not available on Android.\n- Touch-optimised UI (`cedinia/ui/`); momentum-scroll views, bottom sheets, FAB.\n- `flc!` macro (cedinia-specific) in `src/localizer_cedinia.rs`.\n- See `cedinia/AGENTS.md` (\"Differences from krokiet\") for the full comparison table.\n\n**Translation:** `flc!(\"key\")` macro; language files in `cedinia/i18n/<lang-code>/cedinia.ftl`.\n\n---\n\n## czkawka_cli\n\nThin wrapper around `czkawka_core`. Uses `clap` (derive API) for argument parsing and `indicatif`\nfor progress bars. No GUI code. Results printed via the tool's `PrintResults` trait.\n\n---\n\n## czkawka_gui\n\nLegacy GTK 4 GUI. **Maintenance mode only** – no new features are added. Bug-fixes that\nkeep it compatible with core API changes are accepted.\n\n---\n\n## misc/\n\n**Translation tooling** (`ai_translate/`):\n- `translate.py` – AI-powered batch translation into all supported languages.\n- `validate_translations.py` – Checks placeholder consistency across translations.\n  Pass `--fix` to automatically remove invalid entries.\n\n**Dead-code detection** (run by `run_checks.sh`, see `just fix` above):\n- `find_unused_fluent_translations.py` / `find_unused_slint_translations.py` – Unused\n  translation keys.\n- `find_unused_callbacks.py` – Slint callbacks never invoked from Rust.\n- `find_unused_settings_properties.py` – Settings struct fields never read by the UI.\n- `delete_unused_krokiet_slint_imports.py` – Removes dead `import` lines from `.slint` files.\n\n**Packaging / release:**\n- `gen_cedinia_licenses.py` – Generates `THIRD_PARTY_LICENSES.txt` from Cargo metadata.\n- `gen_android_icons.py` – Generates cedinia's Android adaptive-icon assets from an SVG logo.\n- `simplify_and_minify_svg.py` – Minifies SVG icons via Inkscape.\n- `pack_all_backends.sh` / `.ps1` – Bundles an all-backends krokiet binary with per-backend\n  launcher scripts into a release zip.\n- `flathub.sh` – Generates Flatpak cargo-sources metadata for the Flathub manifest.\n- `add_icon_exe/` – Cargo helper crate that embeds the `.ico` into Windows binaries at build time.\n- `docker/` – `Dockerfile` for containerized builds.\n- `nix/` – Nix flake (`flake.nix`, `packages.nix`) for Nix-based builds.\n- `install_scripts/` – `install_linux.sh`, `install_macos.sh`, `install_windows.bat` end-user\n  installers.\n\n**Dev utilities:**\n- `remove_comments.py` – Strips comments from source files (one-off cleanup tool).\n- `compare_files.sh` – Diffs MD5 hashes of CI build artifacts across runs to check determinism.\n- `run_checks.sh` – Runs all the dead-code detection scripts above; invoked by `just fix`.\n\n**Benchmarks** (standalone Cargo crates):\n- `test_image_perf/`, `test_read_perf/` – Microbenchmarks for image hashing / file reading.\n- `test_compilation_speed_size/` – Tracks build time and binary size across changes.\n\n---\n\n## i18n\n\nAll user-visible strings use [Fluent](https://projectfluent.org/) (`.ftl` files).\n\n| Project      | Macro  | File pattern                                |\n|--------------|--------|---------------------------------------------|\n| krokiet      | `flk!` | `krokiet/i18n/<lang>/krokiet.ftl`           |\n| cedinia      | `flc!` | `cedinia/i18n/<lang>/cedinia.ftl`           |\n| czkawka_core | `flc!` | `czkawka_core/i18n/<lang>/czkawka_core.ftl` |\n| czkawka_gui  | `flg!` | `czkawka_gui/i18n/<lang>/czkawka_gui.ftl`   |\n\nEnglish is the source/fallback language. All other locales are AI-translated and then validated.\n\n**Important:** Only edit the English `.ftl` files (`i18n/en/`) directly in this repository.\nAll other language files are managed through [Crowdin](https://crowdin.com/) and will be\n**overwritten** when translations are pulled from Crowdin. Any manual edits to non-English\n`.ftl` files in the repo will be lost on the next `just unpack_translations` run.\n\n---\n\n## Slint UI conventions\n\n- **Hidden Text elements for width measurement** – where a layout element must adapt its width to\n  translated label text, add off-screen `Text` instances (`x: -10000px; y: -10000px; height: 0`)\n  and compute `preferred-width` at runtime (see `LeftSidePanel`, `CompareInfoBar`).\n- **Enums over strings** – UI state that takes a fixed set of values should use a Slint `enum`,\n  not a `string` (e.g. `ConfirmPopupAction`, `ActiveTool`, `ScanState`).\n- **Global state** – Application-wide state lives in Slint `global` blocks (`GuiState`,\n  `AppState`, `Settings`, `Translations`, …). Rust reads/writes via `app.global::<GlobalName>()`.\n\n---\n\n## Build profiles (Cargo.toml)\n\n| Profile        | Purpose                                                              |\n|----------------|----------------------------------------------------------------------|\n| `release`      | Standard release                                                     |\n| `fast_release` | Incremental, stripped – fast iteration                               |\n| `rdebug`       | Release + full debug symbols (profiling)                             |\n| `fastest`      | Max opt, LTO, panic=abort – mostly benchmarks/poc how fast it can be |\n| `fastci`       | Small binary, fast CI builds                                         |\n\n---\n\n## justfile quick reference\n\n```\njust run krokiet          # debug run\njust runr krokiet         # fast_release run\njust fix                  # format + clippy + Python checks\njust translate            # AI-translate all projects\njust validate_translations [--fix]\njust pack_translations    # create i18n_translations.zip for Crowdin\njust unpack_translations <path>\njust android              # build + install + launch on device\njust androidr             # release variant\n```\n\n<!-- File: cedinia/AGENTS.md (Tokens: ~3346 | Category: root-instruction) -->\n# cedinia – Architecture Guide\n\n## Overview\n\nAndroid (and secondary desktop) GUI. Architecture mirrors krokiet but adapted\nfor mobile constraints: touch-optimized layout, JNI file picker, runtime\npermissions, system insets, and no `VideoOptimizer` (FFmpeg-based transcoding\nis unavailable on Android). `SimilarVideos` is still present, using\naudio-fingerprint matching only (`rusty-chromaprint`), not frame hashing.\n\n---\n\n## Entry Points\n\n**Android** (`src/lib.rs`):\n```rust\n#[unsafe(no_mangle)]\nfn android_main(android_app: AndroidApp) {\n    setup_android_paths(&android_app);   // JNI: get /data and /cache paths (sets DATA_DIR)\n    app::setup_logger_cache();           // installs DualLogger: logcat + cedinia.log file\n    asan_smoketest_if_requested();       // no-op unless CEDINIA_ASAN_SMOKETEST is set\n    file_picker_android::init(&android_app); // Load DEX + init JNI\n    slint::android::init(android_app.clone()).expect(…);\n    file_picker_android::setup_nav_bar(); // pin/style the Android nav bar\n    app::run_app_with_insets(inset_bottom_px, scale, android_app);\n}\n```\n\nLogging: on Android `android_logger` would grab the single global `log` slot, so\nczkawka_core's file `WriteLogger` never installs (cedinia.log stays 0 bytes).\n`app.rs::setup_android_logger` installs a `DualLogger` fanning out to BOTH the\n`AndroidLogger` (logcat) and an appended `cedinia.log` in the cache folder, so\nlog export has real content. Desktop keeps `czkawka_core::setup_logger`.\n\n**Desktop** (`src/app.rs`):\n```rust\npub fn run_app() {\n    setup_logger_cache();\n    run_app_with_insets(0.0, 1.0, ());\n}\n```\n\nBoth converge at `run_app_inner()`: load settings → create `MainWindow` →\nwire callbacks → start worker thread → run event loop.\n\n---\n\n## Source Layout\n\n```\ncedinia/src/\n├── lib.rs                         # Android entry (#[unsafe(no_mangle)] android_main)\n├── app.rs                         # Desktop entry + shared run_app_inner()\n├── bin/cedinia.rs                 # Desktop binary wrapper\n├── common.rs                      # Column index enums (StrData*, IntData*)\n├── model.rs                       # FileEntry toggle/count logic\n├── compare.rs                     # Image comparison overlay (cancel token, resize, diff)\n├── file_actions.rs                # delete_path() (trash on desktop, permanent on Android) + DeleteEvent\n├── scan_runner.rs                 # Worker thread + ScanRequest/ScanResult\n├── scanners.rs                    # Tool-specific scan builders\n├── notifications.rs               # Desktop notifications\n├── set_initial_gui_infos.rs       # Initial GUI state\n├── thumbnail_loader.rs            # Async image thumbnail loading\n├── translations.rs                # translate_items() – populates Translations global\n├── volumes.rs                     # Storage volume detection\n├── localizer_cedinia.rs           # flc! macro, LANGUAGE_LIST, apply_language_preference()\n├── file_picker_android.rs         # JNI + embedded DEX file picker\n├── callbacks/\n│   ├── callbacks.rs               # Module re-exports\n│   ├── scan.rs                    # wire_scan() – scan/stop/tool-change callbacks\n│   ├── selection.rs               # wire_selection() – delete + model access\n│   ├── selection_ops.rs           # Shared FileEntry helpers: vm_of, get_val_str/int, full_path_of, size_from_entry\n│   ├── directories.rs             # wire_directories() – add/remove include/exclude dirs\n│   └── misc.rs                    # 10 wire_* fns: cache_info, collect_test, export_logs,\n│                                   #   language_change, licenses_popup, notification_settings,\n│                                   #   open_path, open_url, permission, save_settings_now\n├── settings/\n│   ├── mod.rs                     # CediniaSettings struct; load/save JSON\n│   └── gui_settings_values.rs     # StringComboBoxItems – combo box option arrays\n└── ui/\n    ├── main_window.slint          # Root component\n    ├── globals/\n    │   ├── app_state.slint        # Global AppState, GeneralSettings, tool-specific globals\n    │   ├── common.slint           # Enums: ActiveTool, ScanState, ConfirmPopupAction\n    │   ├── colors.slint           # CediniaColors theme\n    │   └── fonts.slint\n    ├── screens/\n    │   ├── home_screen.slint      # Tool cards\n    │   ├── settings_screen.slint  # Settings screen layout\n    │   ├── results_list.slint     # Generic results table\n    │   ├── similar_images_gallery.slint\n    │   ├── directories_screen.slint # Add/remove directory paths\n    │   ├── image_compare.slint\n    │   ├── scan_progress.slint\n    │   ├── bottom_nav.slint\n    │   └── top_bar.slint\n    └── components/\n        ├── settings/\n        │   ├── toggle_row.slint\n        │   ├── segment_row.slint\n        │   ├── dropdown_row.slint\n        │   ├── text_input_row.slint\n        │   ├── tool_group_header.slint\n        │   └── category_label.slint\n        ├── buttons/\n        │   ├── touch_button.slint\n        │   ├── icon_button.slint\n        │   └── circle_icon_button.slint\n        ├── file_row.slint\n        ├── section_header.slint\n        ├── status_chip.slint\n        ├── divider.slint\n        ├── empty_state.slint\n        ├── scan_fab.slint\n        ├── scrim_dialog.slint\n        ├── popup_title_bar.slint\n        ├── context_menu_sheet.slint\n        ├── deselection_menu_sheet.slint\n        └── selection_menu_sheet.slint\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## Scan Data Flow\n\n```\n1. User selects tool + taps Scan → AppState.on_scan_requested()\n2. scan.rs: collect settings from globals → build ScanRequest\n3. ScanRequest sent to worker thread via channel\n4. Worker: configure tool struct → tool.search(stop_flag, progress_sender)\n5. Results stored; ScanResult sent back to main thread\n6. invoke_from_event_loop: populate model, update AppState.scan_state\n```\n\n---\n\n## Slint UI Architecture\n\nSettings are stored in Slint globals (not set from Rust at startup):\n\n- `GeneralSettings` – language, file sizes, cache, extension filters\n- `DuplicateSettings`, `SimilarImagesSettings`, `SameMusicSettings`, …\n- `AppState` – scan state, active tool, result models, all callbacks\n\n`apply_settings_to_gui()` (`settings/mod.rs`) reads `CediniaSettings` from\nJSON and populates these globals. `collect_settings_from_gui()` reads them back\nfor saving.\n\n### DropdownRow (`ui/components/settings/dropdown_row.slint`)\n\nInline accordion, **not** a `PopupWindow`:\n- Tapping the row toggles `expanded`, which inserts the option list directly\n  below the row inside the same `VerticalLayout`.\n- Scrolling is handled by the parent `Flickable`/`ListView`, not a nested\n  `ScrollView` - this avoids \"close on outside touch\" popup-dismissal issues\n  when the row sits inside an already-scrollable settings list.\n- Each option row tracks pointer movement (`_opt_moved`) so a scroll-drag that\n  ends over an option doesn't register as a selection tap.\n\n## Translation System\n\nSame workaround as krokiet:\n- All UI text bound to `Translations` global properties.\n- `translate_items()` in `src/translations.rs` populates them via `flc!(\"key\")`.\n- `LANGUAGE_LIST` in `localizer_cedinia.rs` defines the 27 supported languages.\n- `GeneralSettings.language_options` in `ui/app_state.slint` is hardcoded and\n  must match `LANGUAGE_LIST` exactly.\n- Language index stored in `GeneralSettings.language_idx`; code stored in\n  `CediniaSettings.language`.\n\n---\n\n## Settings (`src/settings/mod.rs`)\n\nJSON file (path resolved by `czkawka_core::common::config_cache_path::get_config_cache_path()`,\nconfigured via `set_config_cache_path(\"cedinia\", \"cedinia\")`):\n- Desktop: `~/.config/cedinia/cedinia_settings.json` (directories-next ignores\n  the qualifier/org on Linux and lowercases the app name - there is no\n  `Czkawka` path segment)\n- Android: app-private storage, `$DATA_DIR/files/cedinia/cedinia_settings.json`\n\nIncluded/excluded directories are persisted separately in a sibling\n`cedinia_dirs.json` in the same folder.\n\n`CediniaSettings` (serde Serialize/Deserialize) covers all tool parameters.\n`Default` implementation delegates to `serde_json::from_str(\"{}\")` so all\n`#[serde(default)]` values are used.\n\n---\n\n## Android-Specific Code\n\n### File Picker (`src/file_picker_android.rs`)\n\nA Kotlin helper class is compiled at build time (via `android-build` crate) and\nembedded as DEX bytecode:\n\n```rust\ninclude_bytes!(concat!(env!(\"OUT_DIR\"), \"/classes.dex\"))\n```\n\nAt runtime:\n1. DEX is loaded into an `InMemoryDexClassLoader`.\n2. `CediniaFilePicker` instance created via JNI.\n3. `launch_pick_directory(is_include, start_path)` calls the Java\n   `ACTION_OPEN_DOCUMENT_TREE` intent, hinting the picker to open at `start_path`.\n4. Result arrives via `Java_CediniaFilePicker_onDirectoryPicked()` JNI callback\n   → dispatched to Rust via `invoke_from_event_loop()`.\n\n### Storage Permissions\n\nRuntime permission check/request via JNI.\n`AppState.storage_permission_granted` gates scan initiation.\n\n### System Insets\n\nAndroid edge-to-edge layout requires padding for navigation bars. Inset sizes\nare passed from Kotlin via `android_app.content_rect()` and plumbed through to\nSlint as layout properties.\n\n### Android Paths via JNI\n\n`setup_android_paths()` calls `getFilesDir()` and `getCacheDir()` on the\nActivity to find private storage paths; these are set as environment variables\nand used by `czkawka_core`'s config path logic.\n\n---\n\n## Differences from krokiet\n\n| | cedinia | krokiet |\n|---|---|---|\n| `SimilarVideos` | Yes (audio fingerprint only, `rusty-chromaprint`) | Yes (frame hashing) |\n| `VideoOptimizer` | No (ffmpeg-based transcoding unavailable on Android) | Yes |\n| `BadNames` | Yes | Yes |\n| File picker | JNI (Android) / rfd (desktop) | rfd |\n| Settings | JSON, no presets | JSON, 11 presets |\n| Column indices | `common.rs` (own set) | `common.rs` (own set) |\n| Build target | `cdylib` (Android) + `bin` (desktop) | `bin` |\n\n---\n\n## Key Dependencies\n\n### Always\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework |\n| `czkawka_core` | Scanning engine |\n| `crossbeam-channel` | Worker thread communication |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n\n### Android only\n\n| Crate | Purpose |\n|-------|---------|\n| `android-activity` 0.6 | Android lifecycle |\n| `jni` 0.22 | Java FFI |\n| `android_logger` 0.15 | Logcat output |\n\n### Desktop only\n\n| Crate | Purpose |\n|-------|---------|\n| `rfd` 0.17 | Native file picker |\n| `trash` 5.2 | Move-to-trash |\n| `notify-rust` 4 | Desktop notifications |\n\n### Build\n\n```toml\n[build-dependencies]\nslint-build = \"1.15\"\nandroid-build = \"0.1.2\"   # Compiles CediniaFilePicker.java → classes.dex\n```\n\n### Android Manifest Permissions\n\n`READ_EXTERNAL_STORAGE`, `WRITE_EXTERNAL_STORAGE`,\n`MANAGE_EXTERNAL_STORAGE`, `POST_NOTIFICATIONS`\n\n`Cargo.toml`'s `[package.metadata.android.uses_permission]` also lists\n`WAKE_LOCK`, but the gradle-built `android/app/src/main/AndroidManifest.xml`\ndoes not include it - a pre-existing inconsistency in the repo, not a doc bug.\n\n---\n\n## Optional Features (forwarded to czkawka_core)\n\n`heif`, `libraw`, `libavif`, `xdg_portal_trash`\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row (see\n`model.rs::toggle_row`, `compare.rs::wire_compare_toggle_checkbox`).\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n\n<!-- File: czkawka_cli/AGENTS.md (Tokens: ~1609 | Category: root-instruction) -->\n# czkawka_cli – Architecture Guide\n\n## Overview\n\n`czkawka_cli` is a thin CLI wrapper around `czkawka_core`. It handles argument\nparsing, thread orchestration, progress rendering, and result output. All\nscanning logic lives in `czkawka_core`; this crate only wires it to the\nterminal.\n\n---\n\n## Source Layout\n\n```\nczkawka_cli/src/\n├── main.rs       # Entry point, thread spawning, tool dispatchers\n├── commands.rs   # All clap CLI argument definitions\n├── parsers.rs    # parse_* clap value-parser functions used by commands.rs\n└── progress.rs   # indicatif progress bar rendering\n```\n\n---\n\n## Execution Model\n\n```\nmain()\n ├─ Parse args (clap)\n ├─ Create crossbeam channel  (Sender<ProgressData>, Receiver<ProgressData>)\n ├─ Create Arc<AtomicBool> stop_flag\n ├─ spawn calculation_thread → dispatches to one of 14 tool functions\n ├─ Register Ctrl+C handler → sets stop_flag = true\n ├─ connect_progress(&progress_receiver)   [blocks, renders progress bar]\n └─ join calculation_thread → print output → exit(11) if items found\n```\n\nThe **main thread** renders progress. The **calculation thread** runs the scan.\n`progress_receiver.recv()` exits naturally when the sender drops (scan ends).\n\n---\n\n## CLI Subcommands\n\nDefined via `clap` derive in `commands.rs`. Top-level enum `Commands` has 14\nvariants:\n\n| Subcommand        | Alias            | Purpose                                 |\n|-------------------|------------------|-----------------------------------------|\n| `Duplicates`      | `dup`            | Find duplicate files (hash/name/size)   |\n| `EmptyFolders`    | `empty-folders`  | Find empty directories                  |\n| `BiggestFiles`    | `big`            | Find biggest or smallest files          |\n| `EmptyFiles`      | `empty-files`    | Find zero-byte files                    |\n| `Temporary`       | `temp`           | Find temporary files                    |\n| `SimilarImages`   | `image`          | Find similar images (perceptual hash)   |\n| `SameMusic`       | `music`          | Find duplicate music (tags/fingerprint) |\n| `InvalidSymlinks` | `symlinks`       | Find broken symlinks                    |\n| `BrokenFiles`     | `broken`         | Find broken PDF/audio/image/archive     |\n| `SimilarVideos`   | `video`          | Find similar videos (frame hashing)     |\n| `BadExtensions`   | `ext`            | Find files with wrong extensions        |\n| `BadNames`        | `bad-names`      | Find problematic file names             |\n| `VideoOptimizer`  | `video-optimizer`| Transcode or crop videos                |\n| `ExifRemover`     | `exif-remover`   | Remove EXIF metadata from images        |\n\n### `CommonCliItems` (shared by every subcommand)\n\n- `-d` directories (required)\n- `-e` excluded directories\n- `-E` excluded items (glob patterns, e.g. `*/.*`)\n- `-x` allowed extensions (supports macros: `IMAGE`, `VIDEO`, `MUSIC`, `TEXT`)\n- `-P` excluded extensions\n- `-R` disable recursive search\n- `-X` (unix only) exclude other filesystems\n- `-f` save text results to file\n- `-C` save compact JSON results to file\n- `--pretty-file-to-save` (`-p`) save pretty JSON results to file\n- `-N` / `-M` suppress result/message output\n- `-W` suppress non-zero exit code when items found\n- `-T` thread count (0 = all CPUs)\n- `-H` disable cache\n\n### Delete methods\n\n**`DMethod`** (for similarity-grouping tools): `-D NONE|AEN|AEO|ON|OO|AEB|AES|OB|OS|HARD`\n**`SDMethod`** (for simple tools): `-D` delete flag, `-Q` dry-run, `-y` move-to-trash\n\n---\n\n## Tool Dispatcher Pattern\n\nEvery one of the 14 dispatch functions follows the same structure:\n\n```rust\nfn tool_name(args: ToolArgs, stop_flag: &Arc<AtomicBool>, progress_sender: &Sender<ProgressData>) -> CliOutput {\n    // 1. Destructure args\n    // 2. Build tool-specific parameter struct\n    let mut tool = ToolType::new(params);\n    // 3. Apply common settings (paths, extensions, cache, threads)\n    set_common_settings(&mut tool, &common_cli_items, reference_directories);\n    // 4. Apply tool-specific settings (file sizes, delete method, etc.)\n    // 5. Run scan\n    tool.search(stop_flag, Some(progress_sender));\n    // 6. Optionally run fix (rename/delete/etc.)\n    // 7. Collect and return output\n    save_and_write_results_to_writer(&tool, &common_cli_items)\n}\n```\n\n`set_common_settings` applies paths, recursion, extensions, cache flags, and\nthread count via the `AllTraits` bound.\n\n---\n\n## Progress Rendering (`progress.rs`)\n\n`connect_progress(&progress_receiver)` loops on `progress_receiver.recv()` and\nrenders via indicatif:\n\n- **Spinner** while collecting files or loading/saving cache.\n- **Linear bar** `[=>  ]` once the total entry or byte count is known.\n\nThe stage label (e.g. \"Calculating hashes\", \"Reading tags\") is derived from the\n`CurrentStage` enum on each `ProgressData` message.\n\n---\n\n## Result Output\n\n`save_and_write_results_to_writer` writes three optional file formats (text,\ncompact JSON, pretty JSON) and buffers stdout output, which is printed after the\nthread join. The `CliOutput.found_any_files` flag drives the exit code:\n\n- `exit(11)` when items are found (and `-W` is not set) - useful in scripts.\n- `exit(0)` otherwise.\n\n---\n\n## Cancellation\n\n`Arc<AtomicBool>` (SeqCst ordering) is shared between the Ctrl+C signal handler\nand the calculation thread. The tools in `czkawka_core` poll it during scanning\nand stop gracefully when it is `true`.\n\n---\n\n## Key Dependencies\n\n| Crate              | Purpose                          |\n|--------------------|----------------------------------|\n| `clap` 4.5         | CLI parsing (derive API)         |\n| `indicatif` 0.18   | Progress bars                    |\n| `crossbeam-channel`| Unbounded channel for progress   |\n| `ctrlc` 3.4        | SIGINT / Ctrl+C handling         |\n| `humansize` 2.1    | Human-readable byte sizes        |\n| `czkawka_core`     | All scanning logic               |\n\n### Optional features (forwarded to `czkawka_core`)\n\n- `heif` – HEIF/HEIC image support (requires libheif)\n- `libraw` – RAW image support (requires libraw)\n- `libavif` – AVIF image support (requires libavif)\n- `xdg_portal_trash` – FlatPak-compatible trash (XDG portal)\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n<!-- File: czkawka_core/AGENTS.md (Tokens: ~3227 | Category: root-instruction) -->\n# czkawka_core – Architecture Guide\n\n## Overview\n\n`czkawka_core` is the shared scanning engine used by all frontends. It has no UI\ndependency. Every scanning tool is implemented here; frontends only configure the\ntool structs and drive them via the `Search` trait.\n\n`TOOLS_NUMBER = 14` (defined in `src/lib.rs`).\n\n---\n\n## Source Layout\n\n```\nczkawka_core/src/\n├── lib.rs                         # Crate root; TOOLS_NUMBER constant\n├── localizer_core.rs              # Fluent i18n loader (flc! macro)\n├── common/\n│   ├── mod.rs                     # Module declarations + re-exports only (no logic of its own)\n│   ├── traits.rs                  # Search, PrintResults, DeletingItems, FixingItems, ResultEntry, AllTraits\n│   ├── tool_data.rs               # CommonToolData struct + the CommonData trait (defined here, not traits.rs)\n│   ├── model.rs                   # ToolType, CheckingMethod, FileEntry, DeleteMethod\n│   ├── progress_data.rs           # ProgressData, CurrentStage enum\n│   ├── progress_stop_handler.rs   # ProgressThreadHandler, check_if_stop_received\n│   ├── dir_traversal.rs           # DirTraversalBuilder, DirTraversalResult\n│   ├── cache.rs                   # Generic load/save cache (bincode + JSON)\n│   ├── cache/\n│   │   └── cleaning.rs            # Outdated cache cleanup\n│   ├── config_cache_path.rs       # Platform config/cache path lookup\n│   ├── directories.rs             # Directories struct (included/excluded/reference)\n│   ├── extensions.rs              # Extensions struct (allowed/excluded filtering)\n│   ├── items.rs                   # ExcludedItems (glob pattern matching)\n│   ├── consts.rs                  # Extension lists: IMAGE_RS_EXTENSIONS, …\n│   ├── formatting.rs              # format_time and other display formatting helpers\n│   ├── path_utils.rs              # split_path, regex_check, normalize_windows_path\n│   ├── fs_ops.rs                  # make_hard_link, make_file_symlink, remove_single_file/folder\n│   ├── threads.rs                 # get_number_of_threads, set_number_of_threads\n│   ├── deletion.rs                # DeleteResult, DeleteItemType\n│   ├── audio_fingerprint.rs       # Chromaprint fingerprint calculation (used by same_music)\n│   ├── ffmpeg_utils.rs            # FFmpeg invocation helpers\n│   ├── video_utils.rs             # Video metadata extraction\n│   ├── image.rs                   # Image loading helpers\n│   ├── process_utils.rs           # Child-process helpers\n│   ├── logger.rs                  # Logging configuration\n│   └── basic_gui_cli.rs           # Types shared with GUI/CLI boundaries\n├── tools/\n│   ├── mod.rs                     # Re-exports all tool modules\n│   ├── duplicate/                 # Hash/name/size duplicate detection\n│   ├── empty_files/\n│   ├── empty_folder/\n│   ├── big_file/\n│   ├── similar_images/            # Perceptual hashing (image_hasher)\n│   ├── similar_videos/            # Frame-based video similarity\n│   ├── same_music/                # Audio tag + chromaprint fingerprint\n│   ├── broken_files/              # ZIP/PDF/audio/image validation\n│   ├── bad_extensions/            # Extension vs magic-number mismatch\n│   ├── bad_names/                 # Naming policy checks\n│   ├── invalid_symlinks/\n│   ├── temporary/\n│   ├── exif_remover/\n│   └── video_optimizer/           # Crop detection + FFmpeg transcoding\n└── helpers/\n    ├── messages.rs                # Messages struct (errors, warnings, info)\n    ├── delayed_sender.rs          # Rate-limited progress sender\n    ├── audio_checker.rs           # Audio file validation\n    ├── ffprobe.rs                 # ffprobe JSON output parsing\n    └── debug_timer.rs             # Debug-build timing\n```\n\n---\n\n## Tool Module Layout\n\nEach tool lives in its own directory, typically with these files:\n\n| File | Content |\n|------|---------|\n| `mod.rs` | Tool struct + `Info` struct + `Parameters` struct (if needed) |\n| `core.rs` | Internal scanning functions (`check_files_*`, `hash_calculation`, …) |\n| `traits.rs` | Trait implementations: `Search`, `CommonData`, `DeletingItems`, `PrintResults` |\n| `tests.rs` | Unit tests for the tool (every tool has one except `temporary/`) |\n\nA few tools don't fit the plain 4-file pattern: `bad_extensions/` adds a `workarounds.rs`, and\n`video_optimizer/` adds `encoding.rs` plus its own `core/` subdirectory\n(`video_converter.rs`, `video_cropper.rs`).\n\nExample - `EmptyFiles`:\n\n```\nsrc/tools/empty_files/\n├── mod.rs      # pub struct EmptyFiles { common_data, information, files_to_check, params }\n│               #   params: EmptyFilesParameters\n├── core.rs     # fn check_files() → WorkContinueStatus\n├── tests.rs    # Unit tests\n└── traits.rs   # impl Search, CommonData, PrintResults, DeletingItems, AllTraits\n```\n\n---\n\n## Core Traits\n\n`CommonData` is the odd one out: it's defined in `src/common/tool_data.rs` next to\n`CommonToolData`, not in `traits.rs`. Every other trait below lives in `src/common/traits.rs`.\nSignatures are simplified for orientation - both files have more methods (mostly\nsetter/getter pairs and default-impl helpers) than shown here.\n\n```rust\npub trait Search {\n    fn search(&mut self, stop_flag: &Arc<AtomicBool>,\n              progress_sender: Option<&Sender<ProgressData>>);\n}\n\n// src/common/tool_data.rs - NOT traits.rs\npub trait CommonData {\n    type Info;\n    type Parameters;\n    fn get_cd(&self) -> &CommonToolData;\n    fn get_cd_mut(&mut self) -> &mut CommonToolData;\n    fn found_any_items(&self) -> bool;\n    // ~30 more setters/getters for directories, extensions, delete method, cache flags, …\n}\n\npub trait PrintResults: CommonData {\n    fn write_results<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn print_results_to_writer<T: Write>(&self, w: &mut T) -> io::Result<()>;\n    fn save_results_to_file_as_json(&self, file: &str, pretty: bool) -> io::Result<()>;\n    // + write_base_search_paths, print_results_to_output/_to_file, save_all_in_one, …\n}\n\npub trait DeletingItems {\n    fn delete_files(&mut self, stop_flag: &Arc<AtomicBool>,\n                    progress_sender: Option<&Sender<ProgressData>>) -> WorkContinueStatus;\n}\n\npub trait FixingItems {\n    type FixParams;\n    fn fix_items(&mut self, stop_flag: &Arc<AtomicBool>,\n                 progress_sender: Option<&Sender<ProgressData>>, fix_params: Self::FixParams);\n}\n\npub trait ResultEntry {\n    fn get_path(&self) -> &Path;\n    fn get_modified_date(&self) -> u64;\n    fn get_size(&self) -> u64;\n}\n\npub trait AllTraits: DebugPrint + PrintResults + DeletingItems + CommonData + Search {}\n```\n\n---\n\n## CommonToolData (`src/common/tool_data.rs`)\n\n```rust\npub struct CommonToolData {\n    pub(crate) tool_type: ToolType,\n    pub(crate) text_messages: Messages,        // Accumulated warnings / errors\n    pub(crate) directories: Directories,       // Included / excluded / reference paths\n    pub(crate) extensions: Extensions,         // Allowed / excluded extensions\n    pub(crate) excluded_items: ExcludedItems,  // Glob patterns (e.g. \"*/.*\")\n    pub(crate) recursive_search: bool,\n    pub(crate) delete_method: DeleteMethod,\n    pub(crate) maximal_file_size: u64,\n    pub(crate) minimal_file_size: u64,\n    pub(crate) stopped_search: bool,\n    pub(crate) use_cache: bool,\n    pub(crate) delete_outdated_cache: bool,\n    pub(crate) save_also_as_json: bool,\n    pub(crate) use_reference_folders: bool,\n    pub(crate) dry_run: bool,\n    pub(crate) move_to_trash: bool,\n    pub(crate) hide_hard_links: bool,\n}\n```\n\n---\n\n## DirTraversal (`src/common/dir_traversal.rs`)\n\nBuilder-pattern filesystem traversal used by all tools:\n\n```rust\nlet result = DirTraversalBuilder::new()\n    .common_data(&self.common_data)\n    .group_by(|fe: &FileEntry| fe.size)   // Groups entries by this key\n    .stop_flag(stop_flag)\n    .progress_sender(progress_sender)\n    .checking_method(CheckingMethod::Size)\n    .build()\n    .run();\n\nmatch result {\n    DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { … }\n    DirTraversalResult::Stopped => return WorkContinueStatus::Stop,\n}\n```\n\n**Internals:**\n- Rayon `into_par_iter().with_max_len(2)` for parallel folder processing.\n- Two-phase: visit root dirs/files first, then recurse batch by batch.\n- Applies extension, exclusion, and size filters during traversal.\n- On Unix: optional filesystem-boundary checking (`exclude_other_filesystems`).\n- Progress tracked via `ProgressThreadHandler` (dedicated thread, 200 ms interval).\n\n---\n\n## Progress Reporting (`src/common/progress_data.rs` + `src/common/progress_stop_handler.rs`)\n\n`ProgressData`/`ToolStage` are defined in `progress_data.rs`; `ProgressThreadHandler` and\n`check_if_stop_received` live in `progress_stop_handler.rs`.\n\n```rust\npub struct ProgressData {\n    pub stage: ToolStage,           // Current operation\n    pub entries_checked: usize,\n    pub entries_to_check: usize,\n    pub bytes_checked: u64,\n    pub bytes_to_check: u64,\n}\n```\n\n`ToolStage` is a nested enum (`ToolStage::Duplicate(DuplicateStage)`, `ToolStage::SimilarImages(SimilarImagesStage)`, ...) so a\nstage can't be paired with a tool it doesn't belong to - this replaces the old flat `CurrentStage` enum plus its runtime\n`ProgressData::validate()` assertions. Call `progress.to_display()` to get a fully translated, ready-to-render\n`ProgressDisplay` (label + percentages) instead of branching on the stage in frontend code.\n\n`ProgressThreadHandler` spawns a background thread that polls `AtomicUsize`/`AtomicU64`\ncounters and sends `ProgressData` to the frontend channel every ~200 ms.\n\nCall `handler.increase_items(n)` / `handler.increase_size(n)` from rayon tasks\n(using `Relaxed` ordering - fast, no synchronization overhead).\n\n---\n\n## Cancellation\n\n```rust\n// In hot paths:\nif check_if_stop_received(stop_flag) {\n    return WorkContinueStatus::Stop;  // or break / return None in rayon\n}\n```\n\n`stop_flag` is `Arc<AtomicBool>` with `Relaxed` ordering - sufficient for simple\n\"should I stop?\" polling without synchronization cost. The frontend sets it to\n`true`; the backend polls and stops gracefully.\n\n---\n\n## Cache (`src/common/cache.rs`)\n\n```rust\n// Save\nsave_cache_to_file_generalized::<T>(\n    cache_file_name,    // e.g. \"cache_duplicates.bin\"\n    &btree_map,         // BTreeMap<String, T> (String = canonical path)\n    save_also_as_json,\n    minimum_file_size,\n);\n\n// Load\nlet (messages, opt_cache) =\n    load_cache_from_file_generalized_by_path::<T>(\n        cache_file_name,\n        delete_outdated_cache,\n        &used_files,    // Current BTreeMap to validate against\n    );\n```\n\n- Serialized with `bincode` (binary). Optionally also as `.json`.\n- Cached entries validated by path + size + mtime on load.\n- 8 GB memory limit on serialization.\n- Each tool has its own version constant (`CACHE_DUPLICATE_VERSION`, …).\n\n---\n\n## Rayon Usage Patterns\n\n```rust\n// Directory traversal\nfolders.into_par_iter().with_max_len(2).map(|dir| { … }).while_some().collect();\n\n// Deletion with early exit\nitems.into_par_iter().map(|e| {\n    if check_if_stop_received(stop_flag) { return None; }\n    // … process e …\n    Some(result)\n}).while_some().flatten().collect()\n```\n\nThread count is globally controlled via `set_number_of_threads(n)` which stores\nto a `LazyLock<Mutex<Option<usize>>>` and is applied via rayon's thread pool.\n\n---\n\n## ToolType Enum (`src/common/model.rs`)\n\n```rust\npub enum ToolType {\n    Duplicate, EmptyFolders, EmptyFiles, InvalidSymlinks,\n    BrokenFiles, BadExtensions, BadNames, BigFile, SameMusic,\n    SimilarImages, SimilarVideos, TemporaryFiles, ExifRemover,\n    VideoOptimizer,\n    #[default]\n    None,\n}\n```\n\nTools that support reference directories: `Duplicate`, `SameMusic`,\n`SimilarImages`, `SimilarVideos` (`may_use_reference_paths()`).\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `rayon` | Parallel iterators |\n| `crossbeam-channel` | Progress channels |\n| `blake3` | Fast file hashing |\n| `image` + `image_hasher` | Image loading + perceptual hash |\n| `lofty` | Audio tag reading |\n| `symphonia` | Audio decoding |\n| `rusty-chromaprint` | Audio fingerprinting |\n| `bincode` | Cache serialization |\n| `zip` | ZIP validation |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `trash` | Move-to-trash |\n| `directories-next` | Config/cache path |\n| `fun_time` | `#[fun_time]` timing attribute |\n| `similario_core` | Video similarity (sibling path-dependency; replaced `vid_dup_finder_lib`) |\n\nOptional (behind features):\n- `heif` → `libheif-rs` – HEIC/HEIF image support\n- `libraw` → `rawler` / `libraw-rs` – RAW photo support\n- `libavif` – AVIF image support\n- `xdg_portal_trash` – FlatPak trash via XDG portal\n- `blake_pure` → `blake3/pure` – pure-Rust Blake3 (no SIMD asm), for targets where the asm\n  backend doesn't build\n\n---\n\n## Comments\n\nShort and minimal, same baseline as the rest of the workspace (root `AGENTS.md`). The one\nexception in this workspace: `czkawka_core` is a library every frontend depends on, and external\nconsumers often read only its `pub fn`/`pub struct` surface, not the internals. That public API\nmay carry a bit more explanation than code elsewhere in the repo - still terse, no paragraphs,\njust enough to cover what isn't obvious from the signature alone.\n\n<!-- File: czkawka_gui/AGENTS.md (Tokens: ~2106 | Category: root-instruction) -->\n# czkawka_gui – Architecture Guide\n\n## Status: Maintenance mode only\n\nNo new features. Bug-fixes that keep it compatible with `czkawka_core` API\nchanges are accepted.\n\n---\n\n## Overview\n\nGTK4 frontend using XML UI files authored in Cambalache. All scanning tools\navailable in `czkawka_core` are exposed via a notebook (tab-based) interface.\n\n---\n\n## Source Layout\n\n```\nczkawka_gui/src/\n├── main.rs                          # GTK Application setup, GuiData construction\n├── initialize_gui.rs                # Initial widget state\n├── compute_results.rs               # Converts scan results → GTK ListStore rows\n├── saving_loading.rs                # JSON settings load/save\n├── language_functions.rs            # LANGUAGES_ALL constant (26 entries)\n├── localizer_gui.rs                 # flg! macro, Fluent loader\n├── gui_structs/\n│   ├── gui_data.rs                  # GuiData – central struct for all UI components\n│   ├── gui_main_notebook.rs         # 11-tool tab notebook\n│   ├── gui_upper_notebook.rs        # Included/excluded directory tabs\n│   ├── gui_settings.rs              # Settings dialog\n│   ├── gui_header.rs                # Menu bar (About, Settings)\n│   ├── gui_bottom_buttons.rs        # Action buttons (Search, Delete, etc.)\n│   ├── gui_progress_dialog.rs       # Scan progress window\n│   ├── gui_about.rs                 # About dialog\n│   ├── gui_compare_images.rs        # Side-by-side image diff overlay\n│   ├── common_tree_view.rs          # TreeView/ListStore rendering + SharedModelEnum\n│   ├── common_upper_tree_view.rs    # Dir-list TreeViews\n│   ├── gui_popovers_select.rs       # Selection filter popovers\n│   └── gui_popovers_sort.rs         # Sort filter popovers\n├── connect_things/                  # Callback wiring files\n│   ├── connect_button_search.rs     # Scan thread spawn + progress forwarding\n│   ├── connect_button_delete.rs     # File deletion with confirmation\n│   ├── connect_button_save.rs       # Export results (CSV/JSON)\n│   ├── connect_button_select.rs     # Row selection helpers\n│   ├── connect_button_sort.rs       # Column-header sort\n│   ├── connect_button_stop.rs       # Cancel scan (stop flag)\n│   ├── connect_button_move.rs       # Move files to folder\n│   ├── connect_button_hardlink.rs   # Hard-link/symlink creation\n│   ├── connect_button_compare.rs    # Image comparison launcher\n│   ├── connect_duplicate_buttons.rs # Duplicate-specific UI\n│   ├── connect_notebook_tabs.rs     # Tab-switch handler\n│   ├── connect_selection_of_directories.rs  # Dir-picker dialogs\n│   ├── connect_change_language.rs   # Language switch + full UI relabel\n│   ├── connect_settings.rs          # Settings window callbacks\n│   ├── connect_progress_window.rs   # Progress bar updates from channel\n│   ├── connect_show_hide_ui.rs      # Error text visibility toggle\n│   ├── connect_similar_image_size_change.rs\n│   ├── connect_same_music_mode_changed.rs\n│   ├── connect_popovers_select.rs\n│   ├── connect_popovers_sort.rs\n│   ├── connect_header_buttons.rs\n│   ├── connect_about_buttons.rs\n│   ├── connect_krokiet_info_dialog.rs  # One-time Krokiet deprecation notice (shown on first run)\n│   ├── connect_krokiet_promo_dialog.rs # Krokiet promo dialog (shown from the header button)\n│   └── file_chooser_helpers.rs\n├── helpers/\n│   ├── enums.rs                     # Column index enums for all tools\n│   ├── list_store_operations.rs     # Append/read GTK ListStore rows\n│   ├── image_operations.rs          # Image resize/preview for GTK\n│   ├── model_iter.rs                # GTK TreeView iteration\n│   └── mod.rs\n├── help_functions.rs                # HEADER_ROW_COLOR, MAIN_ROW_COLOR, set_buttons\n├── help_combo_box.rs                # Combo box option arrays (hash algs, etc.)\n├── notebook_enums.rs                # NotebookMainEnum (per-tool tab index)\n├── notebook_info.rs                 # Tab label + ListStore column definitions\n├── opening_selecting_records.rs     # Double-click → open file/folder\n├── taskbar_progress.rs              # Windows taskbar progress (real impl)\n├── taskbar_progress_dummy.rs        # No-op stand-in for non-Windows targets\n├── taskbar_progress_win.rs          # Windows-specific taskbar API calls\n└── gtk_traits.rs                    # Custom GTK trait extensions\n\nui/\n├── czkawka.cmb         # Cambalache project file (source of truth for the .ui files below)\n├── main_window.ui      # Cambalache XML (65 KB) – entire main window layout\n├── settings.ui         # Settings dialog\n├── compare_images.ui   # Image comparison panel\n├── popover_select.ui   # Selection filter popover\n├── popover_sort.ui     # Sort popover\n├── popover_right_click.ui # Right-click context menu popover\n├── about_dialog.ui     # About window\n└── progress.ui         # Scan progress dialog\n```\n\n---\n\n## GuiData Struct (`gui_structs/gui_data.rs`)\n\nCentral struct passed by clone/reference to every callback:\n\n```rust\npub struct GuiData {\n    pub window_main: gtk4::Window,\n    pub main_notebook: GuiMainNotebook,         // 11 tool tabs\n    pub upper_notebook: GuiUpperNotebook,       // Dir configuration\n    pub bottom_buttons: GuiBottomButtons,       // Action buttons\n    pub progress_window: GuiProgressDialog,\n    pub settings: GuiSettings,\n    pub header: GuiHeader,\n    pub compare_images: GuiCompareImages,\n    pub about: GuiAbout,\n    pub popovers_select: GuiSelectPopovers,\n    pub popovers_sort: GuiSortPopovers,\n    pub entry_info: gtk4::Entry,\n    pub text_view_errors: gtk4::TextView,\n    pub scrolled_window_errors: gtk4::ScrolledWindow,  // show/hide bottom error panel\n    pub file_dialog_include_exclude_folder_selection: gtk4::FileChooserNative,\n    pub file_dialog_move_to_folder: gtk4::FileChooserNative,\n    pub taskbar_state: Rc<RefCell<TaskbarProgress>>,\n    pub shared_buttons: Rc<RefCell<HashMap<NotebookMainEnum, …>>>,\n    pub stop_flag: Arc<AtomicBool>,\n}\n```\n\nConstructed by `GuiData::new_with_application()` which loads all `.ui` files\nvia `gtk4::Builder`.\n\n---\n\n## Scan Thread Architecture\n\n```\nUser clicks Search\n→ connect_button_search.rs\n→ Disables UI, shows progress window\n→ Spawn worker thread:\n    - Read settings from GTK widgets\n    - Create tool struct (e.g. DuplicateFinder)\n    - Call tool.search(stop_flag, progress_sender)\n    - Send Message::Duplicates(tool) via result_sender\n→ Main thread: receive progress updates → update bars\n→ On result received:\n    - compute_results.rs processes tool data → appends to ListStore\n    - Re-enables UI, hides progress\n```\n\n**Channel types:**\n- `Sender<Message>` – one-shot result channel (tool enum with full data)\n- `Sender<ProgressData>` – real-time progress from core\n\n---\n\n## Result Population (`compute_results.rs`)\n\n1. Receive `Message::Duplicates(DuplicateFinder)` from worker thread.\n2. Extract sorted file groups from the tool.\n3. For each group: insert a **header row** (colored `HEADER_ROW_COLOR`), then\n   one row per file (`MAIN_ROW_COLOR`).\n4. `append_row_to_list_store()` writes individual cells to `gtk4::ListStore`.\n5. `finalize_compute()` stores the tool object in `SubView.shared_model_enum`\n   (for later re-use by selection/sort callbacks).\n\n---\n\n## Language Switching (`language_functions.rs`)\n\n26 languages defined as `LANGUAGES_ALL: &[Language]` with `combo_box_text`\n(display name) and `short_text` (BCP-47 code).\n\nOn language change:\n1. Look up `short_text` from selected combo index.\n2. Call `localizer.select([lang_id])` for both `czkawka_core` and `czkawka_gui`.\n3. Call `gui_data.update_language()` → propagates to all sub-structs which\n   update their widget labels via `flg!(\"key\")`.\n\n---\n\n## Settings Storage (`saving_loading.rs`)\n\nJSON file at `~/.config/Czkawka/czkawka_gui_config.json`. Struct `SettingsJson`\nserialized with `serde_json`. Includes directories, tool parameters, UI state,\nlanguage preference.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `gtk4` 0.11 | GTK4 GUI framework |\n| `gdk4` 0.11 | Drawing/rendering |\n| `glib` 0.22 | Event loop, `spawn_future_local` |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `open` 5.3 | Launch file manager |\n| `image` 0.25 | Preview thumbnails |\n| `resvg` 0.47 | SVG icon scaling |\n| `rayon` 1.10 | Parallel sort of results |\n| `crossbeam-channel` | Result + progress channels |\n| `czkawka_core` | Scanning engine |\n\nOptional features (forwarded to core): `heif`, `libraw`, `libavif`,\n`xdg_portal_trash`.\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n<!-- File: krokiet/AGENTS.md (Tokens: ~3088 | Category: root-instruction) -->\n# krokiet – Architecture Guide\n\n## Overview\n\nPrimary desktop GUI. Built with [Slint](https://slint.dev/) (GPL-3.0). The UI\nis declared in `ui/*.slint`; Rust wires all callbacks and drives the Slint data\nmodels. All scanning logic lives in `czkawka_core`.\n\n---\n\n## Source Layout\n\n```\nkrokiet/\n├── build.rs                          # slint_build::compile(\"ui/main_window.slint\")\n├── src/\n│   ├── main.rs                       # Entry point: load settings, create MainWindow,\n│   │                                  #   wire all callbacks, run event loop\n│   ├── shared_models.rs              # SharedModels – Arc<Mutex<…>> of all tool results\n│   ├── common.rs                     # Column index enums (IntDataDuplicateFiles, …)\n│   │                                  #   and sort index helpers\n│   ├── localizer_krokiet.rs          # flk! macro, LANGUAGE_LOADER_KROKIET\n│   ├── set_initial_gui_info.rs       # Asserts Slint combo lists == Rust enums\n│   ├── set_initial_scroll_list_data_indexes.rs\n│   ├── simpler_model.rs              # Minimal serializable row struct for threading\n│   ├── audio_player.rs               # Play sound on scan completion\n│   ├── notification_manager.rs       # Desktop notifications\n│   ├── create_calculate_task_size.rs # Background total-size calculator\n│   ├── clear_outdated_video_thumbnails.rs\n│   ├── connect_scan.rs               # Routes scan to per-tool functions\n│   ├── connect_scan/                 # One file per tool (duplicate.rs, …)\n│   ├── connect_progress_receiver.rs  # Receives ProgressData → updates UI bar\n│   ├── connect_row_selection/        # Row checkbox logic, group handling\n│   │   ├── mod.rs\n│   │   ├── checker.rs\n│   │   ├── clipboard.rs\n│   │   ├── context_menu.rs\n│   │   ├── opener.rs\n│   │   └── selection.rs\n│   ├── connect_sort.rs               # Column-header sort (string + int indices)\n│   ├── connect_translation.rs        # LANGUAGE_LIST, change_language(), translate_items()\n│   ├── connect_compare.rs            # Image comparison overlay\n│   ├── connect_show_preview.rs       # File preview / thumbnail\n│   ├── connect_open.rs               # Open file/folder in system app\n│   ├── connect_save.rs               # Export results via PrintResults\n│   ├── connect_show_confirmation.rs  # Confirmation popup before destructive action\n│   ├── connect_directories_changes.rs\n│   ├── connect_clean_cache.rs\n│   ├── connect_tab_changed.rs\n│   ├── connect_stop.rs               # Sets stop_flag = true\n│   ├── connect_rfd.rs                # File picker (rfd)\n│   ├── connect_select/\n│   │   ├── mod.rs\n│   │   └── custom_select.rs          # Advanced filter popup\n│   ├── file_actions/\n│   │   ├── connect_delete.rs\n│   │   ├── connect_move.rs\n│   │   ├── connect_rename.rs\n│   │   ├── connect_hardlink.rs\n│   │   ├── connect_symlink.rs\n│   │   ├── connect_optimize_video.rs\n│   │   └── connect_clean_exif.rs\n│   ├── model_operations/\n│   │   ├── mod.rs                    # Slint model ↔ thread-safe model conversion\n│   │   └── model_processor.rs        # Parallel item processing framework\n│   └── settings/\n│       ├── mod.rs                    # Load/save JSON settings (11 presets)\n│       ├── model.rs                  # BasicSettings, SettingsCustom structs\n│       └── combo_box.rs              # StringComboBoxItems + regenerate_items()\n└── ui/\n    ├── main_window.slint             # Root component; imports everything; exports globals\n    ├── globals/\n    │   ├── common.slint              # Enums + structs: ActiveTab, SingleMainListModel, …\n    │   ├── gui_state.slint           # Global GuiState (transient runtime state)\n    │   ├── settings.slint            # Global Settings (persisted)\n    │   ├── translations.slint        # Global Translations (all UI strings)\n    │   ├── callabler.slint           # Global Callabler (all Rust to UI callbacks)\n    │   ├── fonts.slint               # FontSizes global\n    │   ├── color_palette.slint       # Dark-theme color definitions\n    │   └── text_size.slint\n    ├── screens/\n    │   ├── left_side_panel.slint     # Tool selector + tool settings sub-panel\n    │   ├── main_lists.slint          # Results table (bound to model)\n    │   ├── action_buttons.slint      # Scan / Stop / Select / Delete / … buttons\n    │   ├── bottom_panel.slint        # Progress bar + error text + status\n    │   ├── progress.slint            # Real-time scan progress widget\n    │   ├── image_compare.slint       # Side-by-side image diff overlay\n    │   ├── settings_list.slint       # Settings tab form\n    │   ├── tool_settings.slint       # Per-tool options (hash alg, threshold, …)\n    │   ├── about.slint                # About tab\n    │   └── included_paths.slint\n    ├── components/\n    │   ├── preview.slint             # File preview / thumbnail side panel\n    │   ├── selectable_tree_view.slint # Custom tree widget for path lists\n    │   ├── hint_text.slint\n    │   ├── popup_base.slint\n    │   ├── popup_centered_text.slint\n    │   └── popup_context_menu.slint\n    └── popups/                       # 18 modal dialogs: popup_delete, popup_save,\n                                       #   popup_optimize, popup_crop_video,\n                                       #   popup_custom_select, popup_rename_*, …\n```\n\n---\n\n## Slint Performance: ListView vs. for-in / ScrollView\n\nWhen displaying more than a few dozen items, **always use `ListView`** (from\n`std-widgets.slint`)\nrather than a bare `ScrollView` / `for` loop inside a `VerticalLayout`.\n\n```slint\n// SLOW – instantiates every item upfront, O(n) per frame\nScrollView {\n    VerticalLayout {\n        for item in model : Row { ... }\n    }\n}\n\n// SLOW - same as above\nVerticalLayout {\n    for item in model : Row { ... }\n}\n\n// FAST – virtual scroll via Flickable's Repeater optimization\nListView {\n    for item in model : Row { ... }\n}\n```\n\n`ListView` uses Slint's Repeater-inside-Flickable\noptimization: only visible rows (plus a small buffer) are instantiated at any\ngiven time.  With a plain `ScrollView` + `VerticalLayout`, all N items are\ncreated and visited on every frame.\n\nReference: Slint issue [#11021](https://github.com/slint-ui/slint/issues/11021).\n\n---\n\n## SharedModels (`src/shared_models.rs`)\n\n```rust\npub struct SharedModels {\n    pub shared_duplication_state:       Option<DuplicateFinder>,\n    pub shared_empty_folders_state:     Option<EmptyFolder>,\n    // … one field per tool (14 total)\n}\n```\n\nCreated once in `main.rs` as `Arc<Mutex<SharedModels>>`, cloned and passed to\nevery callback that needs scan results (preview, compare, save, delete, …).\n\nWorker threads lock exclusively to **write** results after a scan finishes.\nUI callbacks lock briefly to **read** results for previews / exports.\n\n`VideoOptimizer` is one of the 14 tools but doesn't fit the scan/select/delete\nshape of the others: it has its own `connect_scan/video_optimizer.rs` entry\npoint plus a dedicated `file_actions/connect_optimize_video.rs` action, and\ntwo popups (`popup_optimize.slint` for transcode options,\n`popup_crop_video.slint` for crop-region preview) instead of reusing the\ngeneric delete/save popups.\n\n---\n\n## Scan Data Flow\n\n```\n1. User clicks Scan → on_scan_starting(active_tab)\n2. connect_scan.rs: collect_settings() → ScanData → route to scan_<tool>()\n3. scan_<tool>(): spawn worker thread\n       → create and configure tool struct\n       → tool.search(stop_flag, progress_sender)\n       → store tool in shared_models.lock()\n       → populate Slint VecModel<SingleMainListModel>\n       → app.upgrade_in_event_loop(|app| app.set_<tool>_model(model))\n4. UI re-renders with new model\n```\n\n---\n\n## Slint Data Model\n\n```rust\n// ui/common.slint\nexport struct SingleMainListModel {\n    checked: bool,           // row selected by user\n    header_row: bool,        // group header\n    filled_header_row: bool, // header with formatted summary\n    focused_row: bool,      // UI highlight\n    val_str: [string],       // text columns\n    val_int: [int],          // numeric columns (raw values for sorting)\n}\n```\n\nColumn indices are constants in `src/common.rs`:\n\n```rust\npub enum StrDataDuplicateFiles { Size, Name, Path, ModificationDate }\npub enum IntDataDuplicateFiles { ModificationDatePart1, ModificationDatePart2,\n                                  SizePart1, SizePart2 }\n```\n\nSlint cannot store `i64`, so dates and file sizes are split into two `i32` fields.\n\n---\n\n## Translation System\n\nSlint has no native Fluent support. Workaround:\n1. All UI text is bound to properties in the `Translations` global\n   (`ui/translations.slint`), not hardcoded in `.slint`.\n2. `translate_items()` in `connect_translation.rs` (~440 lines, file is ~750 lines total)\n   sets every property via `flk!(\"key\")` after language changes.\n3. Language list defined as `LANGUAGE_LIST: &[Language]` in\n   `connect_translation.rs`.\n4. Hardcoded combo-box lists in `.slint` files (e.g. `Settings.languages_list`)\n   are verified at startup against Rust enums via `assert_eq!` in\n   `set_initial_gui_info.rs` (workaround for [slint#7632](https://github.com/slint-ui/slint/issues/7632)).\n\n---\n\n## Settings (`src/settings/`)\n\nTwo-tier JSON system:\n\n| File | Content |\n|------|---------|\n| `~/.config/krokiet/config_general.json` | `BasicSettings` – default preset index, preset names, theme, window size |\n| `~/.config/krokiet/config_preset_N.json` | `SettingsCustom` – paths, extensions, tool parameters |\n\n(`directories-next` resolves the config dir from the lowercased app name only - the\nqualifier/organization passed to `set_config_cache_path` are ignored on Linux.)\n\n11 preset slots (indices 0–10). Slot 10 is reserved for CLI-mode overrides.\n`StringComboBoxItems::regenerate_items()` builds all combo box option arrays\nfrom Rust enums; used both for settings serialization and UI initialization.\n\n---\n\n## Callback Registration Pattern\n\n```rust\nlet weak = app.as_weak();\napp.global::<Callabler>().on_some_action(move || {\n    let app = weak.upgrade().expect(\"MainWindow dropped while callback is still live\");\n    // use app …\n});\n```\n\n`expect()` is correct here: if the window is gone, no callback should fire.\nCross-thread updates use `weak.upgrade_in_event_loop(|app| { … })`.\n\n---\n\n## Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `slint` 1.15 | UI framework (GPL-3.0) |\n| `czkawka_core` | Scanning engine |\n| `i18n-embed` + `rust-embed` | Fluent translations |\n| `crossbeam-channel` | Progress + result channels |\n| `rayon` | Parallel result sorting |\n| `serde_json` | Settings persistence |\n| `image` | Thumbnail loading |\n| `rfd` 0.17 | File picker (xdg-portal backend) |\n| `notify-rust` | Desktop notifications |\n| `rodio` | Scan-complete sound (optional `audio` feature) |\n| `fontique` | Font discovery (dlopen, avoids C dep) |\n\n### Optional features\n\n- `audio` – scan-complete sound via `rodio`\n- Renderer features: `winit_femtovg` (default), `winit_software` (default),\n  `skia_opengl`, `skia_vulkan`, etc. – select GPU backend at compile time\n- `heif`, `libraw`, `libavif`, `xdg_portal_trash` – forwarded to `czkawka_core`\n\n### Build\n\n`build.rs` compiles `ui/main_window.slint` with style `fluent-dark` (overridable\nvia `SLINT_STYLE` env var).\n\n---\n\n## Comments\n\nShort and minimal - see root `AGENTS.md`. Only add a comment where the code's behavior can't be\ninferred from reading it (a non-obvious constraint, a workaround, a cross-module coupling);\nnever restate what the code already says.\n\n---\n\n## Slint model `row_data()` - panic, don't silently no-op\n\n`ModelRc::row_data(index)` returns `Option<T>`. When `index` comes from a synchronous,\nuser-triggered callback (the index was just produced by the model that owns it - a click handler,\na toggle, a rename), an out-of-bounds result means the index and the model have already gone out\nof sync with each other - a programmer error, not a legitimate race. Panic loudly instead of\nswallowing it with `if let Some(...) = ... { }` or `let Some(...) = ... else { return };`: a silent\nno-op there hides the desync and risks corrupting whatever state depends on that row.\n\n```rust\nlet mut entry = model\n    .row_data(index)\n    .unwrap_or_else(|| panic!(\"toggle_row: index {index} out of bounds (row_count={})\", model.row_count()));\n```\n\nTwo situations remain legitimate `if let Some(...)` / silent-skip:\n- The index comes from a `0..model.row_count()` loop bound - it cannot be out of range by\n  construction.\n- The lookup happens inside a background/timer task that is already guarded by an explicit\n  staleness check (e.g. a generation/scan-id comparison) - the data may legitimately be gone\n  because the user navigated away mid-flight; that is a real race, not a bug.\n\n<!-- File: cedinia/CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: czkawka_cli/CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: czkawka_core/CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: czkawka_gui/CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: krokiet/CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n</yakaai_skills>"}