chore: add pre-blazor crash diagnostics
This commit is contained in:
@@ -195,6 +195,7 @@ SQLite migration rule:
|
||||
- Workspace reads are resolved through API requests in `WorkspaceQueryService`; browser interop stays focused on auth forms, session storage, SSE wiring, and small DOM helpers.
|
||||
- Interactive authenticated startup begins in `WorkspaceRouteView.razor` after first render because `RpgRollerApiClient` still depends on JS interop-backed `fetch`.
|
||||
- Workspace startup diagnostics now log route initialization, route-content render phases, and browser-side workspace mutation snapshots to help isolate the remaining Firefox startup crash documented in `POSTMORTEM.md`.
|
||||
- Pre-Blazor diagnostics also watch the static `#rr-interactive-host` container before `_framework/blazor.web.js` connects, so extension-driven DOM mutations can be compared against the first failing interactive batch.
|
||||
- Live workspace refresh compares separate roster, per-character sheet, and log versions so unrelated changes do not trigger full reloads.
|
||||
- Campaign log data is loaded in bounded slices: campaign summaries, one selected roster, one selected character sheet, and a 25-row incremental log window from `/api/campaigns/{campaignId}/log/page`.
|
||||
- Log rows return compact summary data first and lazy-load full detail from `/api/rolls/{rollId}` when expanded.
|
||||
|
||||
@@ -25,11 +25,16 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<Routes @rendermode="@(new InteractiveServerRenderMode(prerender: false))"/>
|
||||
<div id="rr-interactive-host" data-request-path="@RequestPath">
|
||||
<Routes @rendermode="@(new InteractiveServerRenderMode(prerender: false))"/>
|
||||
</div>
|
||||
}
|
||||
<script src="js/rpgroller-api.js"></script>
|
||||
@if (UseInteractiveApp)
|
||||
{
|
||||
<script>
|
||||
window.rpgRollerApi.bootstrapPreBlazorDiagnostics("@RequestPath");
|
||||
</script>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
}
|
||||
</body>
|
||||
@@ -55,6 +60,8 @@ else
|
||||
|
||||
private bool AuthStatusIsError => string.Equals(ReadAuthQueryValue("kind"), "error", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private string RequestPath => HttpContext?.Request.Path.Value ?? "/";
|
||||
|
||||
private string BaseHref
|
||||
{
|
||||
get
|
||||
|
||||
@@ -13,6 +13,7 @@ window.rpgRollerApi = (() => {
|
||||
observer: null,
|
||||
route: null,
|
||||
globalHandlersInstalled: false,
|
||||
domOperationDiagnosticsInstalled: false,
|
||||
mutationBatchCount: 0
|
||||
};
|
||||
|
||||
@@ -24,6 +25,14 @@ window.rpgRollerApi = (() => {
|
||||
console.warn(debugPrefix, new Date().toISOString(), ...args);
|
||||
}
|
||||
|
||||
function summarizeChildren(element) {
|
||||
if (!element || !element.childNodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(element.childNodes).slice(0, 20).map(summarizeNode);
|
||||
}
|
||||
|
||||
function summarizeNode(node) {
|
||||
if (!node) {
|
||||
return "<null>";
|
||||
@@ -117,6 +126,53 @@ window.rpgRollerApi = (() => {
|
||||
});
|
||||
}
|
||||
|
||||
function installDomOperationDiagnostics() {
|
||||
if (workspaceDiagnostics.domOperationDiagnosticsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspaceDiagnostics.domOperationDiagnosticsInstalled = true;
|
||||
const originalInsertBefore = Node.prototype.insertBefore;
|
||||
const originalAppendChild = Node.prototype.appendChild;
|
||||
const originalRemoveChild = Node.prototype.removeChild;
|
||||
const originalReplaceChild = Node.prototype.replaceChild;
|
||||
|
||||
Node.prototype.insertBefore = function (newNode, referenceNode) {
|
||||
debug("dom insertBefore", {
|
||||
parent: summarizeNode(this),
|
||||
newNode: summarizeNode(newNode),
|
||||
referenceNode: summarizeNode(referenceNode),
|
||||
referenceParent: referenceNode ? summarizeNode(referenceNode.parentNode) : null
|
||||
});
|
||||
return originalInsertBefore.call(this, newNode, referenceNode);
|
||||
};
|
||||
|
||||
Node.prototype.appendChild = function (child) {
|
||||
debug("dom appendChild", {
|
||||
parent: summarizeNode(this),
|
||||
child: summarizeNode(child)
|
||||
});
|
||||
return originalAppendChild.call(this, child);
|
||||
};
|
||||
|
||||
Node.prototype.removeChild = function (child) {
|
||||
debug("dom removeChild", {
|
||||
parent: summarizeNode(this),
|
||||
child: summarizeNode(child)
|
||||
});
|
||||
return originalRemoveChild.call(this, child);
|
||||
};
|
||||
|
||||
Node.prototype.replaceChild = function (newChild, oldChild) {
|
||||
debug("dom replaceChild", {
|
||||
parent: summarizeNode(this),
|
||||
newChild: summarizeNode(newChild),
|
||||
oldChild: summarizeNode(oldChild)
|
||||
});
|
||||
return originalReplaceChild.call(this, newChild, oldChild);
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeMutation(mutation) {
|
||||
return {
|
||||
type: mutation.type,
|
||||
@@ -181,6 +237,79 @@ window.rpgRollerApi = (() => {
|
||||
logWorkspaceSnapshot(`phase:${label}`);
|
||||
}
|
||||
|
||||
function bootstrapPreBlazorDiagnostics(requestPath) {
|
||||
installGlobalDiagnostics();
|
||||
installDomOperationDiagnostics();
|
||||
|
||||
const host = document.getElementById("rr-interactive-host");
|
||||
workspaceDiagnostics.route = requestPath;
|
||||
debug("bootstrapPreBlazorDiagnostics", {
|
||||
requestPath,
|
||||
readyState: document.readyState,
|
||||
bodyChildren: summarizeChildren(document.body),
|
||||
host: summarizeNode(host),
|
||||
hostChildren: summarizeChildren(host)
|
||||
});
|
||||
|
||||
if (!host) {
|
||||
warn("bootstrapPreBlazorDiagnostics missing host", { requestPath });
|
||||
return;
|
||||
}
|
||||
|
||||
const preconnectObserver = new MutationObserver((mutations) => {
|
||||
debug("preblazor host mutations", {
|
||||
requestPath,
|
||||
mutations: mutations.slice(0, 20).map(summarizeMutation),
|
||||
bodyChildren: summarizeChildren(document.body),
|
||||
hostChildren: summarizeChildren(host)
|
||||
});
|
||||
});
|
||||
|
||||
preconnectObserver.observe(host, {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
attributes: true,
|
||||
characterData: false
|
||||
});
|
||||
|
||||
const bodyObserver = new MutationObserver((mutations) => {
|
||||
debug("preblazor body mutations", {
|
||||
requestPath,
|
||||
mutations: mutations.slice(0, 20).map(summarizeMutation),
|
||||
bodyChildren: summarizeChildren(document.body),
|
||||
hostChildren: summarizeChildren(host)
|
||||
});
|
||||
});
|
||||
|
||||
bodyObserver.observe(document.body, {
|
||||
subtree: false,
|
||||
childList: true,
|
||||
attributes: false
|
||||
});
|
||||
|
||||
queueMicrotask(() => {
|
||||
debug("preblazor microtask snapshot", {
|
||||
requestPath,
|
||||
bodyChildren: summarizeChildren(document.body),
|
||||
hostChildren: summarizeChildren(host)
|
||||
});
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
debug("preblazor raf snapshot", {
|
||||
requestPath,
|
||||
bodyChildren: summarizeChildren(document.body),
|
||||
hostChildren: summarizeChildren(host)
|
||||
});
|
||||
});
|
||||
setTimeout(() => {
|
||||
debug("preblazor timeout50 snapshot", {
|
||||
requestPath,
|
||||
bodyChildren: summarizeChildren(document.body),
|
||||
hostChildren: summarizeChildren(host)
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function toAppUrl(url) {
|
||||
if (!url || typeof url !== "string") {
|
||||
return url;
|
||||
@@ -588,6 +717,7 @@ window.rpgRollerApi = (() => {
|
||||
scrollElementToBottom,
|
||||
clearInputValue,
|
||||
installWorkspaceDiagnostics,
|
||||
markWorkspacePhase
|
||||
markWorkspacePhase,
|
||||
bootstrapPreBlazorDiagnostics
|
||||
};
|
||||
})();
|
||||
|
||||
1
TASKS.md
1
TASKS.md
@@ -24,6 +24,7 @@ The change is complete when a human can run the app, open `/`, observe the corre
|
||||
- [x] (2026-05-04 21:58Z) Removed shell-level `OnAfterRenderAsync` bootstrapping, moved the JS-dependent authenticated startup into a route-owned `WorkspaceRouteView`, removed shell-owned staged control renders, restored the missing development database fixture, and updated README to describe the completed route-first architecture.
|
||||
- [x] (2026-05-04) Updated host tests, Selenium smoke tests, and docs so the real-route model is the documented and verified Milestone 2 behavior.
|
||||
- [x] (2026-05-04) Added expanded workspace startup diagnostics across Blazor lifecycle logging, route-content render logging, and browser-side DOM mutation snapshots to narrow the remaining Firefox batch-2 crash.
|
||||
- [x] (2026-05-04) Extended the diagnostics to page-load time by wrapping the interactive host in a stable container and logging pre-Blazor body and host mutations before the first interactive batch applies.
|
||||
|
||||
## Surprises & Discoveries
|
||||
|
||||
|
||||
Reference in New Issue
Block a user