Skip to content

fix(mobile): Active now tray context filter, stale titles, status meta - #4830

Merged
iscekic merged 7 commits into
mainfrom
sessions-context-d669
Jul 29, 2026
Merged

fix(mobile): Active now tray context filter, stale titles, status meta#4830
iscekic merged 7 commits into
mainfrom
sessions-context-d669

Conversation

@iscekic

@iscekic iscekic commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes three defects in the mobile Agents Active now tray, all in the activeSessions.list path:

  1. The tray ignored the selected personal/organization context. activeSessions.list took no input and returned every live session for the user. It now accepts an optional organizationId (undefined = unfiltered liveness callers, null = personal, uuid = org, authorized via the existing ensureOrganizationAccess) and filters after the enrichment join in JS, because a live session with no cli_sessions_v2 row is unattributable and counts as personal — a case SQL cannot distinguish from "belongs to another org". The router emits organizationId on every row (null included), so a cached row without it is exactly "arrived via WS push, not yet attributed"; the mobile client hides such rows in any filtered context until the next fetch attributes them (they re-appear one enrichment round-trip later — accepted). The tray query key now varies with context (one shared buildActiveSessionsInput for the hook and the live-sync owner, so keys cannot drift), and Home gates its session queries on the loaded org context so an org user never flashes personal rows on cold start.
  2. A renamed session flashed its old title in the tray. The tray title came from the CLI heartbeat, which stays stale forever after a cloud rename (nothing propagates a rename back to the CLI). title is now enriched from cli_sessions_v2 (DB-authoritative, falling back to the worker title when NULL); the mobile cache keeps the enriched title sticky across heartbeats; and rename optimistically patches every cached tray context with snapshot + rollback, symmetric with the stored-list patch. Web's two WS merge paths (applyActiveSessionsHeartbeat, sessions.list) keep cached titles unconditionally via a small preserveCachedTitles helper — without it, DB-authoritative titles would have made the web sidebar oscillate DB → CLI title on every heartbeat (regression prevention, not scope creep).
  3. The tray rendered a raw status word (BUSY/IDLE/RETRY) in the timestamp slot. remoteMeta now returns undefined when updatedAt is absent, so the row renders the live dot alone; the spoken accessibility meta matches. No substitute value, no fixed-width slot.

Socket lease (reviewer focus): the live-sync owner is recreated per context, and its attach() is the app's only retain() caller — so a per-context swap would drop the retain count to zero and close the shared user-web WebSocket on every context switch. The mount now holds a standing useEffect(() => connection.retain(), [connection]) lease above the per-context owner; the socket stays up across switches and across the cold pre-isLoaded window.

Failure semantics: on an enrichment DB failure, unfiltered callers (web, resolveSession) keep the existing unenriched passthrough, while a filtered context fails the query (TRPCError) — attribution is unknowable without the join, and guessing would silently show out-of-context sessions. Mobile lands in the already-shipped retryable inline state when rows are on screen; a cold empty cache shows an empty tray (pre-existing, no new UI).

Verification

E2E — iPhone 17 Pro simulator, iOS 26.5, against this worktree's local stack; real model calls on kilo-auto/efficient. Flows exercised: personal/org context filtering across load, pull-to-refresh and ≥2 heartbeat cycles; 7 personal↔org context switches; org-attribution flip via SQL; cli_sessions_v2 row deletion with the CLI still live (unattributable path); rename from both entry points (history list row → native prompt; session-detail header → RenameModal); NEEDS INPUT via a real permission prompt on efficient; socket-lease check via a tmux pipe-pane capture of the cloudflare-session-ingest pane across every switch.

