Debugging with Lynx DevTool
The styling and gesture failures that only reproduce on a device tend to leave nothing in your terminal, because they happen inside the native engine rather than in your build. Lynx DevTool is the Lynx team's desktop debugger for that gap. It is an Electron app built on a fork of the Chrome DevTools frontend, so its panels will look familiar, and it attaches to a running app over USB.
Start in the browser preview
rspeedy dev prints a preview URL alongside the QR code:
∟ Preview http://192.168.1.24:3000/__web_preview?casename=main.web.bundle
That page runs your web bundle in the browser, so the browser's own inspector covers layout and styling work with no device or desktop app involved. The dev server guide lists what the preview can't tell you, such as touch gestures, native animation timing, and safe areas. Lynx DevTool is for those cases, because it attaches to the native app.
Connecting a device
Download the desktop app from the releases page, then connect the device with a data cable. The app recognizes the device and lists the Lynx app running on it, and the toolbar shows the connection state. Keep the app in the foreground on the device when you switch between devices.
iOS needs Xcode and a matching iOS SDK on your Mac. Android needs Developer Mode and USB debugging turned on, and you have to accept the authorization prompt on the device. If the app never appears, the Lynx docs suggest building a test app from Xcode or Android Studio to confirm the machine can reach the device at all.
What you do next depends on the app hosting your bundle.
LynxExplorer. Turn on the Lynx Debug and Lynx DevTool switches on its Switch page. The official walkthrough is written against LynxExplorer, so install it alongside Lynx Go for debugging sessions. Builds are on the lynx-family/lynx releases page, and your dev server's bundle URL works the same way in both.
Sparkling. Its debug tool turns on the debug flags and LogBox, and the desktop connection takes further setup. Sparkling's debugging notes list the pieces to add, which are enableAllSessions, setLogBoxPresetValue, and the JS bridge loaders.
Your own host. Follow Integrate Lynx DevTool for the pods or Gradle artifacts, and keep their version aligned with your Lynx version. A host built from the Xcode guide needs LynxService/Devtool, LynxDevtool, and DebugRouter in its Podfile, plus LynxEnv.prepareDevToolForDevelopmentBeforeInit() and devtoolEnabled set during Lynx initialization. These switches default to off. The docs recommend limiting the integration to non-production builds, since it adds package size and memory overhead.
Finding your way around a Vue Lynx page
The Preact DevTools panel inspects ReactLynx components, so it does not apply here, and the panel docs list no Vue equivalent. You work from the Lynx elements your components rendered instead. Vy UI's data-vyui-* attributes help with this, because they mark a component's structural parts in the Elements tree.
Several Lynx pages can be alive at once, so check that you have selected the right instance if the tree looks unfamiliar.
Finding styling failures in Elements
Selecting a node shows its CSS rules under Styles, with invalid and overridden declarations crossed out, faded, or flagged. Computed shows the box model and resolved values. Most on-device styling bugs in a Tailwind project fall into one of these patterns.
The class is on the node, but no rule appears. The CSS never reached the bundle. Either Tailwind's content scan dropped the package, which is the exclude fix for @vyui/kit, or the utility compiled to nothing. Tailwind v3 silently emits nothing for classes such as size-4.5, or for an opacity modifier on a color wired through var(). On the Vy UI preset that includes bg-neutral-900/50, while bg-black/50 works.
A color resolves to white or black. Lynx resolves one level of var(), so compare your token's definition with the value in Computed. A token pointing at another token is the cause, and Theming on Lynx has the fix.
A flex row is off-center by a few pixels. Vue Lynx realizes empty-slot and v-if anchors as real zero-size nodes, and gap-* counts them. Look for an unexpected extra child in the tree.
A sheet or modal runs off the preview. Switch the screencast from LynxView to FullScreen, which the docs recommend for overlays outside the LynxView area. If the screen has stopped responding to taps, use Inspect on the dead area to find what is on top, since a leftover transparent backdrop looks identical to a frozen app. The Layers panel gives you a 3D view of the same stack, and right-clicking a layer reveals it in Elements.
Not every node creates platform UI, so a selection that doesn't highlight on the device isn't necessarily a problem. Edits you make here apply live, which makes it a quick way to test a fix before changing the source.
Debugging across both threads
Vue Lynx runs your components on the background thread and your worklets on the main thread, and DevTool treats the two separately.
In Console, background-thread logs appear in full by default, while main-thread logs arrive as strings prefixed with [main-thread.js], so a console.log of an object inside a worklet will not expand. JavaScript you type runs in the selected context, which is Background:-1 until you enable main-thread debugging. Runtime errors always appear as strings. One to recognize is TypeError: cannot read property 'bind' of undefined, which means a package's worklets never registered, and pnpm dlx @vyui/cli check will confirm whether includeWorkletPackages is the cause.
Sources debugs the background thread once DevTool is enabled, including source-mapped original files if you configure Rspeedy's output.sourceMap. Stepping through a worklet takes more setup:
- Start the app with
rspeedy dev. - Check that
debug-info.jsonexists in the intermediate output directory and thattemplateDebugUrlintasm.jsonpoints at it. Vue Lynx's plugin defaultsdebugInfoOutsidetotrue, which emits that file separately. - Turn on the Main Thread Debugging switch, then open or reload the page. The switch resets when the app restarts.
- Select Main in the Threads pane and open
main-thread.js.
Source maps cover the background thread only, so on the main thread you read and step through compiled code. Search for a distinctive line from your worklet rather than expecting your file names in the navigator. Enabling main-thread debugging also gives full-format logs and a Main context in Console.
Exception breakpoints pause on every exception under PrimJS, the default engine, because it does not distinguish caught from uncaught. Android can switch to V8 from the DevTool Switch page if you need that distinction, along with V8-only features such as editing scripts.
Recording a trace
A janky gesture or a slow tab switch is a timing question, and Trace answers it with a timeline of script execution, element construction, layout, and rendering, plus flame charts of task durations. Choose Trace from the top bar, select your app, then press Start and Stop around the interaction. The result opens automatically and stays available under History. Trace needs the latest LynxExplorer or a dev version of Lynx in your own host, and a message tells you if the app you selected doesn't have it.
Recorder is worth knowing about for bugs you can't reproduce on demand. It captures a page's complete runtime state to a file that replays on any Lynx-supported device without live network requests.
Related
Sparkling
TikTok's open-source native shell for Lynx, covering containers, scheme routing, the typed native bridge, and where a Vue Lynx app fits into it.
Lynx UI frameworks
A comparison of the UI component libraries available for Lynx today, covering what each one ships, which framework it targets, and how mature it is.