Debugging
Three surfaces can go wrong — backend, frontend, or IPC between them. Each has its own tool.
Frontend — DevTools
Open Chromium DevTools inside the WebView (debug builds only; release builds compile DevTools out):
import { tyndWindow } from "@tynd/core/client";
await tyndWindow.openDevTools();
await tyndWindow.closeDevTools();Works like any browser DevTools — Console, Elements, Network (you’ll see tynd://localhost and tynd-bin://localhost requests), Sources (set breakpoints), Performance.
Bind to a keyboard shortcut
import { shortcuts, tyndWindow } from "@tynd/core/client";
if (import.meta.env.DEV) {
await shortcuts.register("CmdOrCtrl+Shift+I", () => {
void tyndWindow.openDevTools();
});
}Backend — logs
Both runtimes redirect console.log to stderr (so stdout stays clean for IPC). Watch them:
tynd dev --verbose # prints IPC traffic, cache decisions, Rust events
tynd start --verbose # same, for the no-HMR modeIn a production binary, the backend still writes to stderr — redirect to a file if you want persistence:
./release/my-app 2> app.logStructured logging
Default console.* works but gives you unstructured text. For production apps consider a small wrapper:
type Level = "debug" | "info" | "warn" | "error";
export function log(level: Level, msg: string, data?: Record<string, unknown>) {
console.error(JSON.stringify({
ts: new Date().toISOString(),
level,
msg,
...data,
}));
}
log("info", "user signed in", { userId: "abc" });Or use a lib — pino / consola work in full; in lite, any pure-JS lib is fine.
Backend — inspector (full only)
full mode runs on Bun, which supports the Chrome DevTools inspector:
TYND_BUN_ARGS="--inspect-brk" tynd devOpen chrome://inspect in Chrome and click the target. You get JS breakpoints, stepping, memory profiling — the full package.
Not available in lite (QuickJS has no inspector protocol). Switch to full temporarily if you need step-debugging.
IPC traffic
tynd dev --verbose / tynd start --verbose prints every RPC call, emit, and OS call with timing. Useful for answering “is the call going through? what did it return?”.
Rust host errors
The host writes its own errors to stderr with a [tynd-host] prefix:
[tynd-host] dispatch error: api=fs method=readText: No such fileUsually that’s enough to figure out the issue. For deeper inspection:
TYND_LOG=debug tynd devSets the host’s tracing subscriber to debug level — you’ll see every IPC hop and thread spawn. Verbose, but useful when something mysterious is happening.
Common symptoms
RPC call hangs forever
- Backend didn’t register the function (
exportmissing, typo in name). - Backend crashed before the response (check stderr).
- In
full, the Bun subprocess exited (the host logs this).
Verify: tynd dev --verbose shows RPC call greet sent but no corresponding return. Backend crashed.
Events don’t arrive
- Emitter not
exported frombackend/main.ts. - Handler registered before
createBackendset up the proxy (subscribe after page ready). - In
lite, the host’s__tynd_emit__shim missing — usually means you’re running the lite bundle outsidetynd-lite.
OS call rejects with “Permission denied”
Look at the path. On macOS, the app may need an entitlement (disk access, network client, etc.). On Linux, chmod +x might be needed for a sidecar.
Backend hot reload stalls
Usually a syntax error in the new backend file — the old backend stays alive because the bundler failed. Check tynd dev output for a bundle error.
tynd info + tynd validate
Before digging deep, run these two:
tynd info --verbose # Bun version, Rust, WebView2, host binary path, cache
tynd validate # tynd.config.ts schema, binary discoverabilityThey catch 90% of “it’s not working” reports.
Remote / shipped-app debugging
Once the app is on a user’s machine, you don’t have DevTools. Options:
-
Ship a log file toggle. Behind an “Advanced” / “Debug” settings pane, let the user enable verbose logging. Log to
os.dataDir() + "/logs/". -
Crash reporter. Wire up a simple uncaught-error handler:
window.addEventListener("error", (e) => { // POST to your crash collector void fetch("https://telemetry.example.com/crash", { method: "POST", body: JSON.stringify({ message: e.message, stack: e.error?.stack, version: await app.getVersion(), }), }); }); -
Log the user’s environment.
await os.info()+tynd infooutput pasted into a bug report is usually enough.
Related
- Testing — catch bugs before users see them.
- Performance — profiling specifics.
- Troubleshooting — common errors + fixes.