feat(web): use the Ace JSON editor everywhere, and restore the raw-JSON toggle
Extracts the Ace integration #2094 carried inline into a shared `elements/JsonEditor`, and renders it on every surface where the web client types or displays JSON. The element is deliberately text in, text out. The two editing contracts above it disagree about what an unparseable draft means — JsonObjectInput keeps the last valid object and says nothing to its parent, SchemaJsonField reports `undefined` and blocks submission (#2020) — so parsing, and the draft/value split that goes with it, stays with each caller. Surfaces: SchemaJsonField (inherited by all four SchemaForm consumers), a new "Edit as JSON" switch for the whole arguments object, the server.json File Contents box, the raw JSON-RPC request box, ContentViewer's JSON branch (read-only, inherited by its eleven consumers), and a new "Edit and replay" modal on a Protocol entry. Ace read-only replaces the CodeHighlight JSON path and the Prism `json` grammar loader is dropped: folding, not colorization, is what it adds, and two highlighters for one language drift. `wrap={false}` callers and untyped text that only looks like JSON keep the plain renderer. Closes #2151 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4 Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
95db736a55
commit
1a2a36f0b8
3 changed files with 2026 additions and 448 deletions
|
|
@ -9,14 +9,11 @@ const meta: Meta<typeof CodeHighlight> = {
|
|||
export default meta;
|
||||
type Story = StoryObj<typeof CodeHighlight>;
|
||||
|
||||
export const Json: Story = {
|
||||
// No JSON story: JSON is rendered by `JsonEditor` (read-only), not here (#2151).
|
||||
export const Yaml: Story = {
|
||||
args: {
|
||||
language: "json",
|
||||
code: JSON.stringify(
|
||||
{ name: "my-app", version: "1.0.0", tags: ["a", "b"] },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
language: "yaml",
|
||||
code: "name: my-app\nversion: 1.0.0\ntags:\n - a\n - b",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,6 @@ vi.mock("react-syntax-highlighter/dist/esm/prism-light", () => ({
|
|||
vi.mock("react-syntax-highlighter/dist/esm/styles/prism/tomorrow", () => ({
|
||||
default: {},
|
||||
}));
|
||||
vi.mock("react-syntax-highlighter/dist/esm/languages/prism/json", () => ({
|
||||
default: { __grammar: "json" },
|
||||
}));
|
||||
vi.mock("react-syntax-highlighter/dist/esm/languages/prism/markup", () => ({
|
||||
default: { __grammar: "markup" },
|
||||
}));
|
||||
|
|
@ -53,15 +50,15 @@ beforeEach(() => {
|
|||
describe("CodeHighlight", () => {
|
||||
it("renders plain code initially, then upgrades to the highlighter", async () => {
|
||||
const CodeHighlight = await loadComponent();
|
||||
renderWithMantine(<CodeHighlight language="json" code='{"a":1}' />);
|
||||
renderWithMantine(<CodeHighlight language="yaml" code="a: 1" />);
|
||||
// Plain Mantine Code before the grammar resolves.
|
||||
expect(screen.queryByTestId("prism")).not.toBeInTheDocument();
|
||||
expect(screen.getByText('{"a":1}')).toBeInTheDocument();
|
||||
expect(screen.getByText("a: 1")).toBeInTheDocument();
|
||||
// After the lazy grammar loads, the prism runtime takes over.
|
||||
const prism = await screen.findByTestId("prism");
|
||||
expect(prism).toHaveAttribute("data-language", "json");
|
||||
expect(registerLanguage).toHaveBeenCalledWith("json", {
|
||||
__grammar: "json",
|
||||
expect(prism).toHaveAttribute("data-language", "yaml");
|
||||
expect(registerLanguage).toHaveBeenCalledWith("yaml", {
|
||||
__grammar: "yaml",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -113,14 +110,14 @@ describe("CodeHighlight", () => {
|
|||
it("reuses an already-registered grammar without re-importing", async () => {
|
||||
const CodeHighlight = await loadComponent();
|
||||
const { unmount } = renderWithMantine(
|
||||
<CodeHighlight language="json" code='{"a":1}' />,
|
||||
<CodeHighlight language="yaml" code="a: 1" />,
|
||||
);
|
||||
await screen.findByTestId("prism");
|
||||
expect(registerLanguage).toHaveBeenCalledTimes(1);
|
||||
unmount();
|
||||
// A second mount finds json already registered: ready synchronously, no
|
||||
// A second mount finds yaml already registered: ready synchronously, no
|
||||
// second registerLanguage call.
|
||||
renderWithMantine(<CodeHighlight language="json" code='{"b":2}' />);
|
||||
renderWithMantine(<CodeHighlight language="yaml" code="b: 2" />);
|
||||
expect(await screen.findByTestId("prism")).toBeInTheDocument();
|
||||
expect(registerLanguage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
@ -128,7 +125,7 @@ describe("CodeHighlight", () => {
|
|||
it("loads a second language reusing the already-loaded runtime", async () => {
|
||||
const CodeHighlight = await loadComponent();
|
||||
const { unmount } = renderWithMantine(
|
||||
<CodeHighlight language="json" code='{"a":1}' />,
|
||||
<CodeHighlight language="yaml" code="a: 1" />,
|
||||
);
|
||||
await screen.findByTestId("prism");
|
||||
unmount();
|
||||
|
|
@ -146,7 +143,7 @@ describe("CodeHighlight", () => {
|
|||
const CodeHighlight = await loadComponent();
|
||||
renderWithMantine(
|
||||
<>
|
||||
<CodeHighlight language="json" code='{"a":1}' />
|
||||
<CodeHighlight language="yaml" code="a: 1" />
|
||||
<CodeHighlight language="css" code=".a{}" />
|
||||
</>,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,11 @@ type PrismRuntime = ComponentType<{
|
|||
* below if the canonical Prism name differs) as the type matrix grows.
|
||||
*/
|
||||
const LANGUAGE_LOADERS: Record<string, () => Promise<{ default: Grammar }>> = {
|
||||
json: () => import("react-syntax-highlighter/dist/esm/languages/prism/json"),
|
||||
// No `json` entry, deliberately (#2151). JSON is rendered by the Ace-backed
|
||||
// `JsonEditor` in read-only mode, which folds and numbers a large payload —
|
||||
// things a Prism block cannot do. Registering the grammar here as well would
|
||||
// leave two highlighters for one language, differing in appearance and
|
||||
// drifting apart; there is no caller left that asks for it.
|
||||
markup: () =>
|
||||
import("react-syntax-highlighter/dist/esm/languages/prism/markup"),
|
||||
css: () => import("react-syntax-highlighter/dist/esm/languages/prism/css"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue