Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SchemaIndex } from "./types";
import { buildTree, loadIndex, sortedFlat } from "./schemaIndex";
import { FlatList, Tree } from "./Tree";
import { Detail } from "./Detail";
import { ErrorBoundary } from "./ErrorBoundary";
import "./styles.css";

type ViewMode = "tree" | "flat";
Expand Down Expand Up @@ -95,7 +96,9 @@ export default function App() {
</aside>
<main className="content">
{selectedEntry ? (
<Detail entry={selectedEntry} onSelect={select} />
<ErrorBoundary resetKey={selectedEntry.class_name}>
<Detail entry={selectedEntry} onSelect={select} />
</ErrorBoundary>
) : (
<div className="placeholder">
<h2>Select a schema from the left to view its definition.</h2>
Expand Down
20 changes: 12 additions & 8 deletions web/src/Detail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import type { IndexEntry, FieldDef, SchemaDocument } from "./types";
import { superclassName } from "./types";
import { loadSchema } from "./schemaIndex";

interface Props {
Expand Down Expand Up @@ -61,14 +62,17 @@ export function Detail({ entry, onSelect }: Props) {
<dt>Superclasses</dt>
<dd>
{dc.superclasses && dc.superclasses.length > 0 ? (
dc.superclasses.map((s, i) => (
<span key={s}>
{i > 0 && ", "}
<button className="link" onClick={() => onSelect(s)}>
{s}
</button>
</span>
))
dc.superclasses.map((ref, i) => {
const name = superclassName(ref);
return (
<span key={name}>
{i > 0 && ", "}
<button className="link" onClick={() => onSelect(name)}>
{name}
</button>
</span>
);
})
) : (
<em>none</em>
)}
Expand Down
42 changes: 42 additions & 0 deletions web/src/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Component, type ErrorInfo, type ReactNode } from "react";

interface Props {
children: ReactNode;
// Reset the error state whenever this key changes (e.g. on selection change).
resetKey?: string | null;
}

interface State {
error: Error | null;
}

export class ErrorBoundary extends Component<Props, State> {
state: State = { error: null };

static getDerivedStateFromError(error: Error): State {
return { error };
}

componentDidUpdate(prev: Props) {
if (prev.resetKey !== this.props.resetKey && this.state.error) {
this.setState({ error: null });
}
}

componentDidCatch(error: Error, info: ErrorInfo) {
console.error("Render error:", error, info.componentStack);
}

render() {
if (this.state.error) {
return (
<div className="detail-error">
<h3>Could not render this schema</h3>
<pre>{this.state.error.message}</pre>
<p>Try selecting a different schema, or view the raw JSON in the source file.</p>
</div>
);
}
return this.props.children;
}
}
6 changes: 0 additions & 6 deletions web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,3 @@
body {
margin: 0;
}

main {
max-width: 48rem;
margin: 0 auto;
padding: 2rem 1rem;
}
12 changes: 11 additions & 1 deletion web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export interface SchemaDocument {
document_class?: {
class_name: string;
class_version?: string;
superclasses?: string[];
superclasses?: SuperclassRef[];
maturity_level?: Maturity;
};
depends_on?: DependsOnEntry[];
Expand All @@ -58,3 +58,13 @@ export interface SchemaDocument {
// Meta-schemas and registries may have arbitrary other shapes; keep open.
[key: string]: unknown;
}

// Inside a schema file, `superclasses` is an array of objects with at least
// a `class_name` key. The repo-level index.json normalizes these to plain
// strings, so callers may see either shape -- always pass through
// `superclassName()` to extract the string.
export type SuperclassRef = string | { class_name: string; [k: string]: unknown };

export function superclassName(ref: SuperclassRef): string {
return typeof ref === "string" ? ref : ref.class_name;
}
Loading