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.
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_v1and both data-control managers, sowl-paste --primary --watchsees 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 viahyprctl 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 existingRequest::Input { trigger }with anintentlabel, 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'stouch.active+0.1. Testable hypothesis: if stevia'szwp_virtual_keyboard_v1events 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.