// --------------------------------------------------------------------------- // customer-grid / ScriptViewPanel.tsx — owner item 6's renderer (W36-T04, contract C3). // // ⛔ ITS OWN MODULE, AND THAT IS A HARNESS FACT AS WELL AS A TIDINESS ONE. It began inside // `viewModes.tsx`, where the other display modes live — and that module `require`s `./cells`, // which imports glide-data-grid, whose CJS build cannot load under this repo's node harness. So // putting it there made the whole component untestable: the ONE thing this surface must prove is // that a hostile spec is rendered as text, and that claim can only be made by RENDERING. Here the // import graph is `react` + `./scriptViews` and nothing else, so `gridUx.test.ts` renders it for // real and asserts on the HTML a person would receive. // ⚠ The stylesheet is imported by `CustomerGrid.tsx` (the mount) for the same reason: tsc // preserves a side-effect CSS import into the emitted CommonJS and node dies on it. // --------------------------------------------------------------------------- import { memo, useEffect, useRef, useState } from "react"; import { barScale, renderableSpec, specKindName } from "./scriptViews"; import type { DrawableSpec, ScriptRun, ScriptView as ScriptViewRecord } from "./scriptViews"; // ═══════════════════════════════════════════════════════════════════════════════════════════ // ⭐⭐ W36-T04 (owner item 6 · rulings R3/R10 · contract C3) — THE CODE-SCRIPT VIEW // // Owner, verbatim: *"Add code script as an interface (database View) so a user can build whatever // they want through the Agent chat interface. be able to create any dashboard they want. User // should have the ability to see the code AND the dashboard output of course."* Both halves are // on screen at once, which is the whole of that sentence. // // ⛔⛔ NO BRANCH OF THIS RENDERER EXECUTES A STRING, AND THAT IS THE TICKET'S OWN `done-when`. // R10 runs the Python in a SERVER sandbox exactly so the browser never has to trust the result; // a renderer that then evaluated one would hand back everything the sandbox contains. So there is // no `dangerouslySetInnerHTML`, no `new Function`, no `eval`, no `href`/`src` taken from a spec, // and no `style` built from spec data anywhere below. Every value reaching the DOM is a SCALAR // that `renderableSpec` admitted, rendered as TEXT — the one exception being a bar's width, which // is a number clamped to a percentage here and never a string from the wire. // // ⛔ AN UNKNOWN `kind` IS A SENTENCE, NOT A BLANK. `spec` is whatever the script emitted (E's C3 // note 1: there is no fixed vocabulary, deliberately), so a view whose author invented // `kind: "sankey"` is a legitimate thing this build cannot draw. Saying so, by name, is the // difference between "this build does not know that shape yet" and "your script is broken". // // ⚠ `ok: false` IS A 200. A script that timed out or divided by zero asked a well-formed question // whose ANSWER is that no view was produced; it is rendered beside the code, never thrown, and // never a toast that disappears while the reader is looking at the line that caused it. // ═══════════════════════════════════════════════════════════════════════════════════════════ function SpecTable({ spec }: { spec: DrawableSpec }) { return ( {(spec.columns ?? []).map((c, i) => )} {(spec.rows ?? []).map((row, r) => ( {row.map((cell, c) => )} ))}
{c}
{cell}
); } function SpecMetrics({ spec }: { spec: DrawableSpec }) { return (
{(spec.items ?? []).map((item, i) => (
{item.label}
{item.value}
{item.note ?
{item.note}
: null}
))}
); } function SpecBars({ spec }: { spec: DrawableSpec }) { const series = spec.series ?? []; const scale = barScale(series); return (
{series.map((s, i) => (
{s.label} {/* ⚠ The ONE computed style on this surface, and it is arithmetic on a NUMBER the spec reader already validated as finite, clamped here. Never a string from the wire, which is what would make this an injection point. */} {s.display}
))}
); } /** The refusal, and the four limit fields PRINTED rather than re-worded (C3 note 2). */ function ScriptRefusal({ run }: { run: ScriptRun }) { return (

This script did not produce a view

{run.error ?? "The run ended without emitting anything."}

Reason
{run.code}
{run.limit ? <>
Subject
{run.limit.subject}
: null} {run.limit ? <>
Effect
{run.limit.effect}
: null} {run.limit ? <>
Cause
{run.limit.cause}
: null} {run.limit ? <>
What to do
{run.limit.recommendation}
: null}
); } export function ScriptOutput({ run }: { run: ScriptRun | null }) { if (run === null) return

Run the script to see what it draws.

; const drawable = run.ok ? renderableSpec(run.spec) : null; const claimed = specKindName(run.spec); return ( <> {!run.ok ? : null} {run.ok && drawable === null ? (

This build cannot draw that shape yet

{claimed ? `The script emitted a view of kind "${claimed}", which this version does not render. It knows table, metrics, bars and text.` : `The script emitted a view with no recognisable kind. This version renders table, metrics, bars and text.`}

) : null} {drawable !== null ? ( <> {drawable.title ?

{drawable.title}

: null} {drawable.kind === "table" ? : null} {drawable.kind === "metrics" ? : null} {drawable.kind === "bars" ? : null} {drawable.kind === "text" ?

{drawable.body}

: null} ) : null} {run.stdout ? (
          {run.stdout}
          {run.truncated ? "\n[output truncated]" : ""}
        
) : null} ); } /** * The whole View: the source on the left, what it drew on the right. * * ⚠ A DRAFT RUN NEEDS NO SAVE (C3 note 3), which is what makes this usable: type, run, save when * it works. The Save button is separate and deliberately not automatic, because a PUT is a NEW * VERSION and an autosaving editor would fill the 40-deep history with keystrokes. */ export const ScriptView = memo(function ScriptView({ view, running, run, saving, readOnly = false, onRun, onSave, }: { view: ScriptViewRecord | null; running: boolean; run: ScriptRun | null; saving?: boolean; readOnly?: boolean; onRun: (draft: string) => void; onSave?: (source: string) => void; }) { const [draft, setDraft] = useState(view?.source ?? ""); const loadedId = useRef(null); useEffect(() => { // Re-seed only when a DIFFERENT view is opened. Re-seeding on every `view` identity would // discard what somebody is typing the moment the list refreshes underneath them. if (!view || loadedId.current === view.id) return; loadedId.current = view.id; setDraft(view.source); }, [view]); const dirty = view !== null && draft !== view.source; if (view === null) return (

This script view could not be opened.

); return (
Code {`v${view.version}`} {dirty ? ", unsaved changes" : ""}