--- blocked-by: [0005-kde-shortcuts-mechanism, 0009-kde-shortcut-completion] --- ## What to build `dot kde diff`'s broad-scan (the pass that reports *undeclared* drift, not just already-declared entries) currently only walks schema-backed identifiers via `iter_schema_identifiers`. Shortcuts are treated the same as freeform in `cmd_diff` -- checked only when already present in the manifest -- per the code comment at the top of that loop. That comment is overstated for shortcuts: unlike freeform, which genuinely has no enumeration source, shortcuts *are* enumerable via `kglobalaccel`'s `allMainComponents`/`allActionsForComponent`, and `iter_shortcut_identifiers` (added in 0009 for tab-completion) already walks exactly that. Add a second broad-scan pass in `cmd_diff`, after the existing schema-backed one, over `sorted(set(iter_shortcut_identifiers()))`: for each identifier, compare `shortcutKeys` against `defaultShortcutKeys` (the same live/default read already used for declared shortcuts), and tag `declared`/`undeclared` exactly like the schema loop. Remove the shortcuts branch from the manifest-only loop below it (now redundant), leaving that loop for freeform only, since freeform is the only mechanism that still can't be enumerated. Tolerate two failure modes without aborting the whole command: - The enumeration call itself (`allMainComponents`) failing (no live session, no `busctl`) -- print one diagnostic to stderr and skip the shortcuts block entirely, same as any other reported problem in `diff`. - An individual action failing to resolve (`_resolve_shortcut_action_id` raising because its owning app hasn't registered with kglobalaccel this session) -- print that one identifier's error to stderr and continue, matching the schema loop's existing per-identifier tolerance. Update `DIFF_USAGE` to reflect that shortcuts now participate in broad-scan alongside schema-backed settings, leaving only freeform as declared-only. ## Acceptance criteria - [x] `dot kde diff` reports undeclared shortcut drift (a shortcut changed from its packaged default but never `dot kde save`d) without requiring it to be in the manifest first - [x] Already-declared shortcut drift is still reported, tagged `declared`, with no duplicate line from the old manifest-only loop - [x] A shortcut belonging to an app that hasn't registered with kglobalaccel this session produces one stderr diagnostic for that identifier and does not stop the rest of the scan (schema block, other shortcuts, freeform block) from completing - [x] If the `allMainComponents` enumeration itself fails (no `busctl`, no live session), `diff` prints one diagnostic, skips the shortcuts block, and still completes the schema and freeform passes, exiting 0 - [x] Freeform remains declared-only (unchanged) -- only its loop comment and the removed shortcuts branch change - [x] `DIFF_USAGE` text updated to describe shortcuts as broad-scanned - [x] Verified manually against the real session (consistent with the shortcuts mechanism's existing test carve-out, 0005/0009) -- no new automated tests - [x] Full existing test suite still passes unchanged ## Implementation Notes - `cmd_diff` (`commands/kde/kde.py`) gained a second broad-scan pass between the existing schema-backed loop and the manifest-only loop: it walks `sorted(set(iter_shortcut_identifiers()))` (the same enumeration `cmd_complete` already uses), compares `shortcutKeys` against `defaultShortcutKeys` per identifier, and tags `declared`/`undeclared` exactly like the schema loop. - The manifest-only loop below it lost its `shortcuts` branch entirely (`resolve_mechanism` returning `"shortcuts"` now just falls through `if mechanism != "freeform": continue`), since the new broad-scan pass already reports every declared shortcut mismatch -- keeping the old branch would have double-printed them. - Two failure modes, handled at different granularity: `iter_shortcut_identifiers()` itself is wrapped in `try/except (RuntimeError, OSError)` -- a failure there (no live session, missing `busctl`) prints one diagnostic and skips the whole shortcuts block, letting the schema and freeform passes still run. Inside the per-identifier loop, `read_shortcut_value` raising `RuntimeError` (an app that hasn't registered with kglobalaccel this session yet) prints one diagnostic for that identifier and continues, matching the schema loop's existing per-identifier tolerance. - Real-world validation on this machine: manually ran the same enumeration in a throwaway script before implementing, confirming 29 of 278 registered shortcuts differed from default (the Meta+1-9 desktop-switch remap, Meta+Shift+1-9 window-to-desktop binds, and Meta+A/Meta+Shift+A activity switching) -- all 29 were `dot kde save`d into the manifest in the same session as a prerequisite for testing this cleanly. After implementing, `dot kde diff` reported all 30 shortcuts (29 plus the pre-existing `ksmserver.Lock Session`) as `declared` with correct default values, and ~34 unrelated `RuntimeError`s for apps not launched this session (Konsole, Spectacle, Dolphin, etc.) printed to stderr without aborting the scan. Removing one entry (`kwin.Switch to Desktop 1`) from the manifest and re-running confirmed it flips to `undeclared` with the same live/default values, then restoring the manifest flipped it back to `declared` -- confirms both tags work and the manifest was left untouched by `diff` itself (read-only, as documented). - Full test suite re-run after the change: 101/101 pass, unchanged from before this task. No automated tests added for the new pass itself, per the shortcuts mechanism's existing carve-out (0005's Implementation Notes: a live `kglobalaccel` D-Bus session isn't practically substitutable without disproportionate mock infrastructure) -- the existing tests already exercise `dot kde diff` against the real live session and continued to pass with the new pass active, incidentally covering that it doesn't break anything even though it isn't asserting on the new pass's own output.