Guide

The table HTML generator with a real git undo stack

Tables iterate. Add a column. Restripe. Make the headers sticky. Undo the sticky headers. mk0r treats every one of those as a real git commit inside the sandbox, with an undo that does git checkout to the previous SHA and a redo branch that truncates the moment you edit on top of it. Just like the git you already know.

Generate a table now
m
mk0r
9 min
4.8from 10K+ creators
Per-turn git history
Undo by SHA, not by guess
No signup, no setup

What every other table generator gets wrong about iteration

I opened the five most-cited table builders for this topic and tried the same flow on each: add a column, change the styling, decide I preferred the previous styling, hit undo. On every single one of them, undo went exactly one step deep and lived only in the current browser tab. Reload the page and the undo button greys out. Build six versions of the table and you can never get back to version four. The interaction model is a single in-memory checkpoint and a copy button.

For a static one-off table that is fine. For a table that is going to keep changing because real tables always keep changing, it is the wrong shape. You end up keeping a Notes app open on the side, pasting screenshots of each version, and reasoning about your own history out of band.

mk0r's answer is to push that history into a git repo on disk inside the sandbox, and to give the agent a small set of operations (commit, undo, redo, revert-to-sha) that map to the same primitives you would use from a real terminal. The historyStack is just an ordered list of SHAs the UI walks; the truth is the repo.

What touches the repo when you ask for a table

A single prompt does not just hit a model. It fans out to a Vite dev server, a real git working tree, and a Firestore document that mirrors the timeline so the UI can show it before the VM has even finished resuming.

From your prompt to a versioned commit

Your prompt
Existing files
historyStack
agent turn
git commit
Vite HMR
Firestore mirror

The destination on the right that no other table generator has is the commit. The other two destinations are how you see the result; the commit is how you walk back through it later.

The anchor: how the redo branch gets truncated

This is the part the marketing pages cannot copy because it is wired into the session manager, not the prompt. When commitTurn fires after one or more undos, it dumps the future the moment a new edit lands. Same shape as native git: a fork stops the moment you commit.

src/core/e2b.ts (excerpt)

And here is the undo itself. No model call. No re-prompting. Just a git checkout to the previous SHA, then an empty commit so the undo is itself versioned.

src/core/e2b.ts (excerpt)

The function returns the new SHA so the UI can paint the timeline in real time. activeIndex moves back by one. The Firestore mirror updates so a refresh would show the same state. The actual git checkout inside the sandbox happens in revertToSha around line 1644 of the same file: it runs git checkout <sha> -- . && git add -A && git commit --allow-empty -m 'Undo to <sha>' so the working tree is byte-for-byte the previous version.

The route map for the timeline operations

Three thin API routes drive the whole thing from the chat UI. They all go through the same session manager, so the historyStack and activeIndex stay coherent regardless of which one you call.

undo, redo, revert-to-sha

Chat UIAPI routesessionVM /app/.gitPOST /api/chat/undo { sessionKey }undoTurn(sessionKey)git checkout <prevSha> -- .git commit --allow-emptynewSha, activeIndex--{ newSha }POST /api/chat/revert { sha }revertToSha(sessionKey, sha)git checkout <sha> -- .git commit --allow-emptyFirestore mirror updated

What a real iteration session looks like

Below is a faithful trace of a five-turn session building a table of European cities. Note the SHAs on the left of each commit line. That is the timeline you can walk later.

agent timeline for a cities table

What the agent actually wrote on commit one

Not pseudo-code. Not "here is roughly what the table looks like." This is the file the agent committed at SHA 9f2a1c3 in the trace above, the kind of output a prompt like "table of 30 european cities with population, country, timezone" produces.

/app/src/components/CitiesTable.tsx

The seed rows get inferred. Population is a number, so the sort handler does numeric comparison. Country is a string, so it does localeCompare. tabular-nums on the population cell so digits line up. Each of the next four turns in the trace is a diff against this file, committed in order.

Static table tools vs. mk0r's undo stack

