Skip to content

Glance: the panel's cards run their taps and in-card chat; chat threads keep Mail's account - #278

Merged
ymote merged 1 commit into
mainfrom
fix/glance-panel-taps
Oct 2, 2026
Merged

ymote merged 1 commit into
mainfrom
fix/glance-panel-taps

Conversation

@ymote

@ymote ymote commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Fixes the glance-card mechanics a headless instrument run found (finding B4). No new features.

What was wrong, and why

  1. The glance panel's tiles ran no taps. The panel drew L0 cards with their chips and buttons, but only the card window ran L0 taps (glance_sheet.rs dispatch_taps). The card window opens only from a card's toast, while every new card opens the panel, so in the panel a card's buttons and its in-card chat (sys.chat) did nothing.
  2. A stale doc comment. glance_chat.rs HostResponder's doc said Mail "has no agent yet", stale since Mail and Calendar agents put cards on the glance screen; assistant pane and glance panel polish #267.
  3. desktop/scripts/mail_card_remote.sh waited for notifications: 2 toast(s). Dropped here: Glance: a card's toast always shows; cards and the panel close #273 (merged) shows a notify card's toast again, stacked left of the open panel, so the wait holds again. The script is unchanged and was not run for this PR.
  4. Chat threads landed under the wrong account. The code's rule (glance_chat.rs, crates/l0-chat/README.md): a thread is kept in apps/<app>/accounts/<account>/chat/, for the account the app's agent acts for (contained::account_of, which the shell answers with app_storage::lifecycle::contained_account_in). That is device for an app without accounts or with none yet, and otherwise the app's active account. So device for a signed-in Mail was a bug. contained_account_in decides whether the app keeps accounts from the storage block recorded in memory, which is recorded only when the app opens or its agent is prepared (prepare_agent_with). The demo seeds its thread at startup, before agents::start()'s prepare thread records Mail's block. The lookup then read the default ("no accounts") and answered device. Once the block was recorded, the same thread would have resolved to the account folder, splitting it in two.

