# Code Review — mx-menu-editor

**Branch:** `master`, **Last commit:** `bdd56e5 Update Debian packaging metadata`
**Generated:** 2026-06-30 01:43:28 EDT
**Working tree at start:** clean (`git status --short` empty)
**Verification performed:** `./build.sh -d --clean` completed successfully with GCC/Qt6/Ninja. `desktop-file-validate mx-menu-editor.desktop` reports one deprecated key warning and one category error. A clang probe was not run after command approval blocked `./build.sh -c`.

| Group | Items | Status |
|-------|-------|--------|
| A — Build & packaging infra | 1–3 | Done |
| C — Runtime robustness | 4–10 | Done |
| D — Code duplication extraction | 11–12 | Done |
| E — Code quality cleanup | 13 | Done |
| G — UI & assets | 14–18 | Done |
| H — Testing | 19 | Done |
| **Total** | **19** | **0 open** |

---

### Group A — Build & packaging infra

Items that touch build scripts, packaging metadata, or tracked generated artifacts — no C++ source changes.

1. [x] **Remove stale generated Debian package artifacts from version control** — `git ls-files` shows tracked build outputs under `debs/`: `debs/mx-menu-editor_22.10_*.build`, `debs/mx-menu-editor_22.10_*.buildinfo`, `debs/mx-menu-editor_25.12.2.dsc`, `debs/mx-menu-editor_25.12.2.tar.xz`, and `debs/mx-menu-editor_25.12_amd64.*`. These are generated package sidecars that will drift from `debian/changelog` and local builds. Fixed: untracked with `git rm --cached` (files kept on disk) and added `/debs/` to `.gitignore`.
2. [x] **Tighten package-artifact cleanup globs in `build.sh`** — `build.sh:82`, `build.sh:89`, and `build.sh:103` use broad parent-directory patterns such as `../*build*`, which can move or delete unrelated sibling files whose names contain `build`. The Debian build cleanup should target the package basename/version artifact shapes explicitly. Fixed: globs now use the `mx-menu-editor_*` package basename prefix.
3. [x] **Remove duplicate pedantic flags from CMake** — `CMakeLists.txt:129-132` passes both `-Wpedantic` and `-pedantic`. They are aliases on GCC/Clang, so this is harmless but noisy in an already strict warning setup. Fixed: removed `-pedantic`, kept `-Wpedantic`. Rebuilt clean with no new warnings.

### Group C — Runtime robustness

Items that prevent lost edits, wrong menu contents, blocking UI paths, or ignored runtime failures.

4. [x] **Make save operations report success before navigation/discard continues** — `MainWindow::save()` at `src/mainwindow.cpp:1418-1420` calls `pushSave_clicked()` and then returns `true` unconditionally. If `pushSave_clicked()` returns early because executable validation is declined (`src/mainwindow.cpp:1279-1281`) or file creation/open fails (`src/mainwindow.cpp:1288-1296`), callers such as selection change (`src/mainwindow.cpp:492-499`), search filtering (`src/mainwindow.cpp:547-553`), Add App (`src/mainwindow.cpp:1202-1209`), and Cancel (`src/mainwindow.cpp:1401-1406`) proceed as if the save succeeded, risking silent loss of unsaved edits. Fixed: `pushSave_clicked()` now returns `bool` (false on validation/IO failure, true on success), and `save()` propagates it instead of always returning `true`.
5. [x] **Fix the same failed-save continuation path in Add Custom Application** — `AddAppDialog::saveOrNot()` at `src/addappdialog.cpp:350-356` calls `pushSave_clicked()` and always returns `true`. If validation fails or the output file cannot be created/opened (`src/addappdialog.cpp:267-304`), `pushCancel_clicked()` still resets and closes the dialog at `src/addappdialog.cpp:336-341`, discarding the user's entered launcher. Fixed: `AddAppDialog::pushSave_clicked()` now returns `bool` and `saveOrNot()` propagates the save result instead of unconditionally returning `true`.
6. [x] **Validate edited desktop fields in the main editor, not only newly added apps** — `AddAppDialog` rejects newlines/control characters in Name, Exec, Comment, and Icon (`src/addappdialog.cpp:49-167`), but the existing-item editor writes `ui->lineEditName`, `ui->lineEditCommand`, `ui->lineEditComment`, and selected icon paths directly into the advanced editor (`src/mainwindow.cpp:870-981`) and then saves them (`src/mainwindow.cpp:1302`). A pasted newline in the main editor can corrupt the `.desktop` file format even though the Add dialog protects against it. Fixed: added a `containsInvalidDesktopChars()` check, applied where Name/Command/Comment/Icon values are merged into the advanced editor (`changeName()`, `changeCommand()`, `changeComment()`, `changeIcon()`); rejects with a `QMessageBox::warning()` instead of saving, matching `AddAppDialog`'s pattern. (An earlier version of this fix silently sanitized instead of warning, on the mistaken assumption these handlers fired per-keystroke; a fresh-eyes review found they're wired to `QLineEdit::editingFinished` — fires once per focus-out, same as a discrete Save action — so silent stripping just let the displayed field text and the saved value diverge with no feedback. Corrected in a follow-up commit.)
7. [x] **Match desktop categories as semicolon-delimited tokens** — `populateCategory()` builds regexes like `Categories=.*<category>` at `src/mainwindow.cpp:282-288`, and `listDesktopFiles()` applies them to every line at `src/mainwindow.cpp:677-701`. This can match substrings inside other category names instead of exact semicolon-delimited desktop categories; for example a category token should be matched as start/semicolon + token + semicolon/end, not any text after `Categories=`. Fixed: pattern changed to `Categories=(?:[^;]*;)*<category>(?:;|$)`, verified against 10 substring/boundary test cases.
8. [x] **Use a non-interactive decompressor for the About → Changelog dialog** — `pushAbout_clicked()` starts `zless` and reads stdout at `src/mainwindow.cpp:1354-1366`. `zless` is a pager, not a data decompressor, so in GUI/non-TTY contexts it can time out or return no useful text. Use `zcat`/`gzip -dc` or direct gzip reading, and show a user-visible failure message instead of silently returning on timeout/exit failure. Fixed: switched to `gzip -dc` and added `QMessageBox::warning()` on timeout/non-zero exit instead of silently returning.
9. [x] **Avoid blocking the UI on helper commands and handle failures** — `restartPanel()` uses synchronous `QProcess::execute()` for `pgrep` and `xfce4-panel --restart` at `src/mainwindow.cpp:1321-1325`, and help/license opening uses synchronous `xdg-open` calls at `src/mainwindow.cpp:1344-1346` and `src/mainwindow.cpp:1386-1397`. These calls can freeze the dialog while the command runs and their failures are ignored. Fixed: `restartPanel()` now runs `pgrep` asynchronously via `QProcess::finished`/`errorOccurred` and launches the panel restart with `QProcess::startDetached()`; the License and Help `xdg-open` calls also use `QProcess::startDetached()` and show a warning on failure to launch.
10. [x] **Check delete failures when restoring/deleting local overrides** — `pushRestoreApp_clicked()` calls `QFile::remove()` at `src/mainwindow.cpp:1441-1444` without checking the return value. If removal fails because of permissions, a busy file, or filesystem errors, the UI still disables the button, repopulates the tree, and presents the item as if it was restored/deleted. Fixed: now checks `QFile::remove()`'s return value and shows a `QMessageBox::critical()` with `errorString()` instead of proceeding as if it succeeded.

