@wahengchang2023/tedit
v0.0.10
Published
Local visual template editor: design in browser, render PNG headlessly via CLI
Readme
tedit
tedit is a local, template-driven image generator. Design a layout once in a browser editor, bind the parts that change to named variables, then render the same design into PNGs over and over by swapping a small YAML/JSON data file.
It runs entirely on your machine — the editor is a local server, the renderer is a headless browser, and your projects are just folders. No accounts, no cloud, no upload.
Think of one template as a reusable "frame," and each data file as the content poured into it: market snapshots, quote cards, event banners, product shots — same layout, different content, every time.
The big picture
┌──────────────────────────────────────────────────────────────┐
│ Browser editor (tedit ui) — dark, Figma-style canvas │
│ Design freely → bind layers to named variables → save │
└──────────────────────────────┬───────────────────────────────┘
│ save
▼
┌──────────────────────────────┐
│ template.json │ ← single source of truth
│ canvas + layers + variable │
│ bindings (one JSON tree) │
└──────────────┬───────────────┘
│
tedit render + data.yaml (fills the variables)
▼
┌──────────────────────────────┐
│ out.png │ ← swap data = new content,
└──────────────────────────────┘ identical layoutThe core guarantee: what you see in the editor is exactly what the CLI renders — pixel for pixel. Both load the same engine bundle and run the same Chromium, so the editor preview and the headless render can't drift apart.
Built for human-in-the-loop AI workflows
tedit is designed for the part of an AI pipeline where a human stays in the loop. The editor and the file format are two views of the same thing, so a person and an agent can work on one template without the result drifting apart.
- Everything is JSON — an agent can do everything a human can. The template, every layer (text, image, shape, and full HTML/CSS/SVG), and all variable bindings are plain JSON. Anything you'd do by hand in the editor — produce a layout, add an HTML layer, rebind a variable — an LLM can do by emitting or editing JSON. No GUI automation, no screen-scraping.
- One artifact, edited by both. Humans tweak
template.jsonvisually; agents generate or modify it programmatically; both operate on the same file. The visual editor is where a human reviews and corrects what the AI produced. - Deterministic, so review actually means something. The editor preview and the CLI render are pixel-identical (same engine bundle, same Chromium). Unlike diffusion image models, the same input always yields the same image — exact text, exact layout, every run. What a human approves in the editor is exactly what ships from the CLI.
A typical loop: an agent drafts template.json (layout + HTML layers + bindings) → a human reviews and adjusts in tedit ui → the pipeline renders many PNGs by swapping data files, every one matching what the human signed off on.

Because an HTML layer is just markup, it's a natural target for an LLM — generate a gradient, a chart, or a badge as <html>, drop it in as a layer, and it renders inside the same canvas:

See it
The editor: layers on the left, canvas in the middle, properties + variable binding on the right. Selected layers show their {variable} tag on canvas.

Layers can also be raw HTML/CSS/SVG, edited live with a code panel — useful for gradients, charts, badges, and anything easier to express in markup than to drag by hand.

