Skip to content

feat(live-activity): add shared content-state contract and reconciler helpers - #420

Open
RonenMars wants to merge 1 commit into
feat/live-activity-ios-targetfrom
feat/live-activity-contract
Open

feat(live-activity): add shared content-state contract and reconciler helpers#420
RonenMars wants to merge 1 commit into
feat/live-activity-ios-targetfrom
feat/live-activity-contract

Conversation

@RonenMars

Copy link
Copy Markdown
Owner

Stacked on #419.

Adds types/live-activity.ts, the content-state shape both the iOS widget and the later APNs sender read from, plus the pure half of the reconciler.

Why the contract looks like this

Flat primitives only: the state crosses a native bridge and will eventually ride a 4 KB APNs payload.

startedAt is epoch ms and elapsedMs is deliberately absent.
The server's elapsedMs is a snapshot that nothing in the app ticks, so a surface rendering it would sit frozen between updates.
Handing the OS a start time lets it run its own timer, which also removes any reason to push per-second updates once APNs is in play.

Cap is 3 against an iOS ceiling of 5, leaving headroom.

Helpers

isTerminal checks three signals because the fleet is not uniform: managed servers set completedAt, external ones report processLiveness, and older servers do neither, leaving the legacy idle-without-a-PTY heuristic as the only tell.

decideActions evicts by least-recently-updated rather than oldest-started, so a long-running session still producing output outranks a newer silent one.
Keeping it pure is what makes the cap and eviction policy testable with no device and no native module.

lastOutput is stripped of ANSI escapes and box drawing through the existing utils/stripAnsi + utils/stripBoxDrawing rather than a new stripper, then truncated to 90 chars for the narrow Dynamic Island compact slots.
A word boundary is only honored when it retains over half the budget — otherwise a single long token would collapse the line to a few characters.

Notes

widgets/ is registered in ci-paths.txt and docs/ci-significant-paths.md here, ahead of the directory landing in the next PR, so the commit that adds a layout cannot be mis-tagged [skip-ci].

Verification

15 new unit tests covering each terminal signal independently, truncation behavior, and the eviction/slot-reuse cases.
npm run lint 0 errors; npm run typecheck unchanged from baseline.

… helpers

Adds types/live-activity.ts, the content-state shape both the iOS widget and the later APNs sender read from.
It stays flat primitives because the state crosses a native bridge and will eventually ride a 4 KB APNs payload.

startedAt is epoch ms and elapsedMs is deliberately absent: the server's elapsedMs is a snapshot nothing in the app ticks, so a surface rendering it would freeze between updates.
Handing the OS a start time lets it run its own timer and removes any reason to push per-second updates.

services/live-activity.ts holds the pure half of the reconciler — terminal detection, session-to-state mapping, and the cap/eviction policy — so all the branching is testable with no device and no native module.
isTerminal checks three signals because managed servers set completedAt, external ones report processLiveness, and older servers do neither.
Eviction is by least-recently-updated rather than oldest-started, so a long session still producing output outranks a newer silent one.

lastOutput is stripped of ANSI escapes and box drawing through the existing utils and truncated to 90 chars, since the Dynamic Island compact slots are narrow.
A word boundary is only honored when it retains over half the budget, otherwise a single long token would collapse the line.

widgets/ is registered in ci-paths.txt and docs/ci-significant-paths.md ahead of the directory landing, so the first commit that adds a layout cannot be mis-tagged [skip-ci].
@RonenMars
RonenMars force-pushed the feat/live-activity-contract branch from e60be12 to 2f763ed Compare July 25, 2026 21:22
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