### Group D — Code duplication extraction

Items that consolidate repeated logic so future fixes happen in one place.

11. [x] **Extract shared desktop-entry field validation for Add and Edit flows** — validation logic exists only in `AddAppDialog::validateApplicationName()`, `validateCommand()`, `validateComment()`, and `validateIconPath()` (`src/addappdialog.cpp:49-167`), while `MainWindow` has a separate executable-only validator at `src/mainwindow.cpp:1228-1255`. A shared helper would let both creation and editing enforce the same `.desktop` field rules. Fixed: added static `AddAppDialog::containsInvalidDesktopChars()` (used by all four AddAppDialog validators and MainWindow's editor) and static `AddAppDialog::confirmExecutableExists(parent, command)` (used by `AddAppDialog::validateCommand()` and `MainWindow::pushSave_clicked()`); removed `MainWindow::validateExecutable()` entirely.
12. [x] **Extract desktop-entry line mutation helpers** — `changeIcon()`, `changeName()`, `changeCommand()`, `changeComment()`, `changeNotify()`, `changeHide()`, `changeTerminal()`, `addCategory()`, and `delCategory()` each hand-roll string/regex replacement in `src/mainwindow.cpp:870-1187`. Central helpers for setting, removing, and token-updating desktop keys would reduce newline-formatting inconsistencies and make validation fixes easier. Fixed: added `setDesktopEntryValue()`, used by `changeIcon`/`changeName`/`changeCommand`/`changeComment`/`changeNotify`/`changeTerminal` and `changeHide`'s existing-key branch; also fixed two incidental bugs it surfaced (a missing trailing newline in `changeNotify`/`changeTerminal`'s append branch, and an unanchored-substring existence check in `changeComment`/`changeNotify`/`changeHide`/`changeTerminal` that could silently no-op the anchored regex replace). `addCategory()`/`delCategory()` and `changeHide()`'s "insert after Exec=" placement were left as bespoke — they mutate a semicolon-delimited token list with UI sync, not a simple key=value line.

### Group E — Code quality cleanup

Items that reduce tight coupling and exposed mutable state.