Fixes

  • One tap path for both surfaces (glance_card.rs LiveCards). The card window and the glance panel keep their L0 cards in a LiveCards. A tile's L0 taps run through its card's L0Session, which is made for the app that published the card (the glance store's caller). The taps run the declared transition, then the host's sys.chat write (glance_chat::perform → octosense_l0_chat::perform), then a re-lowering when a tap or the agent's reply moved the card. A newer publish of a card starts it over. The panel forgets a card's state when it closes, as the card window does.
  • Taps per isolate. take_taps(heap) used to take the whole queue and drop every other isolate's taps. That is fine with one dispatching surface, but with two, each would have dropped the other's. It now takes only that isolate's taps. A swept tile drops its queued ones, because a heap key is unique only while its isolate lives.
  • Card window: it now uses the same LiveCards. Its redraw now repaints its own area (draw_bg is never drawn), so an agent's reply that arrives later shows at once. This matches the fix already made in glance_panel.rs.
  • The account rule: contained_account_in now reads the manifest's storage block when none has been recorded yet, as prepare_agent_with does. The demo's thread is now under Mail's account folder. In octosense-l0-chat, a reply is now kept in the thread its message went to. Before, if the account switched while the agent was answering, the reply was written into the new account's thread and the first thread stayed answering.
  • The doc comment: HostResponder now says the canned reply under OCTOSENSE_GLANCE_DEMO=mail is there by design (the demo cards and thread are fake data). The canned reply is kept.
  • desktop/README.md and README.zh-CN.md: the OCTOSENSE_GLANCE_DEMO=mail row now says the demo cards work the same in the glance panel. The root README is not edited.

Security properties kept (crates/l0-chat/README.md, the README's "Cards and questions")

  • The publisher's own policy. Tiles are seated under the app's resolved policy as before (GlanceTiles::seat). This PR only adds the dispatch.
  • Only the publisher's own agent. The session's app is card.app, the caller recorded at publish, never anything from the tile. The sys.chat write still goes through octosense_l0_chat::perform's publisher check and HostResponder.
  • Only what the person typed counts as theirs. Unchanged: event_payload_origin must be UserInput.
  • No tile can drive another. A tile's taps come only from its own isolate, so a card, a script card included, cannot drive another card's session. A test covers this.

Tests

On the Linux build host, at this PR's head (fa88b7d, on main at 4f73ab34), with its own target dir and CARGO_BUILD_JOBS=16:

Check Result
cargo test --locked -p octosense-l0-chat 9 passed
cargo clippy --locked -p octosense-l0-chat --all-targets --no-deps -- -D warnings, cargo fmt -p octosense-l0-chat --check clean
from phone/: cargo test --locked --features mobile-apps -p octosense-shell glance 42 passed
from phone/: ... -p octosense-shell app_storage 58 passed
from phone/: OCTOS_SHELL_TEST_KERNEL=<octos ae230ce0> cargo test --locked --features mobile-apps -p octosense-shell 780 passed
from phone/: OCTOS_SCENARIO_TEST_KERNEL=<octos ae230ce0> ... --lib host_tools::scenario_tests -- --test-threads=1 5 passed
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_card::tests::taps_are_taken_by_the_isolate_that_queued_them
  • glance_card::tests::the_panels_tiles_run_their_own_l0_taps_like_the_card_window
  • glance_card::tests::a_card_with_buttons_dispatches_from_the_panel_without_the_demo
  • glance_chat::tests::a_cards_thread_is_kept_under_the_account_the_agent_acts_for
  • app_storage::lifecycle::tests::should_read_the_manifests_block_when_the_account_is_asked_first
  • octosense-l0-chat a_reply_stays_with_the_account_its_message_went_to

Instrument check (headless)

The release desktop shell at fa88b7d ran on the Linux host under a headless weston (--backend=headless --renderer=pixman), driven through makepad's remote instrument with the instrument harness's scripted mock model. No keys were used. Frames come from MAKEPAD_WRITE_FRAMEBUFFER_PNG, because /g does not answer on Linux GL. Chips were located in the frame by colour inside the tile the panel logs, so no click relied on a guessed position.

# Scenario Result
A1 No demo: Mail signed in and allowed; the system agent has Mail's agent call mail.notify; the card opens the panel and is drawn through the live path. A press on it runs no tap (the notice declares no events) PASS
B1 OCTOSENSE_GLANCE_DEMO=mail: the cards open the panel; the shipping card's Track runs in the panel (glance panel: os.mail/ups-lamp tap track (applied true, relower true)) PASS
B2 The request card's Reply opens the draft in the panel; Cancel goes back PASS
B3 Ask opens the in-card chat in the panel with the host's transcript. The thread is apps/os.mail/accounts/<hash of Mail's account>/chat/ana-contract.json, with nothing under device (before: accounts/device/) PASS
B4 A question typed in the panel's card (tap typing, not re-lowered), Return → tap submit → chat convo recorded; the canned reply follows. The account's thread file holds the seeded exchange, the question and the reply PASS
B5 Closing and reopening the panel forgets the card's state; Reply then Send → "Sent (demo)" PASS
C1 A card's toast (#273) opens the card window; Ask and a sent question work there too, on the same account thread; Esc closes it PASS

Without the demo, main has no card with buttons: the shared notice card every app's <app>.notify publishes (crates/shell/resources/glance/notice.card, #274) and Calendar's event.card and agenda.card declare no events. So "a card with buttons dispatches from the panel without the demo" is covered by the shell test a_card_with_buttons_dispatches_from_the_panel_without_the_demo, which publishes the shipping card as a store app with no demo flag and taps Track and Back from its tile's isolate.

Overlaps

Follow-ups

  • Agent cards have no chat. The card mail.notify publishes (since System apps: every one has its own agent that can notify #274 the shared notice.card) declares no sys.chat, and neither do Calendar's cards. Giving an agent's card an in-card chat is a product decision.
  • A chat card outgrows its tile. In the panel, a chat card grows past the 440 pt tile cap after the first exchange, and the cap clips its field and Back (seen in B4). The card window scrolls a tall card instead.
  • The phone's glance page. It draws the same tiles but does not dispatch L0 taps yet (mobile_pages.rs). Unverified on a device; not changed here.
  • Stale doc. docs/architecture.md §5 (Approvals, "Agent tool calls, not the person's own actions") still says a glance tile has no input and that interactive cards are a PR branch. This has been stale since Interactive glance cards under the publishing app's policy [skip ci] #153.

🤖 Generated with Claude Code

…Mail's account

The desktop glance panel drew L0 cards with their chips and buttons but
ran none of their taps: only the card window did (glance_sheet.rs
dispatch_taps), which only a card's toast opens, while every new card
opens the panel. A card's buttons and its in-card chat (sys.chat) did
nothing there.

- glance_card.rs: `LiveCards`, kept by both surfaces. A tile's L0 taps,
  taken from its own isolate only, run through its card's L0Session, made
  for the app that published the card: the declared transition, the
  host's sys.chat write (glance_chat.rs, octosense-l0-chat) and a
  re-lowering when a tap or the agent's reply moved the card. take_taps
  now takes one isolate's taps and leaves the others' (two surfaces
  dispatch), and a swept tile drops its queued taps (a later isolate may
  get the same heap key).
- glance_panel.rs: tiles draw their card as its session has it and
  dispatch after each event. glance_sheet.rs uses the same path; its
  redraw now repaints its own area, so a reply that comes later shows.
- Chat threads: glance_chat.rs keeps a thread in the account folder of
  the account the app's agent acts for. contained_account_in read whether
  the app keeps accounts from its recorded storage block, recorded only
  when the app opens or its agent is prepared; asked earlier (the demo's
  seed at startup), it answered `device` for a signed-in Mail. It now
  reads the manifest's block when none was recorded yet.
- octosense-l0-chat: a reply is kept in the thread its message went to,
  even when the account switched while the agent answered.
- glance_chat.rs: HostResponder's doc no longer says Mail has no agent;
  the canned reply under OCTOSENSE_GLANCE_DEMO=mail stays by design.
- desktop README (en, zh-CN): the demo cards work the same in the panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote
ymote force-pushed the fix/glance-panel-taps branch from b1ca381 to e5d537e Compare October 2, 2026 17:14
@ymote

ymote commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Local CI passed on e5d537e (tools/ci-local.sh --only all --linux-host, 7m23s, Darwin arm64; the ubuntu jobs on the Linux build host, Linux x86_64).

GitHub would run for this PR: desktop.yml, phone.yml, apps.yml. Merged on this local pass (macOS runner queue); GitHub CI runs on the merge commit on main.

Job Step Result Time
ci-local / drift The local mapping fits the workflows PASS -
desktop.yml / desktop Prepare the pinned framework sources PASS 1s
desktop.yml / desktop Compile the desktop with the shared runtime PASS 1m24s
desktop.yml / desktop One copy of every shell source PASS -
desktop.yml / desktop Desktop tools and scripts PASS 8s
desktop.yml / desktop One Makepad, App Hub, octos and Rinx in the locked graph PASS 4s
desktop.yml / desktop The native apps' generated places match native-apps.json PASS 0s
phone.yml / home Prepare pinned sources and the reviewed runtime patch PASS 0s
phone.yml / home Compile Home and its bundled modules PASS 1m16s
phone.yml / home Verify the single runtime graph PASS 2s
phone.yml / home The native apps' generated places match native-apps.json PASS 0s
phone.yml / home The pinned octos revision PASS -
phone.yml / home Build octos (unless cached) PASS -
phone.yml / home Build Home, App Hub admission and runtime policy tests PASS 37s
phone.yml / home Test Home, App Hub admission and runtime policy PASS 2m45s
phone.yml / home Two-lane scenario (real kernel, scripted model) PASS 30s
apps.yml / services (linux) python3 tools/setup.py PASS 0s
apps.yml / services (linux) Kernel service PASS 12s
apps.yml / services (linux) App peers PASS 25s
apps.yml / services (linux) AI providers config PASS 4s
apps.yml / services (linux) L0 in-card chat (sys.chat, host side) PASS 8s
apps.yml / services (linux) System toolbox (workflow templates) PASS 1m33s
apps.yml / kernel-security (linux) python3 tools/setup.py PASS 0s
apps.yml / kernel-security (linux) The pinned octos revision PASS -
apps.yml / kernel-security (linux) Build octos (unless cached) PASS -
apps.yml / kernel-security (linux) Real-kernel tests PASS 32s
apps.yml / apps python3 tools/setup.py PASS 0s
apps.yml / apps Host services (Mail, AI providers, News) PASS 15s
apps.yml / apps Shell AI services (crates/ai-host) PASS 8s
apps.yml / apps AppCard runtime setup safeguards PASS 0s
apps.yml / apps AppCard clippy (own crates; shared runtime compiles) PASS 4s
apps.yml / apps AppCard transport and store PASS 6s
apps.yml / apps One Makepad, App Hub, octos and Rinx in the locked graph PASS 2s
rom.yml / product (linux) python3 -m unittest discover -s tests -v SKIPPED (!) -
rom.yml / product (linux) Check generated Agent Binder client SKIPPED (!) -
rom.yml / product (linux) bash -n scripts/stage-forks.sh scripts/publish-release.sh scripts/apply-to-tree.sh scripts/build-rom.sh PASS -
rom.yml / web-installer (linux) npm ci --ignore-scripts --no-audit --no-fund SKIPPED (!) -
rom.yml / web-installer (linux) npm test SKIPPED (!) -
rom.yml / web-installer (linux) npx playwright install --with-deps chromium SKIPPED (!) -
rom.yml / web-installer (linux) npm run test:browser SKIPPED (!) -
rom.yml / web-installer (linux) python3 -m unittest discover -s tests -p test_installer_manifest.py -v SKIPPED (!) -
rom.yml / web-installer (linux) bash -n scripts/release.sh SKIPPED (!) -
linux-host / sandbox (linux) python3 tools/setup.py PASS 0s
linux-host / sandbox (linux) Process sandbox on Linux (Landlock, seccomp) PASS 1m21s

Skipped (these did NOT pass; (!) marks a skip that blocks tools/ci-local-merge.sh):

  • rom.yml / product (linux): python3 -m unittest discover -s tests -v: javac -version does not work here: install a JDK (17 or newer) and put its bin/ on PATH or set JAVA_HOME
  • rom.yml / product (linux): Check generated Agent Binder client: needs build-tools/35.0.0/aidl, platforms/android-35/framework.aidl in the Android SDK (no Android SDK found): install the SDK packages 'build-tools;35.0.0' and 'platforms;android-35' (sdkmanager) and set ANDROID_HOME
  • rom.yml / web-installer (linux): npm ci --ignore-scripts --no-audit --no-fund: no node, npm, npx on PATH (the workflow uses node 24); put node on PATH
  • rom.yml / web-installer (linux): npm test: no node, npm, npx on PATH (the workflow uses node 24); put node on PATH
  • rom.yml / web-installer (linux): npx playwright install --with-deps chromium: no node, npm, npx on PATH (the workflow uses node 24); put node on PATH
  • rom.yml / web-installer (linux): npm run test:browser: no node, npm, npx on PATH (the workflow uses node 24); put node on PATH
  • rom.yml / web-installer (linux): python3 -m unittest discover -s tests -p test_installer_manifest.py -v: no node, npm, npx on PATH (the workflow uses node 24); put node on PATH
  • rom.yml / web-installer (linux): bash -n scripts/release.sh: no node, npm, npx on PATH (the workflow uses node 24); put node on PATH

Notes:

  • GitHub runs Python 3.12; this run used Python 3.9.6
  • GitHub runs Python 3.12; the Linux host used Python 3.14.4

@ymote
ymote merged commit 8624112 into main Oct 2, 2026
5 checks passed
ymote added a commit that referenced this pull request Oct 2, 2026
The phone's glance page drew L0 cards with their chips and fields but
ran none of their taps: only the desktop's card window and glance panel
kept their cards live (glance_card.rs LiveCards, #278). A card's buttons
and its in-card chat (sys.chat) did nothing on a phone.

- mobile_pages.rs: GlanceCards, the page's tiles with a LiveCards. A
  tile's taps, from its own isolate only, run through its card's session,
  made for the app that published the card, as on the desktop. The feed
  holds the published GlanceCard itself (its L0 source included) instead
  of a GlanceCardItem copy without it.
- The shell decides what a tap is (GlanceFinger). A card's clicks (NAV
  calls without text) count only for the lift of a plain tap on that
  card: no swipe or pull the recognizer took, under 12 pt of travel, not
  on the shell's own controls, not a finger a long press took. That is
  the rule the shell's own hits follow. A tap target calls NAV at the end
  of the event's cycle, so a click is judged on a later event by the last
  pointer event. Field edits carry the person's text and always run. A
  tile is tappable only where the page shows it (its cap, above the page
  indicator and the dock). Page swipes and the page's scroll are
  unchanged and click no card.
- lib.rs: the page's cards get every event as before, plus the finger,
  read before the shell handles the event.
- glance_card.rs: drop_clicks; docs (the phone dispatches now).
- desktop README (en, zh-CN): the demo cards work on a phone style's
  glance page too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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