Skip to content

feat(mobile): keep Home widgets current and redesign every size - #7324

Open
iscekic wants to merge 32 commits into
mainfrom
igor/widget-refresh-design
Open

iscekic wants to merge 32 commits into
mainfrom
igor/widget-refresh-design

Conversation

@iscekic

@iscekic iscekic commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Home widgets no longer show "Status expired" or a zero ledger. They keep the last known work with an honest Checked time, refresh natively in the background on both platforms, and every size and family was redesigned.

Shared rules — @kilocode/app-shared/home-widget

  • One module decides status priority, freshness, content selection, scheduled-run text and action rules for iOS and Android.
  • Widget retention is separate from Live Activity expiry: the Live Activity snapshot stays privacy-minimal, while titles and the approval key travel only in HomeWidgetDetails.
  • Refresh interval is 30 min while agents are active, waiting or the data is stale, and 2 h when quiet.

Fresh data without the app open

  • A dedicated read-only widget credential (audience kilo-home-widget, 30 days, bound to the device session) authenticates only GET /api/mobile/widgets and POST /api/mobile/widgets/push-token (docs/token-issuance-policy.md).
  • iOS: an authenticated WidgetKit timeline provider, plus the iOS 26 WidgetPushHandler fed by APNs widget reload hints from the notifications service.
  • Android: a WorkManager refresh worker with an AndroidKeyStore-encrypted credential store.
  • Only HTTP 401 is terminal. Sign-out and privacy events clear widget data immediately.

Safe approval

  • The in-place Approve binds to the exact visible request: approvalKey = SHA-256 of [kiloSessionId, permissionId], minted by a new internal Cloud Agent route, so a stale tap cannot approve a newer request.

Design

  • Small: Kilo header with fixed Approve and New-agent slots, dominant count, primary status, one detail line, Checked footer.
  • Medium and large: same header and hero, richer rows for waiting and scheduled agents, no zero rows, pinned slots so controls never move.
  • Scheduled states show the next run time; a missed wake shows "Awaiting update" instead of inventing work.
  • Lock Screen families (circular, rectangular, inline), the locked/privacy state and all ten Android cells follow the same rules, in light, dark, Tinted and Clear, and mirrored in Arabic.

Verification

E2E on an isolated GCP VM (web app, workers, Android emulator API 35) and an owned iOS 26.5 simulator on the Mac, both signed in through native device auth as a fake account. Nothing is committed as an image.

  • Native device-auth sign-in on Android and iOS, with Help improve Kilo off and only the required consent accepted.
  • iOS background fetch: with the app terminated, the widget extension fetched GET /api/mobile/widgets (200) and the widget's Checked time advanced.
  • Android background fetch: the WorkManager job fetched GET /api/mobile/widgets (200) with the app backgrounded and the Checked time advanced.
  • Transient failure keeps last-known content and its Checked time on both platforms; a later refresh updates it.
  • Widget credential persists across a relaunch (Android protectedContext, iOS Keychain item) and drives those fetches.
  • No "status expired" state appears anywhere; retained data is labelled Last known with a real time.
  • Visual matrix: 36 fixtures × light/dark × ten Android cells (portrait and landscape), 36 × light/dark × six iOS families, plus iOS Tinted and Clear, German and Arabic (RTL) subsets, and explicit Android German and Arabic RTL captures.
  • In-place Approve on a live ask (Android): with a live pending permission the widget drew the Approve control (evidence/android-approve3/10-widget-approve.png), and the press reached the server (cloudAgentNext.answerPermission 200, worker "Permission answer forwarded to wrapper"). The approvalKey in the stored payload is exactly SHA-256 of JSON.stringify([kiloSessionId, permissionId]) for that ask.
  • Stale press approves nothing: a second Approve press produced no second answer and opened the agents list; the approval marker file was never created.
  • Approval result not observed in the local environment: the control-plane answer path did not consume the ask or resume the agent, and a direct answer that bypasses the widget failed the same way, so this is not a widget defect.
  • Cold "New agent" press from the widget: the app was terminated, the widget + was tapped, and the app cold-started into the New session composer. The marker is now carried across the press's own widget reload with a 5-minute TTL, and a stale marker is cleared without running. On Android the same press cold-starts the app process, but an Expo dev-client cold start cannot load its bundle without the dev launcher, so the composer is reached there only in a real build.

