Skip to content

fix(mobile): keep a disabled composer and name the continue destination in read-only sessions - #6862

Merged
iscekic merged 1 commit into
mainfrom
kwf/owner-composer-readonly-continue-20260929
Sep 29, 2026
Merged

iscekic merged 1 commit into
mainfrom
kwf/owner-composer-readonly-continue-20260929

Conversation

@iscekic

@iscekic iscekic commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

Changelog for users

  • A read-only session now shows the composer at the bottom, disabled, with 'This is a read-only session' stated beside it.
  • The continue control now reads 'Continue in a new session' and announces the same words to screen readers.
  • The new label is translated in every supported locale.
  • Tapping the control still opens the same Continue screen for that session.

Changelog for maintainers

  • The composer now mounts for every session; only the blocking-interaction flag controls its visibility, so the old isComposerMounted flag is gone.
  • The bottom spacer bar shares that one condition, so it no longer reserves space the composer fails to fill and the layout stops jumping.
  • Review mobile-session-manager first: which sessions resolve read-only and what Continue opens are deliberately unchanged.
  • Add agentChat.session.continueInNewSession to every locale catalog; the English value is 'Continue in a new session'.
  • The control's accessibility label moved from common.continue to the new session-detail key, so any test asserting the old label must follow.
  • The new unit test asserts the disabled composer, the reason text, and the destination-named button, not the routing behind it.

E2E proof

[U2] continue-in-new-session label is present in every locale catalog

Asserted value: locales checked: 87, missing: 0. Sense check (jev): probability 0.91

