📱 🛋️ miso-lynx-gallery
August 17, 2026 · View on GitHub
The LynxJS Product Gallery tutorial, rebuilt in Haskell on miso's native (LynxJS dual-thread) backend.
|
https://github.com/user-attachments/assets/db32d6b8-6a30-48d5-b92e-6f9be92387f2 |
https://github.com/user-attachments/assets/a9c5dfc2-2a53-42ef-83a8-ca8a8a363ba0 |
A full-height black page with a two-column waterfall <list> of rounded
furniture cards, each with a tap-to-like heart (white → red, with a one-shot
ripple), and native auto-scroll. It compiles to a real .lynx.bundle via the
GHC JavaScript backend + rspeedy, with the images embedded
inside the bundle — no asset server, fully offline.
75 cards · MVU · runs on the LynxExplorer / any Lynx host app
Source
Build
Just Nix (flakes enabled) — the flake pulls miso (the
dual-thread branch) and its toolchain, so you don't need anything else:
nix build # -> result/main.lynx.bundle
Serve it and point a Lynx host at the bundle:
nix develop -c http-server result -p 8080
# then open LynxExplorer at http://<your-machine-ip>:8080/main.lynx.bundle
(Any static file server works — e.g. python3 -m http.server 8080 -d result.)
The product-detail branch
The product-detail branch is a second port from the same LynxJS tutorial
series — the Product Detail tutorial,
a faithful reproduction of lynx-family/lynx-examples/examples/swiper (its final
src/Swiper stage). It replaces the gallery Main.hs with a hand-rolled,
high-performance image swiper: a horizontal track of full-width product
photos dragged with the finger and snapped to the nearest page on release, a live
page indicator that supports click-to-jump, over the product-detail chrome (price
card + order bar).
- Dual-thread by construction. All touch handling runs on the main thread
(the sample's
main-thread:bindtouch*) asonTouch*Mainhandlers; the only render-driving state (Model, just the highlighted page) lives on the background thread. The two are bridged explicitly:Snapped/SetCurrentcross MTS→BTS viarunOnBG, and click-to-jump crosses BTS→MTS viarunOnMain. - Main-thread scratch. The drag state (offsets, velocity, cached track ref)
is the sample's
useMainThreadRef, here oneMainThreadRef Dragtouched only inside MTS handlers / rAF callbacks, so it never races the model. - vsync-coalesced follow loop.
touchmoveonly records where the finger wants the track; aneachFrameloop paints the latest offset once per frame (and samples velocity for flick detection), coalescing the device's bursty touch stream to one flush per frame. The snap on release is a native compositortransition(ease-out) — no per-frame JS during the tween. itemWidth. Page width is the sample's exactSystemInfo.pixelWidth / SystemInfo.pixelRatio, read from its true homelynx.SystemInfo(which is MTS-only), with a safe fallback so it never throws during render.
Same build flow as main — nix build → result/main.lynx.bundle. The assets
differ (1.png..8.png, heart.png, star.png, back.png); they're embedded
into the bundle the same way (see How it works → Images above).
Credit
Port of the official LynxJS gallery tutorial; the furniture photos are the tutorial's own sample images.