lody: mount the real mobile branch, scrubbed to the v1 scope — seam patch 16 - #171
Merged
Conversation
pythonlearner1025
force-pushed
the
lody-mobile-mount
branch
from
September 2, 2026 02:02
6aa1002 to
7244bb5
Compare
The 463-row support matrix and the user's KEEP/KILL/HIDE decision lived only outside the tree, so no reader could check a claim against the code. This is that record: the 25 areas and the call on each, the five answered DECIDE questions, the five v1-scope flags and what carries each one, the seam-patch index, and a dated amendment log. Two amendments land with it. Mobile moves from KILL to IN — the branch this commit opens mounts Lody's real phone experience. Connection status is Lody-side removed, because BlitzOS chrome owns it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
The vendoring rule is declare-then-edit, so this commit is the declaration alone. Seam patch 5 wrote down that the mobile branch was deliberately left unpatched, which was correct while both routes dropped it. Mounting the branch makes that the gap. Two halves, and one sentence explains both. session-detail.tsx returns for mobile 952 lines above getSharedChatSurfaceProps, so props are lost at the mobile fork and capabilities are not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
A phone got the desktop pages with their own mobile layouts inside, and none of the shape upstream designed around them: the landing was torn down when a session opened, there was no drawer to swipe back through, and no layer beneath to reveal. Upstream owns that shape in MobileWorkspaceStack, mounted from MainLayout, which BlitzOS does not mount and which takes one prop. MobileSessionStack.tsx is that stack composed on our side, with the seven props the two pages need: the landing stays mounted as the base, the session layers over it in the same Vaul right-drawer the PR view uses, the last session stays sticky so it can animate out, and close navigates rather than pops history. The routes now do what upstream's do: the _auth layout carries the stack on a stack route, and both leaves return null on mobile while ChatRoute keeps publishing the base context the stack reads. The landing keeps host tabs' content on a phone and loses their strip: the strip is the desktop tab affordance, and a second tab system above Lody's own is what plans/LODY-TERMINAL-TABS.md exists to prevent. Two v1-scope props join the five: hideSettingsEntry (the mobile home's gear, the same species as the hint band's Go-to-settings) and hideConnectionStatus behind a new connectionStatus flag, because BlitzOS chrome owns that answer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
Half A makes a workspace terminal reachable. The mobile tab sheet keeps its own kind enum, which seam patch 5 left alone on purpose; it gains 'custom' and an icon slot, the same two things the desktop strip already had. session-detail appends the host list to mobileViewers, routes the tap to onSurfaceTabSelect, mounts each host tab's content on the mobile branch, and lets an active host tab cover the conversation the way it does on a desktop. The sheet gets no close verb: upstream's viewer rows have none, and the BlitzOS rail closes a terminal. Half A also closes a hole. session-detail:4368 has written kind: v.type from a ViewerTabItem since wave 3, and that union gained 'custom' while the mobile enum did not follow. Half B is the scrub. The mobile branch returns 952 lines above getSharedChatSurfaceProps, so it carried readOnly and no hide* at all: the notification prompt and the Agent Role picker rendered on a phone and not on a desktop. Copy URL, Share with team and Change owner each get the term the desktop menu already has, and so does the sharing visibility row. Two surfaces have no desktop twin and so had no gate. The mobile header's GitHub mark re-derives the repo instead of reading the capability-nulled value, which is a bug rather than a BlitzOS opinion. The home header's connection pill and the header's sync indicator answer hideConnectionStatus, because BlitzOS chrome owns that answer. On the landing: the settings gear, the Add-a-GitHub-repository row, the GitHub projects sub-tab and the download-the-client takeover. hideProductHints reached only the desktop hint band before this. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
…chine lody-mobile-mount.test.tsx mounts the real vendored components and drives them. A host tab is listed in the mobile tab sheet, carries the host's glyph and reports its id; a host tab with no glyph renders the fallback, which is the row that used to throw because the kind was not in the icon record. Every scrubbed surface is asserted both ways — dark with the suppression and present without it — because a test that only checks "the button is absent" also passes when the component stopped rendering at all. The source pin moved to lody-seam-pin.test.ts and now costs four readFileSync calls. It was inside lody-surface-tabs.test.tsx, whose file-level beforeAll imports Monaco, shiki, three and the Loro WASM; on a loaded machine that import exceeds its hook budget and vitest reports every test in the file as skipped, the pin included. A pin a slow machine silently turns off is not a pin. The helper it shares with that suite is upstream-seam-pin.ts, and the two mobile files seam patch 15 edits have baselines beside the two that already did. The stack also forwards resetDraftKey, which upstream's own stack drops: the BlitzOS rail's "New session" clears the landing draft through the address, and without this a rail press on a phone would do nothing visible. It wraps the pages in upstream's own container chain, because the mobile layouts size themselves against it and BlitzOS mounts no MainLayout to supply it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
If either `to` were not an address our tree holds, matchRoute would answer false for the life of the surface, the stack would never mount, and a phone would show a blank pane — a failure with no error in it. lody-router-targets already proves both addresses exist; this proves we spell them the way upstream's own mobile layout does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
It was compared to null and never read, so a find() built an object the code did not use. some() says the same thing in one line and reads like the desktop rule it mirrors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
Four assertions encoded the pre-amendment scope. Three were arithmetic — five flags became six and seven suppression props became nine — and the fourth said "the mobile branch is not mounted", which was true only because both routes dropped it. That describe is inverted rather than deleted. What the suite must hold now is the other side of the same claim: the stack hangs off the route both leaves share, both leaves return null on a phone and exactly two do, the chat leaf still publishes the base context, and exactly ONE file in our tree imports a vendored mobile screen. Two importers would mean two things draw the phone's session, which is a duplicate mount rather than a feature. The suppression check now reads both mounts. A prop may live in either — hideSettingsEntry and hideConnectionStatus have no desktop surface at all — but it must live in one of them, or the flag reaches nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
#173 merged first and took the number 15 for "the host owns connectivity". This is the rename, and it is more than a number: that patch and this one both had a `connectionStatus` flag and a `hideConnectionStatus` prop, chosen independently and spelled the same. Theirs is now the only one. Deleted here as duplicates: the flag, the prop, both vendored declarations, the chat-landing connectionUiState gate, and the mobile-header isSyncing gate — their hunk 11 gates the flag at its source, above my call site. Kept, and it is the one thing their patch could not reach: the mobile SessionChatInterface now takes hideConnectionStatus. Their hunk 13 forwards it through getSharedChatSurfaceProps, which session-detail defines 952 lines below its own mobile return. That fork is the same sentence that explains why hideCloudMenuItems, hideNotificationPrompt and hideAgentRoles needed forwarding too. One flag, one prop, one gate story. The pin file carries their new session-detail anchor beside mine, so both patches are checked against one baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
Seam patch 16's mobile stack imports getMobileMainLayoutRootClassName — two class-name helpers MainLayout also calls, and that the stack reproduces precisely BECAUSE it does not mount the layout that would supply them. A bare substring check read that as a mount. It now looks for an element or an import of the component. That is the claim the test was always making, and the old spelling could have been satisfied by deleting a doc comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
pythonlearner1025
force-pushed
the
lody-mobile-mount
branch
from
September 2, 2026 02:55
5951e7e to
6a099e7
Compare
pythonlearner1025
added a commit
that referenced
this pull request
Sep 4, 2026
Brings the quick-action strip up to date with main after #159, #164, #171, #173, #181, #183, #197 and #207. Thirteen files conflicted; the resolutions, so a reviewer can check them one by one: - `WebAppHeader.tsx`: deleted with the native tab strip on main (#159); the PR's edits to it are moot. Its two exports moved where main put them (`NewTabMenu`, `SessionTypeIcon`). - `CloudApp.tsx`: main's strip deletion wins over the PR's drag wiring (`useWorkspaceTabDrag`, `moveTab`, `splitTab`, the drag ghost); the PR's side-panel binding, Connections host tab and mobile panel sheet stay; main's `workspace-chat-memory` stays. `filesHostRegion`, `ShareToDriveDialog` and `FolderAttachmentView` went with the Files pane the PR retires — their only callers were inside it. - `router.tsx`: the PR's side-panel hooks sit above main's mobile early return, because hooks must. - `SessionRailSidebar.tsx`, `SessionRail.tsx`: main's rows with the close button, minus the `filePath` the PR removed with file tabs. - `lody-surface-tabs.test.tsx`: main moved the seam pin into `lody-seam-pin.test.ts`; the PR's anchors for the three new files and the four session-detail lines are ported there. - `shell-smoke.test.tsx`: main's "the split is a placement" assertions, driven by the Connections icon the PR's strip draws; the drag test goes with the drag. - `workspace-panes.test.ts`: `moveTab` is gone, so the backgrounded panel case builds its side-pane document by hand. - `SessionTypeIcon.tsx`: `WebAppTabModel.panel` narrows to `'connections'`, which is the one panel the PR leaves. Main already used seam numbers 10 and 11 for other patches, so the PR's two seams are 19 and 20 everywhere: the ledger, the seam-pin test, the baselines README, the upstream PR drafts and every code comment. The merge runbook's file table keeps the PR's rows with the new numbers and now says plainly that the ledger is the authority for the count. Gates on the merged tree: webapp typecheck, lint gate, and the full webapp vitest run (113 files, 948 tests) pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
The diagnosis: the test, not the poller
packages/webapp/test/shell-gateway-resilience.test.tsx>the platform snapshot poller>never has two reads in flight against a box that does not answertimed out at 5 s on clean main (3881574).
The poller is correct.
useLodyPlatformSnapshotinpackages/webapp/src/lody/platform.tsxhas not changed since #155 landed it.git lognames one commit for that file:6a6cb18d(#155). None of #156-#164,#172 or the cgroup changes touched it. The single-flight invariant holds. I
measured it: with a
fetchthat never answers, the hook issues exactly oneread, and 30 000 ms of fake time add none.
The test's timed region was wrong. The test imported
../src/lody/platform.jsinside its ownit()body. That module staticallypulls
convex/react,@lody/platformand two@lody/componentsproviders, sothe import is seconds of transform. Instrumented on this box with a real clock:
The 5 s test timeout fired on module weight. The property was never reached.
The other ten tests in the file pass because their heavy import
(
SessionSurface) islazy()and mocked away.The fix
The import moves to the file's static import block. The transform then lands in
the file's import phase, which no test timeout governs.
The assertion is unchanged.
expect(reads).toBe(1)afteradvanceTimersByTimeAsync(30_000)against a hanging fetch still states thesingle-flight invariant, word for word. Nothing is relaxed and no test is
removed. The test now takes 27 ms instead of timing out at 5 018 ms.
Gates
npm run typechecknpm run lint:gateBLITZDEV_MANAGED=1 npm test— webappBLITZDEV_MANAGED=1 npm test— box guest-testsvitesttest:python/test:bash/test:host/test:scriptsThe control-plane stages were rerun alone: the box was running another agent's
full webapp suite at the same time, and the first combined run failed under that
contention. Every stage passes on its own with zero assertion failures.
The target suite was also run five times in a row after the fix: 11 passed,
five times out of five.