Skip to content

feat(shortcuts): keys for the two column verbs, and a better rule for why - #653

Merged
PathGao merged 1 commit into
masterfrom
feat/keys-for-the-column-verbs
Aug 12, 2026
Merged

feat(shortcuts): keys for the two column verbs, and a better rule for why#653
PathGao merged 1 commit into
masterfrom
feat/keys-for-the-column-verbs

Conversation

@PathGao

@PathGao PathGao commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

#652 left both column verbs palette-only. Reaching one is Cmd+P, then typing the command's name, then Enter — three steps, and you have to remember what it is called.

verb chord why it earns one
Insert Column Mod+Shift+C mid-frequency, fiddly by hand
Delete Column Mod+Shift+Backspace no manual fallback at all

The rule changed, so the old one is deleted rather than parked

TABLE_VERBS_WITHOUT_A_CHORD said, in effect, destructive table verbs get no keys, because a mis-fire costs too much. That cannot have been the rule being applied — Delete Column destroys strictly more than Delete Row, and it is the one getting a key.

The distinction that actually holds is whether the user can already do it without us:

Delete Row      put the caret on the line, select it, delete       → trivial by hand
Delete Column   edit the pipes on every row without slipping once  → effectively impossible

So Delete Column earns a key because there is no fallback; Delete Row does not, because a key buys nothing over what its user can already do. TABLE_VERBS_WITHOUT_A_CHORD is down to that one row, and the WHY THE DESTRUCTIVE PAIR HAS NO CHORD block in Editor.svelte was rewritten to match — including that its original hazard is gone in fact, since Mod+Shift+K is unbound and the Mod+K namespace no longer exists.

An "the asymmetry is itself a protection" paragraph added in an earlier round was removed, not amended: it argued the rule that has now been replaced, and leaving it would have been two rules standing side by side.

Why Backspace, when Mod+Shift+D is also free

Physical non-adjacency. D sits next to C; one slip would turn "insert a column" into "delete a column". A destructive verb should not neighbour its constructive counterpart — the same shape as the original complaint that Mod+K Shift+R sat one slip from Monaco's delete-line. Backspace is across the keyboard and already means "remove" everywhere.

That Mod+Shift+D is free is stated at the binding, so the next reader sees it was rejected deliberately rather than overlooked. Also noted there: the new chord's nearest neighbour is Mod+Backspace (deleteAllLeft), one modifier away — an ordinary undoable edit rather than a structural one, so it does not disqualify the chord, but "one slip from" is the exact hazard class this rework is about and it belongs on the record.

Two of my premises were wrong

I claimed Mod+Shift+Delete was taken by clipboard.js and Mod+Shift+Y by editorExtensions.js. Both are free on all three platforms. clipboard.js binds Shift+Delete with no Mod — and per its own header binds nothing at all in a browser, since isNative is false in a WebView. Ctrl+Y is redo on Windows and Linux, but without Shift.

So Backspace was not forced by elimination. It stands on the adjacency argument above, which is the reason that was actually being applied.

Mod+Alt+C stays recorded

Above DELIBERATE_OVERRIDES, where the next chord hunt starts. toggleFindCaseSensitive is a registerEditorCommand, not a registerEditorAction, so it has no command palette entry — taking its chord would leave it reachable only by clicking the Aa button, breaking the contract every override row rests on.

Extended past that one command: toggleFindWholeWord, toggleFindRegex and toggleFindInSelection are registered identically and are disqualified for the same reason, so nobody has to work through them one at a time.

On coverage

No new test() was added, and none was needed: the registry↔editor contract test is parameterised over SHORTCUTS, so adding a row is adding per-platform coverage. That is what produced the red.

macOS: the registry says table-insert-column has a shortcut, but
Editor.svelte registers no keybinding for that action id
table-insert-column has no chord, so the panel must not claim one

Same pair for table-delete-column.

npm test 903 · vitest 357 · check 0 errors.

🤖 Generated with Claude Code

… why

Palette-only was the honest answer to the `Mod+Alt+C` collision and the
wrong answer for the user. Reaching Insert Column meant `Mod+P`, typing
the command's name, then Enter — three steps, and you have to remember
what it is called, while both hands are in the middle of building a
table.

  Insert Column   Mod+Shift+C
  Delete Column   Mod+Shift+Backspace

THE RULE THAT DECIDES WHICH VERB GETS A KEY HAS CHANGED, and this is the
part worth reading. `TABLE_VERBS_WITHOUT_A_CHORD` used to say destructive
table verbs get no keys because a mis-fire costs too much. Delete Column
destroys more than Delete Row does and is getting a key here, so that
rule was not the one being applied. It is deleted rather than left
sitting beside its replacement.

What actually decides it is how hard the edit is BY HAND:

  Insert Row      Mod+Enter / Mod+Shift+Enter, Tab at the last cell   frequent
  Insert Column   Mod+Shift+C            mid-frequency, fiddly by hand
  Delete Column   Mod+Shift+Backspace    no manual fallback at all
  Delete Row      palette only           trivial by hand

Deleting a row is putting the caret on the line, selecting it, deleting.
Deleting a column is editing the pipes on every row without slipping
once. The first needs no chord; the second has no other practical route.
So the test for a new row in that table is now "can the user already do
this without us", not "is it dangerous". The old hazard is gone in fact
too: `Mod+Shift+K` is unbound on this branch and the `Mod+K` namespace no
longer exists, so the near-neighbour that started this is not there.

WHY BACKSPACE AND NOT A LETTER. `Mod+Shift+D` is free, but D is the
physical neighbour of C — one slip would turn "insert a column" into
"delete a column". A destructive verb does not go next to its
constructive counterpart; that is the same defect as the old
`Mod+K Shift+R` sitting one slip from Monaco's delete-line. Backspace is
across the keyboard and already means "remove", so it needs no mnemonic.

