Skip to content

Keyboard and mouse

Everything on this page is the desktop app. Android has none of it: the row menus and the drag gesture ride on composition locals that :app-android leaves at their disabled defaults. The design is ADR 0003; the shell around it is Desktop shell.

Every window shortcut is one row in CADENCE_SHORTCUTS in Shortcuts.kt. The same list drives both the dispatcher (onKeyEvent in Main.kt) and the cheat sheet (ShortcutSheet.kt), so the two cannot disagree. Primary is Ctrl on Linux and Windows and Cmd on macOS. A shortcut matches only with exactly its modifiers held, on key-down — except ?, which matches the character typed whatever Shift the layout needs for it (Shift+ß on German QWERTZ).

None of them fire while one of the shell’s modals is open — the quick-add sheet, the command palette, the cheat sheet or a project dialog. A key the modal leaves unconsumed still reaches the window, and without the gate Esc in quick add navigated the list behind it back and x ticked off the row selected there. CadenceDesktopApp reports whether one is up, and onKeyEvent returns early while it is.

Besides the cheat sheet, every place below reads its keys from the same table through keysFor:

Where What
Icon buttons — sidebar header and rail, Search on Today, Settings on Projects, the refresh button Hover tooltip: the button’s name and its keys
Command palette The keys beside each command that has them
Task row right-click menu The selected-row key beside Open, Mark done, Delete and the four due-date items
Empty Today and Inbox “Press Ctrl+N to add a task.”
Sidebar header and rail A keyboard button that opens the cheat sheet

The controls in :ui ask through LocalShortcutHints (ShortcutHints.kt); Android provides none, so none of these are drawn there.

Keys Action
Primary + N New task (opens quick-add)
Primary + K Command palette
Primary + F Search
Primary + , Settings
Primary + B Show or hide the sidebar (collapses it to an icon rail)
Primary + R Sync now — does nothing when signed out
Primary + Z Undo the pending delete
? (any layout: Shift + / on US, Shift + ß on German) Keyboard shortcuts cheat sheet — also the keyboard button in the sidebar
Keys Action
Alt + ← (macOS ⌥ + ←) Back
Alt + → (macOS ⌥ + →) Forward
Esc Back (closes the current detail)
Primary + 1 Today
Primary + 2 Upcoming
Primary + 3 Inbox
Primary + 4 Projects

Primary + 1…4 switch views (DesktopNavigator.switchTo); Search and Settings push onto the back stack (go).

Bare keys that act on the row the keyboard is on (RowSelectionState). They reach the window only when no focused field has taken them first, so typing in the quick-add field never completes a task. A key with no row selected is not consumed.

Keys Action
J or ↓ Select the next row
K or ↑ Select the previous row
Enter Open the selected task
X Mark done / not done
Delete Delete (with the usual undo)
1 / 2 / 3 / 4 Priority P1 / P2 / P3 / P4
T Due today
M Due tomorrow
W Due in one week (today + 7)
0 Remove the due date

Handled by the palette’s text field (CommandPaletteDialog.kt), not by the window table. Prefixes and scoring are in Quick-add grammar.

Keys Action
↓ / ↑ Move the highlight (wraps around)
Enter or numpad Enter Run the highlighted entry
Esc Close

Opened by a secondary click (Modifier.secondaryClickable inside CadenceContextMenu, ContextMenu.kt). Submenus are pages of the same menu with a back row, not cascades. Items are defined in RowMenus.kt.

On every TaskRow in every list, when RowInteractions.enabled.

Item Submenu / effect
Open opens the task
Mark done / Mark not done toggles completion
Importance ▸ Critical, High, Normal, Low (P1–P4)
Due date ▸ Today, Tomorrow, Next week, No due date
Move to project ▸ Inbox, then every project
Move to section ▸ No section, then the project’s sections — only shown when the task is in a project that has sections
Duplicate copies the task
Copy title puts the title on the clipboard
Delete deletes, with undo
Item Submenu / effect
Open opens the project
Add task here quick-add filed under this project
New subproject only on a top-level project
Edit project the project dialog
Nest under ▸ Top level (enabled for a subproject), then every project it may nest under — only shown when the project has no subprojects of its own
Delete project the delete dialog

Section headings and tag rows have an overflow button rather than a right-click menu:

Where Items
Section heading (project detail) Rename section, Delete section
Tag row (Tags screen, both shells) Edit tag, Delete tag, Move up (not on the first row), Move down (not on the last row)

Desktop only: DragAndDropHost wraps the desktop window and nothing on Android. What a drop means is decided in one pure function, resolveDrop, in DragModel.kt; the gesture, ghost and highlight are in DragAndDrop.kt. A drop that would change nothing, or is not allowed, resolves to DropIntent.Rejected and the target does not light up. When zones overlap, the smallest one under the pointer that accepts the drop wins.

Payload Drag source
TaskDrag any TaskRow
ProjectDrag a project row in the desktop sidebar
TagDrag a tag row on the Tags screen (the desktop passes reorderable = true)
DropTarget Registered on A task dropped here A project dropped here A tag dropped here
IntoProject(projectId) sidebar project rows moves to that project, section cleared nests under it (one level, no cycles, not onto a nested project, not if it has subprojects) rejected
IntoProject(null) sidebar Inbox row moves to the Inbox lifts a subproject back to the top level rejected
IntoSection(projectId, sectionId) section headings and the “No section” heading in a project files under that band, moving projects if needed rejected rejected
OntoDate(date) Upcoming day headings; the sidebar Today row (today) reschedules to that date rejected rejected
IntoTag(tagId) sidebar tag rows adds the label, keeping the others; rejected if already worn rejected rejected
Between(list, index, orderedIds) the gaps between rows (DropCaret) reorders; into a different project/section list it moves and orders in one intent reorders among siblings with the same parent only reorders the tag list

Between lists, by OrderedList variant:

OrderedList Where Meaning
Tasks(projectId, sectionId) Inbox (null), project bands a container: dropping here files the task there
LooseTasks Today order only — the task keeps its project
Projects(parentId) sidebar project tree (null = top level) project order
Sections(projectId) — (declared; no section is draggable yet) section order
Tags Tags screen tag order

The resulting DropIntents are MoveTask, RescheduleTask, TagTask, NestProject, ReorderTasks (optionally carrying a MoveTask), ReorderProjects, ReorderSections, ReorderTags and Rejected. Only root rows get carets: a subtask’s place is its checklist’s.