Base log: backend-assert 7ef044fc6a63 exited 1
$ set -euo pipefail
$ REPO=$(git rev-parse --show-toplevel)
$ cd "$REPO/apps/mobile"
$ TEST=src/i18n/continue-label-locales.test.ts
$ trap 'rm -f "$TEST"' EXIT
$ cat > "$TEST" <<'EOF'
$ import { describe, expect, it } from 'vitest';
$ import { CATALOG_LOADERS } from './catalogs';
$ import { SUPPORTED_LANGUAGES } from './languages';
$ describe('read-only continue label translation', () => {
$   it('names the continue destination in every locale', () => {
$     const missing: string[] = [];
$ … 16 more line(s)
 ❯ |mobile-pure| src/i18n/continue-label-locales.test.ts (1 test | 1 failed) 253ms
 Test Files  1 failed (1)
      Tests  1 failed (1)
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
 FAIL  |mobile-pure| src/i18n/continue-label-locales.test.ts > read-only continue label translation > names the continue destination in every locale
AssertionError: expected [ 'af', 'sq', 'am', 'ar', 'hy', …(82) ] to deeply equal []
- Expected
 ❯ src/i18n/continue-label-locales.test.ts:19:21
     17|     }
     18|     console.log(`locales checked: ${SUPPORTED_LANGUAGES.length}, missi…
     19|     expect(missing).toEqual([]);
       |                     ^
     20|   });
     21| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Head log: backend-assert 7ef044fc6a63 exited 0
$ set -euo pipefail
$ REPO=$(git rev-parse --show-toplevel)
$ cd "$REPO/apps/mobile"
$ TEST=src/i18n/continue-label-locales.test.ts
$ trap 'rm -f "$TEST"' EXIT
$ cat > "$TEST" <<'EOF'
$ import { describe, expect, it } from 'vitest';
$ import { CATALOG_LOADERS } from './catalogs';
$ import { SUPPORTED_LANGUAGES } from './languages';
$ describe('read-only continue label translation', () => {
$   it('names the continue destination in every locale', () => {
$     const missing: string[] = [];
$ … 16 more line(s)
stdout | src/i18n/continue-label-locales.test.ts > read-only continue label translation > names the continue destination in every locale
locales checked: 87, missing: 0
 ✓ |mobile-pure| src/i18n/continue-label-locales.test.ts (1 test) 246ms
 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  05:14:53
   Duration  498ms (transform 60ms, setup 46ms, import 38ms, tests 246ms, environment 0ms)
every locale catalog carries a non-empty agentChat.session.continueInNewSession

[D1][U2] read-only session keeps a disabled composer with the reason and names the continue destination

Asserted value: read-only session: disabled composer with the continue-in-new-session control. Sense check (model): Head ui-assert 3b515baa87ec exits 0 naming 'read-only session: disabled composer with the continue-in-new-session control' and 'shown: Continue in a new session', while the base fails with 'Continue in a new session -> not shown (exit 1)' and the head screens show the dimmed composer under 'This is a read-only session' plus the Continue screen 'Continuing 60-message pagination fixture'.

Base da3426980f96 Head c1aeeaae6a83
Base after ui-assert: the read-only 60-message session with 'This is a read-only session' above the dimmed disabled composer at the bottom and the 'Continue in a new session' control Head after ui-assert: the read-only 60-message session with 'This is a read-only session' above the dimmed disabled composer at the bottom and the 'Continue in a new session' control

After ui-assert: the read-only 60-message session with 'This is a read-only session' above the dimmed disabled composer at the bottom and the 'Continue in a new session' control

Base da3426980f96 Head c1aeeaae6a83
Base after ui-assert: the Continue screen headed 'Continuing 60-message pagination fixture' with Mode, Model, Run on and Repository Head after ui-assert: the Continue screen headed 'Continuing 60-message pagination fixture' with Mode, Model, Run on and Repository

After ui-assert: the Continue screen headed 'Continuing 60-message pagination fixture' with Mode, Model, Run on and Repository

Base log: ui-assert 3b515baa87ec exited 1
$ set -euo pipefail
$ check_literal() {
$   local label="$1" out rc
$   out=$(node "$KWF_EXPLORER_SIM_CLI" do check --literal "$label") && rc=0 || rc=$?
$   printf '%s -> %s (exit %s)\n' "$label" "$out" "$rc"
$   [ "$rc" = 0 ]
$ }
$ check_literal 'This is a read-only session'
$ check_literal 'Continue in a new session'
$ check_literal 'Send message'
$ node "$KWF_EXPLORER_SIM_CLI" do bounds 'Continue in a new session'
$ node "$KWF_EXPLORER_SIM_CLI" do bounds 'Send message'
$ … 6 more line(s)
This is a read-only session -> shown (exit 0)
Continue in a new session -> not shown (exit 1)
shown: This is a read-only session (via script, p=1.00, 16 elements, read 2255 ms, total 2257 ms): TextView "This is a read-only session"
not shown: Continue in a new session (via script, p=0.00, 16 elements, read 2178 ms, total 2180 ms)
Base log: ui-assert 66852459f603 exited 1
$ set -euo pipefail
$ out=$(node "$KWF_EXPLORER_SIM_CLI" do check --literal 'Continue in a new session') && rc=0 || rc=$?
$ printf 'continue control label -> %s (exit %s)\n' "$out" "$rc"
$ [ "$rc" = 0 ]
$ node "$KWF_EXPLORER_SIM_CLI" do tap 'Continue in a new session'
$ out=$(node "$KWF_EXPLORER_SIM_CLI" do check --literal 'Continuing 60-message pagination fixture') && rc=0 || rc=$?
$ printf 'continue destination -> %s (exit %s)\n' "$out" "$rc"
$ [ "$rc" = 0 ]
$ echo 'the destination-named continue control opened the Continue screen for the same session'
continue control label -> not shown (exit 1)
not shown: Continue in a new session (via script, p=0.00, 16 elements, read 2161 ms, total 2163 ms)
Head log: ui-assert 3b515baa87ec exited 0
$ set -euo pipefail
$ check_literal() {
$   local label="$1" out rc
$   out=$(node "$KWF_EXPLORER_SIM_CLI" do check --literal "$label") && rc=0 || rc=$?
$   printf '%s -> %s (exit %s)\n' "$label" "$out" "$rc"
$   [ "$rc" = 0 ]
$ }
$ check_literal 'This is a read-only session'
$ check_literal 'Continue in a new session'
$ check_literal 'Send message'
$ node "$KWF_EXPLORER_SIM_CLI" do bounds 'Continue in a new session'
$ node "$KWF_EXPLORER_SIM_CLI" do bounds 'Send message'
$ … 6 more line(s)
bounds Button "Continue in a new session": left=14.1dp top=777.9dp width=383.2dp height=35.8dp right=397.3dp bottom=813.7dp (px [37,2042][1043,2136], 420 dpi)
bounds Button "Send message": left=352.8dp top=833.5dp width=48.0dp height=48.0dp right=400.8dp bottom=881.5dp (px [926,2188][1052,2314], 420 dpi)
tapped EditText "Message" via script
keyboard after tapping the composer field -> keyboard hidden (exit 1)
read-only session: disabled composer with the continue-in-new-session control
shown: This is a read-only session (via script, p=1.00, 16 elements, read 2231 ms, total 2234 ms): TextView "This is a read-only session"
shown: Continue in a new session (via script, p=1.00, 16 elements, read 2166 ms, total 2169 ms): Button "Continue in a new session"
shown: Send message (via script, p=1.00, 16 elements, read 2170 ms, total 2172 ms): Button "Send message"
Head log: ui-assert 66852459f603 exited 0
$ set -euo pipefail
$ out=$(node "$KWF_EXPLORER_SIM_CLI" do check --literal 'Continue in a new session') && rc=0 || rc=$?
$ printf 'continue control label -> %s (exit %s)\n' "$out" "$rc"
$ [ "$rc" = 0 ]
$ node "$KWF_EXPLORER_SIM_CLI" do tap 'Continue in a new session'
$ out=$(node "$KWF_EXPLORER_SIM_CLI" do check --literal 'Continuing 60-message pagination fixture') && rc=0 || rc=$?
$ printf 'continue destination -> %s (exit %s)\n' "$out" "$rc"
$ [ "$rc" = 0 ]
$ echo 'the destination-named continue control opened the Continue screen for the same session'
continue control label -> shown (exit 0)
tapped Button "Continue in a new session" via script
continue destination -> shown (exit 0)
the destination-named continue control opened the Continue screen for the same session
shown: Continue in a new session (via script, p=1.00, 16 elements, read 2217 ms, total 2220 ms): Button "Continue in a new session"
shown: Continuing 60-message pagination fixture (via script, p=1.00, 25 elements, read 3741 ms, total 3745 ms): TextView "Continuing 60-message pagination fixture"
Owner request

Owner audit 2026-09-23 of the mobile session composer (report kwf-orch ~/kwf/var/owner-evidence/composer-gaps.md; evidence ~/kwf/var/owner-evidence/composer-gaps-evidence/, screenshots attached to this item as owner evidence). Gaps D1 and U2. Mobile app (apps/mobile), session page of a read-only session: a stored session that is neither a Cloud Agent session nor live on a connected CLI (the session manager in apps/mobile/src/components/agents/mobile-session-manager.ts resolves read-only when cloud_agent_session_id is NULL and the session is not in activeSessions.list). Android and iOS, both themes. Today, in apps/mobile/src/components/agents/session-detail-content.tsx, isComposerMounted = !isReadOnly || messages.length === 0 (line ~1842) removes the composer, and the read-only footer (lines ~2440-2453) shows 'This is a read-only session' and a full-width 'Continue' button where the composer sits: the user reads a transcript and has nowhere to write. Tapping Continue (handleContinueInNewSession, line ~2095) pushes the Continue screen ('Continuing <title>' with Mode, Model, Run on, Repository) and the transcript leaves the screen, but the button does not say it opens a new session. Change: keep the composer visible at the bottom of a read-only session, disabled, with the reason beside it ('This is a read-only session'); give the continue action a label that names its destination (for example 'Continue in a new session') with a matching accessibility label, translated in every locale. Do not change which sessions are read-only or what Continue opens. Evidence files: out/00-session-page.png and out/design/B1-read-only-session.png (read-only footer, no composer), out/design/B4-after-continue.png (the screen Continue opens). Proof: seed a read-only session in the scenario namespace (pnpm dev:seed app:paged-history <email> creates a 60-message session with cloud_agent_session_id NULL), open it on Android (kiloapp://agent-chat/); the head shows the disabled composer with the reason and the destination-named continue control; the base shows the full-width 'Continue' with no composer; tapping the control on the head still opens the Continue screen for that session. Screenshots of both states.

@iscekic iscekic self-assigned this Sep 29, 2026
@iscekic
iscekic marked this pull request as ready for review September 29, 2026 06:20
@kilo-code-bot

kilo-code-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the read-only composer change (always-mount with disabled from resolveSessionComposerDisabled, renamed continue label) plus the 87 locale catalogs and the new unit test; the composer's effects clean up after themselves and the removed isComposerMounted flag has no remaining references, so no defects were found in the changed code.

Files Reviewed (90 files)
  • apps/mobile/src/components/agents/session-detail-content.tsx - removed isComposerMounted; composer now mounts for every session and is gated only by isComposerVisible = !hasBlockingInteraction; read-only still locks the input via resolveSessionComposerDisabled; continue control relabeled to agentChat.session.continueInNewSession
  • apps/mobile/src/components/agents/session-detail-content.test.ts - new read-only composer test; helper names (mountDetails, childMessage, ROOT_ID, renderedText, renderedTextOutsideSheet, i18n) and mocked ChatComposer/Button host types all exist
  • apps/mobile/src/i18n/locales/*.json (87 catalogs) - agentChat.session.continueInNewSession added once per catalog, all non-empty, translated, valid trailing comma

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants