Docs / Developer / Drawing entity
Drawing entity
A CanonicalHtmlDoc is CanonicalEntity<"htmldoc", HtmlDocBody>: the stable canonical wrapper (see
architecture.md) around an HTML document somebody - usually the agent - wrote to
be looked at. It stores under studio/htmldoc/<id>.json, the same hub-and-spoke shape as the lorebook,
persona and regex entities. src/entities/htmldoc/schema.ts is the source of truth for the types.
Why it is an entity at all
A wireframe that lives in a chat reply cannot be renamed, filed, reopened tomorrow or edited. Every one
of those verbs already exists in this app and every one of them acts on a PIECE, so the whole feature is
the kind: once a drawing is a piece it inherits create, read, save, delete, duplicate, collections and
the Library without a single new tool beyond studio_htmldoc_create.
The alternative - a bespoke API per verb over a transient value - would have been seven tools that each
lose their content on reload.
It is untrusted content, and storing it changes nothing
The html field is model output. Nothing in this repo evaluates it, at any point, on any surface.
It is stored verbatim and unsanitised. Cleaning at rest would be the wrong boundary twice over: the
file would differ from what was written, so a person editing their own drawing would watch it get
mangled, and a second sanitiser would exist that could drift from the one that actually renders.
The seal is at the point of DRAWING, which is the only place it can be enforced. Every surface that
shows a drawing uses the same SealedHtmlPreview:
- a
srcdociframe withsandbox=""- no scripts, neverallow-same-origin - a CSP of
default-src 'none'; script-src 'none'; connect-src 'none'; img-src data: blob:; style-src 'unsafe-inline' - DOMPurify over the markup, with
script,iframe,object,embed,form,input,button,textarea,select,link,metaandbaseforbidden - the list lives incore/render/seal-policy.tsso Kit can read the same policy without a DOM
So: all CSS renders, images work as data: URIs, and JavaScript never runs while no http(s) image,
font, stylesheet or fetch ever resolves. That is a static-drawing surface by construction. See
security/safe-rendering.md.
The frame does not keep the keyboard. Clicking inside an iframe focuses it, and every keystroke after
that belongs to the frame's own document - so the shell's chords (ctrl+/ for the agent, ctrl+1..9 for
the dock) all went dead once somebody clicked a drawing, and the parent cannot listen inside asandbox="" frame to fix it. Focus is handed straight back, deferred by one turn of the loop because
a synchronous blur loses to the browser's own focus assignment. The cost is arrow-key scrolling
inside a drawing; the wheel still scrolls it, and a sealed page has nothing to type into anyway.
A whole document keeps its design: sanitizing returns the parsed BODY, so a <head><style> block
would go into the bin with the head. Every style block is lifted out first and re-injected into the
frame's own <style>, wherever in the document it was written. Form controls are the one thing an
author has to design around - a <button> keeps its label as bare text and loses its shape, so draw
one as a styled div or span. html_will_draw answers that question about any markup before it
is saved.
Three surfaces draw a piece and all three are that one component at different sizes - the agent
transcript's inline preview, the HTML View tab, and the Workbench editor's live pane. One boundary,
three sizes, nothing to drift.
Where it is used
- Workbench - the editor: source on the left, the drawing on the right, live as you type. Autosave
and the conflict bar behave as they do for any piece. The divider between them drags (arrow keys and
Home/End work on it, double-click recentres) and the source hides outright, since a drawing being
looked at does not need half the room spent on markup; both are remembered in settings. - Kit, as a design source -
regex_from_drawingturns a saved drawing into the replacement half of
a regex rule, so the rich thing is designed once here and rendered from compact model output by a
script. See kit/tools.md. - HTML View - a
catalogOnlyapp that draws one at full size. Opened by id from the Library, or by
handoff from an ```html block in a reply. Both it and the editor pane can go fullscreen (the
browser's own where the engine grants it, an overlay over the app where it does not; Escape leaves
either), and both passfillso the drawing takes the whole room rather than the inline ceiling. - Kit -
studio_htmldoc_createcomposes one as a preview-only draft; the Gate turns it into a file.htmldoc.document.updateis the edit half, found by search ("edit the wireframe", "fix the
layout"): a patch of markup, name, summary, tags or notes, previewed as changes and applied at the
same Gate. It shipped late, and its absence was a real dead end - create-only apply correctly
refuses an occupied id, so a model that had written a better version of a drawing had nowhere to
put it. Markup the seal would strip is a warning on the draft, never a refusal: what somebody may
draw is their call at the Gate, not the capability's.
Size ceiling
HTMLDOC_CAP is 200,000 characters, matching the sealed preview's own ceiling. A longer document is one
whose end could never be drawn, so the decoder refuses it rather than storing something that renders
three quarters of the way and stops. The editor says so while the text is still editable rather than as
a failed save.
Fields
The table below is pasted verbatim from
docs/generated/fields.htmldoc.md; improve a schema doc comment
and regenerate rather than editing a cell here.
HtmlDocBody
| Field | Type | Producers | Meaning |
|---|---|---|---|
name |
string |
- | What this drawing is called. Every other kind titles itself from its body; so does this. |
html |
string |
- | The document, as authored. Kept verbatim - NOT sanitised on the way in. Cleaning at rest would be the wrong boundary twice over: it would make the stored file differ from what was written, so a person editing it sees their own text mangled, and it would put a second sanitiser in the app that could drift from the one that actually renders. The seal is at the point of DRAWING, which is the only place it can be enforced, so that is where it lives. |
summary? |
string |
- | A sentence about what this draws, for a list that would otherwise be a wall of filenames. |
tags? |
string[] |
- | Free-form labels, so a person can group drawings without a folder. |
notes? |
string |
- | The author's own notes, same shape every other entity uses. |
See also
- architecture.md: the canonical model and the hub-and-spoke entity shape.
- ui.md: the Workbench editor, HTML View, and the agent transcript's preview.
- security/safe-rendering.md: the seal every drawing is drawn through.