Acceptance criterion (plan §5) Result Evidence
1 Personal context hides org sessions (load/PTR/heartbeat) PASS row gone after pull-to-refresh, still gone after 35 s of heartbeats (step1-d6-clean.png)
2 Org context shows only org sessions PASS step2-org-tray.png; unattributable personal row hidden in org (step3-org-hidden.png)
3 Context switch updates tray, no restart, no flash PASS immediate-after-switch screenshots in both directions show only the correct context's rows
4 Rename: immediate, survives refetch + heartbeat PASS step4-tray-renamed.png → 35 s heartbeat watch → step4-tray-after-heartbeat.png ("Tray rename alpha" never reverted; DB confirmed)
5 Both rename entry points PASS history list row (action sheet → native prompt) and detail header (RenameModal); the tray row itself exposes only Copy-session-ID by design
6 Rename failure rollback + toast SKIPPED (disclosed, sanctioned by the plan handoff) a rename transport failure is not safely producible on device without destabilising the local stack; use-session-mutations.test.ts unit coverage is authoritative for that state
7 No BUSY/IDLE/RETRY in meta (cold/PTR/heartbeat/rowless) PASS rowless label after row deletion + 22 s heartbeat: Tray-test-1 ready test, CLOUD — no status word, no fabricated time (step3-row-deleted-still-listed.png)
8 Timestamped row unchanged; rowless renders nothing; layout stable PASS …, CLOUD, 1 hour ago, from CLI (timestamped) vs …, CLOUD (rowless, live dot only)
9 A11y labels: no status word; NEEDS INPUT/timestamp unchanged PASS real NEEDS INPUT row (CLI permission prompt): Tray rename alpha, needs input, CLOUD
10–12 Covered elsewhere web unit tests (Task 5), automated suites, CI checks — outside the mobile E2E round per plan
13 E2E on simulator with evidence PASS this run

Socket lease (Task 3c standing retain()) — PASS: zero Web socket disconnected/connected pairs across all 7 context switches in the cloudflare-session-ingest capture. Three lone disconnects all correlate with app-process SIGSEGVs inside XCTAutomationSupport (Maestro/XCUITest driver instability under parallel-workflow load), never with a switch commit — the lease-churn signature (a synchronous pair per switch) cannot and did not occur.

The verifier restored its baseline byte-for-byte (worktree porcelain, diff and index all MATCH, 0 changed paths), deleted every test DB row it created (verified 0|0|0|0), stopped the dev stack, shut down and released the simulator, and released the device slot.

  • cd apps/web && NODE_ENV=test pnpm test src/routers/active-sessions-router — 32 tests (context filter both directions, unattributable→personal, non-member rejection, organizationId on every row incl. toHaveProperty('organizationId', null), DB title with NULL fallback, D11 failure branches)
  • cd apps/web && NODE_ENV=test pnpm test src/components/cloud-agent-next — 298 tests (cached title preserved on heartbeat for known ids, payload title for new ids, connection-scoped replace intact)
  • cd apps/mobile && pnpm format && pnpm typecheck && pnpm lint && pnpm check:unused && pnpm test — 2363 tests (org-id carry across merges incl. null preserved as null, filter matrix, two-heartbeat re-admission guard, title stickiness, dual-cache rename rollback, remoteMeta matrix, eyebrow-right rendering guards)
  • Root pnpm typecheck + pnpm lint — clean

Visual Changes

No visual redesign. The tray is pixel-identical except: rows are filtered to the selected personal/org context; a renamed session keeps its new title across heartbeats instead of flashing the stale CLI title; and a row without a timestamp shows the live dot alone — no status word, no fabricated time, no fixed-width slot. Evidence screenshots from the E2E run: org-only tray (step2-org-tray.png), renamed tray after a 35 s heartbeat watch (step4-tray-after-heartbeat.png), rowless-session meta (step3-row-deleted-still-listed.png).

Before After
Tray listed every live session regardless of the selected personal/org context; a cloud-renamed session flashed its old CLI title; a row with no timestamp showed a raw BUSY/IDLE/RETRY word Tray shows only the selected context's sessions; DB-authoritative title sticks across heartbeats; a missing timestamp renders nothing (live dot only)