Screenshots

Attached in the comment below: iOS (all six families, light and dark, Tinted, Clear, German, Arabic) and Android (all ten cells, portrait and landscape, light and dark, Arabic).

Reviewer Notes

  • apps/mobile/plugins/withHomeWidgetRefresh.js patches the Expo widget extension after expo-widgets generates it. The push-handler body uses statement returns so Swift accepts the iOS 26-only opaque type (SE-0360), and the refresh sources join the target's existing Sources phase (a second phase makes Xcode report duplicate tasks).
  • The widget configuration crosses the bridge as JSON text: Expo's Kotlin and Swift argument converters reject a null inside an object argument (a Personal scope's organizationId, a row without an approval key), which previously rejected every configure and left the widget with no credential.
  • user_activity_tokens.kind gains ios_widget in the TypeScript union only; the column is text, so no migration.
  • Legacy agent_* sessions carry no approval key, so they show no in-place Approve.

@iscekic iscekic self-assigned this Oct 9, 2026
Comment thread apps/web/src/lib/auth/home-widget-credential.ts
Comment thread services/kilo-mcp/catalog.json Outdated
Comment thread packages/app-shared/src/home-widget.ts Outdated
Comment thread apps/mobile/src/glanceable-ios/active-agents-widget.tsx Outdated
Comment thread apps/mobile/src/lib/glanceable/widget-actions.ts Outdated
Comment thread apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshStore.swift Outdated
Comment thread apps/web/src/lib/home-widget-http.ts
@kilo-code-bot

kilo-code-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

All five prior findings are fixed in the current commit range, and the incremental changes introduce no new issues: the iOS rebuild now clears the live actionFeedback key, a retry clears the stale failure notice before showing "Approving…", the detail rows are keyed, and the publisher test now advances past the renewal margin so the session-change case is actually exercised.

Files Reviewed (6 files)
  • apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshStore.swift
  • apps/mobile/src/glanceable-android/action-notice.ts
  • apps/mobile/src/glanceable-android/android-sink.test.ts
  • apps/mobile/src/glanceable-ios/active-agents-widget.tsx
  • apps/mobile/src/lib/glanceable/publisher.test.ts
  • services/notifications/src/lib/glanceable-delivery.test.ts
Previous Review Summaries (9 snapshots, latest commit f1cb484)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f1cb484)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

The incremental widget redesign leaves the iOS widget clearing a renamed-away key (so a failed-approve line survives background rebuilds) and the Android retry card showing the failure line instead of "Approving…", while the added publisher test still does not exercise its session-change case; two lower-value test/markup nits round it out.

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/glanceable-ios/view-props.ts 205 The actionLine → actionFeedback rename leaves HomeWidgetRefreshStore.swift:277 clearing the dead actionLine, so a failed-approve line survives server-driven timeline rebuilds
apps/mobile/src/glanceable-android/approve-task.ts 81 showApproving leaves the previous failure notice set, so a retry shows the stale red failure line with Approve dropped instead of "Approving…" (iOS hides it)
apps/mobile/src/lib/glanceable/publisher.test.ts 1039 The second handleSessions call is an unchanged-content heartbeat that early-returns, so the added assertion re-checks the first frame and the session-change case remains untested

SUGGESTION

