Skip to content

fix(mobile): blank the approval count when a glanceable frame lapses - #8573

Merged
iscekic merged 2 commits into
mainfrom
drain/glanceable-expired-approval-count
Oct 22, 2026
Merged

iscekic merged 2 commits into
mainfrom
drain/glanceable-expired-approval-count

Conversation

@iscekic

@iscekic iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

What changed

An expired glanceable frame now carries no work at all. The lapsed-in-place frame kept its approval count while every other work count, the wait anchor, and the scheduled wake were already blank, so the surface could hold a count for an ask the publisher had retired in the same transition.

  • packages/app-shared/src/glanceable-agents-snapshot.ts: one exported blank-work record (running, needsInput, needsApproval, idle, scheduled, needsInputSince, scheduledAt) typed against the snapshot, so a later work field cannot be cleared by one producer and survive the other.
  • apps/mobile/src/lib/glanceable/snapshot-transforms.ts: the expiry branch of withStatus spreads that record instead of a partial literal. withStatus still does not renew expiresAt, and the newest-result fact is untouched.
  • apps/mobile/src/lib/glanceable/cleanup.ts: the terminal snapshot spreads the same record for the same fields.
  • Comments in the Live Activity approve gate no longer claim a lapsed frame keeps the approval count.

Tests

  • publisher.test.ts drives handleFetchError through a real sink past the deadline with a permission row in the tray: the stale frames keep the approval count, both expired frames blank it.
  • cleanup.test.ts seeds an approval count and asserts the orphaned-org republish keeps it while stale and clears it on expiry.
  • view-props.test.ts builds the Live Activity content state from the expired frame and asserts no work count remains, the approval count included.
  • active-agents-live-activity.test.ts covers the approve gate on a frame with neither count.

All three new assertions fail on the previous transform with needsApproval: 1 and pass with this change.

Verification

  • pnpm exec vitest run src/lib/glanceable src/glanceable-ios src/glanceable-android in apps/mobile: 61 files, 1132 tests pass.
  • apps/mobile vitest for the touched files on the pre-fix transform: 3 failures, each needsApproval: 1 where the new assertion expects 0.
  • pnpm --filter @kilocode/app-shared run typecheck: clean. apps/mobile typecheck reports the same error count as main in this checkout (workspace packages unbuilt), with no new error in a touched file.
  • oxlint and oxfmt on every touched file: clean.

Runtime proof on the Android emulator

Local dev stack plus the Android dev client on this VM, signed into an owned fixture account, with a real tray row whose agent waits on a permission prompt. The app published a happy frame carrying the approval count, the backend was made unreachable while the app stayed online, the frame passed its deadline, and the app's query then reported an error. The published frame:

published frame (happy):   needsInput 1  needsApproval 1  idle 0  running 0  scheduled 0
query error past deadline: from happy to expired, previousNeedsApproval 1, needsApproval 0
published frame (expired): running 0  needsInput 0  needsApproval 0  idle 0  scheduled 0
                           needsInputSince null  scheduledAt null

The expiry deadline is 8 hours in the shipped code; the run lowered that constant locally so the deadline was reachable in one sitting. The log line and the constant were reverted before this commit.

