Skip to content

Glance: a tall chat card scrolls in its panel tile, its field in view - #300

Merged
ymote merged 9 commits into
mainfrom
fix/glance-tall-chat-card
Oct 2, 2026
Merged

ymote merged 9 commits into
mainfrom
fix/glance-tall-chat-card

Conversation

@ymote

@ymote ymote commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #278 ("A chat card outgrows its tile", in its follow-ups), built on #290's panel. Stacked on #299: merge that first. Until then this diff also shows its commit. Without it, a press on a chip in the panel opens the card window, so a chat cannot be started in the panel at all.

What was wrong

After its first exchange, a card's in-card chat (sys.chat) grows past the glance panel's 440 pt tile (glance_card TILE_MAX_HEIGHT). The tile clips the card at its bottom, which is where the conversation goes on. On main with #299 the field and its send arrow are cut in half at the tile's edge after one question, and Back is hidden. One more exchange hides them completely. The card window scrolls a tall card, but the panel did not.

The fix

Two options were on the table: grow the tile up to a cap, or scroll the transcript inside it. Scrolling fits the panel better. The panel lists cards a card at a time under one uniform cap, and the card window is the full view, so a chat card that took the whole column would push every other card out of it.

  • A card taller than its tile scrolls inside it (glance_panel.rs TileScroll), with a thin thumb at the tile's edge.
    • A card that reads a sys.chat sits at its newest rows and follows them as the conversation grows. Its field, its send arrow and the latest exchange stay in view, and older rows scroll off the top.
    • Any other tall card starts at its top, as before.
    • A card that peeks in at the list's end shows its top, as before.
  • The wheel over a tall card scrolls it first. At its ends the wheel moves the list a card at a time, as before. Scrolled back down to its bottom, a chat follows its newest rows again. Scrolled up, it stays on the rows being read while a reply comes in.
  • Glance and notifications UX: undo, keyboard, motion, cards that follow the mode #290's check still holds. A press the card's own controls do not claim opens the card window; a press in its field does not. The tile scrolls through its frame's own layout (GlanceTiles::draw_scrolled, View::set_scroll_pos), not through a scroll view, whose bars would claim every press. The card's hits stay where it is drawn, since makepad hit-tests clipped rects.
  • glance_card.rs: draw_scrolled and overflow() (how much taller than its tile a card is). The card window's tiles scroll by themselves and ignore it.
  • The panel logs where its tall cards sit, once per change (glance panel: scrolled os.mail/ana-contract 78/78 newest), for a remote run.
  • README.md and README.zh-CN.md: the "Desktop panel" row says so.

Tests

On the Linux build host, at this PR's head (47563d10, on #299 at e1f3c979, main at 8e65d5c6), with its own target dir and CARGO_BUILD_JOBS=12:

Check Result
from phone/: cargo test --locked --features mobile-apps -p octosense-shell glance 51 passed
from phone/: ... -p octosense-shell mobile; ... phone 104 passed; 5 passed
from phone/: OCTOS_SHELL_TEST_KERNEL=<octos ae230ce0> cargo test --locked --features mobile-apps -p octosense-shell 800 passed
from phone/: OCTOS_SCENARIO_TEST_KERNEL=<octos ae230ce0> ... --lib host_tools::scenario_tests -- --test-threads=1 5 passed
cargo test --locked -p octosense-l0-chat; its clippy with -D warnings 9 passed; clean
cargo check --locked -p octosense (and --features mobile-apps); from phone/: cargo check --locked -p octosense-home --features mobile-apps; python3 tools/setup.py --check --cargo pass

tools/ci-local.sh was not run.

New tests:

  • glance_panel::tests::a_chat_card_taller_than_its_tile_shows_its_newest_rows
  • glance_panel::tests::the_wheel_scrolls_a_tall_card_and_leaves_the_list_its_ends
  • glance_card::tests::a_card_taller_than_its_tile_overflows_by_the_difference

Instrument check (headless, desktop panel)

The release desktop shell ran on the Linux host under a headless weston (--backend=headless --renderer=pixman), driven through makepad's remote instrument with OCTOSENSE_GLANCE_DEMO=mail: two fake Mail cards, the canned reply, no mail read and no model called. Its default style was used (light), and the demo cards open the glance panel. Frames come from MAKEPAD_WRITE_FRAMEBUFFER_PNG. Chips were found by colour inside the tiles that the panel logs.