File Line Issue
apps/mobile/src/glanceable-ios/active-agents-widget.tsx 951 The detailRows map returns a keyless shifted(...) element; key is on the inner HStack, producing a React missing-key warning
services/notifications/src/lib/glanceable-delivery.test.ts 2032 New test duplicates the updatedAt assertion already covered by the toEqual at line 1993
Files Reviewed (5 files)
  • apps/mobile/src/glanceable-ios/view-props.ts - 1 issue
  • apps/mobile/src/glanceable-android/approve-task.ts - 1 issue
  • apps/mobile/src/lib/glanceable/publisher.test.ts - 1 issue
  • apps/mobile/src/glanceable-ios/active-agents-widget.tsx - 1 issue
  • services/notifications/src/lib/glanceable-delivery.test.ts - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit a8b9a78)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The new incremental test in publisher.test.ts claims to cover a changed session behind the same visible ask but is byte-for-byte identical to an existing test, so it adds no coverage of the rule it names.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/lib/glanceable/publisher.test.ts 1031 Duplicate of the existing "visible ask unchanged" test; the row carries no id, so it does not exercise a session change
Files Reviewed (1 file)
  • apps/mobile/src/lib/glanceable/publisher.test.ts - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit dc6d969)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental pass over commit dc6d9693b (retain the server-minted approval key across a tray-derived details rebuild): the new retention in GlanceablePublisher.handleSessions fires only when the derived details carry no key and the visible ask (primary title, waiting rows, scheduled rows) is otherwise unchanged, and the press still re-checks the key digest against the live pending permissions before approving, so no new defects were found on the changed lines.

Files Reviewed (2 files)
  • apps/mobile/src/lib/glanceable/publisher.ts
  • apps/mobile/src/lib/glanceable/publisher.test.ts

Previous review (commit 5d741c5)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental pass over commit 5d741c5fd (keep a widget press alive until the app consumes it): the new pendingActionAt press time with a 5-minute TTL is consistent between the iOS extension (HomeWidgetRefreshStore.pendingActionTTL) and the JS sweep (PENDING_ACTION_TTL_MS), stale markers are stripped from stored entries rather than run, and no new defects were found.

Files Reviewed (5 files)
  • apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshStore.swift
  • apps/mobile/src/glanceable-ios/active-agents-widget.tsx
  • apps/mobile/src/glanceable-ios/view-props.ts
  • apps/mobile/src/glanceable-ios/widget-actions.ts
  • apps/mobile/src/glanceable-ios/widget-actions.test.ts

Previous review (commit 19773e2)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the previous review is a as const annotation on the Android widget cell-size tuple in active-agents-widget.test.ts; it is a type-only test refinement with no behavioral impact and no new issues.

Files Reviewed (1 file)
  • apps/mobile/src/glanceable-android/active-agents-widget.test.ts

Previous review (commit 6e51e64)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental changes size the Android card's stacked support rows to the room the cell actually has, clamp the hero to a floor of max(32, counts beside the hero), and switch the dev fixture approval key to a schema-valid hex value; both prior warnings are resolved and no new issues were found on the changed lines.

Files Reviewed (3 files)
  • apps/mobile/src/glanceable-android/active-agents-widget-card.tsx
  • apps/mobile/src/glanceable-android/active-agents-widget.test.ts
  • apps/mobile/src/lib/glanceable/fixture-harness.ts

Previous review (commit f8bcd2e)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The incremental redesign adds a negative-height path in the Android card layout and a fixture approval key that is not schema-valid; both are correctness issues on changed lines.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/glanceable-android/active-agents-widget-card.tsx 180 Row-less heroHeight uses unclamped free, which can be negative for narrow cards (e.g. 172x180 with 3 counts + detail) and ignores besideCounts for wide cards (10dp overflow).
apps/mobile/src/lib/glanceable/fixture-harness.ts 52 FIXTURE_APPROVAL_KEY ('devfixture'.padEnd(64,'0')) is not hex, so it fails the approvalKey schema regex and the persisted Home record is dropped on restore.
Files Reviewed (13 files)
  • apps/mobile/modules/home-widget-refresh/android/src/main/java/com/kilocode/homewidgetrefresh/HomeWidgetRefreshModule.kt
  • apps/mobile/modules/home-widget-refresh/android/src/main/java/com/kilocode/homewidgetrefresh/HomeWidgetStore.kt
  • apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshModule.swift
  • apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshStore.swift
  • apps/mobile/src/glanceable-android/active-agents-widget-card.tsx - 1 issue
  • apps/mobile/src/glanceable-android/active-agents-widget-parts.tsx
  • apps/mobile/src/glanceable-android/active-agents-widget-short.tsx
  • apps/mobile/src/glanceable-android/active-agents-widget.tsx
  • apps/mobile/src/glanceable-android/home-copy.ts
  • apps/mobile/src/glanceable-ios/active-agents-widget.tsx
  • apps/mobile/src/glanceable-ios/widget-actions.test.ts
  • apps/mobile/src/lib/glanceable/fixture-harness.ts - 1 issue
  • apps/mobile/src/lib/glanceable/home-widget-refresh.ts

