Skip to content

feat(desktop): per-surface context menus + shortcut interception policy (#40) - #41

Merged
Xuepoo merged 3 commits into
mainfrom
carryctx/web-0039-context-shortcuts
Aug 25, 2026
Merged

Xuepoo merged 3 commits into
mainfrom
carryctx/web-0039-context-shortcuts

Conversation

@Xuepoo

@Xuepoo Xuepoo commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

PR body

Closes #40.

Per-surface context menus + global shortcut interception policy.

Context menus (right-click intercepted everywhere except the Browser viewport, DEC-0026)

  • Desktop: Refresh · New text document · Change wallpaper · Display settings · Task Manager · About
  • Window titlebar: Minimize / Maximize / Restore / Close (state-aware)
  • Files: Open · Rename… (file-only, DEC-0029) · Delete (cascades); empty area: New folder… (collision-suffixed)
  • Notepad: Cut · Copy · Paste · Select All (clipboard denial surfaced in status line)
  • Paint: Undo (visibility-aligned stroke count) · Clear Canvas
  • Keyboard: ContextMenu key / Shift+F10 open the focused surface's menu; arrows + Enter + Escape navigate

Shortcut policy (chord check before editable bail-out)

  • Owned: Ctrl+S (save focused Notepad doc), Ctrl+P/O/G/D, Ctrl+R, F5 (desktop refresh outside editables; Ctrl+Shift+R stays the native valve), F12, ContextMenu/Shift+F10
  • Passthrough: F11, PrintScreen, clipboard chords in editables, Ctrl+Shift+R
  • Impossible (documented, engine mappings dropped per DEC-0031): Ctrl+W/T/N, Alt+Tab/Win — vectojs://shortcuts page + SEED_DOCS made truthful

Review round fixed

Right-click routing now resolves by visual stacking order (was creation order — overlapping windows mis-captured); Notepad client-area dead zone closed (editor-anchored fallback + surface-owned keyboard anchors); keyboard menu focus scoped to the active menu; Paint dot-stroke count aligned with render.

Gates: 247 pass / 0 fail · tsc · oxlint · oxfmt repo-wide · markdownlint · vite build.

Xuepoo added 3 commits August 25, 2026 17:34
…cy (#40)

Right-click interception moves from a canvas-only listener to a document
CAPTURE router: the a11y projection mounts mirrors (Buttons, Inputs, the
Notes textarea) ABOVE the canvas, so every projected surface leaked the
native browser menu before. Zones classify purely (context-routing.ts):
empty desktop, titlebar, window client, taskbar, desktop icon, and the one
deliberate passthrough — the Browser viewport (DEC-0026, page-content
semantics).

Menu inventory (issue #40):
- desktop: Refresh, New text document, Change wallpaper (preset cycle),
  Display settings, Task Manager, About
- titlebar: state-aware Minimize/Maximize/Restore/Close
- Files entries: Open / Rename… / Delete (rename file-only, DEC-0029);
  empty areas: New folder… with Windows-style suffixing (Paste deferred,
  DEC-0027)
- Notepad editor: Cut/Copy/Paste/Select All on the projected mirror
  (paste via async clipboard + synthetic input event)
- Paint canvas: Undo (existing strokes[] history, DEC-0025) / Clear

Shortcut policy (model/shortcut-policy.ts + main.ts block): owned chords
preventDefault BEFORE the editable bail-out — Ctrl+S saves the focused
Notepad document through its registered window surface; Ctrl+R and F5 are
reload protection with F5 as desktop refresh outside editables (DEC-0028;
Ctrl+Shift+R stays the native valve); ContextMenu key/Shift+F10 open the
focused surface's menu. F11/PrintScreen/Ctrl+A,X,C/V pass through by
design; browser-reserved chords documented impossible, and the engine
config stops claiming them (Control+n/w, Meta+w dropped, DEC-0031) with
vectojs://shortcuts + seed docs made truthful.

New text document writes /docs/New Document.txt (collision-suffixed via
model/vfs-names.ts) and opens Notepad ON it via requestNoteDocument;
exposed as webos.newTextDocument(). Input dialog (app/input-dialog.ts)
mirrors ConfirmDialog modal semantics. Context menus focus their first
enabled item on open (arrows/Enter immediate) and Escape dismisses.

Tests: routing matrix, inventories, policy resolution both directions,
VFS rename/delete/folder round-trips, Paint undo/clear API, input-dialog
modal contract, smoke integrations for routing + policy dispatch.
Gates: 247 tests / tsc / oxlint --deny-warnings / oxfmt / markdownlint /
build green.

Refs: #40
Task: WEB-0039
A denied clipboard-read (paste) or blocked clipboard-write (cut/copy
fallback) previously failed silently — the menu item appeared dead. The
status line now carries a transient "Clipboard unavailable" hint until
the next status refresh.

Refs: #40
Task: WEB-0039
… batch

Review fixes for the WEB-0039 verdict (issue #40, PX-0222..PX-0228):

- classifyRightClick now requires windows TOPMOST-FIRST; the router feeds
  reverse scene.overlayRoot.children order instead of windowManager.list()
  creation order, so overlapping windows no longer let the earliest-created
  window capture clicks aimed at the one stacked above it. The overlap test
  pinned in context-routing.test.ts is flipped to topmost-wins.
- Notepad: points outside the editor rect fall back to an editor-anchored
  edit menu instead of silently swallowing the already-prevented click;
  surfaces own their ContextMenu-key anchor via WindowSurface.keyboardAnchor,
  so Notepad's menu no longer lands on its status line.
- openKeyboardContextMenu closes an open start menu like the pointer path,
  and focusFirstMenuItem scopes to the active menu's projection so roving
  focus cannot land on another projected menu's items.
- Paint strokeCount counts only strokes render() draws (>=2 points), so
  Undo/Clear never enable for nothing visible.
- renameVfsFile: document the crash-window (death between write and remove
  duplicates the file; no data-loss path) next to DEC-0029.

Ref #40
@Xuepoo
Xuepoo merged commit 6e03dd0 into main Aug 25, 2026
4 checks passed
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.

feat(desktop): per-surface context menus + global shortcut interception policy

1 participant