Repository navigation
fix(mobile): rename sessions from the list and stop inventing titles - #3919
Open
braindeadz wants to merge 1 commit into
Open
braindeadz wants to merge 1 commit into
braindeadz wants to merge 1 commit into
Conversation
… inventing placeholder titles The session action menu offered view details, archive, export and delete but never rename, even though the rename path already existed: the conversation header dispatched RemoteSessionIntent.RenameSession, the shared store handled it, and a remote session's title was already sent to the desktop as update_session_title. The list, action sheet, popup and sidebar simply never drew a row for it, because SessionActionPolicy exposed no rename capability. Three separate English placeholders made unnamed sessions look named and made a rename look unapplied: the shared fallbackTitle returned "New Session", the shared RemoteResponseMapper invented "Session <id>" from the id prefix, and the Android header fell back to "Remote session". A host that names no title sends none, so the shared layer now keeps the title blank and each surface renders its own localized "untitled" wording (sidebar_untitled on Android). Design: - SessionActionCapabilities gains an appended canRename flag rather than folding rename into archive/export: for a remote session rename is a round trip to the desktop, for a general chat it rewrites the local row, so it is neither of the local-storage operations those flags describe. The flag is appended so the existing four positional arguments keep their meaning. - The sheet, popup and sidebar take an optional onRename callback: a shell that cannot dispatch a rename renders no row instead of a row that does nothing. - The store keeps authority: the list keeps the old title until the desktop answers update_session_title. The editor enforces a 120-character client-side limit and refuses a blank or unchanged draft; the host normalizes the title and refuses an empty one. - A blank title is rendered per surface via sidebar_untitled rather than a fabricated English name. Compatibility: SessionActionCapabilities is a Kotlin data class consumed only by Kotlin code; the HarmonyOS client has its own parallel ETS SessionActionCapabilities and does not consume it, and the iOS client has no caller of it, so the appended field breaks neither. fallbackTitle's only Kotlin caller is the shared RemoteSessionStore; HarmonyOS uses its own ArkTS mapper. iOS surfaces that render the title raw will show a blank row where a fabricated "Session <id>" used to be, while most iOS surfaces already localize empty titles. Tests: new shared unit tests cover the appended flag position and the blank-title mapping. SessionRenameActionTest (10 instrumented tests) was executed on a real Android device against the original revision of this branch and passed 10/10 (author-reported, run outside this environment). The amended revision adds aSurfaceWithoutARenameCallbackDrawsNoRenameRow and removes the unused conversation_title_default string resource; that amended revision is compile-checked only (:app:compileDebugAndroidTestKotlin BUILD SUCCESSFUL) with no device run here, and makes no claim that the added test passed. Co-authored-by: Driss <[email protected]>
braindeadz
force-pushed
the
fix/mobile-session-rename
branch
from
November 14, 2026 16:30
6d7e621 to
5401aef
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Let Android users rename a remote session from the session list, the action
sheet/popup and the sidebar rail, and stop the shared mobile layer from
inventing placeholder titles for sessions the desktop has not named.
Root cause, verified in code on the base commit: the rename path already
existed end to end — the conversation header dispatched
RemoteSessionIntent.RenameSession,RemoteSessionStore.renameSessionhandledit, sending
update_session_title— butSessionActionPolicynever exposed a rename capability, and the action sheet, the session list and
the sidebar never drew a row for it. Separately, three different English
placeholders made an unnamed session look named and made a successful rename
look like it had not been applied:
SessionNaming.fallbackTitlereturned"New Session";RemoteResponseMapperfabricated"Session <id-prefix>"from the id;"Remote session".A host that has not named a session sends no title at all. The shared layer now
keeps that title blank and each surface renders its own localized "untitled"
wording (
sidebar_untitledon Android).Fixes #
This PR is related to #3809 but does not close it. #3809 is about the legacy Android APK persisting
"Remote Code Session"as a user-edited title on 1.0.7+ hosts. This change stops this client from inventing titles and adds the rename action, but it cannot clear a title the host already holds, and it does not add a host-side compatibility path or ship a new APK. That work remains open in #3809.Type and Areas
Type:
surfaced).
Areas:
Motivation / Impact
What was broken for a remote-session user. Opening the overflow menu on a
session offered View details, Archive/Export (general chat only) and
Delete — never Rename. To rename, the user had to open the conversation and
use the header's title editor. Worse, a session the desktop had not named yet
showed a fabricated English title (
"New Session","Session <id-prefix>","Remote session"depending on the surface), so an untitled session lookedtitled, and after a rename the header and the list could disagree until a
refresh.
What changes.
Rename row that opens an inline title field seeded with the current title.
sidebar_untitledlabel onevery Android surface (list row, rail, header, sheet, home recent row) instead
of an invented English name.
empty string.
Design.
SessionActionCapabilitiesgains an appendedcanRenameflag rather thanfolding rename into
canArchive/canExport. For a remote session rename is around trip to the desktop; for a general chat it rewrites the stored row. It
is neither of the local-storage operations those flags describe, so a separate
flag reads correctly in both scopes. Appending it keeps the four existing
positional arguments meaning what they meant.
onRenamecallback. A shell thatcannot dispatch a rename renders no row rather than a row that does nothing —
the same pattern archive and export already use.
keeps the old title in the list until the desktop answers
update_session_title(it updates the local row only after the command issent, and re-reads
busyso a command that started while the field was opencannot submit).
120 characters client-side (the host normalizes and refuses an empty title;
the cap is the client's own guard against pasting a document into a one-line
row). A session whose existing host title already exceeds 120 characters
therefore cannot be saved without shortening it, and Save is disabled with no
explanation of its own.
sidebar_untitled, not replaced bya fabricated English string.
Verification
All commands were run on Windows with the project's local Android build
environment (node 22.23.5, JDK 17, Android SDK). Test counts are read from the
Gradle JUnit XML reports.
Baseline (base commit, no changes applied)
src/apps/mobile/shared:.\gradlew.bat --console=plain :core-domain:jvmTest :core-feature:jvmTest→ 655 tests, 0 failures, 0 errors, 0 skipped (BUILD SUCCESSFUL).
src/apps/mobile/android:.\gradlew.bat --console=plain :app:testDebugUnitTest→ 65 tests, 1 failure, 0 errors, 0 skipped. The single failure is
pre-existing on the official base commit:
com.openbitfun.mobile.app.ui.chat.ProcessPresentationTest.completedWorkSummarizesWithoutHidingLiveOrFailedToolswith
java.lang.AssertionError: expected:<3> but was:<1>(
ProcessPresentationTest.kt:44).After the change
(BUILD SUCCESSFUL). The two extra tests are the new/renamed ones below.
The failure is the same
ProcessPresentationTest.completedWorkSummarizesWithoutHidingLiveOrFailedToolstest with the same
java.lang.AssertionError: expected:<3> but was:<1>message — an unchanged baseline failure, not a regression. This suite is not
green on the base commit and this PR does not make it green.
SessionRenameActionTestwere executed on areal Android device against the original revision of this branch and passed
10/10 (author-reported; not run by the AI). The amended revision adds
aSurfaceWithoutARenameCallbackDrawsNoRenameRowand drops the unusedconversation_title_defaultstring resource; that amended revision iscompile-verified only, from
src/apps/mobile/android,.\gradlew.bat --console=plain :app:compileDebugAndroidTestKotlin→ BUILD SUCCESSFUL. No instrumented test was re-run here, and this does not
claim the added test passed. AppSidebar's
onRenameRemoteSessionid+titleadaptation is covered by compilation only (no widget test renders the sidebar
rail).
Node guards (from the repo root)
node scripts/check-mobile-architecture.mjs→ PASS:"Mobile shared core architecture contracts are satisfied."
node --test scripts/mobile-client-build.contract.test.mjs→ PASS:2 tests, 0 failures.
node scripts/mobile-ui-design-system.mjs --check→ PASS:"Contract and generated files are in sync."
node scripts/check-git-object-sizes.mjs --base upstream/main --head HEAD→ PASS: "Checked 16 Git blobs; no unapproved objects exceed 5242880
bytes."
node scripts/mobile-ui-preview.mjs --check→ SKIPPED:"Missing canonical design-system asset for /design-system-tokens.css." The
script needs the generated
design-system/packages/design-tokens/dist/tokens.css(produced by a design-system build); this checkout has no
node_modulesandpnpmis unavailable, so the asset is absent. Nothing was installed.node scripts/i18n-audit.mjs→ SKIPPED:"Failed to load TypeScript for i18n audit: Cannot find module 'typescript'
… Run pnpm install before running pnpm run i18n:audit." Missing dependency;
nothing was installed.
node scripts/audit-frontend-colors.mjs --kind native-mobile→ SKIPPED:"exclusions[2].path does not exist: src/web-ui/public/monaco-editor". The
script needs a vendored/generated web-ui asset that a full install would
provide; absent here.
None of the three skipped scripts read the files changed by this PR (verified by
grep), and all three fail for missing build output or missing packages, not for
anything in this diff.
Verification evidence (for the reviewer)
New/updated shared unit tests:
SessionListPolicyTest.fallbackTitleStaysBlankForEveryMode(replacesfallbackTitleIsIndependentOfMode).SessionActionPolicyTest.aRemoteSessionOffersRenameButNeverTheLocalStorageActions(renamed from
aRemoteSessionNeverOffersTheLocalStorageActions).SessionActionPolicyTest.theRenameFlagWasAppendedSoEarlierPositionsKeepTheirMeaning(new — pins that the appended flag does not shift the earlier positions).
SessionActionPolicyTest.archiveAndExportBelongToGeneralChatAlone(updated tothe new capability shape).
SessionActionPolicyTest.aCommandInFlightWithdrawsEverything(updated).RemoteResponseMapperTest.aHostTitleThatIsMissingStaysBlankAndATitledSessionKeepsItsName(new — blank/missing title stays blank, a real title is kept).
New instrumented tests (
SessionRenameActionTest, test tagssession-rename-rowandsession-rename-field):theRemoteMenuOffersRenameAndSeedsTheFieldWithTheCurrentTitlesubmittingSendsOneRenameForThisSessionAndDismissestheSubmittedTitleIsTheTrimmedDraftaBlankHostTitleIsShownAsUntitledAndCanBeGivenANamecancellingReturnsToTheActionListAndSendsNothinganUnchangedOrBlankTitleCannotBeSubmittedaCommandStartingWhileTheEditorIsOpenCannotBeSubmittedaCommandInFlightOffersNoRenametheWideSidebarPopupOffersTheSameSteptheHomeRecentRowShowsTheUntitledLabelForABlankHostTitleaSurfaceWithoutARenameCallbackDrawsNoRenameRowUpdated instrumented test:
ConversationHeaderTestnow asserts a blank titlerenders
sidebar_untitled.The now-unreferenced
conversation_title_defaultstring was deleted from bothsrc/apps/mobile/android/app/src/main/res/values/strings.xmlandsrc/apps/mobile/android/app/src/main/res/values-zh/strings.xml; no productionor test code reads it any more.
Reviewer Notes
This PR closes no issue. (
Fixes #is deliberately left empty; see the Summary note about #3809.)Compatibility (this touches shared code used by other clients)
SessionActionCapabilitiesis a Kotlin data class consumed only by Kotlincode (the Android app and shared tests). The HarmonyOS client has its own
parallel
SessionActionCapabilitiesclass inpages/policy/SessionActionPolicy.etsand does not consume the Kotlin one, sothe appended field does not touch it. The iOS client has no caller of the
Kotlin
SessionActionCapabilities, so the appended field is not a compilebreak for it.
SessionNaming.fallbackTitlenow returns"". Its only Kotlin caller is theshared
RemoteSessionStore(when a new session has no title). HarmonyOS usesits own ArkTS
RemoteResponseMapperand is unaffected. On iOS, severalsurfaces already render an empty title as a localized "untitled" label
(
SessionActionComponents,MobileAppModel+RemoteSession,ConversationHeader), but a few rawText(session.title)sites (e.g. thehome recent row and some sidebar rows) will now show a blank line where a
fabricated
"Session <id>"used to appear. That is a rendering difference oniOS, not a crash or compile break; a follow-up may want to route those raw
sites through the same localized "untitled" label.
RemoteSessionUiState.Ready(...)is constructed only inside the shared storeand shared tests; iOS/HarmonyOS reach it through their bridges, not by
constructing it directly, so no break.
RemoteSessionIntent.RenameSessionalready existed on the base commit(handled in
RemoteSessionStore.renameSession, dispatched by the Androidconversation header, sending
update_session_title). The claim of this PR istherefore "the rename path existed but no list/sheet/sidebar UI offered it",
not "rename was implemented".
What a remote-session user sees
reachable from the conversation header.
Android surface; Rename is available from the list, the sheet, the popup and
the sidebar rail; the row keeps its old title until the desktop acknowledges
the change; if the desktop refuses, the title is unchanged and the failure is
reported only through the connection state (see Limitations).
Limitations / risks to attack
update_session_titleagainst a real (and especially an older or third-party) host is unverified here. A host that rejectsupdate_session_titledoes not merely leave the old title: the store's sharedhandleFailurediscards the rejection message and, for aRemoteRejected/CLI_NOT_IMPLEMENTEDanswer, sets the connection phase toFAILED(RemoteSessionStore.kt:2215-2233). The session list survives, but until the 10 s health probe recovers, the status bar reads as a connection error and the remote composer is disabled. The user is told nothing about the rename itself. This is pre-existing store behaviour that this PR makes reachable from the list, and it is not fixed here.original revision (10/10
SessionRenameActionTesttests, author-reported). Theamended revision — the added
aSurfaceWithoutARenameCallbackDrawsNoRenameRowtest and the removed
conversation_title_defaultresource — has only beencompile-verified here, not re-run on a device, so changes in that delta are not
device-proven.
ui/chat/ConversationHeader.TitleEditorcaps nothing. The two editors differhere; that inconsistency is known and deliberately not widened by this PR.
already-open confirmation can still submit after
busyflips). It is out ofscope and is left untouched, with a code comment marking it.
Verification); none read the files in this diff.
Honesty disclosure
AI-assisted implementation; testing level: lightly tested. The AI ran no
device and no live desktop host. A real-device run of the original instrumented
suite exists and is author-reported (see Verification and Limitations); the AI
only compiled the amended revision.
Author-reported (not independently verified by the AI): the contributor
manually smoke-tested the rename on a real desktop host from one Android device,
and the new title appeared both in the session list and in the conversation
header. A screenshot of that run is withheld because it shows a private
workspace name.
Checklist