Fix these issues in Kilo Cloud

Previous review (commit 6c18d66)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental fix set resolves all seven prior findings; the only new concern is an unexplained deletion of an existing profiles-copy test that removes coverage present on the base branch.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/i18n/profiles-copy.test.ts N/A Deleted by commit 4df96dd1f (an unrelated widget-credential fix). The file exists on the base branch (main) and is the only test pinning en.profiles; its removal is unexplained and unrelated to this PR. Restore it or state why it is obsolete.
Files Reviewed (27 files)
  • .kilo/skills/kilo-mcp/SKILL.md
  • apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshModule.swift
  • apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshStore.swift
  • apps/mobile/modules/home-widget-refresh/ios/WidgetExtension/HomeWidgetTimelineProvider.swift
  • apps/mobile/src/glanceable-ios/active-agents-widget.tsx
  • apps/mobile/src/glanceable-ios/layout-copy.test.ts
  • apps/mobile/src/glanceable-ios/layout-copy.ts
  • apps/mobile/src/i18n/profiles-copy.test.ts - deleted (1 issue)
  • apps/mobile/src/lib/glanceable/front-approval.ts
  • apps/mobile/src/lib/glanceable/widget-actions.test.ts
  • apps/mobile/src/lib/glanceable/widget-actions.ts
  • apps/web/src/app/api/mobile/widgets/push-token/route.ts
  • apps/web/src/app/api/mobile/widgets/route.ts
  • apps/web/src/lib/active-sessions-list.ts
  • apps/web/src/lib/auth/home-widget-credential.test.ts
  • apps/web/src/lib/auth/home-widget-credential.ts
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.ts
  • apps/web/src/lib/glanceable-agents-snapshot-server.test.ts
  • apps/web/src/lib/glanceable-agents-snapshot-server.ts
  • apps/web/src/lib/home-widget-http.ts
  • apps/web/src/lib/home-widget-routes.test.ts
  • apps/web/src/scripts/mcp-catalog/catalog.test.ts
  • apps/web/src/scripts/mcp-catalog/catalog.ts
  • packages/app-shared/src/home-widget.test.ts
  • packages/app-shared/src/home-widget.ts
  • services/kilo-mcp/catalog.json
  • services/notifications/src/lib/glanceable-delivery.test.ts

Fix these issues in Kilo Cloud

Previous review (commit 1d73214)

Status: 7 Issues Found | Recommendation: Address before merge

Executive Summary

No blocking bug, but a new 30-day widget credential issuance path does not enforce the organization-scope invariant its sibling issuance paths enforce, and that credential is newly published to the Kilo MCP catalog without a stated rationale.

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 4
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/auth/home-widget-credential.ts 106 Caller-supplied organizationId is stamped into claims without comparing authority.organizationId, unlike sibling issuance paths
services/kilo-mcp/catalog.json 68 activeSessions.widgetCredential mints a 30-day bearer but is published to MCP as a query with no justification
apps/mobile/modules/home-widget-refresh/ios/HomeWidgetRefreshStore.swift 48 Force-unwrapped app-group container URL plus precondition can crash every widget/background path

SUGGESTION

