Skip to content

Commit 39282ad

Browse files
committed
Operation log: alpha "Operation log" UI — View menu, ⌘⌥L, soft dialog, i18n (M7)
Ships the thin alpha surface (requirement 6b), the last build milestone: users can now see their file-operation history and roll operations back, from a menu item and a keyboard shortcut. Debugging/demo quality by design (it may become a sidebar once the agent's decision log converges onto the same timeline), but fully i18n'd, style-guide compliant, and a11y-basic. Frontend (`src/lib/operation-log/`), modeled on the What's-new dialog: - `operation-log-trigger.svelte.ts`: reactive `$state` + `openOperationLog()`/`loadMoreOperations()` over the M4 read API (newest 50, then 50 more; offset is `entries.length` so appends can't dup or desync). Opens even on a read failure so the menu item never feels dead. - `OperationLogDialog.svelte`: soft `ModalDialog` (`dialogId="operation-log"`), one collapsible row per operation, expandable to its per-item rows (lazy-fetched, cached). ALPHA badge via `StatusBadge` + a new `operation-log` entry in `feature-status.json`. - `operation-log-labels.ts`: PURE typed-enum→i18n mapping. The per-op summary ("Moved 214 items") is formatted client-side from `kind` + `itemCount` via an ICU plural key, so it localizes per viewer with a thousands separator — the backend ships no rendered English string. `failed` statuses read "Didn't finish", never "failed". - `$lib/tauri-commands/operation-log.ts`: wrappers unwrapping the two `Result` read commands. Menu + command (`log.operationLog`, App scope): the four places (registry entry, `app-dialog-handlers.ts` handler, `mod.rs` mappings + `MenuItem::with_id` in BOTH `macos.rs` and `linux.rs` under the **View** menu, `menuCommands`) plus the `COMMAND_IDS` id. Shortcut: configurable default **⌘⌥L**. The plan's first pick ⌥⌘O is already bound to `file.showInFinder`, so `L` (for "log") is the mnemonic; Command-then-Option order matches what `formatKeyCombo` emits, so it fires via the in-app JS dispatch (not native-menu-only). Guarded by a collision test and a full-chain dispatch test. i18n: new `operationLog.json` area + the `commands.logOperationLog.*` keys, propagated and translated to all 10 locales (each reusing the term already settled for "operation log" / "roll back" in that language from earlier milestones). `operationLog` added to the message-key-naming allowlist. Tests: trigger paging (append-without-dupes), the dialog component (grouped rows, ALPHA badge, lazy expand), the pure labels (every enum branch), the wrapper unwrap, and an E2E opening the dialog BOTH from the View menu and the ⌘⌥L shortcut with the ALPHA badge asserted. Docs: `operation_log/DETAILS.md` § Alpha UI (scope, client-side summary, shortcut-order rationale, agent-log convergence per D7/naming), the `CLAUDE.md` shipped-line, and a frontend row in `docs/architecture.md`.
1 parent aa4307e commit 39282ad

58 files changed