VERIFIED, NOT ASSUMED. Monaco's real keybinding registry dumped per
platform under jsdom says `Shift+Meta+C` / `Ctrl+Shift+C` and
`Shift+Meta+Backspace` / `Ctrl+Shift+Backspace` are unclaimed on all
three, as complete chords and as chord prefixes. The only Monaco chords
ending in C anywhere are `Alt+Meta+C` / `Alt+C`
(toggleFindCaseSensitive) and `Mod+K Mod+C` (addCommentLine, behind a
prefix this branch already ended); the nearest neighbour of the Backspace
chord is `Mod+Backspace` (`deleteAllLeft`), an ordinary undoable edit.
Also checked and free: the app's registry, every `addAction` and bare
`addCommand` in `Editor.svelte`, the document handler in
`MarkdownViewer.svelte` (which handles no Backspace or Delete at all),
and the Rust menu, still claiming only `CmdOrCtrl+,` and `CmdOrCtrl+Q`.
Nothing is taken from anyone, so no DELIBERATE_OVERRIDES row is needed.

`Mod+Alt+C` IS NOW RECORDED where the next chord hunt happens, above
DELIBERATE_OVERRIDES: it is `toggleFindCaseSensitive`, registered with
`registerEditorCommand` rather than `registerEditorAction`, and only the
latter contributes a `MenuId.CommandPalette` entry — so it has no palette
fallback and can never be allow-listed, whatever the argument. Its
`kbExpr` is a bare `EditorContextKeys.focus`, so it is live whenever the
editor has focus, not only while the find widget is open. The same
disqualification applies to `toggleFindWholeWord`, `toggleFindRegex` and
`toggleFindInSelection`, registered identically, so the next person does
not work through them one at a time.

Tests: 903 pass, vitest 357, check 0 errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao merged commit ecca6cd into master Aug 12, 2026
4 checks passed
@PathGao
PathGao deleted the feat/keys-for-the-column-verbs branch August 12, 2026 17:12
PathGao added a commit that referenced this pull request Aug 12, 2026
…660)

* docs: bring the release runbook and the syntax reference up to 2.7.4

Four claims in the docs describe behaviour that PRs since 2.7.3 changed, and
two features shipped without reaching the file that documents what Markpad
can do.

RELEASING.md
- snapcraft.yaml no longer builds the app; it unpacks the release's own .deb
  (#579), so the `npm ci` note and the `rust-deps` troubleshooting row both
  described a file that no longer exists in that shape (#577).
- The .deb/.rpm coverage note still said *Check for Updates…* offers an
  update and then fails to install it. #573 asks `self_update_supported`
  first, so it says where updates come from instead.
- Adds the AppImage checks (#584, #658) as their own troubleshooting row, and
  notes that release builds are serialized (#612).

README, README.zh-CN
- The same pre-#573 claim, in both languages. The Chinese one also told
  .deb/.rpm users to update "through their distribution channels", which is
  the sentence #566 removed from the English one — there is no apt or dnf
  repository.

snapcraft.yaml
- The comment pointed at .github/workflows/test_snap.yml, deleted in #601.

samples/markdown-syntax{,.zh-CN}.md
- Lists: Enter continues the marker, Tab changes level, Enter on an empty
  item leaves the list (#636), and the three list chords (#652).
- Tables: cell, row and column keys, why deleting a row has no chord (#645,
  #653).
- Links: Ctrl/Cmd+K (#652).

npm test 904 pass, vitest 365 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(syntax): drop two design arguments that belong in the PRs, not here

The syntax reference carries no issue numbers, no history and no defence of
a decision anywhere in its 500 lines; where it does give a reason -- the
three deliberate incompatibilities -- the reason tells the reader what to
write instead. Two sentences I added argued for the design at the reader:
that four other editors use Mod+K, and why deleting a row has no chord while
deleting a column does. Neither changes anything the reader would type.

What is left of the second is the part that was actually useful: to delete a
row, delete its line.

* docs(readme): drop the only issue number a reader ever sees

#570 was the last issue number in either README, and it was one I added a
few days ago. It is a closed bug report, and after #573 the sentence it was
attached to describes ordinary behaviour -- so a reader asking "how does my
.deb update" now clicks through to a fault that no longer exists.

The half-sentence in front of it went for the same reason: "rather than
offering one it cannot install" describes what the app used to do. What is
left is what the reader needs -- .deb and .rpm are one-time installs, there
is no apt or dnf repository, and Check for Updates says so.

Both READMEs keep the Report a Bug link, which is the only issue link either
of them had before.

* docs(releasing): cut the post-mortems back out of the runbook

This file is what you read on the day you cut a release. Over three days in
August I turned it into a runbook with post-mortems threaded through it:
which run pushed Chocolatey 2.7.2 at 15:37, that the snap served 2.6.11 for
three months and six versions, which two of the three v2.7.2 attempts the
AppImage strip failed in, how the 2.7.2/2.7.3 lock skew was found. All true,
none of it changes what you do next, and it sits between you and step 5
while a build is running.

Test applied to each one: can it change an action taken on release day.

Gone: the "Why package managers publish after the release" section entirely
-- its one operational sentence (workflow_dispatch works against a published
tag, the only way to exercise snapcraft.yaml) moves into step 7, where you
would need it. The failure histories in three troubleshooting rows, keeping
the symptom and the fix. The placeholder-pubkey era. Two design defences.

Kept: reasons that stop you doing the wrong thing -- why not to create
anything at alecdotdev/Markpad, why the Cargo.lock bump is the one that gets
forgotten. A prohibition without its reason gets deleted as superstition by
whoever comes next.

1957 -> 1563 words. The version before I started was 883.

---------

Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant