Watch
1
0
Fork
You've already forked souveraine
0

the avatar draws: serve the rig over loopback, not the opaque scheme

This commit is contained in:
casey 2026-08-05 22:14:07 -04:00
commit 3d3eb2cc8e
4 changed files with 144 additions and 42 deletions

View file

@ -45,27 +45,6 @@ Item {
anchors.fill: parent
hoverEnabled: !Config.options.bar.tooltips.clickToShow
// Double tap summons her face, and dismisses it. TASK-59 Q0 asked how
// she is reached; Casey, 2026-08-05: *"I double tap on the clock widget
// and I get the avatar."* Deliberately not always-on the webview is
// 283 MB measured, which on a 3.5 GB daily driver is a design argument
// and not only a taste one.
//
// A toggle rather than a summon, because the same gesture has to be the
// way out: a presence you cannot dismiss is the user's column being
// ignored (doctrine §13, and TASK-59 Q4).
property real lastTapAt: -1
readonly property int doubleTapInterval: 350
onClicked: {
const now = Date.now();
if (lastTapAt > 0 && now - lastTapAt <= doubleTapInterval) {
lastTapAt = -1;
Face.toggle();
} else {
lastTapAt = now;
}
}
ClockWidgetPopup {
hoverTarget: mouseArea
}

View file

@ -219,4 +219,42 @@ Item {
style: Config.options.background.widgets.clock.cookie.dateStyle
}
}
// Double tap summons Annie. Casey, 2026-08-05: *"I double tap on the clock
// widget and I get the avatar."* The desktop clock, not the bar's this is
// the one on the wallpaper, on home, where she has room to stand.
//
// A toggle, because the same gesture has to be the way out: a presence you
// cannot dismiss is the user's column being ignored (doctrine §13). The
// flip/fade/puff the clock should do on the way is deliberately not here
// yet the trigger first, the theatre after.
// Touching the singleton here is what constructs it. Quickshell builds
// singletons lazily, so `Face`'s IpcHandler did not register until
// something reached for it `qs ipc call face status` answered "Target not
// found" while the double tap below worked fine, because the tap was the
// first reference. The dial and the agent both need it addressable before
// anyone has tapped anything.
readonly property bool faceReady: Face.rigPresent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
// Above the clock's children, not below. `z: -1` was the first guess
// and it is why the first version did nothing: the clock's own visual
// items sit at the default z, so the handler was underneath every one
// of them. Nothing here competes for the tap the clock has no other
// input at all, and never has.
z: 1
property real lastTapAt: -1
onClicked: {
const now = Date.now();
console.log("[face] clock tap at " + now
+ " (delta " + (lastTapAt > 0 ? now - lastTapAt : -1) + ")");
if (lastTapAt > 0 && now - lastTapAt <= 350) {
lastTapAt = -1;
Face.toggle();
} else {
lastTapAt = now;
}
}
}
}

View file

@ -50,25 +50,18 @@ Singleton {
+ "These are the postures the presence system already uses. "
+ "Use them sparingly, where the shift is real."
// The rig is not in the repo and never will be it is licensed assets on
// Casey's own device (TASK-59). So its absence is the ordinary state of any
// machine that has not staged it, and it must read as "not set up here"
// rather than as a broken face.
property bool rigPresent: rigProbe.exists
FileView {
id: rigProbe
path: root.rigDir + "/index.html"
preload: true
}
// No pre-flight check on the rig.
//
// There was one, reading `FileView.exists`, and it reported false for a
// directory that was plainly there so the guard meant to explain a
// missing rig became the thing preventing a present one from loading. The
// host already fails loudly and specifically when the directory is wrong,
// and `onExited` puts `joined` back, so the honest answer is to let it try
// and report what actually happened. A guard that can be wrong about the
// world is worse than no guard.
function join() {
if (root.joined)
return;
if (!root.rigPresent) {
console.log("[face] no rig at " + root.rigDir
+ " — stage it with tools/stage-face.sh; not joining");
return;
}
host.running = true;
root.joined = true;
}
@ -114,6 +107,33 @@ Singleton {
signal tapped(string area)
// Reachable by name, so the dial, a launcher and the agent all summon her
// the same way rather than each growing a copy (TASK-30/31). `status`
// answers rather than assumes an agent that cannot ask whether she is up
// has to guess, and guessing is what a verb table exists to stop.
IpcHandler {
target: "face"
function toggle(): void {
root.toggle();
}
function join(): void {
root.join();
}
function leave(): void {
root.leave();
}
function status(): string {
return JSON.stringify({
joined: root.joined,
rig: root.rigDir
});
}
}
Socket {
id: sock
path: root.socketPath