Repository navigation
Debug
🌐 This page in: English · Português
eQuantic.UI provides professional debugging tools similar to Next.js, with development-only features that help you identify and fix issues quickly.
The framework automatically detects the environment using IWebHostEnvironment.IsDevelopment() and exposes it to the browser via:
window.__EQ_DEV__; // true in development, false in productionAll 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:
[eQuantic.UI]
The logger is implemented in src/eQuantic.UI.Runtime/src/utils/logger.ts:
const isDev = typeof window !== "undefined" && window.__EQ_DEV__;
export const logger = {
debug(...args: any[]) {
if (isDev) console.debug("[eQuantic.UI]", ...args);
},
info(...args: any[]) {
if (isDev) console.info("[eQuantic.UI]", ...args);
},
warn(...args: any[]) {
console.warn("[eQuantic.UI]", ...args);
},
error(...args: any[]) {
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
Escto 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:
- Unhandled Errors: Any uncaught exception in JavaScript
- 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)│
├─────────────────────────────────────────────┤
│ │
│ Error message here │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Stack trace: │ │
│ │ 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";
if (window.__EQ_DEV__) {
errorOverlay.show({
message: "Custom error message",
stack: error.stack,
componentStack: "Component hierarchy...",
});
}// Programmatically clear
errorOverlay.clear();
// User actions
// - Press Esc key
// - Click "Close" buttonThe error overlay is implemented in src/eQuantic.UI.Runtime/src/dev/error-overlay.ts:
class ErrorOverlay {
private overlay: HTMLDivElement | null = null;
private errors: ErrorInfo[] = [];
show(error: ErrorInfo) {
if (!window.__EQ_DEV__) return; // Dev only
this.errors.push(error);
this.render();
}
clear() {
this.errors = [];
if (this.overlay) {
this.overlay.remove();
this.overlay = null;
}
}
private render() {
// Creates full-screen overlay with error details
}
}
export const errorOverlay = new ErrorOverlay();
// Auto-capture errors
if (window.__EQ_DEV__) {
window.addEventListener("error", (event) => {
errorOverlay.show({
message: event.message,
stack: event.error?.stack,
});
});
window.addEventListener("unhandledrejection", (event) => {
errorOverlay.show({
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:
- Open DevTools (F12)
- Go to Sources tab
- Find the C# file in the tree, named by its path inside the project (
Pages/Home.cs), never by the build machine's - Set breakpoints directly in the C# source
- 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>
</PropertyGroup>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:
{
"version": 3,
"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
- Step through C# logic
- Inspect C# variable names
- See original line numbers in stack traces
To inspect component state and props:
// In browser console
window.__EQ_DEBUG = true; // Enable debug mode
// Components expose their state
const component = document.querySelector(
'[data-component-id="abc"]',
).__component;
console.log(component.state);
console.log(component.props);For debugging SSR vs CSR rendering issues:
test("SSR matches CSR", async ({ page }) => {
// Get SSR HTML
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();
// Compare
expect(normalizeHtml(ssrHtml)).toBe(normalizeHtml(csrHtml));
});Debug C# code normally with Visual Studio or VS Code:
- Set breakpoints in
.csfiles - Run with debugger attached:
dotnet run - Breakpoints hit during:
- Server-side rendering (SSR)
- Server Action invocations
- Component compilation
Monitor Server Actions in browser DevTools:
- Open Network tab
- Filter by
_equantic/actions - Inspect:
- Request payload (method name, arguments)
- Response data
- Timing information
- Errors (with stack traces)
When using IRequireAssets, verify that dependencies are correctly injected:
- Inspect Source: Open browser "View Page Source" and search for the script/style tags.
- Network Tab: Check if the external URLs (e.g., CDNs) are loading successfully (Status 200).
- Deduplication: Verify that multiple components didn't inject the same script twice.
- Order: Stylesheets should appear before scripts for proper rendering.
If assets are missing in the initial HTML:
- Verify the component implements
IRequireAssets. - Ensure
AddUI()is called inProgram.cs. - Check if the
AssetCollectionis correctly gathering the assets during the render pass.
The reconciler tracks performance metrics in development mode:
// Enable performance tracking
window.__EQ_PERF = true;
// View metrics
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:
dotnet build -v:detailedLook for:
-
CompileEQuanticUItarget duration - Number of components compiled
- TypeScript generation time
- Bun bundling 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/
# Force rebuild
dotnet clean
dotnet build -v:nSymptom: Server-rendered HTML is themed, but client-side rendering isn't
Root Cause: the theme bridge blob was not adopted at boot
Solution:
- Verify runtime.js loads before component scripts
- Check browser console for
[eQuantic.UI] Boot process started - Inspect
window.__EQ_THEME__in console - should contain the serialized theme
Symptom: Can't debug original C# code in browser DevTools
Solution:
- Build in Debug, or set
EQuanticSourceMapstofull: a build in any other configuration writes no map, and anexternalmap is not linked from its module, so the browser does not find it on its own - Check that
.mapfiles exist inwwwroot/_equantic/ - Enable source maps in browser DevTools settings
- Clear browser cache and rebuild
- 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")
Symptom: Errors logged to console but no overlay
Checks:
- Is
window.__EQ_DEV__true? (Check in console) - Is error overlay imported? (Check runtime.js includes it)
- Is error overlay CSS loaded? (Check for
#equantic-error-overlaystyles)
Force Show:
// Manually trigger overlay
import { errorOverlay } from "@equantic/ui-runtime/dev";
errorOverlay.show({ message: "Test error" });- Use Logger Liberally: Add debug logs during development, they're free in production
-
Test Both Modes: Always test with
DevelopmentandProductionenvironment - Monitor Network: Keep DevTools Network tab open to catch failed Server Actions
-
Source Maps Come with Debug: a Debug build writes them (
EQuanticSourceMapsisfullthere), so there is nothing to enable - Use Error Overlay: Don't suppress errors - let the overlay show them
For production issues:
- Server Logs: Check ASP.NET Core logs for Server Action errors
-
Browser Console: Only
warnanderrorlogs appear - Sentry/AppInsights: Integrate error tracking services
-
Source Maps: a publish ships none. Build with
EQuanticSourceMaps=externaland upload the maps it writes inwwwroot/_equantic/to your error tracker; never serve afullmap 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
- Runtime Architecture - Understanding the runtime system
- Build Flow - How compilation and bundling works
- Performance - Optimization techniques
🌐 English · Português
🏁 Start here
🏗️ Architecture
- Architecture Overview
- Write-Once Components
- Declarative Surface
- Package Architecture
- Components
- Styling
- Localization
- Analytics & GTM
📱 Write-once
⚙️ Compilation
⚡ Runtime
🔌 Server
🎨 Ecosystem
🚀 Development