Debugging & Development ToolseQuantic.UI provides professional debugging tools similar to Next.js, with development-only features that help you identify and fix issues quickly.
🔍 Development Mode Detection
The framework automatically detects the environment using IWebHostEnvironment.IsDevelopment() and exposes it to the browser via:
window.__EQ_DEV__; // true in development, false in production
All development tools are conditionally loaded based on this flag.
The logger provides consistent, prefixed logging that only outputs in development mode.
import { logger } from "@equantic/ui-runtime";
// Development only (silenced in production)
logger.debug("Component state:", state);
logger.info("API call completed");
// Always logs (even in production)
logger.warn("Deprecated API used");
logger.error("Failed to load data:", error);
Method
Output
Production
Prefix
debug()
Console debug
❌ Silenced
[eQuantic.UI]
info()
Console info
❌ Silenced
[eQuantic.UI]
warn()
Console warn
✅ Always
[eQuantic.UI]
error()
Console error
✅ Always
[eQuantic.UI]
In browser DevTools, you can filter by the prefix:
The logger is implemented in src/eQuantic.UI.Runtime/src/utils/logger.ts:
const isDev = typeof window !== "undefined" && window.__EQ_DEV__;
if (isDev) console.debug("[eQuantic.UI]", ...args);
if (isDev) console.info("[eQuantic.UI]", ...args);
console.warn("[eQuantic.UI]", ...args);
console.error("[eQuantic.UI]", ...args);
The error overlay provides a full-screen, Next.js-style error UI that appears automatically when runtime errors occur.
•
Automatic Capture: Catches unhandled errors and promise rejections
•
C# stack traces ✨: the overlay is source-map aware: it fetches each bundle's .js.map, decodes it (src/dev/source-map.ts + src/dev/stack-remapper.ts), and rewrites the Call Stack as the original C# frames, plus a snippet of the failing C# source line. Falls back to the JS view if no map is available. (This is what makes the "0 JS knowledge" promise real at debug time: a C# developer sees C#, not transpiled JavaScript.)
•
Keyboard Support: Press Esc to close
•
Development Only: Never appears in production (loaded via a dynamic import() in dev)
•
Clean UX: Red header, monospace font, scrollable content
The error overlay automatically displays for:
1.
Unhandled Errors: Any uncaught exception in JavaScript
2.
Promise Rejections: Unhandled async errors
// These will trigger the error overlay in dev mode
throw new Error("Something went wrong");
Promise.reject("Async error");
await fetch("/api/data"); // If fetch fails and not caught
┌─────────────────────────────────────────────┐
│ ⚠️
Build Error Close (Esc)│ ├─────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────┐ │
│ │ at MyComponent.render (page.js:42) │ │
│ │ at Reconciler.patch (reconciler.js:12)│ │
│ └─────────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ This error overlay only appears in │
│ development. Fix the error to continue. │
└─────────────────────────────────────────────┘
You can manually show errors in the overlay:
import { errorOverlay } from "@equantic/ui-runtime/dev";
message: "Custom error message",
componentStack: "Component hierarchy...",
// Programmatically clear
// - Click "Close" button
The error overlay is implemented in src/eQuantic.UI.Runtime/src/dev/error-overlay.ts:
private overlay: HTMLDivElement | null = null;
private errors: ErrorInfo[] = [];
if (!window.__EQ_DEV__) return; // Dev only
// Creates full-screen overlay with error details
export const errorOverlay = new ErrorOverlay();
window.addEventListener("error", (event) => {
stack: event.error?.stack,
window.addEventListener("unhandledrejection", (event) => {
message: `Unhandled Promise Rejection: ${event.reason}`,
stack: event.reason?.stack,
A Debug build writes a source map beside each module, linked from it and carrying the C# the module came from, so the browser's debugger shows the C# itself.
Chrome DevTools:
3.
Find the C# file in the tree, named by its path inside the project (Pages/Home.cs), never by the build machine's
4.
Set breakpoints directly in the C# source
5.
Inspect state, props, and local variables
What a module's map carries is the EQuanticSourceMaps property, and its default follows the configuration:
EQuanticSourceMaps
Default for
What the build writes
full
Debug
a map beside each module, linked from it, with the C# inside, for the browser's debugger
external
(opt in)
a map without the C# and without the link, for an error reporter that uploads maps itself; bun's debugId is kept
none
every other configuration
no map, and no link
A map with the C# inside is the component's source, a [ServerAction]'s body included, and the output folder is a web root: that is why only Debug writes one. Whatever the property says, a publish takes no map from the compiler's output. To keep maps for a deployed build, write them without the C#:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<EQuanticSourceMaps>external</EQuanticSourceMaps>
eqc composes each map itself, in C#, with nothing installed: bun maps the JavaScript to the TypeScript eqc wrote, eqc's own V3 map leads from that TypeScript to the C#, and the two become one map from the JavaScript the browser runs to the C#. Its sources are named relative to the map and inside the project, so a debugger shows the project's own path; a file from outside the project (a package's sources in the NuGet cache) is named by its file name alone. A module written from more than one file names each one, with its own C#: a class that takes the default an interface supplies leads the default's lines to the interface's file, where the debugger shows them (Since 0.2.0-preview.60). A map is JSON whatever the C# holds, a form feed or any other control in a comment included (Since 0.2.0-preview.60). A full map, shortened:
"sources": ["../../Pages/Home.cs"],
"sourcesContent": ["…the C# of Pages/Home.cs…"],
"mappings": "AAAA;AACA;..."
Each statement has a segment of its own (Since 0.2.0-preview.58): a frame or a breakpoint lands on the C# statement that threw or that it was set on, not on its method's first line. A line the compiler writes by itself belongs to the statement that produced it: a pattern switch's else if maps to its case, a using's dispose to the using, a do's condition to itself, and a local function's statements and an expression-bodied member's expression to their own lines. A lambda's block maps statement by statement too (Since 0.2.0-preview.60), and what follows the block on its closing line maps to the statement again. That holds for a lambda passed to an instance or static method of the app's own, to a list's own method (ForEach, Find, Exists…) or to Where, Select, Any, All, Aggregate and the other LINQ operators written as one shape, for one in a collection expression (children: [ … ]), and for one held by a local, a local function or a delegate; and, since the same release, for one inside an expression-bodied member, an object creation, an object or collection initializer or an anonymous object. Every statement of a body with an out or ref parameter maps to its own line as well. That body runs inside an arrow the compiler adds, so a stack read in the browser has one frame more than .NET's between the throw and the caller. Elsewhere a lambda's block maps, for now, to the statement that holds it: passed to an extension method, to a delegate or through ?., given to First, Count, Sum, OrderBy, GroupBy and the other operators with a translation of their own.
This allows you to:
•
Set breakpoints in C# code
•
Inspect C# variable names
•
See original line numbers in stack traces
To inspect component state and props:
window.__EQ_DEBUG = true; // Enable debug mode
// Components expose their state
const component = document.querySelector(
'[data-component-id="abc"]',
console.log(component.state);
console.log(component.props);
Integration Tests with Playwright
For debugging SSR vs CSR rendering issues:
test("SSR matches CSR", async ({ page }) => {
const ssrResponse = await page.goto("http://localhost:5000");
const ssrHtml = await ssrResponse.text();
// Wait for CSR hydration
await page.waitForLoadState("networkidle");
const csrHtml = await page.content();
expect(normalizeHtml(ssrHtml)).toBe(normalizeHtml(csrHtml));
Debug C# code normally with Visual Studio or VS Code:
1.
Set breakpoints in .cs files
2.
Run with debugger attached: dotnet run
3.
Breakpoints hit during:
•
Server-side rendering (SSR)
•
Server Action invocations
Monitor Server Actions in browser DevTools:
2.
Filter by _equantic/actions
•
Request payload (method name, arguments)
•
Errors (with stack traces)
When using IRequireAssets, verify that dependencies are correctly injected:
1.
Inspect Source: Open browser "View Page Source" and search for the script/style tags.
2.
Network Tab: Check if the external URLs (e.g., CDNs) are loading successfully (Status 200).
3.
Deduplication: Verify that multiple components didn't inject the same script twice.
4.
Order: Stylesheets should appear before scripts for proper rendering.
Server-Side Rendering (SSR) Assets
If assets are missing in the initial HTML:
1.
Verify the component implements IRequireAssets.
2.
Ensure AddUI() is called in Program.cs.
3.
Check if the AssetCollection is correctly gathering the assets during the render pass.
The reconciler tracks performance metrics in development mode:
// Enable performance tracking
console.table(window.__EQ_PERF_DATA);
Metrics include:
•
Render Time: How long each component took to render
•
Diff Time: Time spent in reconciler
•
DOM Operations: Number of actual DOM changes
•
Event Listeners: Active listener count
Monitor compilation times:
Look for:
•
CompileEQuanticUI target duration
•
Number of components compiled
•
TypeScript generation time
Symptom: boot() never executes, GET /_equantic/runtime.js returns 404
Solution: the browser is answered by the Server, not by the file in wwwroot/_equantic/: app.MapUI() maps /_equantic/runtime.js to the bundle the eQuantic.UI.Server assembly embeds. A 404 comes either from that endpoint, whose response then names the resources the assembly does carry, or from no endpoint at all, so check that app.MapUI() runs. The build's own copy, wwwroot/_equantic/runtime.js, is the same bytes for the tools that load the modules outside a server; the CopyEQuanticRuntime target takes it from the Server package, and the build fails with "the served runtime was not found" when it cannot (see Package Architecture) # The copy comes from the Server package (not the SDK's)
ls ~/.nuget/packages/equantic.ui.server/<version>/tools/runtime/
Theme Tokens Missing in CSR
Symptom: Server-rendered HTML is themed, but client-side rendering isn't
Root Cause: the theme bridge blob was not adopted at boot
Solution:
1.
Verify runtime.js loads before component scripts
2.
Check browser console for [eQuantic.UI] Boot process started
3.
Inspect window.__EQ_THEME__ in console - should contain the serialized theme
Symptom: Can't debug original C# code in browser DevTools
Solution:
1.
Build in Debug, or set EQuanticSourceMaps to full: a build in any other configuration writes no map, and an external map is not linked from its module, so the browser does not find it on its own
2.
Check that .map files exist in wwwroot/_equantic/
3.
Enable source maps in browser DevTools settings
4.
Clear browser cache and rebuild
5.
A frame that stops in the TypeScript intermediate (obj/eQuantic/ts/…) comes from a map eqc could not compose, and eqc reports each one in the build log ("… leads to the TypeScript only")
Error Overlay Not Appearing
Symptom: Errors logged to console but no overlay
Checks:
1.
Is window.__EQ_DEV__ true? (Check in console)
2.
Is error overlay imported? (Check runtime.js includes it)
3.
Is error overlay CSS loaded? (Check for #equantic-error-overlay styles)
Force Show:
// Manually trigger overlay
import { errorOverlay } from "@equantic/ui-runtime/dev";
errorOverlay.show({ message: "Test error" });
1.
Use Logger Liberally: Add debug logs during development, they're free in production
2.
Test Both Modes: Always test with Development and Production environment
3.
Monitor Network: Keep DevTools Network tab open to catch failed Server Actions
4.
Source Maps Come with Debug: a Debug build writes them (EQuanticSourceMaps is full there), so there is nothing to enable
5.
Use Error Overlay: Don't suppress errors - let the overlay show them
For production issues:
1.
Server Logs: Check ASP.NET Core logs for Server Action errors
2.
Browser Console: Only warn and error logs appear
3.
Sentry/AppInsights: Integrate error tracking services
4.
Source Maps: a publish ships none. Build with EQuanticSourceMaps=external and upload the maps it writes in wwwroot/_equantic/ to your error tracker; never serve a full map from a web root, since it is the component's source
Before reporting issues:
•
[ ] Check browser console for errors
•
[ ] Verify window.__EQ_DEV__ is true (dev) or false (prod)
•
[ ] Confirm runtime.js loads (Network tab)
•
[ ] Check the theme bridge blob (window.__EQ_THEME__)
•
[ ] Test with browser cache disabled
•
[ ] Try in incognito/private mode
•
[ ] Compare SSR HTML with CSR HTML
•
[ ] Check MSBuild output for warnings
•
[ ] Verify NuGet packages are correct versions