# Scenario Result
B main + #299 (e1f3c979): after one question the chat card is at the 440 pt cap. The tile cuts its field and send arrow at its bottom edge (26 of the arrow's 40 pt show), and Back is hidden reproduced
A1 The demo cards open the panel PASS
A2 Ask opens the in-card chat in the panel (a 362 pt tile with the seeded exchange) PASS
A3 One question (typed, then Return): the card overflows the 440 pt tile by 78 pt and sits at its newest rows (glance panel: scrolled os.mail/ana-contract 78/78 newest). The field and its arrow show whole, and Back too PASS
A4 A second question, typed in that field: the card grows (233/233 newest) and the field stays in view PASS
A5 The wheel up over the card shows older rows, and the list stays put. The wheel down returns to the newest rows. At that end the wheel moves the list a card, and back PASS
A6 A press in the field opens no card window (the field claims it). A press on the transcript opens the card window (#290's check), and Esc closes it PASS

Not covered

  • The phone's glance page. It has the same 440 pt cap (Glance: the phone's glance page runs its cards' taps and in-card chat #292's last instrument frame), but its wheel scrolls the page, so pinning a chat card there is left for a follow-up. draw_scrolled makes that a small change.
  • The person's own message does not snap a chat they scrolled back from to its newest rows; the wheel down does.

🤖 Generated with Claude Code

ymote and others added 9 commits October 2, 2026 13:57
The supervisor saw the kernel end (its stdout closing, or its exit
status) and read the stderr tail at once, but stderr is copied into the
tail by a task of its own. The line the kernel wrote just before it
exited could still be in the pipe, so the reason lost it: CI's
`a_kernel_that_dies_closes_its_connections_with_its_last_words` failed
with "the kernel closed its output: fake kernel up", without the
"asked to exit" the fake kernel wrote last.

The tail is now read after the kernel is stopped and the stderr task has
reached the end of the pipe (bounded at 2 s, for a process the kernel
started that still holds the pipe). Failed reasons get the same complete
tail. The test asks for the last words themselves, not just "exit".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… probes

The glance tile tests register probe system apps (os.glancetile,
os.glanceinput, os.glancequiet, os.glancesweep) in App Hub's
system-app registry. That registry is process-wide, has no removal, and
is the one `system_card_apps` lists, so a catalog test running after a
glance test in the same process saw the probes as launcher rows:
`bundled_apps_open_without_catalog_files_or_child_processes` failed its
catalog comparison 7 of 10 times beside `glance_card::` on main, and
`the_system_apps_ship_as_card_apps` compares the same list.

Tests now register their system apps through
`apps::test_system_apps::register`, which notes the id before it
registers it, and the tests of the build's own catalog leave noted ids
out. Production code is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`approvals::tests::the_relay_gets_decisions_made_before_it_was_installed`
began with `init_memory()`, which replaces the shell's process-wide
approvals with empty ones. Every consent a test running beside it had
given was gone. `app_chat::tests::the_panel_opens_a_sharing_context_and_sends_person_turns_there`
allows its app, then reads `agents::line()`, which reports a peer slug
only while the app is allowed. Beside `approvals::` it failed 50 of 50
runs with `peer_slug: null`. The same test also installed its recording
relay into the shared approvals, where it would take other tests'
decisions.

The relay seam (`approval_requested`, `set_relay`, `bus_requested`) is
now methods on `Approvals`. The free functions call them on the shell's
approvals, as before. The test builds `Approvals::memory()` and never
touches the shared one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the whole connect's

`a_jail_only_app_reads_its_jail_and_is_refused_everything_else` failed
on GitHub's macOS 14 runners with "the hub is reachable: " and nothing
after it: the sandboxed `nc -z` exited 1 without a word, because nc
prints why a connect failed only with `-v`. Its exit status says only
that the connect did not complete. A sandbox refusal and a refusal past
the sandbox (no listener on the port then, the network stack) look the
same, though only the first is what this test is about. Closing the
hub's listener before the probe reproduces the CI message exactly.

The probe now runs `nc -v -n` and the test reads what nc says. The hub
fails only on the sandbox's own refusal (EPERM from Seatbelt and
seccomp, EACCES from Landlock), or when nc never reported a connect at
all. A refusal past the sandbox is printed and passes. Any other port
must now be refused by the sandbox itself, not just fail, which the old
check could not tell apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
octos#2675 makes session/hydrate rows name their tool calls: a tool row
carries tool_call_id and tool_name, an assistant row its tool_calls. The
shell's system chat and "Ask <app>" panel already read tool_call_id and
name/tool_name, and the broker's merged history passes rows through, so
reloaded tool rows now show their tool's name instead of "tool". The
two-lane scenario checks it against the real kernel: News's agent's
news_share call in the panel's reloaded history, and the system chat's
peer_send_input.

