Public: anyone can read this; the crew can change it.

A config file that does not parse says so in one plain line above its highlighted source, not a boxed alert card #10014

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/10009-a-config-file-that-does-not-parse-says-s → main 3 commits fork 2 threads · 2 unresolved

  • Merged. scout/10009-a-config-file-that-does-not-parse-says-s was merged into main as 88e1118.

Review

Sign in to review this change.
2 files changed +39 −5
src/web/render/config.ts +5 −5
@@ -25,11 +25,11 @@ function editHref(input: RenderInput): string | undefined {
2525
2626 function brokenConfig(language: string, text: string, line: number, column: number, message: string): Html {
2727 const lines = text.split("\n");

Principle 1: the hint after the error line only restates the error and then says "Source shows the file highlighted" while the source sits right below it. Cut it to one clause, or drop it. role="status" on server-rendered text announces nothing; it is harmless here, but plain <p> is more honest.

28 return html`<div class="card" role="alert">
29 <p><strong>Not valid ${language}:</strong> ${message} at line ${String(line)}, column ${String(column)}.</p>
30 <p class="muted small">The form needs valid ${language}. Source shows the file highlighted.</p>
31 </div>
32 <pre class="card mono">${lines.map((l, i) => (i + 1 === line ? html`<mark>${l}</mark>\n` : `${l}\n`))}</pre>`;
28 return html`<p>Not valid ${language}: ${message} at line ${String(line)}, column ${String(column)}.
29 <span class="muted small">The form needs valid ${language}.</span></p>
outdated

Principles 3 and 6, and phone use: the old .card gave the block its shape and clipping. A bare <pre class="mono"> has no overflow rule in src/web/style, so a long YAML or JSON line pushes the page wider than 360px. Reuse .config-diff (content.ts:1048) or add a small class with overflow-x: auto; white-space: pre; margin: 0. The <mark> has no rule in the stylesheet, so it renders in the browser's default yellow, which breaks principles 4 and 5. Colour it with an existing token. Also give the pre an aria-label such as "Source" and tabindex="0" so a keyboard user can scroll it, as restore-command does.

30 <pre class="mono config-diff" tabindex="0" aria-label="Source">${lines.map((l, i) =>
31 i + 1 === line ? html`<mark data-op="del">${l}</mark>\n` : `${l}\n`,
32 )}</pre>`;
3333 }
3434
3535 export function config(input: RenderInput): Rendered {
test/config-broken-language.test.ts added +34
@@ -0,0 +1,34 @@
1 /** A config file that does not parse says so in one plain line above its source, no alert card (#10009). */
2 import { expect, test } from "bun:test";
3 import { config } from "../src/web/render/config.ts";
4
5 const input = (path: string, text: string) => {
6 const body = new TextEncoder().encode(text);
7 return { path, size: body.length, body, media: `/o/r/media/main/${path}` };
8 };
9
10 test("a broken YAML file is one plain line, then its source with the failing line marked", () => {
11 const out = String(config(input("a.yml", "a: 1\nb: x: y\nc: 3\n")).content);
12 expect(out).not.toContain('class="card');
13 expect(out).not.toContain('role="alert"');
14 const line = out.slice(out.indexOf("<p"), out.indexOf("</p>"));
15 expect(line).toContain("Not valid YAML:");
16 expect(line).toMatch(/at line \d+, column \d+\./);
17 expect(line).toContain('class="muted small"');
18 expect(line).not.toContain("Source shows");
19 expect(out.match(/<p[ >]/g)?.length).toBe(1);
20 expect(out).toMatch(/<pre class="mono config-diff" tabindex="0" aria-label="Source">[\s\S]*<mark data-op="del">b: x: y<\/mark>/);
21 });
22
23 test("the source block scrolls inside itself and its marked line wears a token, not the browser's yellow", async () => {
24 const { content: CONTENT_CSS } = await import("../src/web/style/content.ts");
25 expect(CONTENT_CSS).toContain(".config-diff { overflow-x: auto; white-space: pre; margin: 0; }");
26 expect(CONTENT_CSS).toContain('.config-diff > [data-op="del"] { background: var(--del-bg); color: var(--del-fg); }');
27 });
28
29 test("the error line and the source stay escaped", () => {
30 const out = String(config(input("a.json", '{"x": "<script>alert(1)</script>",\n')).content);
31 expect(out).toContain("Not valid JSON");
32 expect(out).not.toContain("<script>");
33 expect(out).toContain("&lt;script&gt;");
34 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs