Skip to content

Fix/ios replay flicker - #555

Merged
Makisuo merged 3 commits into
mainfrom
fix/ios-replay-flicker
Aug 20, 2026
Merged

Fix/ios replay flicker#555
Makisuo merged 3 commits into
mainfrom
fix/ios-replay-flicker

Conversation

@Makisuo

@MakisuoMakisuo commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

… onto Effect
The CLI is Effect at the command layer but raw async below it. Three of those
raw spots cost real correctness, so convert them and leave the rest alone.
Leaves, each with only Effect callers:
* credential-store spawns `security`/`secret-tool` through ChildProcessSpawner
instead of Bun.spawn behind a bare catch. That catch made a broken keychain
indistinguishable from a machine that has none; the cause is now logged
before degrading. MapleConfig captures the spawner in `make`, the way it
already captures `fs`, so MapleConfigValues keeps R = never.
* `maple auth login --with-token` reads stdin through Stdio rather than a
hand-rolled Promise over stdin events. Deliberately NOT Terminal.readLine:
that waits for a readline "line" event and never resolves at EOF, so
`printf tok | maple auth login --with-token` would hang forever.
* update.ts drives tar/xattr through ChildProcess and its filesystem work
through FileSystem. mapFsError detected EACCES via a bare `.code`, which a
PlatformError does not expose, so the actionable "re-run the installer"
message would have silently disappeared — it now also reads the
PermissionDenied reason tag and the wrapped cause.
Calibration child runner (archive.ts), converted as one unit so raw code never
wraps Effect:
* The `settled` flag, the setTimeout watchdog and the 500ms setInterval disk
poller become a forked killer fiber racing a sleep against a sleep-first
poll loop. The poll keeps its fail-loud catch INSIDE the poll so a read
error still kills the candidate rather than silently killing the poller.
* The group reap moves into a scope finalizer, so it runs on interruption and
defects too. Previously it only ran from inside a timer callback, and a
Ctrl-C mid-candidate orphaned the Maple grandchild.
* `pgid` could be 0, and POSIX kill(0, sig) signals the CALLER's own process
group — the CLI would have SIGKILLed itself. Guarded.
* Completion still gates on the pipes draining, not on exit: exitCode alone
resolves on "exit", which Node emits before stdio is guaranteed to drain.
exitCode also FAILS on signal death, and every watchdog kill is a signal
death, so that is collapsed to a null code — otherwise one killed candidate
would abort all six signals instead of eliminating one matrix cell.
* The closing reconcile keeps its ArchiveError instead of being orDie'd, and
no longer masks a matrix failure the way the original `finally` did.
runCandidateChild had no unit coverage at all (it was an unexported promise
closure reachable only from the shell probes); it is exported now with four
tests, including a group-kill test that fails if the reap is child-only.
checkpoints.ts pins/locks, durable-files, serve.ts and the archives/migrations
bulk stay raw: their callers are still promise-based, and converting them would
mean either shims or a 15k-line diff.
The replay recorder snapshotted the key window with
`drawHierarchy(in:afterScreenUpdates: true)`, which forces UIKit to
commit and re-render the whole window off-screen, synchronously, before
drawing. At the recorder's 1 fps that reads as a full-screen flash, so
the app flickered constantly on every screen.
Fixed in maple-swift 0.3.1. This picks it up, and with it 0.3.0's crash
reporting as OTel exception spans and the foreground bound on
`ui.screen`.
This branch carried its own copy of the CLI Effect refactor, which landed
on main separately as #554. Every conflict was that duplicate, and main's
copy is the later one in each case, so the conflicted files are resolved
to main verbatim:
- credential-store.ts — main writes the secret twice, because `security
-w` asks the caller to retype it and a single piped line silently stores
an empty password while exiting 0, then reads the credential back to
prove the keychain actually owns it. This branch predates both.
- update.ts — main's `__testables` also exports `extractTar` and
`mapFsError`.
- archive-candidate-child.test.ts — main splits drain-to-EOF and
truncation into separate cases with a payload small enough that
`/usr/bin/time`'s output cannot make the assertion platform dependent.
What remains of this branch after the merge is one line: project.yml's
MapleSwift pin, 0.2.1 -> 0.3.1. Main already resolved maple-swift to 0.3.1
in Package.resolved, so the pin was the half left behind.
@Makisuo
Makisuo merged commit 473011e into mainAug 20, 2026
26 checks passed
@Makisuo
Makisuo deleted the fix/ios-replay-flicker branch August 20, 2026 23:04
@github-actions

Copy link
Copy Markdown

🍁 Maple PR preview

Note

Preview resources were removed when this pull request closed.

Final commit da8f6f8 · View workflow run

Sign up for freeto 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

@Makisuo