13. [x] **Hide UI pointers and mutable model state behind methods** — `MainWindow` exposes hashes, desktop-file caches, and many helper methods as public in `src/mainwindow.h:46-77`, while `AddAppDialog` exposes `ui`, `icon_path`, `lastSavedPath`, and `lastSavedCategories` publicly in `src/addappdialog.h:41-57`. This forces `MainWindow` to reach directly into `add->ui` throughout construction and callbacks (`src/mainwindow.cpp:106-148`, `src/mainwindow.cpp:895-899`, `src/mainwindow.cpp:1184-1194`) instead of using a small dialog API. Fixed: `ui`/`icon_path` are now private; `AddAppDialog` owns its own field-change handlers (icon picker, command browser, Name/Command editingFinished, category add/remove) wired internally in `setConnections()`. `lastSavedPath`/`lastSavedCategories` replaced by an `appSaved(path, categories)` signal; a new `addCategoryRequested()` signal plus a public `addCategoryToList()` method let `MainWindow`'s category picker (which needs the global category list) still drive the Add dialog without touching its widgets directly. `MainWindow`'s shared slots (changeName/changeCommand/changeComment/changeIcon/addCategory/delCategory/selectCommand) dropped their now-dead "add dialog" branches as a result. `MainWindow`'s own public surface (hashes, helper methods) was left as-is — narrowing that is a much larger, separate change with its own risk/benefit tradeoff than this item's literal "AddAppDialog exposes ui/icon_path/lastSaved*" complaint.

### Group G — UI & assets

Items affecting visible desktop metadata, help, icons, and user-facing presentation.

14. [x] **Fix desktop-file validation errors in both desktop sources** — `desktop-file-validate mx-menu-editor.desktop` reports deprecated `Encoding=UTF-8` at `mx-menu-editor.desktop:2` and an invalid custom category `MX-Maintenance` at `mx-menu-editor.desktop:95`. The same values exist in `translations-desktop-file/desktop-in/mx-menu-editor.desktop.in:2` and `:11`; fixes need to keep the installed `.desktop` and translatable template synchronized. Fixed: removed `Encoding=UTF-8`; renamed `MX-Maintenance` to `X-MX-Maintenance` per spec (confirmed with the user this category isn't matched literally by any external menu file in this repo before renaming). `desktop-file-validate` now exits 0.
15. [x] **Remove dead `custom.css` links from license HTML files** — both `help/license.html:5` and root `license.html:5` reference `custom.css`, but neither `help/custom.css` nor root `custom.css` exists. The installed help page therefore points browsers at a missing stylesheet. Fixed: removed the `<link>` tag from both files.
16. [x] **Refresh stale help text and version details** — `help/mx-menu-editor.html:16-17` contains typos/stale wording, `help/mx-menu-editor.html:25` still says menu item names display only in English, and `help/mx-menu-editor.html:32` reports version `20161225`. This conflicts with current localized category handling and the current package version from `debian/changelog`. Fixed: typos corrected, version updated to `25.12.2` (matches `debian/changelog`), and the English-only known-issue line removed now that item 18 fixes that.
17. [x] **Use a resource/theme icon for the Add dialog window icon** — `src/addappdialog.ui:25-27` embeds a long relative path to `/usr/share/pixmaps/mx-menu-editor.png` as the window icon. In source builds, alternate build directories, or clean packaging environments that path is not reliable; use the resource icon or theme icon consistently like the main window. Fixed: now uses `<iconset theme="mx-menu-editor">`, matching `mainwindow.ui`'s existing pattern.
18. [x] **Show localized application names from desktop entries** — `addToTree()` reads only the first plain `Name=` key at `src/mainwindow.cpp:589-606`, while desktop files commonly provide `Name[locale]=...`. The category reader already handles localized names in `getCatName()` (`src/mainwindow.cpp:378-427`), so application rows should use the same locale fallback behavior. Fixed: extracted `getCatName()`'s locale-fallback logic into a shared `pickLocalizedName()` helper and applied it to `addToTree()` too; this required collapsing `addToTree()`'s fragile two-loop early-exit scan into one straight forward scan so a `Name[locale]=` line appearing after `Exec=` isn't missed. Verified against the repo's own `.desktop` file (43 localized names) across several locale fallbacks.

### Group H — Testing

Items that make automated verification meaningful.

19. [x] **Add at least a small CTest suite instead of explicitly skipping tests** — no `enable_testing()`, `add_test()`, or `BUILD_TESTS` hooks are present, and `debian/rules:18-19` overrides `dh_auto_test` with “Skip tests as no test targets are defined.” The parsing and validation logic is a good candidate for small non-GUI unit tests: executable parsing, desktop key mutation, filename sanitization, exact category matching, and failed-save behavior. Fixed: extracted the pure helpers into `src/desktoputils.h/cpp` (`DesktopUtils` namespace) and added `tests/test_desktoputils.cpp` (Qt Test, `QTEST_GUILESS_MAIN`, 37 cases) covering exactly those five candidates. Wired up via `enable_testing()` + `BUILD_TESTS` option in `CMakeLists.txt`; `debian/rules`' `dh_auto_test` override now actually runs `ctest` (verified `qt6-base-dev`, already a Build-Depends, provides `Qt6Test`'s CMake config, so no new Build-Depends needed — worth a final check against the real packaging pipeline before trusting it in production, since that couldn't be fully replicated here).

---

## Summary

- Total findings: 19 open, 0 remaining after this pass.
- Biggest concern (now fixed): the save confirmation path treated failed saves as successful, so navigation/cancel actions could discard unsaved edits after validation or file I/O failure.
- Normal clean debug build passed with GCC/Qt6/Ninja, and with Clang. Desktop metadata validation now passes (`desktop-file-validate` exits 0).