FeatureStatic table HTML generatormk0r
Undo depth1 step, JavaScript state onlyUnlimited, every turn is a real git commit
Survives a reloadNo, undo state is in memoryYes, historyStack persists in Firestore + the repo
Undo as model calln/aNo. Undo is git checkout, deterministic, sub-second
Restore by SHANo SHA existsClick any prior turn, /api/chat/revert restores it
Branch the timelinen/aEdit on top of an undo, redo branch is truncated, just like git
Output shapeHTML + CSS stringLive React table on Vite, or single-file HTML on demand
Signup requiredOften yes (to save your work)No, history works anonymously from turn one

How to walk a long timeline back without losing your place

The most common move on a table that has gone through 10 to 20 iterations: scroll the chat history, find the version that was actually right, restore it, then keep editing. Here is the flow.

1

Scroll the assistant messages

Each one carries the short SHA of the commit that ended that turn. The active commit is highlighted; everything below it is part of the redo branch and shown muted.

2

Click 'restore this version' on the turn you want

The UI calls POST /api/chat/revert with the SHA. revertToSha runs git checkout <sha> -- . inside the sandbox, then git commit --allow-empty -m 'Restore <sha>' so the move is itself a turn.

3

Confirm the preview is the version you remember

Vite HMR repaints the iframe in place, no hard reload. Sort, filter, and scroll position are preserved if you had any state at the moment of the checkout.

4

Edit on top of the restored turn

commitTurn truncates the redo branch (everything above activeIndex) and pushes the new SHA. Same shape as if you had never done the original turns; the abandoned SHAs still exist in the .git reflog if you ever ssh in, but the in-app redo arrow goes dim.

5

Build forward without re-pasting state

Because every turn is a real commit, you can fork from any past point any number of times and the Firestore mirror keeps the active branch consistent across reloads and across devices once you sign in.

The numbers that drive the timeline

Round-trip and depth metrics from the actual runtime. None of these are theoretical maxes; they are the constants the session manager and the sandbox template are wired with right now.

0+Undo depth
0Default git timeout (ms) for revert
0VM exec timeout (ms)
0Signup steps before turn one
0 commit

per chat turn that changes files. Undo and revert are themselves commits, so the trail is forensic.

0 chars

short SHA shown beside each turn in the chat. The same format you would use in a real git log oneline.

0 models

called by undo. It is git, not a re-prompt, so the previous file comes back byte for byte every time.

What the timeline gives you that a copy-paste tool cannot

versioned table edits

  • Walk back through every column you ever added, in the order you added it
  • Restore any past version with one click, no model call, no re-paste
  • Edit on top of an undo and have the redo branch truncate, just like git
  • Survive a browser reload because the timeline lives in Firestore + the repo, not in memory
  • Per-turn diffs the agent itself can read on follow-up prompts
  • Anonymous out of the box, signed-in only when you want cross-device persistence

When you do not want any of this

Plenty of people land on this topic because they want a one-off table to drop into a CMS block, an invoice, a MailChimp template, an old Wordpress editor. That is a legitimate use case, and the git history would be overkill. Ask for:

"Give me a single self-contained HTML file with inline styles for a 5-row pricing table. No JavaScript, nothing to install. I just want to copy and paste it into my site."

You get exactly that. The first turn writes the .html file, you copy it out of the preview, and you walk away. The historyStack still records the commit, but you never have to interact with it. The trade you make: no Vite preview, no per-column iteration loop, no Tailwind. Pick the shape that fits the job.

Build a table with an undo stack you can trust

Want to see the timeline UI live?

Book 20 minutes with the team and we will walk through how the historyStack drives the chat, with a real session in front of you.

Book a call

Frequently asked questions

Frequently asked questions

What does mk0r mean by 'every column is a git turn'?

