fix: bound navigation and inspector rendering

This commit is contained in:
2026-07-22 20:12:52 +02:00
parent e44ff78511
commit 141a06dbf3
5 changed files with 692 additions and 48 deletions

View File

@@ -1,8 +1,16 @@
import { useState } from 'react';
import type { ParserDiagnostic } from '../onenote/model/diagnostics.js';
import {
DEFAULT_UI_RENDER_LIMITS,
limitUiString,
resolveUiLimit,
} from './ui-render-budget.js';
interface DiagnosticsPanelProps {
diagnostics: ParserDiagnostic[];
title?: string;
pageSize?: number;
}
function formatOffset(offset: number): string {
@@ -12,13 +20,69 @@ function formatOffset(offset: number): string {
export function DiagnosticsPanel({
diagnostics,
title = 'Diagnostics',
pageSize: requestedPageSize,
}: DiagnosticsPanelProps) {
const [requestedPage, setRequestedPage] = useState(0);
const pageSize = resolveUiLimit(
requestedPageSize,
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticsPerPage
);
const pageCount = Math.max(1, Math.ceil(diagnostics.length / pageSize));
const page = Math.min(requestedPage, pageCount - 1);
const firstDiagnostic = page * pageSize;
const limitedTitle = limitUiString(
title,
DEFAULT_UI_RENDER_LIMITS.maxPanelTitleCharacters
);
const displayedDiagnostics = diagnostics
.slice(firstDiagnostic, firstDiagnostic + pageSize)
.map((diagnostic, index) => {
const code = limitUiString(
diagnostic.code,
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticCodeCharacters
);
const message = limitUiString(
diagnostic.message,
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticMessageCharacters
);
const structure =
diagnostic.structure === undefined
? undefined
: limitUiString(
diagnostic.structure,
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticContextCharacters
);
const propertyId =
diagnostic.propertyId === undefined
? undefined
: limitUiString(
diagnostic.propertyId,
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticContextCharacters
);
return {
diagnostic,
index: firstDiagnostic + index,
code: code.value,
message: message.value,
structure: structure?.value,
propertyId: propertyId?.value,
truncated:
code.truncated ||
message.truncated ||
structure?.truncated === true ||
propertyId?.truncated === true,
};
});
const textTruncated =
limitedTitle.truncated ||
displayedDiagnostics.some((diagnostic) => diagnostic.truncated);
return (
<section className="diagnostics" aria-labelledby="diagnostics-title">
<div className="panel-heading">
<div>
<p className="eyebrow">Parser details</p>
<h2 id="diagnostics-title">{title}</h2>
<h2 id="diagnostics-title">{limitedTitle.value}</h2>
</div>
<span className="count-badge">{diagnostics.length}</span>
</div>
@@ -27,36 +91,36 @@ export function DiagnosticsPanel({
<p className="empty-message">No parser diagnostics for this view.</p>
) : (
<ul className="diagnostic-list">
{diagnostics.map((diagnostic, index) => (
{displayedDiagnostics.map((displayed) => (
<li
className={`diagnostic diagnostic--${diagnostic.severity}`}
key={`${diagnostic.code}-${diagnostic.offset ?? 'none'}-${index}`}
className={`diagnostic diagnostic--${displayed.diagnostic.severity}`}
key={displayed.index}
>
<div className="diagnostic__summary">
<strong>{diagnostic.code}</strong>
<span>{diagnostic.severity}</span>
<strong>{displayed.code}</strong>
<span>{displayed.diagnostic.severity}</span>
</div>
<p>{diagnostic.message}</p>
{diagnostic.structure !== undefined ||
diagnostic.offset !== undefined ||
diagnostic.propertyId !== undefined ? (
<p>{displayed.message}</p>
{displayed.structure !== undefined ||
displayed.diagnostic.offset !== undefined ||
displayed.propertyId !== undefined ? (
<dl className="diagnostic__context">
{diagnostic.structure !== undefined ? (
{displayed.structure !== undefined ? (
<>
<dt>Structure</dt>
<dd>{diagnostic.structure}</dd>
<dd>{displayed.structure}</dd>
</>
) : null}
{diagnostic.offset !== undefined ? (
{displayed.diagnostic.offset !== undefined ? (
<>
<dt>Offset</dt>
<dd>{formatOffset(diagnostic.offset)}</dd>
<dd>{formatOffset(displayed.diagnostic.offset)}</dd>
</>
) : null}
{diagnostic.propertyId !== undefined ? (
{displayed.propertyId !== undefined ? (
<>
<dt>Property</dt>
<dd>{diagnostic.propertyId}</dd>
<dd>{displayed.propertyId}</dd>
</>
) : null}
</dl>
@@ -65,6 +129,41 @@ export function DiagnosticsPanel({
))}
</ul>
)}
{diagnostics.length > 0 ? (
<nav className="panel-heading" aria-label="Diagnostic pages">
<p>
Showing diagnostics {firstDiagnostic + 1}
{firstDiagnostic + displayedDiagnostics.length} of{' '}
{diagnostics.length}.
</p>
<div>
<button
type="button"
disabled={page === 0}
onClick={() => setRequestedPage(Math.max(0, page - 1))}
>
Previous
</button>{' '}
<span>
Page {page + 1} of {pageCount}
</span>{' '}
<button
type="button"
disabled={page + 1 >= pageCount}
onClick={() =>
setRequestedPage(Math.min(pageCount - 1, page + 1))
}
>
Next
</button>
</div>
</nav>
) : null}
{textTruncated ? (
<p className="render-limit-notice" role="status">
Long diagnostic text was shortened for safe browser display.
</p>
) : null}
</section>
);
}