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
7 changes: 4 additions & 3 deletions docs/how-it-works.html
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,10 @@ <h2>Edits are events; the document stays the state</h2>
stands, which is what a reload has always shown you.
</p>
<p id="reactions">
The cheapest answer is one token. Select a passage, ⌥-click an element, or
stand in an agent reply, then open <strong>…</strong> or press <code>r</code>.
The ellipsis becomes the <code>ok</code>, <code>no</code>, <code>lost</code>,
The cheapest answer is one token. Select a passage, select an item with
<code>s</code>, or stand in an agent reply, then open <strong>…</strong> or
press <code>r</code>. Alt-clicking an item opens its comment box directly. The
ellipsis becomes the <code>ok</code>, <code>no</code>, <code>lost</code>,
<code>cut</code>, <code>more</code>, and <code>this</code> buttons; digits are
optional shortcuts. The token paints as a glyph in the margin and a faint wash
on the words, opens no thread, and comes off with a press on the glyph. An
Expand Down
9 changes: 5 additions & 4 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,11 @@ <h2>Example: Review a plan</h2>
<strong>Mark it up.</strong> Select any passage and comment; the passage
stays lit while you write, and the comment anchors to it and stays anchored
as versions change — to the passage you picked, even where the page says the
same thing twice. Diagrams and images take comments by click, and a
suggestion mode proposes replacement text that the agent takes verbatim or
answers with why not. The agent opens threads the same way — a question
about one sentence arrives in the margin beside it, not in the terminal.
same thing twice. Alt-clicking an item opens its comment box directly;
diagrams and images also expose response actions by click. A suggestion mode
proposes replacement text that the agent takes verbatim or answers with why
not. The agent opens threads the same way — a question about one sentence
arrives in the margin beside it, not in the terminal.
</li>
<li id="loop-wake">
<strong>Your comment reaches the session.</strong> On Claude Code, a
Expand Down
3 changes: 2 additions & 1 deletion examples/corpus.html
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,8 @@ <h2>Left out on purpose</h2>
<p id="rn-omitted-body">
The dependency bumps and the flaky-test fixes stay in the changelog only —
nothing a user would change behavior over. If one of them deserves a note
after all, say so in a comment and a draft for it arrives in the next version.
after all, Alt-click this section and say why; a draft arrives in the next
version.
</p>
<lf-specimen id="rn-bump-shape" label="the shape such a note would take">
<lf-draft id="rn-bump-example">
Expand Down
3 changes: 2 additions & 1 deletion examples/release-notes.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ <h2>Left out on purpose</h2>
<p id="rn-omitted-body">
The dependency bumps and the flaky-test fixes stay in the changelog only —
nothing a user would change behavior over. If one of them deserves a note
after all, say so in a comment and a draft for it arrives in the next version.
after all, Alt-click this section and say why; a draft arrives in the next
version.
</p>
<lf-specimen id="rn-bump-shape" label="the shape such a note would take">
<lf-draft id="rn-bump-example">
Expand Down
17 changes: 10 additions & 7 deletions skills/leaf/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1122,12 +1122,13 @@ has no hover. Export keeps the glyph with its press taken off and writes the was
into the words as a `<mark>` (BAKE), the highlight registry being script state
no file can hold.

The bar the selection raises is `.lf-fab-bar`: the `.lf-fab` comment glyph every
route into the composer still goes through, followed by one reaction ellipsis.
The bar a selection or keyboard-selected item raises is `.lf-fab-bar`: the `.lf-fab`
comment glyph followed by one reaction ellipsis.
For a page target, the ellipsis hands Comment and the layer's token buttons to
that target's shared margin item; it never opens a box below the floating bar.
`showFab` shows and places the compact bar; `activateAimTarget` raises it for both
the ⌥ press and keyboard item hint. `r` opens the same choices on the selection,
`showFab` shows and places the compact bar; `selectResponseTarget` raises it for a
keyboard item hint. The ⌥ press has already chosen Comment, so `openTargetComposer`
opens the composer directly on the same captured anchor. `r` opens the same choices on the selection,
the standing item, or the latest agent message in the thread the reader is in.
With none of those targets, it shows “Select something to react to” and opens
nothing. Page-wide reactions remain an explicit ellipsis above the panel's general
Expand Down Expand Up @@ -1629,9 +1630,11 @@ so its ordinary Escape rung remains the route back.
### Item selection is explicit

