Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17473,6 +17473,98 @@ the last the request with the ring on Chat; with no vault the video opens, waiti
personal Bitwarden the principle plays without the step, and a member and a connected
personal Bitwarden play the approved video and principle.

### F56 addendum (7) of 2026-10-25: Buddy presents the guide himself where his bubble shows, as in the prototype

**Decided by Matěj (2026-10-25)** after he played the guide on his Osobní Prostředí with Buddy:
"ještě vyskočí lazurio kámen namísto buddyho, jako je to v prototypu" ("the Lazurio stone still
jumps out instead of Buddy, as he does in the prototype"). Root decision 0210 point 1 keeps the
neutral guide of Lazurio for a person without Buddy; where his bubble shows, Buddy is the guide.
Point 6 drew the neutral mark everywhere ("Buddy's character comes with his bubble") because no
Buddy origin existed yet, and F60 point 5 only made the mark start at his launcher. The prototype
(`HumanAndMachine-ai/prototypes-lazurio` 8d13cac6: `docs/buddy-guide.md`, "Buddy vyskočí do
rozhraní"; `app/v1/src/GuideOverlay.tsx`, `guide.css` and `guide.ts`) has him jump out of his
bubble, stand beside the element, point at it and say the step beside him.

- **Who presents.** Where the page shows Buddy's bubble (F60), Buddy presents every explanation,
a "?"'s and one his frame asks for alike. The engine names him in the step it draws
(`GuideView.buddy`: his picture, `/assets/avatar.webp` of his origin, and his name, as his
launcher shows them), from the same fact that lets his frame ask and invites nobody to get a
Buddy. Without his bubble the neutral guide presents exactly as point 6 draws it.
- **His figure.** His picture at 48 × 56 px in a box of 56 × 64 (the prototype's `.gbuddy` and
`BUDDY_SIZE`), with no disc, a soft shadow under him and his pixels kept sharp; `object-fit:
contain` shows T3 Code's 192 × 208 picture at 48 × 52 without stretching it. He leaps out of
his launcher, hops to every step and back home, and breathes while he stands. His hand, a 22 px
blue disc with Iconoir's ArrowLeft, stands beside him toward the element and pokes at it. All
of it moves only where motion is not reduced; with less motion he appears where he stands and
every step is a still frame.
- **Where he stands** (`guideBuddyPlacement`, the prototype's `placeGuide`). 30 px beside the
element: on its right where he and the card have room, else on its left, else below it, else
above it. The card stands 10 px past him and, beside an element on its right or left, 24 px
higher. Without an element the card stands in the middle and he on its left, without a hand.
Below 768 px the card stays the sheet of point 6 and he stands on its top edge at its left,
without a hand.
- **The card is his speech bubble.** Its tail points at his head, not at the element, and its
title loses the indent the mark's disc needed. Its accessible name names who presents, as his
launcher names him: "Buddy: <title>" instead of "Průvodce Lazuriem: <title>" ("Lazurio
guide"). Its width (300 px, or the sheet), padding, shadow, contents and one size through an
explanation stay.
- **His launcher waits empty while he is out**, from his leap until he is back, as the
prototype's `.buddyw__launcher.is-away`: paper with a grey ring, without his picture. The
bubble's model says it (`launcher.away`), the view draws `data-away`, and the guide's layer
says when he leaves and when he is back. The launcher still opens his window, and the
document's `--lazurio-buddy-clearance` (F60's addendum of 2026-10-25) stays, since its place
is still taken.
- **Where his picture does not load** (no session at his origin in this browser, a network that
does not reach it), the neutral mark, its 40 px white disc with the Lazurio symbol, stands in
his place with his hand, as on his launcher. The picture is asked for again only when it
changes.

**What stays.** The explanations and their words, steps, scenes, documentation links and the
invitation; the ring and the page dimmed around it; the engine's rules (never two tours at once,
who may ask, Escape and the focus); the guide without Buddy, with the neutral mark at the card's
corner and the card placed as the tour's bubble; the layer's travel of 0.7 s (the prototype's is
0.9 s). Interface v1 does not change: `GuideView` and the bubble's model are internal. Nothing
in the Folder.

| Alternative | Trade-off / disposition |
| --- | --- |
| Only Buddy's picture in the mark's disc at the card's corner | The smallest change, but Buddy stays a badge on the card and neither points nor speaks, which is not the prototype Matěj asked for; rejected |
| The prototype's figure and placement for the neutral guide too | Matěj asked about Buddy, and without Buddy the guide stays as F56 draws it (F60 point 1); not in this change |
| The layer asks its element for Buddy's picture | A second answer to whether Buddy is here, beside the engine's for the frame and the invitation, and untested without a DOM; rejected for `GuideView.buddy` |
| The prototype's card, 352 px wide beside a scene | Changes every explanation's lines and its stage, and the card's width would depend on who presents; rejected |
| The bubble hides Buddy from the guide's `started` to `closed` | He walks home for 0.7 s after `closed`, so two Buddies would meet; rejected for the layer saying when he leaves and when he is back |

**Verified by:**
- `tests/shell-guide.test.ts`: the prototype's distances on each side of an element, in the
middle and on the sheet; one rule across three window sizes (he and his card whole in the
window and apart, off the element, 30 px from it on the side he points from, the card 10 px
past him, its tail at him); the neutral guide's step without Buddy.
- `tests/shell-buddy.test.ts`: Buddy presents with his picture and name where his bubble shows,
with the same explanations, steps, scenes and words as without him; the launcher waits empty
while he is out, said once, across another origin, and shows him again once he is back.
- Headless Chrome against the personal preview with `buddy`, whose origin served T3 Code's
`avatar.webp`, and without it, the Apps home's "?" (`environment`) at 1440 and 390 px:
- with Buddy at 1440 px: at the first step he stood 30 px right of the Environment picker, his
hand toward it and the card 10 px past him with its tail 41 px down, named "Buddy: …"; at
the second he stood left of the browser's toggle and the card on his left; the launcher was
empty while he was out, its clearance unchanged, and showed him again after Escape once he
was back;
- with Buddy at 390 px: he stood on the sheet's top edge, without a hand;
- with his picture refused: the neutral mark stood in his place with his hand, on the
launcher too;
- with `prefers-reduced-motion`: he appeared in place without motion, and the launcher showed
him again at once after Escape;
- without `buddy`: the neutral mark at the card's corner and the card as before, named
"Průvodce Lazuriem: …", and no launcher.

**Honest limits.** Where the prototype says nothing, the choices are the agent's, for Matěj's
review: Buddy on the sheet's top edge below 768 px, the neutral mark in his place where his
picture does not load, the card's accessible name, and the card's width and the layer's travel
kept. Checked in headless Chrome only; Safari and Firefox are not. The forks' pages (T3 Code,
MausBot), where Buddy presents the principles his frame asks for, were not opened. This ships
with the Platform release that carries F60's addendum of 2026-10-25, after the forks read
`--lazurio-buddy-clearance`.

## F57 — Sleeping tabs: a tab nobody uses closes its window and keeps its place, under its id

**Proposed by the agent on 2026-10-20 on Matěj's direction of the same day (root decision 0195
Expand Down
26 changes: 21 additions & 5 deletions docs/launchpad-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -929,8 +929,9 @@ thread's composer, not sent (Lazurio/t3code#35, Lazurio/LazurioPlatform#153).
A "?" right after a page's heading makes Buddy, or without him the neutral guide of
Lazurio, explain the page in place
([F56](decisions.md#f56--průvodce-buddy-nebo-průvodce-lazuria-vysvětlí-stránku-na-místě-kreslí-shell-hostitele-rám-buddyho-smí-jen-požádat-id),
root decision 0180's addendum of 2026-10-18): a ring around the real element, the
guide's mark and a card beside it, and the layer lets every click through.
root decision 0180's addendum of 2026-10-18): a ring around the real element, Buddy
himself where the page shows his bubble, else the neutral guide's mark, and a card
beside it, and the layer lets every click through.

- **In the shell.** `src/shell/guide.ts` decides what plays and when, over seams, so its
rules are tested without a DOM; `guide-layer.ts` draws in `<lazurio-buddy>`'s own
Expand All @@ -941,6 +942,17 @@ guide's mark and a card beside it, and the layer lets every click through.
`guide-vectors/v1/`. `elements.ts` defines `<lazurio-buddy>` and exports
`provideGuides` (interface v1), and for the Launchpad alone `guideAttention`, the
arbiter with its tour.
- **Who presents** ([F56's addendum (7) of 2026-10-25](decisions.md#f56-addendum-7-of-2026-10-25-buddy-presents-the-guide-himself-where-his-bubble-shows-as-in-the-prototype)).
Where the page shows Buddy's bubble the engine names him in the step it draws
(`GuideView.buddy`, his picture from his origin and his name), and `guide-layer.ts`
draws him as the prototype does: his picture 48 × 56 without a disc, leaping from his
launcher and hopping to every step, his hand toward the element (`data-side` on the
figure), and the card as his speech bubble, named after him, with its tail at him,
placed by `guideBuddyPlacement` (30 px beside the element, the card 10 px past him;
on the sheet below 768 px he stands on its top edge). His launcher waits empty while
he is out (the bubble's `launcher.away`, drawn as `data-away`), and where his picture
does not load the neutral mark stands in his place. Without his bubble the neutral
mark stands at the card's corner and the card where the tour's bubble would.
- **The principles in motion** ([F56's addendum (3) of 2026-10-19](decisions.md#f56-addendum-3-of-2026-10-19-the-six-principles-in-motion)).
`environment`, `buddy`, `reach`, `files` and `integrations` play a scene in the card,
one beat a step; Trezor keeps its video and has none, and so does a host's own
Expand Down Expand Up @@ -1104,9 +1116,13 @@ the origin nothing changes: the neutral guide stays as F56 draws it.
context it hears. Check at 1440 and 390 px: the launcher and its fallback mark, the
panel, full screen over the whole window and Escape, the context after a move to
another page, and an explanation asked by the frame (the window steps aside, returns
at the end). A work Environment's page gets the bubble from the account (route
`/.lazurio/account/environments` to a document with `buddy`). Tests:
`tests/shell-buddy.test.ts`, `tests/shell-guide-protocol.test.ts`.
at the end). Serve `/assets/avatar.webp` of Buddy's origin (T3 Code's picture) to see
him present a "?"'s explanation: beside the element with his hand, the card his
speech bubble, the launcher empty until he is back after Escape; with the picture
refused, the neutral mark in his place. A work Environment's page gets the bubble
from the account (route `/.lazurio/account/environments` to a document with
`buddy`). Tests: `tests/shell-buddy.test.ts`, `tests/shell-guide-protocol.test.ts`,
`tests/shell-guide.test.ts`.

### The offline guide (decision F41)

Expand Down
33 changes: 27 additions & 6 deletions src/shell/buddy-bubble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import { createGuideReceiver } from "./guide-protocol";
// principles known on every page, and the answers `started`, `done`,
// `closed` and `busy` go back to it. While the explanation plays the window
// steps aside, and it returns when the explanation ends.
// - Buddy presents every explanation himself (F56's addendum (7) of
// 2026-10-25): while the guide's figure is out of the launcher, from its
// leap until it is back, the launcher waits empty, as in the prototype.

/** The size of the window this tab remembers, in `sessionStorage`. */
export const buddyLayoutKey = "lazurio.buddy.layout";
Expand All @@ -66,9 +69,15 @@ export type BuddyFrameModel = Readonly<{
/** What the view draws. */
export type BuddyBubbleModel = Readonly<{
origin: string;
/** Buddy's picture, its name for assistive technology, and whether the
* window shows. */
launcher: Readonly<{ avatar: string; label: string; expanded: boolean }>;
/** Buddy's picture, its name for assistive technology, whether the
* window shows, and whether Buddy is out presenting the guide, the
* launcher waiting empty. */
launcher: Readonly<{
avatar: string;
label: string;
expanded: boolean;
away: boolean;
}>;
/** The frame, from the first open on; null before. */
frame: BuddyFrameModel | null;
layout: BuddyLayout;
Expand Down Expand Up @@ -100,14 +109,17 @@ export type BuddyBubbleSeams = Readonly<{
}>;
}>;

/** Buddy's name on the launcher and the frame: a name, the same in every
* language. */
const buddyName = "Buddy";
/** Buddy's name on the launcher and the frame, and on the guide's card
* where he presents it: a name, the same in every language. */
export const buddyName = "Buddy";

export function createBuddyBubble(seams: BuddyBubbleSeams) {
let origin: string | null = null;
let frame: BuddyFrameModel | null = null;
let layout: BuddyLayout = "closed";
// Whether the guide's figure is out of the launcher; it stays out across
// another origin, as the figure does.
let away = false;
// The explanation the frame asked for while it plays, and the size its
// window returns to.
let asked: Readonly<{ explainer: string; restore: BuddyLayout }> | null =
Expand Down Expand Up @@ -140,6 +152,7 @@ export function createBuddyBubble(seams: BuddyBubbleSeams) {
avatar: buddyAvatarSource(origin) ?? "",
label: buddyName,
expanded: layout !== "closed",
away,
}),
frame,
layout,
Expand Down Expand Up @@ -305,6 +318,14 @@ export function createBuddyBubble(seams: BuddyBubbleSeams) {
if (layout === "closed" && was.restore !== "closed")
show(was.restore, "frame");
},
/** The guide's figure, Buddy presenting an explanation, left the
* launcher (true) or is back in it (false): the launcher waits empty
* meanwhile. The window and the frame are as they were. */
guideOut(out: boolean): void {
if (out === away) return;
away = out;
draw();
},
/** What is drawn now. */
model,
});
Expand Down
10 changes: 7 additions & 3 deletions src/shell/buddy-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { buddyBubbleCss, buddyLauncherClearance } from "./buddy-styles";
// closed with the frame kept alive inside (a frame moved to another parent
// would load again). Full screen covers the whole window, so the way back
// is Escape or the frame's own control. The guide's layer stands after the
// bubble in the same shadow root (guide-layer.ts). While the launcher is
// bubble in the same shadow root (guide-layer.ts); while Buddy is out of the
// launcher presenting an explanation, the launcher waits empty (`data-away`,
// decision F56's addendum (7) of 2026-10-25). While the launcher is
// drawn the page keeps its corner free: `--lazurio-buddy-clearance` on the
// document says how wide it is (decision F60's addendum of 2026-10-25).

Expand Down Expand Up @@ -56,8 +58,8 @@ export type BuddyBubbleView = Readonly<{
frameWindow(): Window | null;
/** Moves the focus to the launcher or into the frame. */
focus(target: "launcher" | "frame"): void;
/** The launcher's centre on the screen, where the guide's mark starts and
* ends; null without it. */
/** The launcher's centre on the screen, where the guide's figure starts
* and ends; null without it. */
home(): Readonly<{ x: number; y: number }> | null;
}>;

Expand Down Expand Up @@ -161,6 +163,8 @@ export function createBuddyBubbleView(
}
launcher.setAttribute("aria-label", model.launcher.label);
launcher.setAttribute("aria-expanded", String(model.launcher.expanded));
if (model.launcher.away) launcher.dataset.away = "";
else delete launcher.dataset.away;
dialog.setAttribute("aria-label", model.launcher.label);
if (model.frame === null) {
frame?.remove();
Expand Down
7 changes: 6 additions & 1 deletion src/shell/buddy-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import { baseCss, hostTokens } from "./base-styles";
// A host still moves the launcher with `--lazurio-buddy-right` and
// `--lazurio-buddy-bottom` (interface v1), and the panel stays above it.
// Buddy's figure stands in the disc at 40 x 44 px with its pixels kept
// sharp, as in the prototype, rather than filling it.
// sharp, as in the prototype, rather than filling it. While he is out
// presenting the guide (decision F56's addendum (7) of 2026-10-25) the disc
// waits empty, paper with a grey ring, as the prototype's
// `.buddyw__launcher.is-away`.

/** The launcher's size. */
export const buddyLauncherSize = 58;
Expand Down Expand Up @@ -64,6 +67,8 @@ const bubbleCss = `
.launcher:focus-visible { outline: 2px solid var(--shell-focus); outline-offset: 3px; }
.launcher img { display: block; width: ${buddyLauncherFigure.width}px; height: ${buddyLauncherFigure.height}px; object-fit: contain; image-rendering: pixelated; pointer-events: none; }
.launcher[data-fallback] img { width: 34px; height: 34px; object-fit: contain; }
.launcher[data-away] { background: var(--lz-paper); box-shadow: inset 0 0 0 1.5px var(--lz-gray-300); }
.launcher[data-away] img { opacity: 0; }
.window { position: fixed; inset: auto var(--buddy-x) calc(var(--buddy-y) + ${above}px) auto; z-index: ${buddyLayer}; box-sizing: border-box; width: min(${buddyPanelSize.width}px, calc(100vw - 2 * var(--buddy-x))); height: min(${buddyPanelSize.height}px, calc(100dvh - var(--buddy-y) - ${above + 16}px)); max-width: none; max-height: none; margin: 0; padding: 0; overflow: hidden; border: 1px solid var(--shell-line); border-radius: var(--lz-radius-md); background: var(--shell-raised); color: var(--shell-ink); box-shadow: var(--lz-shadow-float); }
.window:modal { inset: 0; width: 100%; height: 100%; margin: 0; border: 0; border-radius: 0; box-shadow: none; }
.window iframe { display: block; width: 100%; height: 100%; border: 0; background: transparent; }
Expand Down
Loading
Loading