Skip to content

v0.2.0-nightly.112

@nixuuu nixuuu tagged this 09 Nov 16:28
Updates and every mobile sheet can now be dragged down from their
grabber or header. The shared surface enters, settles and exits by
position only; only the backdrop fades. Compact pickers, confirmations
and full-height forms share the same spring and dismissal lifecycle on
iOS and Android, including wide-window cards.

The gesture follows downward travel 1:1, adds diminishing upward
resistance, and dismisses after 28% of the measured height or a downward
fling of at least 900 dp/s after 8 dp. It preserves release velocity,
returns interrupted/short drags, and prevents a late touch event from
overwriting an exit. Shared close buttons, backdrop taps, accessibility
actions and Android back finish the exit before notifying a parent that
may unmount the sheet. The first drag with an open keyboard dismisses
the keyboard; another drag moves the sheet. Reduced motion makes
automatic positioning instant.

Coverage: `Overlay`, `PickerSheet` (models, effort, permissions,
projects, workers, thread actions and attachment menus), confirmations,
Updates, connection/update details, goal/side-thread/queued-message
forms, and all `SheetSurface` forms/details. Full-screen image/text/file
viewers retain their own viewer behavior.

### Scope and limitations

- JS/TS only. No dependencies, app config, native modules, generated
native projects or runtime policies changed. No app build, OTA
publication, device/simulator run or UI tests.
- Content scroll views keep native scrolling. With the installed
modules, PanResponder cannot reliably take over an active native
ScrollView at offset zero on both platforms without responder conflicts
or a jump. Dragging is deliberately limited to the grabber and marked
header areas.
- PanResponder delivers touch events on JS. Gesture math, shared values,
styles and springs run on the UI thread without React renders per move.
No device FPS measurements were possible; a blocked JS thread can still
delay touch delivery.

### Validation

- Passed: `bun install`, `bun run gen:check`, `bun run typecheck`, `bun
run lint`, `bun run format:check`, and `bun run --filter @canopy/web
build`.
- Passed: `bun test tests/integration/mobile-sheet-gesture.test.ts` — 3
focused math tests, 27 assertions, covering dismissal distance/velocity,
the rubber-band curve and re-grabbing without a position jump.
- Passed: Expo ESLint over every changed mobile file. The additional
whole-app Expo lint reports existing hook-rule violations in untouched
attachment, settings and state files; they are outside this change.
- Full-suite results are not clean locally; the unstable baseline
failures and clean targeted reruns are recorded below. No gateway,
worker, package or existing test code changed.

### Unstable baseline tests on the shared host

This machine is shared by several agents. Timing-sensitive integration
tests failed under concurrent load (host load exceeded 60). The files
below are unchanged by this feature, and every affected file passed a
targeted rerun. These reruns support the load-related explanation but do
not establish a clean full-suite result. Per maintainer authorization,
CI on isolated runners is the full-suite gate for this PR.

| Full-suite attempt | Observed result |
| --- | --- |
| `bun run check:tmp` | 1904 passed, 161 skipped, 13 failed, 3 errors;
2078 tests across 343 files, 1693.25 s. |
| `bun run check:tmp --timeout 30000` | 1916 passed, 161 skipped, 1
failed; 2078 tests across 343 files, 1241.66 s. All failures from the
first run passed; only the shell Stop RPC failed. |
| Third full attempt with `--timeout 30000` | The same shell Stop RPC
failure recurred. Stopped after 629 passing cases because the run was
far from completion, following authorization to avoid more full runs.
Only the owned checker received SIGTERM; it forwarded shutdown and
exited 143 after cleanup. This attempt is incomplete. |

All three attempts reported `check:tmp: nothing left in the temp dir`.
No further full runs were started.

Affected files are under `tests/integration/`:

