Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/tasks/18-selection-menu.md
Fimeg d4ea575404 tasks: settle viewtop scene composition + selection design; cross-link
TASK-43 gains the §Scene composition design settled 2026-07-30: the
texture/content wall, pose (geometry, the agent's) and expose (legibility,
gate-veiled) as authority-split verbs, the selection surface as a scene element
not a layer surface, and the primary-selection/data-control source now wired
(234a631) with its owed refinements named.

TASK-18: the selection chip's Hyprland surface layer is terminal (the tap bug
is layer-shell); its backends are portable. TASK-30: viewtop's wire table is one
of its surfaces, grown by pose/expose. TASK-17: the OSK z-order this task fought
is structurally enforced under viewtop.
2026-07-30 11:36:50 -04:00

6.8 KiB

TASK 18 — shell: highlight/selection action menu

Status: shipped-partial 2026-07-29, live on blueline. Component first; backends light up as they earn it (doctrine §6). Read Aloud is no longer gated — TTS is Qwen3-TTS on 10.10.20.123:7865, with the old contract shimmed on :7863, as of 2026-07-29.

Shipped 2026-07-29

  • Selection detection is settled: compositor-level, no per-app hooks, no viewtop. blueline's Hyprland advertises zwp_primary_selection_device_manager_v1 and both data-control managers, so wl-paste --primary --watch sees every selection. Verified firing on device. This closes the open question below.
  • services/Selection.qml — watcher, 220 ms settle debounce (a drag fires continuously as it grows), pointer anchor via hyprctl cursorpos. Opt-in (Config.options.selection.enable, default false) because it observes every highlight on the device including passwords; the watcher is killed while locked and selection text is never persisted, logged or trailed.
  • modules/souveraine/selection/ — chip → action list. WlrLayer.Overlay, keyboardFocus: None. The chip shows a character count, never a preview: it floats over the app that owns the selection, so a preview both duplicates what is on screen and creates a surface that can outlive its context.
  • services/DeviceEvidence.qml — reports to the state machine's existing Request::Input { trigger } with an intent label, so a new input surface is not another isolated actor (DEVICE-STATE-MACHINE §1). The OSK still does not report and should: an open keyboard is stronger presence evidence than §4's touch.active +0.1. Testable hypothesis: if stevia's zwp_virtual_keyboard_v1 events do not reset Hyprland's idle notifier, the screen can dim mid-sentence while typing.

Anchoring is limited and it shaped the UX. No Wayland protocol exposes the selection's bounding rectangle — Android does this at the toolkit layer (ActionMode/FloatingToolbar), Apple in-app. So "tap on the highlighted text" is not directly detectable; we know what is selected, never where. Hence the chip near the touch point. True selection-rect anchoring is the one thing viewtop would buy — but only for first-party surfaces, where we own both ends and can carry geometry through wire. Third-party opaque textures stay pointer-hint-anchored: the texture/content wall is the Wayland contract itself, not a gap viewtop closes. See TASK-43 §Scene composition.

Still broken (Casey, on device 2026-07-29): the chip is not tappable or dismissable. It appears, and it correctly follows a new selection, but neither the body nor the ✕ responds. Two attempts failed: a fullscreen shield with mask: Region { item: card }, then a window sized to the card and moved by margins (147f7f2, last state). Untested by Casey. Next step is to determine whether the layer surface receives touch at all — the WlrKeyboardFocus.None + ExclusionMode.Ignore combination on Overlay is the prime suspect, and DialHost (which works) uses OnDemand. It also "fights a bit with Firefox's own highlight selection stuff".

Not yet wired: both agent actions (need the side-shoot / primary-conversation seams in Ai.qml) and all three reference lookups. They render with the reason they cannot act rather than dead-tapping.

The selection surface is being rebuilt for viewtop (TASK-43 §Scene composition). There it is a scene element Annie composes, not a WlrLayer.Overlay client — so the tap / z-order bug above is Hyprland-terminal; do not spend a third attempt on it. The selection backends (Speech.speak Read Aloud, the Ai.qml side-shoot / primary-conversation seams, the reference lookups) are surface-agnostic; finish those, they carry straight across to viewtop.

Repo: ~/Projects/souveraine surfaces/quickshell (shell surface), plus per-action backends noted below.

Priority order

Get the component itself solid and fluid — surface type, anchor behavior, animation — before wiring any backend. The stevia keyboard shipped glitchy because the surface work was rushed; do not repeat that. A menu whose actions are stubs is acceptable; a janky menu is not.

Action set

1. Reference / Lookup — deterministic, local/offline preferred

No agent round-trip. Whether these may ever touch the agent is an open decision (below); default stance: strictly local/dumb.

  • Define / Dictionary
  • Thesaurus (synonyms/antonyms)
  • Translate
  • Search the web
  • Look up (Wikipedia-style)
  • Pronunciation

2. Agent Actions — routes through Ani, agent-context-aware

  • Talk about this → spins off a new side-shoot conversation, seeded with the selection
  • Add to our conversation → appends to the current primary conversation
  • Explain — context-aware (uses surrounding text; needs connectivity). Deliberately contrasts with dictionary Define.
  • Summarize / simplify
  • Fact-check / verify

3. Read Aloud — its own section

Single TTS action on the literal selected text; no agent ingestion. Backend reality (verified end-to-end on device 2026-07-21): STT live (faster-whisper, 10.10.20.19:7862) and TTS live (VibeVoice, 10.10.20.19:7861, POST /audio/speech {input, voice, model} → mp3). The shell already has the seam: services/Speech.qml (Speech.speak(text)) reads the who→voice mapping from souveraine /v1/config and plays via mpv — the sidebar Speak button uses it, and this menu's Read Aloud action should call the same singleton. Gate: Config.options.speech.tts.enable.

4. Utility

  • Copy / Share (system defaults)
  • Add to notes/memory (bookmark into Ani's memory, no new conversation)
  • Create reminder/task from selection

Open design decisions (Casey's to make, ask before building past them)

  • Flat vs progressive disclosure. Leaning progressive: top level = Read Aloud + Agent Actions + "More…", Reference behind the second tap — the full set is long for a phone screen.
  • Reference actions and the agent: strictly local/dumb, or may they fall through to the agent? Default: never touch the agent.
  • Selection-detection mechanism. Settled 2026-07-29: compositor-level via primary-selection + data-control. Works everywhere, no per-app wiring.
  • Z-order / exclusion contract: which layer the menu renders on and how it coexists with the OSK. The stevia pill/menu layer-collision bug (fixed by pill→WlrLayer.Top + oskLift, souveraine 271e4f2) is the cautionary precedent — settle the contract before polishing visuals.

Connects to

  • TASK-17 (stevia keyboard) — shares the OSK z-order/exclusion problem.
  • services/Ai.qml — side-shoot vs primary-conversation seams for the Agent Actions section.
  • modules/settings/SpeechConfig.qml / Config.options.speech — the Read Aloud gate; single source of truth for both CLIs and the shell.