aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs/2026-10-24-companion-seat-design.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/specs/2026-10-24-companion-seat-design.md')
-rw-r--r--docs/superpowers/specs/2026-10-24-companion-seat-design.md103
1 files changed, 103 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-10-24-companion-seat-design.md b/docs/superpowers/specs/2026-10-24-companion-seat-design.md
new file mode 100644
index 0000000..3e7fb0f
--- /dev/null
+++ b/docs/superpowers/specs/2026-10-24-companion-seat-design.md
@@ -0,0 +1,103 @@
+# Companion seat: design
+
+Date: 2026-10-24. Status: implemented; the sitting art is generated by the user.
+
+## Goal
+
+While the assistant canvas shows its tall card (a chat, or the history,
+picker or models panel), the companion sits on the card's top edge, right
+of centre, legs dangling over the empty middle of the header. Typing makes
+her lean over and read; BackSpace makes her point and smirk from the ledge;
+thinking, listening and the reply wave happen there too. The bar and the
+file card do not seat her: she stays in her corner, since standing on a
+short card would need its own art to read anything below her feet.
+
+## Verified before designing
+
+- **Hyprland's typing binds fire under the canvas.** With the canvas open
+ and holding the keyboard `Exclusive`, socket2 carried
+ `custom>>keystone-typing` and `custom>>keystone-delete` for every burst
+ typed into it, so the companion needs no input of the canvas's own.
+- **The tall card has a fixed top.** `Card.tall` is `mode === "chat" ||
+ panel`, and while tall its height is `fullHeight`, set by the window to
+ 70% of the screen height. The card is centred, so its top is
+ `(screenH - fullHeight) / 2` and only changes on entering or leaving the
+ tall state. Nothing needs to follow a moving edge.
+- **The header's middle is empty.** The model name is on the left, the
+ context meter and buttons on the right, both in a 32 px row inside a
+ 14 px margin. Her legs fall in the gap between them.
+- **Sitting art exists.** `base-sitting.png` in the art directory is the
+ original rest pose: front view, sitting, legs dangling. `dress/base` was
+ made from it, so the new sitting frames edit it directly.
+- **Opening the canvas already remaps her.** `openlayer` for
+ `keystone-assistant` unmaps and maps her again above the dim, with a drop
+ out and a drop in. A seat change made while she is unmapped needs no
+ animation of its own.
+
+## Behaviour
+
+### Seat
+
+`Assistant.qml` gains `readonly property bool tall`, the same expression
+as `Card.tall`, and `readonly property real cardTop`, the card's top on
+its screen. The canvas owns both so the companion does not reach into the
+card.
+
+The companion's `seated` is `asst.open && asst.tall`. Seated, her window
+is on `asst.screenObj` and its margins put her seat's bottom 35 px below
+the card's top, with her right edge 90 px right of the screen's centre:
+the window stays anchored right and bottom, `margins.bottom = screenH -
+cardTop - 35`, `margins.right = screenW / 2 - 90`. Her right edge rather
+than her centre, because frames differ in width and the window would move
+with every frame. Both numbers were checked by eye. Not seated, the
+margins are today's 24 and 16.
+
+`seated` changing runs the monitor hop's path: she drops out, the window
+moves when the exit has settled, and the entrance plays. The first seat on
+opening a chat coincides with the remap and is hidden by it. While seated,
+a focused monitor change does not hop her; on unseating she goes to the
+focused monitor.
+
+Balloons do not shove her while seated: their stack is in a screen corner,
+not on the card.
+
+### Frames
+
+Seated, the frame Mood picks goes through `Mood.seatFrame(frame)`:
+
+| Standing | Sitting |
+| --- | --- |
+| `watch` | `sit-read`: leaning forward, looking down at the card |
+| `point` | `sit-point` |
+| `smirk` | `sit-smirk` |
+| `think` | `sit-think` |
+| `listen` | `sit-listen` |
+| `wave`, `wave-l`, `wave-r` | `sit-wave` |
+| anything else | `sit` |
+
+Cycles (the goodbye wave, dj, headbang) map frame by frame, so the
+goodbye shows `sit-wave` throughout and dj shows `sit`. `Mood.pick` is
+unchanged: typing still gives `watch`, delete still gives the mock.
+
+Seven new frames in each outfit, from `base-sitting` in `frames.txt`, with
+a sitting counterpart of `POSE` and the leotard stanzas like the others.
+`gen-frames` then `prep-frames`, by the user, as with every other frame.
+A missing frame falls back to `base`, standing, which is how an
+ungenerated set shows.
+
+### Inputs
+
+Typing and BackSpace keep arriving as `keystone-typing` and
+`keystone-delete` from Hyprland, which fire under the canvas (see above).
+
+## Check
+
+`test_mood.js` gains `seatFrame` cases: each row of the table, a cycle
+frame, and an unknown frame to `sit`. The seat position and the bind check
+are by hand.
+
+## Not doing
+
+- Standing on the bar or the file card: needs new art.
+- Following the card while it animates: the tall card does not move.
+- Sitting on ordinary windows.