| File | Observed full-suite errors |
| --- | --- |
| `agent-plugins.test.ts` | Tag reassignment/reinstall exceeded the
default 5000 ms test timeout. |
| `agent-skill-sources.test.ts` | Periodic updates and
catalog/subpath/reference discovery exceeded 5000 ms; timed-out teardown
also reported `Fixture git failed: push` and asynchronous errors. |
| `agent-skills-files.test.ts` | Large concurrent drift-report case
exceeded the 5000 ms timeout. |
| `bots-harness-regressions.test.ts` | Claude routine/wakeup exceeded
the 5000 ms timeout. |
| `bots-harness.test.ts` | Grok missing-session/history retry exceeded
the 5000 ms timeout. |
| `config-sync-projects.test.ts` | Aliased project routing returned gRPC
unavailable for the fixture peer; policy propagation failed its 20 s
eventual assertion. |
| `linked-timeouts.test.ts` | Local read took 511.55 ms against a <500
ms assertion; half-open recovery returned missing updates (`undefined`);
stale-read recovery failed a 20 s eventual assertion; BFF request-slot
scenario did not observe the expected peer request. |
| `thread-mirror-deltas.test.ts` | Gap/epoch full/background recovery
failed its 20 s cursor-convergence assertion. It passed in the second
full run. |
| `composer-shell.test.ts` | “Stop during a shell steer kills its group
and persists the failed delivery” returned `ConnectError [canceled] The
operation was aborted.` around the 10.1 s RPC deadline in attempts two
and three. It passed in attempt one. |

Clean targeted reruns (all using `bun run check:tmp --timeout 30000`):

1. The seven files from `agent-plugins.test.ts` through
`linked-timeouts.test.ts` listed above: **80 passed, 0 failed, 6591
assertions**, 221.49 s.
2. `tests/integration/composer-shell.test.ts`: **17 passed, 0 failed, 72
assertions**, 14.94 s.
3. `tests/integration/thread-mirror-deltas.test.ts`: **12 passed, 0
failed, 88 assertions**, 22.71 s.

Every targeted rerun also confirmed the temporary directory was clean.
The current isolated rerun has finished; no test run remains active.

Fingerprint command, run before and after for both platforms from
`apps/mobile`:
`bunx eas-cli@27.4.1 fingerprint:generate --platform <ios|android>
--build-profile production --json --non-interactive`

| Platform | Before | After |
| --- | --- | --- |
| iOS | `2a0ef03a2a0fda8e7fad2e3df8609803d2edbda3` |
`2a0ef03a2a0fda8e7fad2e3df8609803d2edbda3` |
| Android | `c5c880ae8d0f5d7bc867fcbaf8f756b2ca56a07d` |
`c5c880ae8d0f5d7bc867fcbaf8f756b2ca56a07d` |

### Manual checks required on iOS and Android

These are not performed: the installed clients cannot run this checkout.
Check both light/dark themes, phone width and a wide/foldable window.

1. Open Updates, then close separately with ✕, backdrop tap and Android
back. Reopen repeatedly. The surface must remain opaque, slide fully
below the screen edge, and dim/undim only the backdrop. Check the same
behavior in model/effort/project/permissions pickers, attachment menus,
thread actions, connection details and a full thread/bot details sheet.
Use short and long titles; check safe areas, rounded corners and seams.
2. Drag both grabber and header slowly. Release below and above 28% of
the sheet height; try a short downward fling. Short drags spring back;
long drags/flings continue down with finger velocity. Drag upward,
reverse direction and grab again during spring-back. Check resistance,
continuous position, backdrop tracking, cancellation and Android back
while a drag is active. Content must still scroll normally at every
offset.
3. Open searchable model choices and the goal/queued-message forms,
focus an input and type. The sheet must stay above its own keyboard.
Drag once to close only the keyboard, release, then drag again to close
the sheet. Return to the composer and verify draft, choices, typing and
interactive keyboard behavior remain intact.
4. Choose an attachment action that opens the photo library/document
picker. Verify the native picker opens once, after the menu has
disappeared. Check Updates → Open update settings and thread actions →
Rename/Goal likewise. Rotate/fold with a sheet open and verify it
remains reachable.
5. With VoiceOver/TalkBack, activate the grabber's Close action and
existing close controls; test the accessibility escape gesture where
supported. Enable reduced motion and repeat opening, closing, dragging
and spring-back: no automatic spring/slide should play, and dragging
must still work. Inspect interaction smoothness on 60/120 Hz hardware.

### Copy audit (`docs/copy.md` §8)

Before: a decorative, unlabeled grabber. After: button label/action
`Close`, with hint `Drag down to close.` The action starts with a verb;
the hint has four words, sentence case and a period. The control has a
spoken action and does not rely on its shape or position. Existing
contextual close labels remain unchanged. No new errors, statuses,
glossary terms or decorative text. The new fixed strings cannot
truncate; surrounding short/long titles retain existing wrapping rules.
Device checks of VoiceOver/TalkBack and visual layout are pending as
described above.

Related: #371
Assets 2
Loading