Lines changed: 3222 additions & 15 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/desktop/src-tauri/src/menu/linux.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ use super::{
1515
EDIT_PASTE_MOVE_ID, ENTER_LICENSE_KEY_ID, FAVORITES_ADD_ID, FILE_COMPRESS_ID, FILE_COPY_ID, FILE_DELETE_ID,
1616
FILE_DELETE_PERMANENTLY_ID, FILE_MOVE_ID, FILE_NEW_FOLDER_ID, FILE_VIEW_ID, GET_INFO_ID, GO_BACK_ID, GO_FORWARD_ID,
1717
GO_LATEST_DOWNLOAD_ID, GO_PARENT_ID, GO_TO_PATH_ID, HELP_SEND_ERROR_REPORT_ID, HELP_SEND_FEEDBACK_ID,
18-
HELP_SHORTCUTS_ID, HELP_WHATS_NEW_ID, MenuItems, NEW_TAB_ID, NEXT_TAB_ID, OPEN_ID, PIN_TAB_MENU_ID, PREV_TAB_ID,
19-
QUEUE_SHOW_ID, QUICK_LOOK_ID, RENAME_ID, REOPEN_CLOSED_TAB_ID, SEARCH_FILES_ID, SELECT_ALL_ID, SELECT_FILES_ID,
20-
SETTINGS_ID, SHOW_HIDDEN_FILES_ID, SHOW_IN_FINDER_ID, SORT_BY_EXTENSION_ID, SORT_BY_MODIFIED_ID, SORT_BY_NAME_ID,
21-
SORT_BY_SIZE_ID, SWAP_PANES_ID, SWITCH_PANE_ID, VIEW_MODE_BRIEF_LEFT_ID, VIEW_MODE_BRIEF_RIGHT_ID,
22-
VIEW_MODE_FULL_LEFT_ID, VIEW_MODE_FULL_RIGHT_ID, ViewMode,
18+
HELP_SHORTCUTS_ID, HELP_WHATS_NEW_ID, MenuItems, NEW_TAB_ID, NEXT_TAB_ID, OPEN_ID, OPERATION_LOG_ID,
19+
PIN_TAB_MENU_ID, PREV_TAB_ID, QUEUE_SHOW_ID, QUICK_LOOK_ID, RENAME_ID, REOPEN_CLOSED_TAB_ID, SEARCH_FILES_ID,
20+
SELECT_ALL_ID, SELECT_FILES_ID, SETTINGS_ID, SHOW_HIDDEN_FILES_ID, SHOW_IN_FINDER_ID, SORT_BY_EXTENSION_ID,
21+
SORT_BY_MODIFIED_ID, SORT_BY_NAME_ID, SORT_BY_SIZE_ID, SWAP_PANES_ID, SWITCH_PANE_ID, VIEW_MODE_BRIEF_LEFT_ID,
22+
VIEW_MODE_BRIEF_RIGHT_ID, VIEW_MODE_FULL_LEFT_ID, VIEW_MODE_FULL_RIGHT_ID, ViewMode,
2323
};
2424

2525
/// Linux menu: builds all menus from scratch, matching the macOS menu structure.
@@ -242,6 +242,9 @@ pub(crate) fn build_menu_linux<R: Runtime>(
242242
true,
243243
Some("Cmd+Shift+P"),
244244
)?;
245+
// Default ⌘⌥L (Cmd+Opt+L). ⌥⌘O — the plan's first choice — is taken by "Reveal in file manager".
246+
// The accelerator syncs from the `log.operationLog` registry shortcut; this is the initial label.
247+
let operation_log_item = MenuItem::with_id(app, OPERATION_LOG_ID, "&Operation log", true, Some("Cmd+Alt+L"))?;
245248