`s` names the visible items and declared visual parts that Alt-click can aim at. Both
routes read `aimTargetAt`, then raise the same comment and reaction bar. The target kind
changes only the anchor: a whole item names its authored id, while a visual part adds
its declared token and resolves the bar against that part's geometry.
routes read `aimTargetAt`, and the target kind changes only the anchor: a whole item
names its authored id, while a visual part adds its declared token. Their next surface
follows the gesture's stated intent. `s` selects the target and raises its Comment and
reaction bar; Alt-click promises Comment and opens the composer directly. The same
anchor resolves either surface against the target's geometry.

The short, viewport-local hints form a prefix-free tree over one alphabet. Most targets
cost one letter; only the tail branches when the viewport holds more targets than the
Expand Down
7 changes: 4 additions & 3 deletions skills/leaf/assets/leaf.js
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,6 @@ const { landTyping, mayLandTyping, pageSelection, selectionAnchor, snapSelection
});

const {
activateAimTarget,
BANNER_CLEAR,
activateVisual,
beside,
Expand All @@ -1201,9 +1200,11 @@ const {
fabTargetAt,
fabReturnTo,
openOnItem,
openTargetComposer,
placeClear,
placeComposer,
refreshFab,
selectResponseTarget,
showFab,
standDown,
updateFab,
Expand Down Expand Up @@ -1257,12 +1258,12 @@ const {
});

const { AIM, aimIsOn, aimedItem } = createAim({
activateAimTarget,
aimTargetAt,
designIsOn: () => designOn,
designPress,
designTarget,
inChrome: (node) => inChrome(node),
openTargetComposer,
openOnDesign,
pointerAt,
refreshAim,
Expand Down Expand Up @@ -1928,7 +1929,6 @@ const { GO, GOTO, isChordArmed, paintAddresses, setChord } = createAddress({

const { PAGE_SEARCH, SELECT, isSelecting, paintTargets, startSelecting } =
createTargetSelection({
activateAimTarget,
aimTargets,
allButTheReference,
anchoringIsReady: () => anchoringReady,
Expand All @@ -1952,6 +1952,7 @@ const { PAGE_SEARCH, SELECT, isSelecting, paintTargets, startSelecting } =
selectionLayer,
selectionSearch,
selectionStatus,
selectResponseTarget,
shownParts,
shownRect: (...args) => shownRect(...args),
updateFab,
Expand Down
5 changes: 2 additions & 3 deletions skills/leaf/assets/runtime/anchors.js
Original file line number Diff line number Diff line change
Expand Up @@ -742,9 +742,8 @@ export function createAnchors(dependencies) {
// a promise has to interrupt where an annotation may whisper, so the aim has a box
// of its own in the chrome's layer (refreshAim, and the .lf-aim rule's account of
// why). An open composer doesn't stand the aim down — a press while the box is up
// selects another target and raises its action bar — so the two can show at once,
// which is the true state: where the draft stands, and where the next response would
// land.
// moves the draft onto another target — so the two can show at once, which is the
// true state: where the draft stands, and where the next comment would land.
const draft =
composerIsOpen() && composerAnchor()
? resolveAnchor(composerAnchor(), text)
Expand Down
18 changes: 11 additions & 7 deletions skills/leaf/assets/runtime/composing/aim.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function createAim({
activateAimTarget,
openTargetComposer,
aimTargetAt,
designIsOn,
designPress,
Expand Down Expand Up @@ -32,19 +32,19 @@ export function createAim({
// is spelled from the modifier through the register's own table rather than written out
// twice in two platforms' glyphs.
const AIM = {
id: "aim.respond",
id: "aim.comment",
modifier: "Alt",
keys: [],
label: `${spell("Alt")} click`,
does: "Respond to the item under the pointer",
does: "Comment on the item under the pointer",
when: aimIsAvailable,
};
// What the pointer is over, asked of the page rather than of an event, so pressing the key
// without moving the mouse answers too — the user holds ⌥ to find out what they would
// get, and the answer cannot wait for them to jiggle the mouse first. An open composer
// is no reason to say nothing: the press still acts (it selects another target and
// raises its actions), so the promise still paints — what stood down here left that
// one press made blind.
// is no reason to say nothing: the press still acts (it moves the draft onto another
// target), so the promise still paints — what stood down here left that one press made
// blind.
function aimedTarget() {
const pointer = pointerAt();
if (pointer.x < 0) return null;
Expand Down Expand Up @@ -148,7 +148,11 @@ export function createAim({
if (ev.type === "mousedown" || ev.type === "click") ev.preventDefault();
ev.stopPropagation();
if (ev.type !== "click") return;
if (claimedPress.aim) activateAimTarget(claimedPress.aim);
if (claimedPress.aim)
openTargetComposer(claimedPress.aim, {
left: ev.clientX + 6,
top: ev.clientY - 40,
});
else if (claimedPress.design)
openOnDesign(claimedPress.design, {
left: ev.clientX + 6,
Expand Down
18 changes: 14 additions & 4 deletions skills/leaf/assets/runtime/composing/surface.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,20 @@ export function createSelectionSurface({
showFab(null, null, { returnFocus: "none" });
openComposer({ section: item.id }, "", from.left, from.top);
}
// Pointer and keyboard aim choose a semantic target. Every target then raises the
// same actions; its anchor alone decides which element supplies the mark and geometry.
function activateAimTarget({ anchor }) {
// Keyboard selection chooses a semantic target before the reader has chosen what to
// do with it, so it raises the shared response actions. The anchor alone decides which
// element supplies the mark and geometry.
function selectResponseTarget({ anchor }) {
showFab(anchor);
}
// Alt-click already names the action as well as the target. It opens the composer in
// the same transaction instead of asking the reader to choose Comment a second time.
// Target capture still happened before this door, so comments and reactions keep one
// durable anchor model and the claimed press still reaches nothing underneath it.
function openTargetComposer({ anchor }, from) {
showFab(null, null, { returnFocus: "none" });
openComposer(anchor, "", from.left, from.top);
}
// The button follows the selection. What counts as one is measured on the quote it would
// store, not on the selection's own toString(): those are different strings, and gating on
// the one the reader sees while storing the one the document holds lets a two-character
Expand Down Expand Up @@ -602,10 +611,11 @@ export function createSelectionSurface({
fabTargetAt,
fabReturnTo,
openOnItem,
activateAimTarget,
openTargetComposer,
placeClear,
placeComposer,
refreshFab,
selectResponseTarget,
showFab,
standDown,
updateFab,
Expand Down
10 changes: 5 additions & 5 deletions skills/leaf/assets/runtime/composing/targets.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { sameAnchor } from "../anchors.js";
import { bindings } from "../keyboard/bindings.js";

// Keyboard item aim and whole-page text search. `s` opens a viewport-local map of the
// same stable items and visual parts Alt-click reaches; `/` opens the page's text search
// directly or from that map.
// Keyboard item selection and whole-page text search. `s` opens a viewport-local map of
// the same stable items and visual parts Alt-click reaches, then raises their general
// response actions; `/` opens the page's text search directly or from that map.

const HINT_KEYS = [..."asdfghjklqwertyuiopzxcvbnm"];
const HINT_INDENT = 10;
const MIN_SEARCH = 3;

export function createTargetSelection({
activateAimTarget,
selectResponseTarget,
aimTargets,
allButTheReference,
anchoringIsReady,
Expand Down Expand Up @@ -273,7 +273,7 @@ export function createTargetSelection({
function choose(target) {
setOpen(false);
document.body.focus({ preventScroll: true });
activateAimTarget(target);
selectResponseTarget(target);
announce(`Selected ${target.label}. Choose a response.`);
}

Expand Down
Loading
Loading