mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
Merge remote-tracking branch 'origin/main' into ci/simplify-test-suite
# Conflicts: # packages/client/src/daemon-client.ts
This commit is contained in:
@@ -92,7 +92,7 @@ For testing rules, see [testing.md](testing.md).
|
||||
- Subscribe narrowly: select primitives from stores, pass `status` not `agent`, use `useShallow` / deep-equal when returning derived arrays/objects.
|
||||
- Collection rows do not independently subscribe to a high-frequency global store. The collection owner selects structurally shared indexes once, derives a keyed row model with `useMemo`, and passes entries to rows. This keeps retained hidden collections current without running one selector per row on every store update.
|
||||
- Equality functions prevent React renders; they do not prevent selector callbacks from running. A selector attached to a hot store must be O(1) when its relevant source references have not changed.
|
||||
- Retained native panels use `RetainedPanel`. If an existing gesture/layout wrapper must own visibility, wrap its contents in `RetainedPanelActivity` instead. Keep keyed panel roots in a stable sibling order, include the newly active panel in the same render, centralize subscriptions, and gate genuine effects through `useRetainedPanelActive`. Do not use `Suspense` or render freezing for this on native: those techniques change native tree ownership instead of merely stopping work.
|
||||
- Retained native panels use `RetainedPanel`. If an existing gesture/layout wrapper must own visibility, wrap its contents in `RetainedPanelActivity` instead. Keep keyed panel roots in a stable sibling order, include the newly active panel in the same render, centralize subscriptions, and gate genuine effects through `useRetainedPanelActive`. Follow the [native ownership constraints and chat suspension boundary](mobile-panels.md) when stopping hidden rendering.
|
||||
- Infinite animations are subscriptions. Start them only while their retained panel is active, and cancel a shared clock when its final active consumer leaves. Synchronized animations use one clock per animation family and feed active instances through local shared values; retained hidden instances stay mounted but unsubscribed. Match state updates to the actual visual cadence; do not run every style worklet at 60 fps when the rendered value changes only a few times per second.
|
||||
- Stable references for props that cross `memo` boundaries or feed dependency arrays. Static literals at module scope `as const`; derived with `useMemo`; handlers with `useCallback` only when there's a memoized beneficiary.
|
||||
- Use stable ids for `key`, never array index for reorderable/filterable lists.
|
||||
|
||||
@@ -187,6 +187,10 @@ its own.
|
||||
[floating-panels.md](floating-panels.md).
|
||||
- **One overlay per menu.** Submenus render inside their parent's layer and paint no second
|
||||
backdrop, so there is exactly one `Modal` on native no matter how deep the menu goes.
|
||||
- **Retained panels own visibility.** The shared menu surface unmounts when its panel becomes
|
||||
inactive. An async action can navigate before its menu closes; a hidden panel must not leave a
|
||||
portal backdrop blocking the destination. On web the chat suspends one commit after it goes
|
||||
inactive, and that inactive commit is where the surface unmounts.
|
||||
- Anchoring, flipping, and edge clamping live in `menu-anchor.ts` and are unit-tested. Fix
|
||||
positioning bugs there, not at a call site.
|
||||
- Everything else about floating surfaces on Android — Portal/Modal escape, lifecycle gates,
|
||||
|
||||
@@ -116,6 +116,8 @@ definition, no longer eligible to begin.
|
||||
- Do not suspend retained native subtrees with `Suspense`/`react-freeze`. Suspension changes native
|
||||
ownership and can detach descendants. Keep the tree mounted, stabilize its subscriptions/selectors,
|
||||
and use the retained-panel active signal to stop timers, polling, and other genuine background work.
|
||||
Hidden chat content is suspended on web only, through `retained-chat-content.web.tsx`, where DOM
|
||||
nodes carry no Fabric ownership; the native file keeps the chat live.
|
||||
|
||||
## Tests
|
||||
|
||||
|
||||
+20
-11
@@ -76,11 +76,20 @@ cannot be reconstructed from sequence metadata. Forking the current context rema
|
||||
|
||||
## Resume behavior
|
||||
|
||||
Opening, reconnecting, or revisiting after a selective-delivery coverage gap fetches the latest tail
|
||||
page.
|
||||
Focus alone does not mutate timeline state; the tail response is compared with the local
|
||||
Opening, reconnecting, and returning from app background establish the current timeline through
|
||||
bounded catch-up. Switching between continuously subscribed open chats needs no fetch.
|
||||
Focus alone does not mutate timeline state; the response is compared with the local
|
||||
authoritative range first.
|
||||
|
||||
Cached history remains readable during recovery. The chat shows Reconnecting to host while the host is
|
||||
offline, then Updating messages until authoritative catch-up completes. Socket connectivity alone cannot
|
||||
certify that the displayed conversation is current. The timeline owner publishes freshness; the
|
||||
view renders it without a toast timer or a separate resume workflow.
|
||||
|
||||
Foregrounding probes a nominally connected session immediately. A healthy response preserves the
|
||||
socket; a failed three-second probe starts reconnecting without waiting for the background heartbeat
|
||||
or retry backoff. This cannot keep a mobile socket alive after the operating system suspends it.
|
||||
|
||||
- The same epoch and `window.maxSeq` is an exact display no-op. The app advances synchronization
|
||||
bookkeeping without replacing timeline arrays, preserving an upward-scrolled viewport.
|
||||
- When the page overlaps or is adjacent to the local end cursor, only projected items newer than
|
||||
@@ -157,14 +166,14 @@ replica cache.
|
||||
|
||||
The app chooses one delivery policy from `server_info.features.selectiveAgentTimeline`:
|
||||
|
||||
- Selective daemons receive every agent visible in any pane plus the most recently viewed hidden
|
||||
agents, up to five subscribed agents. Visible agents always win: if more than five are visible,
|
||||
they all remain subscribed and no hidden agent does. Switching and app backgrounding preserve
|
||||
this connection-scoped hot set, so returning to an agent still covered by it needs no catch-up.
|
||||
Losing window keyboard focus does not make a selected pane invisible. Disconnecting clears hidden
|
||||
hot agents; reconnect restores the currently visible set before authoritative catch-up. Revisiting
|
||||
an evicted retained timeline displays its cached state immediately while authoritative catch-up
|
||||
advances it to the current tail.
|
||||
- Selective daemons receive every open workspace chat plus any visible agent pane. Workspace layout
|
||||
owns open-chat lifetime, independently of mounted or retained React views. Switching workspaces,
|
||||
evicting a retained view, and app backgrounding preserve that demand; closing the chat releases it.
|
||||
Reconnect restores the open set and gives visible chats the first catch-up attempt. Hidden chats
|
||||
follow when those attempts settle, including failures, so a failed visible chat does not starve
|
||||
background recovery. Split panes catch up together. Hidden chats update the replica; on web their
|
||||
retained presentation stays suspended until revealed, on native it keeps rendering. Revealing a
|
||||
chat reads the current store and preserves its local UI state. There is no recent-agent limit.
|
||||
- Legacy daemons keep globally streaming agent timelines. Visibility still triggers the existing
|
||||
authoritative catch-up, but the app does not issue selective-subscription RPCs.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user