Skip to content

feat(harmonyos): show every account desktop in the sidebar - #2366

Merged
wgqqqqq merged 6 commits into
GCWing:mainfrom
wgqqqqq:feat/harmonyos-sidebar-device-directory
Aug 18, 2026
Merged

feat(harmonyos): show every account desktop in the sidebar#2366
wgqqqqq merged 6 commits into
GCWing:mainfrom
wgqqqqq:feat/harmonyos-sidebar-device-directory

Conversation

@wgqqqqq

@wgqqqqq wgqqqqq commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

The phone could only ever see one desktop at a time. To look at another
machine's workspaces you had to move the global control target, and the
machine you came from vanished from the sidebar. This makes the whole
account visible at once: the workspace section becomes `device > workspace

session`, while the conversation surface stays deliberately single-device.

The fan-out needs no new transport. deviceRpc was already stateless —
each call carries its own relay URL, session and target device id — so a
read-only directory service builds a transport per device and leaves
RemoteSessionManager's single live connection untouched.

Four things this fixes along the way, each of which was a real defect
rather than a consequence of the new layer:

  • Creating a session no longer moves the host desktop's open workspace.
    CreateSession already binds the workspace_path it is given, so the
    extra set_workspace was redundant — and under multi-device it was
    wrong: opening a session on B should not drag B's window elsewhere.
  • A device switch the target never answers is now bounded. The account
    transport was dropping the caller's timeout and always taking the 130s
    read timeout, so a failed switch hung for two minutes with no waiting
    state and no way back. The handshake gets its own 15s budget, and a
    failed switch restores the previous target.
  • Presence is refreshed rather than fetched once at cold start, and a live
    link now overrides the relay's answer. The relay's online flag is a
    projection of a socket registry that lags and drops entries; when we are
    pinging a desktop every fifteen seconds, that is first-hand proof.
  • The active device's row no longer renders a flattened placeholder
    forever. ForEach binds a child to the item it was first created with,
    so the placeholder used before the device list arrives has to key
    differently from the real row, or the child is never rebuilt.

Verification: harmony:architecture, assembleHap, LocalTest and
theme:color-audit:all all pass; the six manual checks in the design doc
were walked on a physical phone against two live desktops, including the
two failure paths (a frozen desktop reproduced with kill -STOP, and a
second-device session creation confirmed not to touch that desktop's
current_workspace_id).

Design notes: src/apps/mobile/harmonyos/docs/mobile-control-console-design.md.
The last commit is documentation only — where the phone sits in Detached
Dispatch, which is not implemented here.

wgqqqqq and others added 6 commits August 18, 2026 16:52
Creating a session in a workspace other than the open one first sent
set_workspace, so the phone moved the desktop's own view before the create
landed. CreateSession already binds whatever workspace_path it is handed, so
that round trip bought nothing and cost the desktop its place — harmless
enough with one machine in view, wrong once the sidebar lists several.

Carrying the path on CreateSessionOptions collapses the three-branch dance
around it, along with the recovery path that existed only to undo a host
workspace this no longer touches.

Co-authored-by: Cursor <cursoragent@cursor.com>
The remote surface only ever faced one machine: the workspace section drew
the desktop the phone was currently driving, and reaching another one meant
moving the global control target, which took the first one out of view. A
console that can only see where it happens to be pointed is not a console.

deviceRpc is already stateless — every call carries the relay URL, session
and target device — so a read-only directory can fan out without touching
RemoteSessionManager's single transport, which still owns the conversation.
Expanding a device is what dials it; offline rows are never dialed at all,
which is what keeps opening the drawer off a row of 130s timeouts.

Each device gets its own SidebarDeviceGroup because SessionListProjectionCache
holds a single slot keyed on the workspace, so one instance serving N devices
would miss on every rebuild. Catalogs persist per device_key and the live
device's own catalog is captured on publish, so a device keeps its rows after
the control target moves on.

Co-authored-by: Cursor <cursoragent@cursor.com>
Scoped to the HarmonyOS app: what was verified against main, why the
observation surface fans out per device while the conversation surface stays
on one, and why cross-device execution status is out of reach until the
remote SessionInfo payload carries a status field at all.
…ooked at

Device presence was fetched once at cold start and never again. The relay
keeps presence in a live socket registry, so its answer is current whenever
it is asked — the phone simply stopped asking, and a desktop that came
online after launch stayed grey in the sidebar until the app was killed.

Returning to the foreground and opening the sidebar each refetch, with a
15s freshness window so the two firing together on a cold start cost one
request rather than three.
Tapping a session on another desktop tears the current link down before
dialling the new one, so a desktop that has gone away used to cost the user
both machines for two minutes: AccountDeviceCommandTransport dropped the
caller's timeout on the floor and always took deviceRpc's hardcoded 130s,
nothing on screen said a switch was underway, and when it finally failed the
phone was left on no device at all with polling and the heartbeat stopped.

The timeout now travels from the transport through to deviceRpc, and the
connect handshake passes 15s of its own — a desktop that is running answers
get_workspace_info from memory, so silence there means it is gone, not busy.
The switch seam shows a toast, and a failed dial restores the device it
started from, snapshotting it before the teardown makes it unreadable.

Also from the same round on the device: the active desktop rendered grey
because the ForEach key for the unpaired placeholder collided with the real
row for that same machine, freezing the child on an object nothing updates;
and a live link now outranks the relay's presence answer, which is hearsay
from a socket registry that lags. Verified against two desktops, the failure
path reproduced by freezing one so the relay kept reporting it online.
The phone decides; it never executes. Detached Dispatch already has the
shape this needs — the phone remote-controls A, A is the controller that
owns the Git baseline, and B is the target that runs the worker. What was
missing was never authority, only an entry point on the phone.

So the design is written as a checklist of decisions, not of features: for
every decision the desktop can make about a job, the phone needs a verb,
and every desktop command left out has to say why it is work rather than a
decision. What cannot travel is the conversation — the submit request
carries no history, on any client — and that boundary is stated rather
than papered over.
@wgqqqqq
wgqqqqq merged commit c28e5dd into GCWing:main Aug 18, 2026
8 checks passed
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