After every chat turn that touches files, the agent runs git add and git commit inside the sandbox at /app. The function is commitTurn in src/core/e2b.ts at line 1588. The new SHA gets pushed onto a per-session historyStack with an activeIndex pointer. So 'add a sortable Price column' is one commit, 'make every header sticky on scroll' is another, 'undo the sticky headers' is a third commit (a revert one), and so on. There is no batching of turns into a single edit and no client-only state. The history is the repo, not the UI.

How is 'undo' different here than in a normal table generator?

Generators like tablesgenerator.com or divtable.com keep your changes in JavaScript memory. Their undo is one step deep, lives only in that browser tab, and ends the moment you reload. mk0r's undo calls /api/chat/undo which triggers undoTurn in e2b.ts. Internally that runs git checkout <prevSha> -- . then git commit --allow-empty -m 'Undo to <sha>' so the undo itself becomes a commit. You can walk back through every column you ever added, find the version where the table looked right, and branch from there.

What happens if I undo three times and then add a new column?

The redo branch gets truncated. Look at e2b.ts line 1629: when commitTurn fires after one or more undos, it runs session.historyStack.slice(0, session.activeIndex + 1) before pushing the new SHA. Same shape as native git: as soon as you commit on top of an undo, the abandoned future is dropped from the stack. If you wanted that future, the underlying SHAs still exist in the repo's reflog, but the in-app redo arrow goes dim because it has nowhere to point.

Where does the table itself live, in HTML or in React?

By default the agent scaffolds a React component at /app/src/components/ on the running Vite dev server, port 5173, with Tailwind v4 styling. That is what the live preview iframe is showing. If you want a single-file HTML table you can paste into a CMS or an email template, ask for one and the agent emits a self-contained .html with inline styles, no JavaScript required. Both shapes get committed the same way.

Can I see the SHAs in the chat UI, or is the git history hidden?

The history is exposed. Every chat turn shows a short SHA next to the assistant message; the older messages get an explicit 'restore this version' affordance that calls /api/chat/revert with that SHA. Under the hood that calls revertToSha in e2b.ts at line 1644, which does the same git checkout + commit dance as undo, just targeting a specific SHA instead of the previous index.

What is the historyStack actually persisting across sessions?

An ordered list of commit SHAs and an integer activeIndex pointing at the live one. Both are stored in Firestore under the app_sessions/{sessionKey} document. When the sandbox is paused and resumed (which happens on idle to save compute), historyStack is restored alongside the rest of the session. The git repo on disk inside the VM is the source of truth, the Firestore copy is so the UI can show your timeline before the VM has finished resuming.

Why not just let the model rewrite the file for 'undo'?

Two reasons. First, models drift. 'Undo the striping' from a model is a guess at what striping was, not a deterministic restore of the previous file. git checkout is exact: byte for byte, the file that was committed comes back. Second, undo as a model call costs tokens and time; undo as a git operation is sub-second and costs nothing. Tables iterate fast, this is one of the few flows where determinism matters more than cleverness.

Do I need an account to get a table and use undo?

No. mk0r has no signup wall in front of the first session. You land on mk0r.com, type a prompt like 'table of 30 European cities with population, country, and timezone', and the sandbox boots, the table renders, and the historyStack is initialized with the first commit. Undo works from your very first turn. If you want to keep the project across devices later, you can sign in, but the git history works fine as an anonymous session.

If the agent makes a bad call mid-edit, do I lose the rest of the changes?

No, because the failed turn is its own commit candidate that simply does not get pushed onto the historyStack until the agent declares the turn done. If the file write fails or the verification step fails, commitTurn never runs for that turn. Your previous good commit is still the activeIndex. Hit retry and the agent tries again from there.

Is this overkill for a table that I just want to paste into a Wordpress block?

If you genuinely want a one-off, ask for a single-file HTML table and copy it. You will not need the undo stack and you will not be charged for the iteration loop. The git history is there for the case where the table is going to keep changing, which in practice is most tables you build for a real product.

The fastest way to understand the undo stack is to use it

Describe a table. Add a column. Restripe. Undo. Restore the version from three turns ago. Watch the redo branch dim the moment you edit on top of an undo.

Open mk0r and build a table