The pin also brings #2261, #2664, #2665, #2669, #2671 and #2672.
octos-core, octos-cli, octos-research and the kernel artifact move
together; Cargo.lock changes only the 17 octos source lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w behind it

A module tile claimed presses but never a hover. A mouse move over a part
of a card window that takes no hover (Mail's empty page) went on to the
tile behind unclaimed, and the app there took it: a News button under Mail
lit up and showed its hand cursor. A tile behind also handed its app every
move, claimed or not, as it handed presses before #277.

The live tile now treats a hover (a mouse move with no button held) the
way #277 treats a press. A hover inside it that none of its app's widgets
took is the tile's, and one a window in front already took is let by, so
its app never sees it. The one exception is the move that leaves a widget
of the app still holding the hover: it reaches the root, so that widget
sees its hover end, and being claimed it lets no other hover start. The
closed face of a stopped app claims a hover inside it too. Moves with a
button held (drags) pass as before, so captures keep working.

Tests, in module_input_tests.rs: a hover on the window in front never
reaches the app behind; makepad's Button behind it never lights up or
shows its hand; a hover on a stopped face never reaches the app behind it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot at its top left

The closed face draws its line and Restart button centred, but it kept
the rect `walk_turtle` returned for the button as Restart's hit rect. The
face's turtle centres its content when it ends, which moves the drawn
button after it was walked, so the kept rect stayed at the face's top
left. A press on the visible button only raised the window, and a press
at the top left, where nothing is drawn, restarted the app.

The face now hit-tests Restart by the button's drawn area, as any widget
is hit-tested: clipped, at the time of the press. It keeps no rect of its
own.

Tests, in module_input_tests.rs: Restart responds where it is drawn (a
click and a tap on the visible button restart the app, a press at the top
left only raises the window), and #277's stopped-face test now presses the
drawn Restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e card window

Since #290 a press on a card in the glance panel that the card's own
controls do not claim opens the card in the card window on its release
(pointer_claimed_area across the tiles' dispatch). A field claims its
press, but a chip does not: an L0 card's tap targets are OctoscriptTap,
which never calls event.hits (so a list can still scroll under it) and
fires on the release. So every press on Reply, Ask, Track or Send opened
the card window and closed the panel, and the chip's tap ran in the
panel's session as the panel forgot it: the card's buttons and its
in-card chat could not be used in the panel.

- glance_panel.rs: a press released on its card opens the card window a
  frame later, unless the card clicked by then. A tap target calls its
  card once the release's handlers have run, and the click is seen
  before the card's taps run, on any event after the release's.
- glance_card.rs: has_clicks, whether an isolate has a click waiting (a
  NAV call with no text; a field's carries what was typed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… view

After its first exchange a card's in-card chat (sys.chat) grew past the
glance panel's 440 pt tile (glance_card TILE_MAX_HEIGHT), and the tile
clipped the card's bottom: its field, its send arrow and Back were cut
off, so the person could not type the next question in the panel.

- glance_card.rs: GlanceTiles::draw_scrolled draws a card scrolled up
  inside its tile, through the tile frame's own layout scroll (no scroll
  view: its bars would claim every press, and an unclaimed press on a
  card is what opens the card window). overflow() says how much taller
  than its tile a card is. The card window's tiles scroll by themselves
  and ignore it.
- glance_panel.rs: TileScroll. A card taller than its tile scrolls inside
  it, with a thumb at its edge. A card that reads a sys.chat sits at its
  newest rows and follows them as the conversation grows, so the field
  and the latest exchange stay in view. Any other card starts at its top,
  and one that peeks in at the list's end shows its top. The wheel over a
  tall card scrolls it first and moves the list only at its ends; scrolled
  back to the bottom, a chat follows its newest rows again. The panel logs
  where its tall cards sit, once per change.
- README (en, zh-CN): the desktop panel row says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote

ymote commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Merged in a merge train; local CI (Mac with rom toolchain + --linux-host) passed on the train head 72e5d55, which contains this PR.

  • tools/ci-local.sh --only all --linux-host, started 2026-10-02T14:00:45: passed. Desktop, phone and apps passed on the Mac; apps services, kernel-security and linux-host / sandbox passed on the Linux build host. Its rom.yml steps were skipped on the Linux host, and the train doesn't trigger rom.yml.
  • tools/ci-local.sh --only all on the Mac with the rom toolchain (JDK 17, Android SDK, node 24), started 2026-10-02T14:51:21: 42/42 steps passed, none skipped.

The train was #291, #293, #297, #294, #298, #295, #296, #299, #300 and #292, on main f6678b5. After the last merge, main 301be0c has the tested head's tree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant