Skip to content

mobile: the hosted AppCard stays in its client slot and its card survives the light theme - #24

Closed
ymote wants to merge 1 commit into
feat/mobile-standalonefrom
fix/hosted-appcard-mobile
Closed

ymote wants to merge 1 commit into
feat/mobile-standalonefrom
fix/hosted-appcard-mobile

Conversation

@ymote

@ymote ymote commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Two defects in how the standalone mobile shell hosts the in-process AppCard module, verified on the OnePlus (bf0a4730).

A — the hosted AppCard painted over the whole window

Root cause. The phone desk records every client into a WindowFrame — a pass of its own — and presents the texture in the client's slot under the shell overlays (src/desk/phone.rs record_capture / present_capture, then draw_overlay). A hosted module draws into that pass like a process's frames land in it, except for everything it draws through begin_overlay_*: the AppCard kit's glass surfaces (kit_shared.rs draws a glass role in an overlay list, GlassLayer, GaussRoundedView), its sheets, popups. WindowFrame::begin (src/dock_warp.rs) never scoped the overlay, so DrawList2d::begin_overlay_inner stored those lists in cx.overlay_id — the WM window's Root overlay — painted last of all, unclipped, over the shade's sheet (controls-now2.png: only the strip above the app rect showed), over the keyboard and the home page after a home swipe, and kept on screen while the tile was no longer being drawn (state-now.png: AppCard's sheet over Home). Remote clients never hit this: their frames are pixels.

Fix (OctoSense only). WindowFrame now owns an Overlay and brackets every recording with the fork's Overlay::begin_nested_for_pass / end_nested (added to the fork for exactly this case, unused until now). Everything the app draws as an overlay composites into the capture, after the frame's body, and the enclosing frame's overlay state comes back untouched. The module is therefore drawn in the client's slot — the app viewport, a split pane, a Recents card, a home tile capture — under island, groups, keyboard and shade, and not at all on Home/Recents/Drawer except as its capture. This is the desktop's rounded window surface and the dock warp too.

Touches. MpModuleView::handle_event forwarded every pointer event to the app's root. It now forwards a press, a new touch or a wheel step only when it begins inside the tile's rect (pointer_start, tested) — the gate a process tile gets from event.hits(cx, self.area) — and only after mobile_app.rs::phone_pointer_at has fed the recognizer and it declined the finger (that gate already existed: a claimed finger returns before ui.handle_event). Moves and releases pass, as for any widget.

B — the card rendered empty under the Android light theme

Not a theme or kit defect: a fresh turn on the same standalone build and the same Android light theme rendered the card completely (rerun-3.png: Beijing, 19°, day rows, the city photo backdrop, all readable). The broken weather-card3.png (16:27) came from a retry turn carrying a "[Conversation summary]" compaction context (2959 completion tokens vs 1779 for the good turns; captured as llm-reqs/req-7.json vs req-2.json) that generated a different card. Its pixels are the light kit's opaque photo wash (l0_card.rs::kit_for, photo_wash for a light mood on a Photo root) with l0_dim captions and dark icons — the card declared a light mood the good turns did not. The 16:27 declaration line has rolled out of logcat and the kernel does not keep the generated DSL on disk, so the two sources could not be diffed; noted as model variance, renderer left alone. The light-mood photo wash with white hero text is a kit look worth a separate look in Octoscript-AppCard, not something this shell can fix.

Card approvals across builds

octos-app pins every admitted card to its runtime bundle and fails closed on a stale receipt, so every new APK drew blank cards until l0-approvals was moved aside by hand. That is the host's explicit deployment action (l0_approval_store::archive_for_reapproval, which octos-app runs itself for MAKEPAD_REAPPROVE_CARDS), so OctoSense now does it: build.rs stamps OCTOSENSE_BUILD_ID (renewed by any source change) and the first launch of a new build on Android archives <files>/.config/octos-app/l0-approvals under the app's own backup name (octosense_appcard::reapprove_cards_for_host_build, tested against a temp store). Desktop builds leave the developer's store alone. docs/android-appcard-build.md describes both this and the capture overlay scope.

Verification

Desktop: cargo test (256), cargo test --features mobile-only (253), cargo test -p octosense-appcard (3) green; cargo check --features mobile-only,app-appcard clean. Desktop standalone build, AppCard hosted in the phone window: hosted-desk-appcard.png (the app in its slot), hosted-desk-shade.png (the controls sheet fully over it, the app's glass pills blurred beneath).

Phone (see the screenshots attached to the report):

  • hosted-card-light.png — AppCard in its slot (shell status band above, navigation band below), the Beijing weather card complete: photo backdrop, title, 20°, day rows, all readable, Android light theme (local qwen3.5 turn, 1779 tokens).
  • hosted-shade-over-appcard.png — shade pulled from the top band over the card: the controls sheet is fully visible, the card blurred beneath it (before: controls-now2.png, only the strip above the app rect showed).
  • hosted-shade-dark.png / hosted-card-dark.png — Dark mode tile toggled from that shade: the shell goes dark, the card keeps its photo backdrop with white text in both appearances.
  • hosted-home.png — swipe up from the bottom: the home page, AppCard as a live tile capture (the card's top in the tile), no AppCard chrome over the page (before: state-now.png, AppCard's sheet over everything).
  • hosted-reopened.png — the tile reopens the app in its slot; hosted-recents.png — swipe-up-and-hold: AppCard as a Recents card with its Split button.
  • The re-approval hook fired on the first launch of the new build (wm: host build 1789430568 is new: card approvals archived to .../l0-approvals-before-studio-1789430568), and the card admitted cleanly afterwards.

Still floating over everything: octos-app's native Android composer — the green "+" FAB is a Java view above the GL surface (cx.show_native_composer() under cfg(mobile)), so it shows over the shade, Home and Recents. That is octos-app's to hide when it is not in front (a host signal it does not have yet), not something the compositor can order.

🤖 Generated with Claude Code

…re-admits its cards

The phone desk records every client into a WindowFrame — a pass of its
own — and presents the texture in the client's slot, under the shell's
overlays. A hosted module drew into that pass like a process's frames land
in it, except for what it draws through `begin_overlay_*`: the AppCard
kit's glass surfaces, its sheets, any popup. WindowFrame never scoped the
overlay, so those lists went to `cx.overlay_id` — the WM window's Root
overlay — and were painted last of all: over the shade's sheet (only its
top strip showed), over the keyboard, over the home page after a home
swipe, unclipped, and kept on screen while the tile was no longer drawn.
The fork already has the scope for exactly this case; WindowFrame now
holds an Overlay and brackets every recording with
`Overlay::begin_nested_for_pass` / `end_nested`, so the module's overlays
composite into the capture, last, and the compositor order and the client
rect hold for it as for a remote client. This is the desktop's rounded
window surface and the dock warp too.