File Line Issue
packages/app-shared/src/home-widget.ts 251 homeWidgetRefreshAt treats waiting/stale/expired as quiet, diverging from refreshInterval
apps/mobile/src/glanceable-ios/active-agents-widget.tsx 189 COPY.homeEmpty is never defined; iOS Home empty state falls back to the accessory copy
apps/mobile/src/lib/glanceable/widget-actions.ts 218 Approval candidate ranking can differ from the server-selected visible request
apps/web/src/lib/home-widget-http.ts 16 The 10s deadline does not cancel the work it bounds
Files Reviewed (160+ files)

Reviewed the full changed set sharded across web auth/security, web snapshot/sessions, shared package, notifications/cloud-agent service, MCP catalog, mobile core lib, Android and iOS native modules, glanceable Android/iOS UI, i18n catalogs, and tests. No memory leaks were identified on changed lines; teardown for listeners, timers, URLSession, and WorkManager was present.

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 72.9K · Output: 11.2K · Cached: 982.3K

Review guidance: REVIEW.md from base branch main

Comment thread apps/mobile/src/glanceable-android/active-agents-widget-card.tsx Outdated
Comment thread apps/mobile/src/lib/glanceable/fixture-harness.ts Outdated
@iscekic

iscekic commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

iOS widget families — iPhone 17 Pro, iOS 26.5, current branch

All six families from the final English capture set (36 fixtures × light/dark × six families), plus German, Arabic (RTL), Tinted and Clear. Light first, then dark.

needs-approval-light-small

needs-approval-light-medium

needs-approval-light-large

needs-approval-light-circular

needs-approval-light-rectangular

needs-approval-light-inline

needs-approval-dark-small

needs-approval-dark-medium

needs-approval-dark-large

mixed-light-small

mixed-light-medium

mixed-light-large

scheduled-only-light-small

scheduled-only-light-medium

scheduled-only-light-large

empty-light-large

stale-idle-light-large

de-small

ar-medium

tinted-medium

clear-small

@iscekic

iscekic commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Android widget cells — Pixel 6 (API 35), light and dark, portrait and landscape, English and Arabic

Every launcher cell the widget can occupy (2x1 through 4x4, plus landscape rows), the locked state, and the Arabic (RTL) mirroring. The fixtures cover the empty, scheduled, stale, long-title and large-count states; the full 36-fixture × 10-cell × 2-theme × 2-orientation capture set lives in the E2E evidence.

ar-mixed-4x4

ar-privacy-4x4

empty-light-2x2

empty-light-4x4

large-counts-light-2x1

large-counts-light-4x4

long-title-light-2x2

long-title-light-4x4

mixed-dark-4x4

mixed-light-2x1

mixed-light-2x2

mixed-light-2x3

mixed-light-3x1

mixed-light-3x2

mixed-light-3x3

mixed-light-4x1

mixed-light-4x2

mixed-light-4x3

mixed-light-4x4

mixed-light-landscape-4x4

needs-approval-light-2x1

needs-approval-light-2x2

needs-approval-light-3x3

needs-approval-light-4x3

needs-approval-light-4x4

privacy-light-4x4

privacy-light-landscape-3x1

scheduled-tomorrow-light-4x3

scheduled-tomorrow-light-4x4

@iscekic

iscekic commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

In-place Approve and cold New agent

Left: with a live pending permission the Android widget draws the Approve control, and the press reaches the server (the binding is SHA-256 of the session and permission ids). Right: on iOS, after terminating the app, tapping the widget + cold-starts the app into the New session composer.

android-approve

ios-cold-new-agent

Comment thread apps/mobile/src/lib/glanceable/publisher.test.ts Outdated
Comment thread apps/mobile/src/glanceable-ios/view-props.ts
Comment thread apps/mobile/src/glanceable-android/approve-task.ts
Comment thread apps/mobile/src/lib/glanceable/publisher.test.ts
Comment thread apps/mobile/src/glanceable-ios/active-agents-widget.tsx Outdated
Comment thread services/notifications/src/lib/glanceable-delivery.test.ts Outdated

This branch has not been deployed

No deployments
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