refactor(channel): run target-message on createMachine - #6036
Conversation
The highlight lifecycle is a state machine already. This commit writes the table down as a pure reduce so later controller work can match it instead of five optional store fields. Co-authored-by: teo <synoet@users.noreply.github.com>
The public accessors stay selectors over MachineState. Methods dispatch events. The container keeps one timer and the two world-to-event effects. Channel.tsx is unchanged. Co-authored-by: teo <synoet@users.noreply.github.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: macro-inc/macro/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 SummarySummary by CodeRabbit
WalkthroughTarget-message navigation now uses a reducer-based state machine with loading, viewport-waiting, scrolling, and flashing states. The controller dispatches navigation and scroll events, executes flash and pagination commands, and reacts to message-key and initial-scroll signals. Tests cover reducer transitions, selectors, stale events, deduplication, flash cleanup, target loading, and viewport readiness. Merge Risk: ⚪ Minimal · up to This refactor makes target-message navigation transitions explicit while preserving the existing controller interface and client-side scope. No actionable merge-blocking risk remains after normal checks and review. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Export makeTarget for the table helpers. Make initialState.targetLoaded optional so the controller can seed without reading messageKeys. root-scroll-done stays a no-op until control is scrolling. Co-authored-by: teo <synoet@users.noreply.github.com>
Co-authored-by: teo <synoet@users.noreply.github.com>
Keep the target-message reduce table. Add hasPendingElementScroll from #6080 as a selector over pending root and reply scroll. Co-authored-by: teo <synoet@users.noreply.github.com>
Pure MachineDef table plus a Solid runner with per-state scopes, serialized dispatch, and command execution. Domain code stays free of owners; tests exercise the def as values via step/simulate. Co-authored-by: teo <synoet@users.noreply.github.com>
Replace the five-phase Mealy table and two world-to-event effects with idle/targeting/flashing, a readiness derivation, and one restore command issued on scroll ack. Public controller API is unchanged. Co-authored-by: teo <synoet@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit cffa8a7. Configure here.
Keep the machine next to its controller instead of a new domain/ folder. Match events and selectors exhaustively with ts-pattern. Co-authored-by: teo <synoet@users.noreply.github.com>
Adding @macro-inc/machine changed the fixed-output Bun tree. Linux hash verified locally; Darwin hash from the CI rebuild. Co-authored-by: teo <synoet@users.noreply.github.com>
…-state-machine-1221 # Conflicts: # apps/web/src/features/channel/Channel/create-target-message-controller.ts # apps/web/src/features/channel/Channel/tests/create-target-message-controller.test.ts # nix-support/node_modules-hashes.json

Why
createTargetMessageControlleralready runs a deep-link lifecycle (navigate → load around → scroll → flash → release). That lifecycle lived in five independent| undefinedfields, then in a five-phase Mealy table with two world-to-event effects. Both encodings made readiness a state and left a race: ChannelThread can ack a root-only target before aviewport-readyeffect fires, and the five-phase table ignored that ack.This rewrite writes the machine down as three states and tests it as values. Readiness is a derivation. The flash timer is a state-scoped lifetime. There are no
createEffects.What landed
@macro-inc/machine(packages/machine)MachineDef<S, E, C>— one required entry per state;onreturns{ state, commands? }orundefined(ignored). Nodispatchinon.createMachine— signal + per-state scopes (dispose/remount on every transition, including same-tnew payload) +executecommands + serialized dispatch with a cycle cap.step/simulate— pure table helpers. No Solid, no timers.nix-support/node_modules-hashes.jsonupdated so the fixed-output.#js-node-modulestree includes the new workspace package.Target-message machine (
apps/web/src/features/channel/Channel/target-message.ts)Lives next to the controller, same as other Channel helpers. Events and selectors are exhaustive
ts-patternmatches.idle | targeting | flashing, each carrying orthogonalloadAround.navigate,root-scroll-done,reply-scroll-done,flash-elapsed,pagination-restored,release,reset.restore-default-pagination, emitted on the ack →flashingarrow when an around-query is anchored. Cleared only by a successful restore handshake orreset.Controller (
create-target-message-controller.ts)#6080'shasPendingElementScroll.Channel.tsx/ThreadListuntouched.flashingscope (onCleanupcancels on navigate / release / reset / unmount).navigation,didInitialScroll, andmessageKeys— not a memo, becauseChannel.tsxconstructs the controller beforemessageIndexexists.goToMessageuntracksmessageKeysandready.Behavior change
Restore moves from "viewport ready, before the scroll" to "after the scroll is acknowledged." Between those moments
Channel.tsxkeeps sourcing from the around query — which it already did for the whole pre-ready window.loadAroundMessageIdstill selectsuseChannelMessagesQuery; the missing-message effect still watches it;threadPaginatorstill wraps that query.An early root ack now does restore (the original skipped that path). By the time a row can ack it is mounted, so the around data is present.
Tests
matches— 15.it.eachtables per event, ChannelThread selector table,simulatesequences (root, nested with ignored root ack, rapid-nav, failed restore, mid-flight reset) — 48.72 tests passed locally. Machine and container ran against solid-js's browser build. This Cloud VM cannot start the apps/web vitest workspace runner (
EINVAL: scandir '//proc/.../net'during project init); CI'sbunx vitestfromapps/webincludes the newmachineproject plus--project channel.Out of scope
Channel.tsx,ThreadList, orclearStaleRestoredChannelData.createMachinecandidates, not this PR.