Reviewer Notes

  • Heartbeats carry no org id and the server-filtered fetch replaces the cache wholesale, so the client filter deliberately hides unattributed rows in filtered contexts — treating them as personal would re-admit out-of-context sessions on every heartbeat. Cost: a brand-new in-context session appears one enrichment round-trip later (the existing enrichment machinery kicks that fetch immediately).
  • Web title stickiness is intentionally unconditional (no isEnriched gate like mobile's): web drops enrichment fields on every heartbeat, so a gate would never hold. A CLI-side retitle reaches web on the next list fetch / 10s refetch — same freshness class as mobile's accepted trade-off.
  • mobile-session-manager.resolveSession deliberately keeps calling activeSessions.list with no input (unfiltered liveness check); filtering it would misclassify org sessions as read-only.
  • Hook composition (useActiveSessions × filter × live-sync mount) is intentionally not unit-tested — the vitest harness cannot import the RN/TRPC providers (apps/mobile/src/test/render-with-providers.tsx header); covered by the pure-helper tests + E2E.

iscekic added 6 commits July 28, 2026 16:09
Preserve cached titles on heartbeat and sessions.list so cloud renames
do not oscillate back to the CLI title after list enrichment.
Emit organizationId on every row (null for unattributable), authorize
org access first, and fail filtered callers when the enrichment DB
fails. Unfiltered callers keep the unenriched passthrough.
Return undefined from remoteMeta when updatedAt is missing so the row
shows the live dot alone; align spoken accessibility meta the same way.
Carry organizationId through WS merges, keep enriched titles sticky,
and add pure filter/rename helpers for the context-filtered tray.
Key activeSessions.list by context, hold a standing WS lease across
owner swaps, filter unattributed heartbeat rows client-side, gate
Home's session queries on the loaded context, and optimistically
retitle tray rows on rename.
Use Reflect.apply passthrough for ensureOrganizationAccess selects so
root typecheck accepts the enrichment-failure mock.
@iscekic iscekic self-assigned this Jul 28, 2026
@kilo-code-bot

kilo-code-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Since the previous review, the only changes are two new workflow-learnings documentation files (.kilo_workflow/learnings/) with no source, migration, or test code changes; no new Code Review Findings.

Files Reviewed (2 files)
  • .kilo_workflow/learnings/mobile-rename-entry-points-and-field-clearing.md
  • .kilo_workflow/learnings/mobile-session-ingest-log-empty-use-pipe-pane.md
Previous Review Summary (commit e4540ec)

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

Previous review (commit e4540ec)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed all 20 changed files across the mobile Active-now tray context filter, stale-title, and status-meta fixes plus the web router/hook changes; no high-confidence security, correctness, or logic issues found on changed lines, with high confidence in the review scope.

Files Reviewed (20 files)
  • apps/web/src/routers/active-sessions-router.ts
  • apps/web/src/routers/active-sessions-router.list.test.ts
  • apps/web/src/components/cloud-agent-next/hooks/useActiveSessions.ts
  • apps/web/src/components/cloud-agent-next/hooks/useActiveSessions.test.ts
  • apps/mobile/src/lib/active-sessions-live.ts
  • apps/mobile/src/lib/active-sessions-live.context.test.ts
  • apps/mobile/src/lib/active-sessions-live.merge.test.ts
  • apps/mobile/src/lib/active-sessions-live.title.test.ts
  • apps/mobile/src/lib/active-sessions-live-sync-mount.tsx
  • apps/mobile/src/components/home/home-screen.tsx
  • apps/mobile/src/components/agents/remote-session-row.tsx
  • apps/mobile/src/components/agents/session-list-helpers.ts
  • apps/mobile/src/components/agents/session-list-helpers.test.ts
  • apps/mobile/src/components/share/share-destinations.ts
  • apps/mobile/src/components/share/share-gate-sheet.tsx
  • apps/mobile/src/lib/agent-session-input.ts
  • apps/mobile/src/lib/hooks/use-agent-sessions.ts
  • apps/mobile/src/lib/hooks/use-agent-sessions.test.ts
  • apps/mobile/src/lib/hooks/use-session-mutations.ts
  • apps/mobile/src/lib/hooks/use-session-mutations.test.ts

Reviewed by claude-sonnet-5 · Input: 22 · Output: 3K · Cached: 403.6K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Jul 28, 2026
@iscekic
iscekic requested a review from pandemicsyn July 29, 2026 00:11
@iscekic
iscekic merged commit fcd2d08 into main Jul 29, 2026
21 checks passed
@iscekic
iscekic deleted the sessions-context-d669 branch July 29, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants