Desktop transparent pal — no window chrome, corner-scalable #18
Labels
No labels
needs-info
needs-triage
ready-for-agent
ready-for-human
v2
wayfinder:grilling
wayfinder:map
wayfinder:prototype
wayfinder:research
wayfinder:task
wayfinder:ticket
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
aiko/denpa#18
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What to build
Card 1b, plus 7e. The window has no background and no titlebar: Tauri
transparent: true,decorations: false,alwaysOnTop: true. Set a click-through region (ignoreCursorEventsoutside hit areas) so only the model, bubbles and bar are hit-testable — empty space must not eat desktop clicks.She is most of the window; the bar is 40px and only as wide as she is.
data-tauri-drag-region): 最前面 grip, carrier meter, an invocation line that always states what caused the current turn (自発 · trigger / 送信 / 待機), then 文 / 受信 / 割り込み keys.transform-origin: bottom left, so she grows up and out and never drifts off-screen. Clamp 0.68×–1.32×, show a live readout and dashed bounds while dragging, and persist scale per monitor. Same stored value as the 相手 scale slider.⌥held = push to talk, so the window never needs focus.7e — faults at pal scale. At 0.7× a 25px headline and a two-hop chain will not fit, so a fault becomes one line plus a way in: the meter dies, the bar border turns acid, 不通 replaces the station name, she says one short line in her own voice, and a small なぜ button opens the full failure screen in her window. Per fault: 401 → 鍵が通らない; no model → 姿がない and she keeps talking; mic lost → the 受信 key turns acid and nothing else changes.
Design reference:
docs/design/denpa-receiver/README.md(card ids1a,4a… index intoDenpa Receiver.dc.html, openable in a browser).Acceptance criteria
Blocked by
Branch:
issue/denpa-18-desktop-transparent-pal(offmainat875a261).npm test— 17 files, 138 passed (baseline was 60 in 8; nothing deleted or skipped).npm run typecheckstill reports exactly 585 errors and none of them name a path I touched.What landed
components/pal/hit-regions.ts— the click-through region. Tauri'sset_ignore_cursor_eventshas no equivalent of Electron's{ forward: true }, so a window that is ignoring the cursor never receives the mouseover that would hand control back. The pal therefore pollscursorPosition()and hit-tests it against the rects of the elements it markeddata-denpa-hit; the root also setspointer-events: noneso the poll lag cannot let empty space take a desktop click. It applies the flag through the existingupdate_component_hovercommand rather thanset_ignore_mouse_events, because that one also makes the window focusable again.pal-scale.ts— the 0.68×–1.32× clamp, the bottom-left grip arithmetic, the1.14×readout and per-monitor persistence. This is the only store for the scale, so 5b's 大きさ slider reads and writes the same value.pal-turns.ts(last two turns only, relay rows excluded),invocation-line.ts(自発 · trigger / 送信 / 待機, never blank),pal-composer.ts(⏎ sends, ⇧⏎ newlines, IME commit does not send),pal-fault.ts(7e, one line plus a way in, per fault).desktop-pal.tsx+ bar / bubbles / composer / corner grip, consuming #10's tokens,CarrierMeteranduseReducedMotion. Three tokens added todenpa-tokens.tsin the same style:hairline.acid,text.onSignal, and awash.*group for the translucent panels a window with no background needs.PalContainerreplacesInputSubtitleas pet mode's UI, wired to the existing contexts and send path.src-tauri: a bare-⌥ global hotkey emittingptt-press/ptt-release, and fourcore:windowpermissions for the cursor/monitor reads.Deliberately left out
faultprop and are covered by tests, butPalContainerpasses none — 401 / no-model / upstream-down are not reported to the client until the adapter route lands (#20/#21), and なぜ has no failure screen to open yet. A fault that cannot happen would be a disabled affordance.ProactiveSpeakContextdoes not carry the trigger yet, per the design's own deferral.Assumptions (all five are in
.denpa-work/QUESTIONS.md)AltLeftis registrable as a global shortcut; failure is logged and PTT is simply absent, as with the other two hotkeys.--denpa-pal-scaleCSS variable, rather than being remounted inside the pal group.Not verified: I could not run the app, so every layout and visual claim here rests on reading 1b/7e, not on seeing it rendered. Specifically unverified: whether the bubble actually meets her shoulder, whether the declared model hit rect coincides with the drawn canvas, and whether the click-through poll feels immediate in practice.
Branch:
issue/denpa-18-desktop-transparent-pal(pushed, no PR, issue left open).What landed
The desktop pal as a background-less window.
App.tsxnow rendersPalContainerwhere it renderedInputSubtitle, composed ofDesktopPal,PalBar,PalBubbles,PalComposerPanelandCornerGrip. Logic was kept out of the components and put in testable modules:hit-regions.ts(which rects are opaque to the cursor),pal-scale.ts(the shared scale store),pal-turns.ts(grouping messages into turns),invocation-line.ts,pal-composer.ts, andpal-fault.ts(the 7e fault treatment table).Click-through is a 60ms renderer-side poll that hit-tests
data-denpa-hitrects and applies the result through the existingupdate_component_hoverTauri command;use-click-through.tsandlib/tauri-pal.tsare the seams. Rust side: a held-AltLeftglobal shortcut for push-to-talk insrc-tauri/src/lib.rsplus the matchingsrc-tauri/capabilities/default.jsonentries. Three tokens were added totheme/denpa-tokens.tsin the existing style rather than invented locally:hairline.acid(7e's acid bar border),text.onSignal(1b's#c8f7f4counter-bubble text), and awash.*group for the translucent panel/edge values a background-less window needs.Tests
npm test— 17 files, 138 tests, all passing:They cover hit-region geometry and the rects the pal declares; the scale store; turn grouping, including that a message with no question in front of it becomes a turn of its own and that relay rows never open or extend a turn; invocation-line and composer state; and the whole fault treatment table (station name, meter, keeps-talking, line text, bar border) per fault. The component tests assert structure and attributes — which element carries the drag region, which carry
data-denpa-hit, the transform and its origin, which strings appear per fault. They assert nothing about appearance.npm run typecheckstill reports exactly 585 errors, the pre-existing count, and grepping its output forpal,tauri-pal,App.tsxandthemereturns nothing. Two new errors did appear mid-work inpal-bar.tsxandpal-composer-panel.tsx(ChakraBox as="button"rejectstype/disabled); they were fixed by switching tochakra('button')before committing.npm run lintis broken onmainand was not run.No Rust tests were added or run. cargo is not part of the gate and building the Tauri tree in a worktree with no target dir of its own was not worth the turn, so the ⌥ hotkey and the new capability entries are unexecuted code.
Visual claims never observed rendering
I never rendered the pal. Every layout claim below rests on reading design 1b/7e, not on seeing it:
bottom: 296pxApp.tsxdrawsAlso unconfirmed at runtime: that the click-through poll feels immediate, and that a bare
AltLeftregisters as a global hotkey at all.Deliberately left out
The 7e fault treatments render from a table and are tested, but nothing drives them —
PalContainerpasses no fault. The two-hop status that names 401 / no-model / upstream-down is not reported to the client until the adapter route lands (#20/#21), and なぜ has no failure screen (5a/6a–6d) to open. Rendering a fault that cannot occur would be a disabled affordance, which the design forbids. 自発 does not reach the invocation line either:ProactiveSpeakContextdoes not carry the trigger yet, per the design's own deferral.The 5b 大きさ slider is not built — different card, different issue.
pal-scale.tsis the single store both surfaces are meant to share, so the slider is a read/write against it.InputSubtitleis no longer rendered, but the file and its hooks were left untouched rather than deleted, since a sibling branch may reference them.Assumptions
Code::AltLeftis registrable as a global shortcut for held-modifier PTT. Registration failure is logged and PTT is simply absent, matching how the two existing hotkeys behave. Unverified on a running app, and a global Alt hold may interfere with other apps' menu activation on Windows.App.tsxowns the Live2D container outsidePalContainer's subtree — so the pal publishes--denpa-pal-scaleon the document element and pet mode's canvas style reads it with the sametransform-origin: left bottom, rather than remounting the Cubism/WebGL canvas inside the pal group.update_component_hovercommand (which also restores focusability), rather than a new Rust cursor watcher.PalContainerreplacesInputSubtitleinApp.tsx.denpa-tokens.ts, not local to the pal.Review findings
Two findings, both addressed, none skipped.
Blocker —
pal-fault.ts: no-model (6c) setbarBorderAcid: true. The code contradicted its own adjacent comment ("both hops are up … the carrier is alive"), and design row 6c states "Both hops ✓" with 声だけで続ける as "a real path, not a dead end". The acid bar edge is the signal that the transmission stopped (README:497, under the base fault treatment), so firing it on a live carrier mis-draws the fault. Flipped tofalse. Verified the acid-bordered bubble that carries the 姿がない line is computed independently inpal-bubbles.tsxand is unaffected —barBorderAcidis consumed only atpal-bar.tsx:122, for the bar edge colour. The adjacent comment was tightened to say why the bar edge stays cyan.Major —
pal-fault.test.ts: the no-model case asserted station name, meter, keeps-talking and line but notbarBorderAcid, which is how the above passed. Addedexpect(treatment.barBorderAcid).toBe(false);. Ran it against the old behaviour first and watched it fail for the right reason:Then it passes with the fix. The 138/138 tail above is post-fix.
One process gap to be honest about: the
caveman:cavecrew-reviewerpass the global instructions require was not run — no agent-spawning tool is available to me as a subagent. Treat this branch as having had one pair of eyes, not two.Shipped. Merged to
mainatd73f4ed(phase B2i — client chrome integration).Closed as part of reconciling the board with
main: the autonomous run's policy was not to auto-close, so shipped work stayed open and the tracker read as though nothing had landed. That policy is now reversed — issues close when they land.