Then the last step is one command — turn that design into a PNG from the terminal:
tedit render ./my-card data.yaml -o out.pngThe output is the same canvas you see above, rendered pixel for pixel.
Why tedit?
- Visual first — compose templates in a local editor instead of hand-writing coordinates.
- Data driven — bind layers like
title,photo, orpriceand replace them at render time. - Repeatable output — render a batch of PNGs from one template by looping over data files.
- Rich layers — text, images, shapes, and full HTML/CSS/SVG layers in the same canvas.
- Local by default — projects are plain folders; assets live next to the template.
- Editor/CLI parity — the editor and the headless renderer share one engine and one Chromium, so what you design is what you render.
Installation
npm install -g @wahengchang2023/tedit
npx playwright install chromium # Chromium used for headless renderingThe package installs a tedit command on your PATH.
Requires Node.js 20 or newer. Prefer not to install globally? Use
npx @wahengchang2023/tedit ...in any command below.
Quick start
Create a project folder and open the editor:
mkdir my-card
cd my-card
tedit ui .In the editor:
- Add text, image, shape, or HTML layers.
- Select a layer and bind an editable property to a variable name such as
titleorphoto. - Save. tedit writes
template.jsoninto the project folder.
Create a data file:
# data.yaml
title: Hello from tedit
photo: images/photo.pngRender a PNG:
tedit render . data.yaml -o out.pngYour generated image is written to out.png.
Try the bundled showcase
From a cloned checkout you can run complete example projects (crypto snapshot, quote card, event banner):
npm install
npm run build
npx playwright install chromium
npm run ui:demo # open the demo card in the editorRender a showcase template with sample data:
npm run tedit -- render examples/showcase/crypto examples/showcase/crypto/crypto-btc.yaml -o examples/showcase/out/btc.pngCLI reference
<project> can be a project folder or a direct path to its template.json.
| Command | Description |
| --- | --- |
| tedit ui [<project>] [--port <n>] [--no-open] | Start the local editor server and open the browser. |
| tedit vars <project> [--json] | List the variables a template defines. |
| tedit render <project> [<data>] [-o <out.png>] [--scale <n>] [--strict] | Render a template to PNG with optional JSON/YAML data. |
tedit ui ./my-card # open a project
tedit vars ./my-card # list variables
tedit vars ./my-card --json # variables as JSON
tedit render ./my-card ./my-card/data.yaml -o out.png # render with data
tedit render ./my-card ./my-card/data.yaml -o [email protected] --scale 2 # 2x scale
tedit render ./my-card ./my-card/data.yaml -o out.png --strict # fail on missing varsRender behavior
- No data file → renders with the design-time values stored in the template.
- A missing variable → falls back to its design-time value and prints a warning.
--strict→ a missing variable fails the render (exit code4).- On success,
renderprints only the absolute output path to stdout, so scripts can capture it.
Exit codes
| Code | Meaning |
| --- | --- |
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments |
| 3 | Invalid template |
| 4 | Missing variable under --strict |
| 5 | Asset loading failure |
How a variable becomes pixels
data.yaml template.json
title: "New title" ─┐ bindings: [{ var: title, element: txt1, prop: content }]
photo: ./a.png │ │
▼ ▼
┌───────────────────────────────────────┐
│ resolver (pure function, no I/O) │
│ writes variable values into the scene │ missing → design value + warning
│ remaps image paths to project-relative │ --strict missing → exit 4
└────────────────────┬──────────────────┘
▼ resolved scene
┌───────────────────────────────────────┐
│ headless (Playwright + Chromium) │
│ load engine bundle → inject scene │ waits for fonts + image decode
│ → wait for render-ready → screenshot │ locks deviceScaleFactor (--scale)
└────────────────────┬──────────────────┘
▼
out.pngProject structure
A tedit project is one folder with one template:
my-card/
├── template.json # required template, created by the editor
├── data.yaml # optional render data
├── data.json # optional render data
├── images/ # image assets referenced by the template or data
├── fonts/ # optional custom fonts
├── project.json # optional canvas defaults and font registry
└── .tedit/history/ # timestamped template backups created on saveImages resolve relative to the project folder — photo: images/photo.png loads my-card/images/photo.png. template.json and project.json are reserved names.
Data files
tedit accepts YAML or JSON. Keys match the variables you created in the editor.
title: Summer Launch
subtitle: New templates in seconds
photo: images/launch.png{
"title": "Summer Launch",
"subtitle": "New templates in seconds",
"photo": "images/launch.png"
}Run tedit vars <project> when you're unsure which keys a template expects.
Fonts
Projects can use built-in Noto Sans TC plus optional custom fonts registered from the project fonts/ directory. If a required font can't be loaded, rendering exits with code 5 instead of silently falling back to a different font.
Recommended workflow
- Design —
tedit ui ./project, then save the template. - Inspect —
tedit vars ./projectto confirm the public variable names. - Render once —
tedit render ./project data.yaml -o out.png. - Automate — loop over many data files to generate a batch.
for data in ./project/data/*.yaml; do
name="$(basename "$data" .yaml)"
tedit render ./project "$data" -o "./project/out/$name.png"
doneLearn more
docs/OVERVIEW-VISUAL.md— visual walkthrough of the workflow and how it fits together.docs/SPEC-CLI-AND-FILES.md— full CLI and project-file reference.docs/SPEC-SCENE-SCHEMA.md— thetemplate.jsonschema in detail.docs/USE-CASES.md— example use cases and ideas.docs/ARCHITECTURE.md— how tedit is built, for contributors.