246249
let view_submenu = Submenu::with_items(
247250
app,
@@ -259,6 +262,7 @@ pub(crate) fn build_menu_linux<R: Runtime>(
259262
&swap_panes_item,
260263
&PredefinedMenuItem::separator(app)?,
261264
&command_palette_item,
265+
&operation_log_item,
262266
],
263267
)?;
264268
menu.append(&view_submenu)?;
@@ -417,10 +421,11 @@ pub(crate) fn build_menu_linux<R: Runtime>(
417421
register_item(&mut items, DESELECT_FILES_ID, &deselect_files_item, &select_menu, 4);
418422

419423
// View menu positions: left_pane_submenu(0), right_pane_submenu(1), sep(2), hidden(3),
420-
// sort(4), zoom(5), sep(6), switch(7), swap(8), sep(9), palette(10)
424+
// sort(4), zoom(5), sep(6), switch(7), swap(8), sep(9), palette(10), operation_log(11)
421425
register_item(&mut items, SWITCH_PANE_ID, &switch_pane_item, &view_submenu, 7);
422426
register_item(&mut items, SWAP_PANES_ID, &swap_panes_item, &view_submenu, 8);
423427
register_item(&mut items, COMMAND_PALETTE_ID, &command_palette_item, &view_submenu, 10);
428+
register_item(&mut items, OPERATION_LOG_ID, &operation_log_item, &view_submenu, 11);
424429

425430
// Sort by submenu positions: name(0), extension(1), modified(2), size(3), created(4),
426431
// sep(5), ascending(6), descending(7).

apps/desktop/src-tauri/src/menu/macos.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use super::{
1919
FILE_DELETE_PERMANENTLY_ID, FILE_MOVE_ID, FILE_NEW_FOLDER_ID, FILE_VIEW_ID, GET_INFO_ID, GO_BACK_ID, GO_FORWARD_ID,
2020
GO_LATEST_DOWNLOAD_ID, GO_PARENT_ID, GO_TO_PATH_ID, HELP_SEND_ERROR_REPORT_ID, HELP_SEND_FEEDBACK_ID,
2121
HELP_SHORTCUTS_ID, HELP_WHATS_NEW_ID, MenuItems, NEW_TAB_ID, NEXT_TAB_ID, OPEN_ID, OPEN_ONBOARDING_ID,
22-
PIN_TAB_MENU_ID, PREV_TAB_ID, QUEUE_SHOW_ID, QUICK_LOOK_ID, RENAME_ID, REOPEN_CLOSED_TAB_ID, SEARCH_FILES_ID,
23-
SELECT_ALL_ID, SELECT_FILES_ID, SETTINGS_ID, SHOW_HIDDEN_FILES_ID, SHOW_IN_FINDER_ID, SORT_BY_EXTENSION_ID,
24-
SORT_BY_MODIFIED_ID, SORT_BY_NAME_ID, SORT_BY_SIZE_ID, SWAP_PANES_ID, SWITCH_PANE_ID, VIEW_MODE_BRIEF_LEFT_ID,
25-
VIEW_MODE_BRIEF_RIGHT_ID, VIEW_MODE_FULL_LEFT_ID, VIEW_MODE_FULL_RIGHT_ID, ViewMode,
22+
OPERATION_LOG_ID, PIN_TAB_MENU_ID, PREV_TAB_ID, QUEUE_SHOW_ID, QUICK_LOOK_ID, RENAME_ID, REOPEN_CLOSED_TAB_ID,
23+
SEARCH_FILES_ID, SELECT_ALL_ID, SELECT_FILES_ID, SETTINGS_ID, SHOW_HIDDEN_FILES_ID, SHOW_IN_FINDER_ID,
24+
SORT_BY_EXTENSION_ID, SORT_BY_MODIFIED_ID, SORT_BY_NAME_ID, SORT_BY_SIZE_ID, SWAP_PANES_ID, SWITCH_PANE_ID,
25+
VIEW_MODE_BRIEF_LEFT_ID, VIEW_MODE_BRIEF_RIGHT_ID, VIEW_MODE_FULL_LEFT_ID, VIEW_MODE_FULL_RIGHT_ID, ViewMode,
2626
};
2727

2828
pub(crate) fn build_menu_macos<R: Runtime>(
@@ -269,6 +269,9 @@ pub(crate) fn build_menu_macos<R: Runtime>(
269269
let swap_panes_item = MenuItem::with_id(app, SWAP_PANES_ID, "Swap panes", true, Some("Cmd+U"))?;
270270
let command_palette_item =
271271
MenuItem::with_id(app, COMMAND_PALETTE_ID, "Command palette...", true, Some("Cmd+Shift+P"))?;
272+
// Default ⌘⌥L (Cmd+Opt+L). ⌥⌘O — the plan's first choice — is taken by "Show in Finder".
273+
// The accelerator syncs from the `log.operationLog` registry shortcut; this is the initial label.
274+
let operation_log_item = MenuItem::with_id(app, OPERATION_LOG_ID, "Operation log", true, Some("Cmd+Alt+L"))?;
272275

273276
let view_submenu = Submenu::with_items(
274277
app,
@@ -286,6 +289,7 @@ pub(crate) fn build_menu_macos<R: Runtime>(
286289
&swap_panes_item,
287290
&PredefinedMenuItem::separator(app)?,
288291
&command_palette_item,
292+
&operation_log_item,
289293
],
290294
)?;
291295
menu.append(&view_submenu)?;
@@ -443,10 +447,11 @@ pub(crate) fn build_menu_macos<R: Runtime>(
443447
register_item(&mut items, DESELECT_FILES_ID, &deselect_files_item, &select_menu, 4);
444448

445449
// View menu positions: full(0), brief(1), sep(2), hidden(3), sort(4), zoom(5), sep(6),
446-
// switch(7), swap(8), sep(9), command(10)
450+
// switch(7), swap(8), sep(9), command(10), operation_log(11)
447451
register_item(&mut items, SWITCH_PANE_ID, &switch_pane_item, &view_submenu, 7);
448452
register_item(&mut items, SWAP_PANES_ID, &swap_panes_item, &view_submenu, 8);
449453
register_item(&mut items, COMMAND_PALETTE_ID, &command_palette_item, &view_submenu, 10);
454+
register_item(&mut items, OPERATION_LOG_ID, &operation_log_item, &view_submenu, 11);
450455

451456
// Sort by submenu positions: name(0), extension(1), modified(2), size(3), created(4),
452457
// sep(5), ascending(6), descending(7). Only the four shortcut-bound columns are

apps/desktop/src-tauri/src/menu/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ pub const QUEUE_SHOW_ID: &str = "queue_show";
239239
/// Menu item ID for "What's new" (opens the changelog popup, under the Help menu).
240240
pub const HELP_WHATS_NEW_ID: &str = "help_whats_new";
241241

242+
/// Menu item ID for "Operation log" (opens the alpha operation-log dialog, under the View menu).
243+
pub const OPERATION_LOG_ID: &str = "operation_log";
244+
242245
/// Menu item ID for "Check for updates…" (under the Cmdr / Help menu).
243246
pub const CHECK_FOR_UPDATES_ID: &str = "check_for_updates";
244247

@@ -317,6 +320,7 @@ pub fn menu_id_to_command(menu_id: &str) -> Option<(&'static str, CommandScope)>
317320
HELP_SHORTCUTS_ID => Some(("help.openShortcuts", CommandScope::App)),
318321
QUEUE_SHOW_ID => Some(("queue.show", CommandScope::App)),
319322
HELP_WHATS_NEW_ID => Some(("help.whatsNew", CommandScope::App)),
323+
OPERATION_LOG_ID => Some(("log.operationLog", CommandScope::App)),
320324
HELP_SEND_ERROR_REPORT_ID => Some(("help.sendErrorReport", CommandScope::App)),
321325
HELP_SEND_FEEDBACK_ID => Some(("feedback.send", CommandScope::App)),
322326
CHECK_FOR_UPDATES_ID => Some(("app.checkForUpdates", CommandScope::App)),
@@ -418,6 +422,7 @@ pub fn command_id_to_menu_id(command_id: &str) -> Option<&'static str> {
418422
"help.openShortcuts" => Some(HELP_SHORTCUTS_ID),
419423
"queue.show" => Some(QUEUE_SHOW_ID),
420424
"help.whatsNew" => Some(HELP_WHATS_NEW_ID),
425+
"log.operationLog" => Some(OPERATION_LOG_ID),
421426
"help.sendErrorReport" => Some(HELP_SEND_ERROR_REPORT_ID),
422427
"feedback.send" => Some(HELP_SEND_FEEDBACK_ID),
423428
"app.checkForUpdates" => Some(CHECK_FOR_UPDATES_ID),
@@ -789,6 +794,7 @@ mod tests {
789794
"help.openShortcuts",
790795
"queue.show",
791796
"help.whatsNew",
797+
"log.operationLog",
792798
"help.sendErrorReport",
793799
"feedback.send",
794800
"app.checkForUpdates",

apps/desktop/src-tauri/src/operation_log/CLAUDE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ future undo. **The app's first durable DB** (`operation-log.db` in the app data
55
on-disk store here is a disposable cache. Full design + rationale: [DETAILS.md](DETAILS.md). Plan:
66
[`docs/specs/operation-log-plan.md`](../../../../../docs/specs/operation-log-plan.md).
77

8-
**Shipped: durable store (M1), capture (M2), rollback engine (M3), read/search API + retention (M4), MCP tools (M5, in
9-
`mcp/executor/operation_log.rs`).** The UI (M6/M7) builds on the read side.
8+
**Shipped end to end (M1–M7): durable store (M1), capture (M2), rollback engine (M3), read/search API + retention (M4),
9+
MCP tools (M5, in `mcp/executor/operation_log.rs`), retention settings + Debug panel (M6), and the alpha "Operation log"
10+
dialog (M7).** The UI is frontend-only over the M4 read API: Debug panel in `routes/debug/DebugOperationLogPanel.svelte`,
11+
alpha dialog in `src/lib/operation-log/` (see [DETAILS.md](DETAILS.md) § Alpha UI).
1012

1113
## Module map
1214

apps/desktop/src-tauri/src/operation_log/DETAILS.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,49 @@ handlers (`mcp/executor/operation_log.rs`) do the same off the MCP task.
387387
carry interned prefixes, so `get_operation` returns `OperationItemView`s with `source_path`/`dest_path` reconstructed —
388388
the frontend never sees a `dir_id`.
389389

390+
## Alpha UI (M7) — the "Operation log" dialog
391+
392+
The thin alpha surface (requirement 6b): a menu- and shortcut-triggered soft dialog listing recent operations, newest
393+
first, each expandable to its per-item rows. **Debugging/demo quality by design** (it may become a sidebar later), so it
394+
is deliberately un-gold-plated — but i18n, style-guide copy, and a11y basics are not optional. It lives entirely in the
395+
frontend (`apps/desktop/src/lib/operation-log/`) over the existing M4 read API; no new backend command.
396+
397+
- **Modeled on the What's-new dialog** (the menu-triggered, list-rendering, App-scoped soft-dialog template):
398+
- `operation-log-trigger.svelte.ts` — the reactive `$state({ open, entries, loading, loadError, hasMore, loadingMore })`
399+
plus `openOperationLog()` (fetch page 1 = 50 via `getRecentOperationLogEntries`), `loadMoreOperations()` (append the
400+
next 50), `closeOperationLog()`. **Paging offset is `entries.length`** (one source of truth), so an append can't
401+
desync or duplicate. Opens even on a read failure (`loadError`) so the menu item never feels dead.
402+
- `OperationLogDialog.svelte` — the `ModalDialog` (`dialogId="operation-log"`, registered in `dialog-registry.ts`),
403+
mounted in `routes/(main)/+page.svelte` against `operationLogState.open` (and added to that page's
404+
`isModalDialogOpen()` guard). One collapsible row per operation; expanding lazily fetches its items via
405+
`getOperationLogDetail` (cached per `opId` for the dialog's lifetime).
406+
- `operation-log-labels.ts` — PURE label mapping. **Every label derives from a TYPED enum, never a backend-rendered
407+
string** (`no-string-matching`): the per-operation summary ("Moved 214 items") is formatted client-side from
408+
`kind` + `archiveSubkind` + `itemCount` via an ICU plural key, so it localizes per viewer and shows a thousands
409+
separator (Finding 3 — the backend ships no rendered English summary for the dialog). Status, kind, initiator, and
410+
item-outcome labels each map their enum to a catalog key with an exhaustive switch (a new variant is a compile error
411+
until mapped). Style-guide: `failed` statuses/outcomes read "Didn't finish", never "failed".
412+
- IPC wrappers: `$lib/tauri-commands/operation-log.ts` unwraps the two `Result`-returning read commands (the Debug
413+
panel calls the raw bindings directly — it's dev-only and bindings-import-exempt).
414+
- **ALPHA badge** via `StatusBadge` keyed off the `operation-log` entry in the repo-root `feature-status.json`
415+
(`"status": "alpha"`).
416+
- **Menu + command wiring (the "four places", plus the `COMMAND_IDS` id):** command `log.operationLog` (App scope) — the
417+
`command-registry.ts` entry, the `app-dialog-handlers.ts` handler (`openOperationLog()`), the `mod.rs`
418+
`menu_id_to_command`/`command_id_to_menu_id` mappings for `OPERATION_LOG_ID` + the `MenuItem::with_id` in BOTH
419+
`macos.rs` and `linux.rs` (**View menu**, after the command palette), and `menuCommands` in `shortcuts-store.ts`.
420+
- **Default shortcut ⌘⌥L** (configurable via the registry `shortcuts` field). The plan's first pick ⌥⌘O is already bound
421+
to `file.showInFinder`, so `L` (for "log") is the mnemonic. The modifier order is **Command-then-Option (⌘⌥)**, not the
422+
Apple display order (⌥⌘): `formatKeyCombo` emits ⌘⌥ and the JS keydown dispatch (`shortcut-dispatch.ts`, keyed off
423+
`getEffectiveShortcuts``toPlatformShortcut`) matches that, so the shortcut fires via the in-app dispatch on macOS
424+
and Linux — an ⌥⌘-order default would fire only via the native menu accelerator. Guarded by a collision test in
425+
`command-registry.test.ts`.
426+
427+
**Convergence with the agent log (Naming section / D7).** The dialog is the mutation half of a future unified timeline:
428+
when the in-app agent ships, its decision log (`agent_log`) joins the same user-facing surface, and whether that merged
429+
surface is later *labelled* "Activity" is a UI-copy call for then. "Operation" stays the entity/row name; "action" stays
430+
reserved for the agent spec's navigation/intent stream. The dialog is intentionally throwaway so that convergence can
431+
reshape it (likely into a sidebar) without sunk cost.
432+
390433
## Retention (D9) — prune by age + size, GC dirs, reclaim
391434

392435
Retention runs the writer's `Prune` on startup and on a periodic timer (`retention.rs`, every 6 h), with the age/size

apps/desktop/src/lib/commands/command-ids.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export const COMMAND_IDS = [
4646
'feedback.send',
4747
'queue.show',
4848

49+
// Operation log (alpha)
50+
'log.operationLog',
51+
4952
// Search
5053
'search.open',
5154

apps/desktop/src/lib/commands/command-registry.parity.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const EXPECTED_NAMES: Record<string, string> = {
3434
'help.sendErrorReport': 'Send error report…',
3535
'help.whatsNew': "What's new",
3636
'feedback.send': 'Send feedback',
37+
'log.operationLog': 'Operation log',
3738
'search.open': 'Search files',
3839
'nav.goToPath': 'Go to path…',
3940
'favorites.add': 'Add to favorites',
@@ -154,6 +155,7 @@ const EXPECTED_DESCRIPTIONS: Record<string, string | undefined> = {
154155
'help.sendErrorReport': 'Send Cmdr logs to the team to help fix something that went wrong',
155156
'help.whatsNew': 'See what changed in the latest releases of Cmdr',
156157
'feedback.send': 'Tell the maker of Cmdr what you think: ideas, wishes, anything',
158+
'log.operationLog': 'See a history of your file operations, and roll them back',
157159
'nav.goToPath': 'Jump the focused pane to a typed, pasted, or recent path.',
158160
'favorites.add': "Add the focused pane's current folder to the switcher's Favorites.",
159161
'downloads.goToLatest': 'Open ~/Downloads and select the most recent file.',

apps/desktop/src/lib/commands/command-registry.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const EXPECTED_PALETTE_IDS: readonly CommandId[] = [
2929
'help.sendErrorReport',
3030
'help.whatsNew',
3131
'feedback.send',
32+
'log.operationLog',
3233
'search.open',
3334
'nav.goToPath',
3435
'favorites.add',
@@ -319,3 +320,22 @@ describe('fixedKey flag', () => {
319320
}
320321
})
321322
})
323+
324+
describe('operation-log shortcut binding', () => {
325+
// M7: the alpha "Operation log" command opens the dialog from the View menu and
326+
// via a configurable default shortcut. The plan's first pick (⌥⌘O) is taken by
327+
// `file.showInFinder`, so the default is ⌘⌥L. Guard against a silent double-bind.
328+
it('binds log.operationLog to exactly ⌘⌥L', () => {
329+
const cmd = commands.find((c) => c.id === 'log.operationLog')
330+
expect(cmd, 'log.operationLog must be registered').toBeDefined()
331+
expect(cmd?.shortcuts).toEqual(['⌘⌥L'])
332+
})
333+
334+
it('does not double-bind ⌘⌥L (its default) or ⌥⌘O (Show in Finder, the plan pick it avoided)', () => {
335+
const claimants = (shortcut: string): CommandId[] =>
336+
commands.filter((c) => c.shortcuts.includes(shortcut)).map((c) => c.id)
337+
338+
expect(claimants('⌘⌥L')).toEqual(['log.operationLog'])
339+
expect(claimants('⌥⌘O')).toEqual(['file.showInFinder'])
340+
})
341+
})

apps/desktop/src/lib/commands/command-registry.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,19 @@ const commandSources: CommandSource[] = [
190190
shortcuts: [],
191191
descriptionKey: 'commands.feedbackSend.description',
192192
},
193+
{
194+
// Default ⌘⌥L. The plan's first pick ⌥⌘O is already bound to `file.showInFinder`,
195+
// so L (for "log") is the mnemonic. Modifier order is Command-then-Option (⌘⌥),
196+
// matching what `formatKeyCombo` emits, so the JS keydown dispatch fires it on
197+
// macOS in addition to the native menu accelerator (⌥⌘-order defaults are
198+
// native-menu-only). See `shortcut-dispatch.ts`.
199+
id: 'log.operationLog',
200+
nameKey: 'commands.logOperationLog.label',
201+
scope: 'App',
202+
showInPalette: true,
203+
shortcuts: ['⌘⌥L'],
204+
descriptionKey: 'commands.logOperationLog.description',
205+
},
193206

194207
// ============================================================================
195208
// Main window - Search

apps/desktop/src/lib/intl/keys.gen.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ export type MessageKey =
184184
| 'commands.helpSendErrorReport.label'
185185
| 'commands.helpWhatsNew.description'
186186
| 'commands.helpWhatsNew.label'
187+
| 'commands.logOperationLog.description'
188+
| 'commands.logOperationLog.label'
187189
| 'commands.navBack.label'
188190
| 'commands.navDown.label'
189191
| 'commands.navEnd.label'
@@ -1520,6 +1522,42 @@ export type MessageKey =
15201522
| 'onboarding.wizard.restart'
15211523
| 'onboarding.wizard.stepProgress'
15221524
| 'onboarding.wizard.title'
1525+
| 'operationLog.dialog.close'
1526+
| 'operationLog.dialog.empty'
1527+
| 'operationLog.dialog.itemsError'
1528+
| 'operationLog.dialog.loadError'
1529+
| 'operationLog.dialog.loadMore'
1530+
| 'operationLog.dialog.loading'
1531+
| 'operationLog.dialog.moreItems'
1532+
| 'operationLog.dialog.noItems'
1533+
| 'operationLog.dialog.title'
1534+
| 'operationLog.initiator.agent'
1535+
| 'operationLog.initiator.aiClient'
1536+
| 'operationLog.initiator.user'
1537+
| 'operationLog.outcome.done'
1538+
| 'operationLog.outcome.failed'
1539+
| 'operationLog.outcome.rolledBack'
1540+
| 'operationLog.outcome.skipped'
1541+
| 'operationLog.rollback.notRollbackable'
1542+
| 'operationLog.rollback.partiallyRolledBack'
1543+
| 'operationLog.rollback.rollbackable'
1544+
| 'operationLog.rollback.rolledBack'
1545+
| 'operationLog.rollback.rollingBack'
1546+
| 'operationLog.status.canceled'
1547+
| 'operationLog.status.done'
1548+
| 'operationLog.status.failed'
1549+
| 'operationLog.status.queued'
1550+
| 'operationLog.status.running'
1551+
| 'operationLog.summary.archiveEdit'
1552+
| 'operationLog.summary.archiveExtract'
1553+
| 'operationLog.summary.compress'
1554+
| 'operationLog.summary.copy'
1555+
| 'operationLog.summary.createFile'
1556+
| 'operationLog.summary.createFolder'
1557+
| 'operationLog.summary.delete'
1558+
| 'operationLog.summary.move'
1559+
| 'operationLog.summary.rename'
1560+
| 'operationLog.summary.trash'
15231561
| 'queryUi.age.days'
15241562
| 'queryUi.age.hours'
15251563
| 'queryUi.age.justNow'

0 commit comments

Comments
 (0)