The module tile forwarded every pointer event to its root. It now forwards
a press, a new touch or a wheel step only when it begins inside the
tile's rect — the gate a process tile gets from `event.hits` — after the
shell's recognizer has already declined that finger; moves and releases
pass, as for any widget. `pointer_start` is the pure rule, with a test.

octos-app pins each admitted card to the runtime bundle and fails closed
on a stale receipt, so every new APK drew blank cards until the approval
store was moved aside by hand — the app's own deployment action
(`MAKEPAD_REAPPROVE_CARDS`). The host does it now: build.rs stamps
OCTOSENSE_BUILD_ID (renewed by any source change), and the first launch of
a new build on Android archives `<files>/.config/octos-app/l0-approvals`
under the app's own backup name, remembering the id beside it. Desktop
builds leave the developer's store alone. Tested against a temp store.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ymote

ymote commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded: every commit in this stack (#22–#28) is already on OctoSense-ROM main, where the phone shell continued. Per ADR 0001 the phone layer moves into the shared crates/shell from that copy when OctoSense-Desktop and OctoSense-ROM are merged into one OctoSense repository, so nothing here is lost. #25's octos-app pin is obsolete: AppCard now comes from OctoSense-System-Apps on octos main. The branches are kept.

🤖 Generated with Claude Code

@ymote ymote closed this Sep 27, 2026
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
)

Without it bindService throws SecurityException on the ROM and the client
reports denied; verified on the first OctoSense ROM boot.

Co-authored-by: ymote <151983+ymote@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
- Apps' assistant access: src/app_peers_host.rs gives each native module
  whose declared octos.* services host policy grants a scoped service from
  octosense-app-peers (System-Apps crates/app-peers), offered for the
  duration of its create and released at teardown. It is backed by ONE octos
  peer per app and account, owned by the system agent
  _main:api:octosense#system, on the shell's kernel. Modules without granted
  services get no peer.
- Rinx is linked as a native module (app-rinx, default and mobile-apps) with
  octosense-module only. CI checks that its standalone entry and local kernel
  stay out of every graph; its App Hub, System-Apps and makepad sources
  resolve to this workspace's (no URL-alias patches).
- Runtime: OctoScript-Makepad c3d53ba8 (makepad db4691d0: Splash
  reapply_text, stateful mini-app inputs); System-Apps 4fa0f122 (app-peers).
  The Settings runtime patch applies unchanged on the new base.
- Module viewports bound app overlays (from OctoSense-org#24).

AppCard stays opt-in. Supersedes OctoSense-org#24.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
…apps

Integrate hosted Rinx and keep editors above the mobile keyboard
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
ADR 0002 decision C: an app's AI keeps its main path through its own
octos agent and the toolbox's templates; contained script apps also get a
narrow, direct, one-shot model call. The ADR gains section 14, "Direct
one-shot model calls (model.complete)".

The `model` host service (octosense-llm-service, src/complete), registered
by crates/ai-host with the llm service:

- host.request("model.complete", {task, input, schema, class?, allow_urls?})
  answers {output, meta: {class, requested, attempts, usage, budget}};
  model.budget answers the caller's budget.
- The app names a class (fast or strong, octos's catalog types); the host
  picks the provider from the person's profile in their order, that class
  first, and falls back past a failing provider. The app never sees the
  provider, model id or key; meta carries the class and token usage.
- One shot: no tools, memory or history. The JSON Schema (Octoscript's
  bounded subset, 8 KiB) is required; a reply that is not JSON, fails the
  schema, carries a URL (unless allow_urls) or is over 16 KiB is retried
  once, then refused.
- No output-token cap is sent (Anthropic gets the model's own catalog
  maximum, which its API requires).
- A per-app ledger in <host dir>/model/ledger.json: 6 calls a minute,
  100 calls and 100,000 tokens a UTC day by default, with per-app
  overrides for a Settings page (follow-up).
- Refusals are "<code>: <sentence>": capability, no_provider, rate, budget,
  bad_request, invalid_output, too_large, provider.
- The service checks the app's own manifest for `model` behind the Card
  runner's isolate gate. ModelHost::complete is the Rust entry the
  toolbox's ModelClient (OctoSense-org#82) can share, so budgets live in one place.

Tests with a fake provider (tests/complete.rs) and a live example against
DeepSeek (examples/model_complete_live.rs). No pins move: the `model`
capability is in App Hub (OctoSense-org#24) and reaches the shells with the App Hub pin
move in OctoSense-org#70.

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