No screenshot: nothing visible changes. Every control was already gated on the wait count, so the expired card drew no Approve before or after; the fix removes the inconsistent count from the frame itself, which no surface rendered distinctly.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Fix shape: Three producers build an expired glanceable frame: GlanceablePublisher.applyExpiry (via buildGlanceableSnapshot with no rows) and cleanup.buildTerminalSnapshot already blank needsApproval; only the in-place expiry branch of withStatus keeps it. How should the fix be shaped?
    Answer: Shared blank-work record in app-shared
    Confidence: 0.75 · Precedent: Fix the root cause and the class of the issue: session 00eb2a1d3e4b, "if anything repeats the same mistake over and over again, fix the class"; also extract shared code into an internal library: session 5d8113f51c48
  2. Question: Field scope: Should the expired branch also blank the newest-result fact (newestResultKind/newestResultAt), which the builder-produced expired and terminal frames set to null while withStatus keeps?
    Answer: Keep it, blank only the work counts
    Confidence: 0.50 · Precedent: Put each separate concern in its own PR: session debabfb38c82, owner asked for the change "in a separate PR"
  3. Question: Proof: No surface draws an approval count on an expired frame before or after this change (the wait count already gates every control, and Android surfaces never read the snapshot's approval count), so there is no visual delta to capture. What proof should the pull request carry?
    Answer: Keep the production-path tests: handleFetchError past the deadline through a real sink, the cleanup republish, and the iOS Live Activity content state. Run only those suites plus a narrow typecheck. Also capture one real runtime proof on the Android emulator on the VM, against the dev stack with a real session that has a pending approval. Cut the network or the backend so that handleFetchError runs past the deadline, then log the published expired frame and show that needsApproval is 0 and the other wait fields are cleared. Post those logs in a PR comment. No screenshot is needed because nothing visual changes; say so in the PR. Add missing-e2e only if the emulator run truly cannot reach the expiry path.
    Confidence: 0.45 · Precedent: Proof and verification: require valid e2e proof as logs or screenshots that make sense, and use real backend state when a fixture cannot mimic real usage (session 55c9c80332a7: "logs and/or screenshots are mandatory proof"; session b1be07191a1d fixture rules)

A frame that lapses in place cleared every work count except the
approval count, so a stale surface kept a count for an ask the publisher
had already retired. The frames built from no rows already blanked it.

Both producers now spread one blank-work record from the shared snapshot
module, so a future work field cannot be cleared by one path and survive
the other. Tests drive the fetch-error path past the deadline, the
orphaned-org republish, and the Live Activity content state.
@iscekic iscekic added drain Work item from the findings drain janitor:features-workflows Janitor finding family: features-workflows labels Oct 22, 2026
@iscekic

iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Runtime proof for the expired frame, captured from the Android dev client against the local dev stack on a work VM.

Exercised: an owned fixture account with one tray row whose agent waits on a permission prompt. The app published a happy frame carrying the approval count, the backend was then made unreachable while the app stayed online, the frame passed its deadline, and the app's query reported an error on the next refresh.

Observed on the published frames:

  • happy frame: needsInput 1, needsApproval 1
  • query error past the deadline: from happy to expired, previousNeedsApproval 1, needsApproval 0
  • expired frame: running 0, needsInput 0, needsApproval 0, idle 0, scheduled 0, needsInputSince null, scheduledAt null

The shipped deadline is eight hours, so the run lowered that constant locally to make the deadline reachable in one sitting; both the constant and the diagnostic log line were reverted before the commit, which is why this comment carries the observation rather than the branch.

No screenshot: nothing visible changes. Every control was already gated on the wait count, so an expired card drew no Approve before or after this change. The fix removes the inconsistent count from the frame itself, which no surface rendered distinctly.

@kilo-code-bot

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

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the prior review is c85e6028ff, which replaces the hand-maintained satisfies Pick<...> field list on NO_GLANCEABLE_WORK with Omit<GlanceableAgentsSnapshot, GlanceableFrameField>; the ten frame fields plus the seven work fields account for all seventeen snapshot fields, so a newly added work field now fails to compile until it is blanked or explicitly classified as non-work, resolving the previous finding.

Assumptions: [INFERENCE] — newestResultKind/newestResultAt remain on a lapsed frame by design (their exclusion from the blank-work record is documented in the PR), and no surface was verified to render that combination; the two spread consumers (snapshot-transforms.ts, cleanup.ts) only spread the record and never mutate it, so dropping as const changes no behavior. Typecheck was not executed in this read-only review.

Files Reviewed (1 file)
  • packages/app-shared/src/glanceable-agents-snapshot.ts

Fix these issues in Kilo Cloud

Previous Review Summary (commit aef69bb)

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

Previous review (commit aef69bb)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The expiry fix itself is correct and well covered; the only finding is that the new shared blank-work record does not actually enforce the future-field guarantee its comment advertises.

Assumptions: [INFERENCE] — withStatus still keeps newestResultKind/newestResultAt on a lapsed frame (a deliberate scope choice described in the PR), so an expired frame is blank except for the newest-result fact; no surface was verified to render that combination. The third hand-written blank shape at apps/mobile/src/glanceable-ios/view-props.ts:330 was left untouched and is harmless today because GlanceableViewProps never carries needsApproval, but unchanged lines were not flagged. Whether the TypeScript build is clean could not be executed in this read-only review.

Overview

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

SUGGESTION

File Line Issue
packages/app-shared/src/glanceable-agents-snapshot.ts 152 satisfies Pick<...> lists fields by hand, so a newly added work field is not caught and can still be blanked by one producer and survive the other
Files Reviewed (8 files)
  • apps/mobile/src/glanceable-ios/active-agents-live-activity.test.ts - 0 issues
  • apps/mobile/src/glanceable-ios/active-agents-live-activity.tsx - 0 issues
  • apps/mobile/src/glanceable-ios/view-props.test.ts - 0 issues
  • apps/mobile/src/lib/glanceable/cleanup.test.ts - 0 issues
  • apps/mobile/src/lib/glanceable/cleanup.ts - 0 issues
  • apps/mobile/src/lib/glanceable/publisher.test.ts - 0 issues
  • apps/mobile/src/lib/glanceable/snapshot-transforms.ts - 0 issues
  • packages/app-shared/src/glanceable-agents-snapshot.ts - 1 issue

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 28.2K · Output: 3.4K · Cached: 197.8K

Review guidance: REVIEW.md from base branch main

…field

The record's field list was hand-written, so a work field added to the
snapshot schema would compile while both producers kept omitting it. The
annotation now covers every field the frame itself does not describe, so
a new work field is a type error here until it is blanked.
@iscekic iscekic added the merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. label Oct 22, 2026
@iscekic
iscekic enabled auto-merge October 22, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain janitor:features-workflows Janitor finding family: features-workflows merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants