Skip to content

Inbox mobile: guided reviews in the iPhone app (M4) - #1790

Merged
backnotprop merged 2 commits into
mainfrom
feat/mobile-m4-guides
Oct 9, 2026
Merged

backnotprop merged 2 commits into
mainfrom
feat/mobile-m4-guides

Conversation

@backnotprop

@backnotprop backnotprop commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #1785 (M2, feat/mobile-m2-attachments, rebased onto its head 2eb4480). M4 needs M2's surface host (SurfaceHost.swift, the two schemes, the main-frame message rule, the touch window). Retarget to main when M2 merges; I rebase once then.

The review's fixes (REVIEW-PLANNOTATOR-MOBILE-M4-2026-10-08, one round)

  • Blocker 1, a failed tick stayed drawn as reviewed. saveTicks now keeps the ticks the Inbox last confirmed while taps are in flight. When a save fails, the thread is read again. If the computer cannot answer that either, the phone goes back to the confirmed ticks and hands them to the surface, so Reviewed reads off again. A successful save writes the Inbox's answer when it is the last tap in flight (review item 5). The alert is "Not saved" with the error's own words, not the two sentences that said the same thing. Proof step: on section 03 with the proxy down, Reviewed is tapped, "Not saved" shows (frame 6.2-not-saved), and after OK Reviewed reads off (value 0) while the Inbox still keeps true,true,false,false.
  • Blocker 2, Courier New in the diffs. .sf-gcard sets --diffs-font-family: var(--font-mono) (the surface's ui-monospace list). The renderer's own list reached Courier New on iOS. The variable crosses the renderer's shadow root. Frames 6.2 light and dark show the system monospace.
  • Item 1, the section card on a phone. The section's collapse chevron is not drawn (.sf-gcard button[aria-label="Collapse section"]): on a one-section screen it collapsed the only content, and it was a 32 pt target. The diff header uses the review app's own phone layout (compactTouchLayout, passed by the surface's guide reader through getDiffRendererProps; ReadOnlyDiff accepts the prop). It shows the whole path, puts its ellipsis at the leading edge only when the path does not fit, and has a 44 pt header and toggle. One CSS rule starts a path that fits at the left. Plannotator's components are unchanged.
  • Item 4: ios.yml paths gain packages/guide-viewer/**.

The story

An agent's guided review now opens on the phone. The thread shows the guide as a tile. A tap opens Plannotator's guide viewer full screen: the sections with their reviewed marks and Continue (6.1), then one section per screen, with its diffs unified and wrapped and Previous, Reviewed and Next at the thumb (6.2). The app's bar follows the guide ("02 of 04", close becomes back). Each Reviewed tick is saved through the device door and shows in the desktop window. The guide's text follows Dynamic Type. The section card did not follow it before this PR.

What changed

  • apps/ios/Plannotator/Guides/GuideCover.swift (new): the full-screen cover on M2's warm web view. It loads GET messages/:id/guide (7.19) and sends set_appearance, set_wrap and open_guide with the ticks kept on the message. It reads reviewed (selection haptic, then a save through the door) and section (title, back, a VoiceOver screen change). Back sends open_section null. The wrap button sends set_wrap. Loading, error with Try Again, links through M2's https/mailto rule, a reload after the web process ends.
  • apps/ios/Plannotator/Attachments/AttachmentTiles.swift, Thread/ThreadScreen.swift: the guide's tile (the tinted book mark; "Guided review: <title>"; "4 sections, 6 files, +70 −14"). It counts in "N attachments", as 8.1 draws it.
  • apps/ios/Plannotator/Attachments/SurfaceHost.swift: the reviewed and section events; OpenGuide, OpenSection and SetWrap; sendAppearance shared by both covers (moved out of AttachmentCover.swift).
  • apps/ios/Plannotator/App/SourceSession.swift: saveTicks. A tick shows at once. Ticks on one guide go out one after another, each with every section's tick, so the last tap wins. When a tick is refused or cannot reach the computer, the thread is read again, an alert says "Not saved", and the surface gets back the ticks the Inbox keeps.
  • apps/ios/PlannotatorKit/Sources/PlannotatorKit/Guides.swift (new), Models.swift: guide(message:) and saveGuideReviewed (7.20). The message gains guide and guide_reviewed. The snapshot stays opaque JSON for the surface.
  • packages/core/inbox-surface-bridge.ts, packages/inbox/surface/SurfaceApp.tsx: set_wrap (shell to surface) sets Plannotator's diffOverflow (wrap or scroll). The read-only diff renderer already follows that setting.
  • packages/inbox/surface/surface.css: Dynamic Type for the guide (below).
  • adr/implementation/inbox-mobile.md section 5: the set_wrap row and its reason, edited first.
  • Proof: apps/ios/PlannotatorUITests/GuideProofTests.swift (new); apps/ios/scripts/proof.ts (/guide sends the fixture through submit_guide; /guide-ticks reads the ticks through the window's own GET /api/inbox/threads/:id); fixture apps/ios/scripts/fixtures/ledger-export.guide.json + .patch (the record's "Stream ledger exports" guide, four sections, six files, made from a real git diff).
  • .github/workflows/ios.yml: paths gain packages/inbox/guide/** (the surface's guide reader).
  • AGENTS.md Inbox section and apps/ios/README.md: M4 written in.

Dynamic Type on the guide (the spike's finding, left to M4 by M2)

iOS WebKit lays out a zoomed box larger but keeps its letters at their own size. The section card (.sf-gcard) now also takes -webkit-text-size-adjust (--sf-guide-adjust, set with --sf-text-adjust on set_appearance), as M2 did for documents. Desktop WebKit ignores that property. Seen at the largest size, three more changes in the same file:

  • The card's layout zoom stops at the largest size that is not an accessibility size (23/17). Otherwise its padding takes a third of the screen's width.
  • Continue, Previous, Reviewed and Next stop growing at 23 px, as the shell's bars do. Before, "Reviewed" ran under "01" and "03".
  • The guide's title is the body plus 11 px (28 px at the default, as before). A native title grows by points. At 1.65 times the body it reached about 100 px.

Record sections made real

  • 6.1 The guide and its sections: title, intent, counts, sections with marks, Continue at the first section not reviewed (frames 6.1, 6.1-two-reviewed).
  • 6.2 A section with its diff: prose, files, diff unified and wrapped, the wrap button, Previous, Reviewed, Next, "02 of 04" and back in the bar (frames 6.2, 6.2-reviewed, 6.2-unwrapped).
  • 8.1 (the guide's tile at the foot of Pi's message) (frame 8.1-guide-tile).
  • Haptics: selection when Reviewed is marked (6.2's note).
  • Frames light and dark beside the renders: research/operational/mobile-proofs/M4-2026-10-08/compare/ (record left, built right). Recording: M4-guide-flow.mp4. Frames at the largest Dynamic Type size: ax-6.1, ax-6.2.

Proofs

  • bun apps/ios/scripts/proof.ts --binary <compiled plannotator> --only PlannotatorUITests/GuideProofTests, on a fresh iPhone 17 simulator (iOS 26.5), against plannotator inbox --background under a temp data dir:
    • Pi sends the fixture with submit_guide through the MCP SDK's stdio client (scripts/inbox-sim.ts).
    • The guide opens from the thread. 01 is marked in the sections, Continue goes to 02, and 02 is marked with Reviewed. After each tick the window route reads true,false,false,false, then true,true,false,false.
    • Next goes to "03 of 04" and Previous back to "02 of 04", checked on the bar's title.
    • Wrap goes off and on again (the accessibility value reads Off, then On). The largest Dynamic Type size is drawn on a section and on the sections. Back reads "Guided review" again, with "Continue with 03".
  • The full proof (proof.ts with no --only: pairing and answering, attachments, the guide) on this head, rebased onto M2 at 2eb4480, with the review's fixes: 3 of 3 pass (pairing and answering 255 s, attachments 241 s, guide 106 s). The guide loads nothing from the network and opens as before under M2's rule list. Log: research/operational/mobile-proofs/M4-2026-10-08/proof-run-all-flows.log in the Workspaces meta repo.
  • tests/e2e/surface.spec.ts (Playwright WebKit, compiled binary): 9 of 9 pass. This includes S1's guide test and the theme and text-size test.
  • Still needs the device: camera pairing (M1's, the proof pairs by the typed code) and the guide read by a real finger with VoiceOver on. The simulator run covers the ticks, the bar and Dynamic Type.

Gates run

bun run typecheck; bun test tests/entry-assets.test.ts tests/favicon-surfaces.test.ts packages/core (348 pass) under a temp data dir; bun run --cwd packages/ui smoke:package (core changed); bun run check:release-version; the surface Playwright spec; xcodegen generate leaves no diff; gitleaks git . --config .gitleaks.toml --log-opts="origin/feat/mobile-m2-attachments..HEAD": no leaks. The guide chain is untouched (only the surface's own sf- CSS), so the guides.show manifest is unchanged.

Deviations from the record and the contract, with reasons

  1. No "..." button on 6.1. The record draws one and says nothing about what it holds. On a phone in v1 there is nothing to put in it. Sharing the guide as Plannotator's portable file would need a new bridge pair to run createGuideHtml in the surface. An empty menu is worse than none, so the trailing slot is blank and the title stays centred. Follow-up if the owner wants Share there.
  2. 6.2's section is Plannotator's GuideSectionCard, as S1 mounted it, with its card chrome, "02 / 04" and the grouped background. The record draws a bare title on the plain screen. The record's rule is Plannotator's components as they are, and the review agreed. The rough edges on a phone are fixed in the surface's CSS (the chevron is not drawn, the header uses the compact layout, the diff is in the system monospace).
  3. No swipe down to dismiss (BRIEF section 4). Same as M2's attachment cover. A swipe down inside a scrolling web view fights the scroll. Close and back are in the bar. Follow-up for both covers together.
  4. Contract: set_wrap added to section 5 (reason above). This is the only shape change.
  5. Ticks made on the computer while the guide is open on the phone show the next time it is opened, not live. A live redraw would remount the guide and throw away the person's place. Named, not built.
  6. The fixture's counts (+70 −14) differ from the render's (+212 −38). This is content, not design.

Limits

None.

Leaves out

Comments on diff lines (owner decision 5); everything in later steps. Follow-ups from the review: swipe down to close for both covers (item 2); a dropped connection reads as "Your computer sent an answer this app can't read. Update Plannotator on both", M1's error mapping (item 3); the proof's wrap step asserts the shell button, not what the surface drew (item 6); raw <img src> in guide prose is auto-linked by Plannotator's inline markdown, inert on the phone (item 7, for the guide-viewer owners).

@backnotprop
backnotprop force-pushed the feat/mobile-m4-guides branch 6 times, most recently from 0104723 to 1373a4b Compare October 8, 2026 20:00
@backnotprop
backnotprop changed the base branch from feat/mobile-m2-attachments to main October 9, 2026 01:31
A guide's tile in the thread opens a full-screen cover on the bundled surface
(6.1, 6.2): the sections with their reviewed ticks and Continue, one section
per screen with Previous, Reviewed and Next. The bar is the shell's: its title
follows the surface's section messages ("02 of 04"), close turns into back,
and the wrap button sends the new set_wrap. Each tick is saved through the
device door (7.20) and shows in the desktop window.

Dynamic Type reaches the guide's section card through text-size-adjust (iOS
WebKit ignores zoom for letters); the card's layout zoom and the controls at
the thumb stop at the largest non-accessibility size, and the guide's title
grows by points, not by a factor. The proof sends a shipped ledger-export
guide through submit_guide and marks two sections reviewed on the phone.
…fs in the system monospace, the phone's diff header, no collapse on the only section, guide-viewer in the CI paths)
@backnotprop
backnotprop force-pushed the feat/mobile-m4-guides branch from 1373a4b to 03affd5 Compare October 9, 2026 01:36
@backnotprop
backnotprop merged commit 4fd9fec into main Oct 9, 2026
33 checks passed
@backnotprop
backnotprop deleted the feat/mobile-m4-guides branch October 9, 2026 02:19
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