Studio
The web editor — a three-panel workspace where you (and an AI agent) edit your docs on a branch and publish as a commit or pull request.
The web editor — a three-panel workspace where you (and an AI agent) edit your docs on a branch and publish as a commit or pull request.
The editor is a three-panel workspace for editing a docs site directly in the dashboard:
an AI editing agent, the navigation tree, and a multi-modal MDX editor. It opens at
/:org/:site/editor (the Editor item in the rail, and Open editor on the site
overview).
The editor and the authoring MCP are the same write path, not two parallel ones — both edit one server-side draft buffer on a working branch and publish through one backend. Whatever the agent does, you can do by hand, and vice versa.
A read/write assistant. It can search and read your docs and edit them — ask it to “rewrite the intro” or “add an FAQ entry” and it writes to the same draft you see. When it finishes an edit, the editor pane refreshes to show the change. It never publishes unless you ask.
The agent panel is hidden by default so the page has room to write; open it with the Ask agent button (or ⌘I / Ctrl I) and close it from the button, the panel’s ✕, or the same shortcut. Your conversation is kept while it’s closed.
Your docs.json navigation, rendered from the draft of the current branch. Click any
page to load it into the editor.
Each group carries two controls — a + to add to it, and a cog that opens its settings (a page’s cog opens that page’s frontmatter). The + offers:
Everything here writes to your draft, so a new page and its navigation entry publish together as one change — and revert together too.
Drag to reorganize. Each row has a grip: drag a page to reorder it inside its group or move it to another one, and drag a group to move it among its siblings. Drop a page onto a group’s own row to put it in a group that has no pages yet. The grip is a button, so this works from the keyboard too — focus it, press Space, move with the arrows, and press Space again to drop.
Edit the page in Visual mode (WYSIWYG — the page rendered, editable in place) or Source mode (raw MDX), and use Diff to compare your draft against what’s published. Edits autosave to the draft buffer, and your chosen view stays put as you move between pages. Switching pages also saves your in-progress edit first, so a quick click away never loses what you just typed. Pages can be edited together, in real time — see Real-time collaboration.
Three side-by-side panels don’t fit on a phone, so below roughly 1024px the two side panels become overlays instead of columns: the editor keeps the full width, and Navigation, Files, or Ask agent slide in over it. Dismiss an overlay by tapping outside it, by its ✕, or — for the navigation — just by picking a page, which closes it so you can see what you opened.
Nothing is hidden at narrow widths; the panels are the same panels, they just take turns. At desktop widths the layout is unchanged.
Controls that a mouse reveals by hovering — a page’s settings cog in the navigation, the revert icon beside a changed file in the Publish panel, a tab’s drag grip — are always visible on a touch device, where there’s no hover to reveal them. Nothing in Studio is reachable only by hovering.
Visual mode renders your real components, so the links in a page are real links — a markdown
link, or the href on a <Card>. Clicking one moves the editor to that page, the same as
clicking it in the navigation tree: your unsaved edit is written to the draft first, and you stay
in the editor.
Links are resolved against your site, not the dashboard, so /quickstart,
../guides/components, and /quickstart#install all land on the page a reader would get:
Loads in the editor. Root-absolute and relative paths both work, and a #fragment, a query
string, or a trailing .mdx is ignored when matching the page.
An https://, mailto:, or protocol-relative link opens in a new tab, so your editing
session is never replaced.
Nothing is followed — you get a “No page /… in this site” message. That’s a broken link in your docs, surfaced while you’re writing rather than after you publish.
A <Card href=…> wraps its body text in the link. Clicking that text puts the cursor in
it so you can edit; clicking anywhere else on the card follows the link.
A <Tabs> block in Visual mode is a working tab strip, not a preview of one. The bar sits
above the content and the tab you select is the one you edit:
Click a tab to show its content. Only the selected tab’s body is on screen, so you edit one pane at a time — the same way readers see it.
Double-click a tab’s label, type, and press Enter. That writes the title
attribute on the <Tab>.
+ at the right end of the bar appends a new empty tab and selects it. The ✕ on the
selected tab removes it — it’s hidden on the last remaining tab, since a <Tabs> with
nothing in it would vanish.
Each tab has a grip above it — drag that along the bar to move the tab. Its content moves with it, and the order you see is the order in the MDX. The grip is a button, so this works from the keyboard too: focus it, press Space, move with the arrows, and press Space again to drop.
Every one of these is a normal edit: it round-trips to MDX, autosaves to the draft buffer, and ⌘Z undoes it. Which tab is showing is not — that’s just what you’re looking at, so it isn’t saved and other people editing the page keep their own selection.
MDX has two ways to write a tab, and only one of them is editable as a strip. A <Tab> whose
body sits on the same line — <Tab title="npm">`npm install`</Tab> — is parsed by MDX
as inline content rather than a block, so Studio shows that block as labelled chrome with its
source intact instead of an empty bar. Put a blank line around each tab’s body and it becomes a
strip:
<Tabs>
<Tab title="npm">
`npm install`
</Tab>
</Tabs>
Either form renders identically for readers. Anything you insert from the / menu already uses
the editable form.
/ menuType / anywhere in Visual mode to insert a block. Filter by typing, move with
↑ / ↓, insert with Enter, and dismiss with Esc —
which leaves what you typed in place, so nothing is lost if you change your mind.
Anywhere means anywhere: the same menu, with the same blocks, opens inside a component — a tab pane, a callout, a card, a step — and inserts into whichever one holds the cursor.
Two commands under Media in the / menu:
For a video your site serves. Give it a path (/videos/demo.mp4) or a direct URL, and the
player appears in the page — playable right there in Studio, not a placeholder.
For YouTube, Loom, Vimeo, or any other iframe. Paste the URL from your address bar — the
editor converts it to the provider’s embeddable form, because a youtube.com/watch?v=… link
won’t play in a frame. A ?t= timestamp is carried across.
/embed asks for a URL and nothing else — the file lives on YouTube, so there’s nothing to
browse. It names the provider it recognised before you commit, and Add stays disabled until
the link is one that can be inserted.
/video and /image open a picker instead, because those files belong to your site:
Everything the site holds of that kind, searchable by filename — including anything you uploaded earlier in this editing session but haven’t published yet.
Upload takes a file from your computer and puts it in your site’s storage. Videos are
MP4 or WebM up to 200MB; images are PNG, JPEG, GIF, WebP, AVIF or SVG up to 20MB. It lands
in videos/ or images/, named after the file you chose (lower-cased and hyphenated,
because it becomes part of a public URL). Uploading a name that’s already taken adds a
number rather than overwriting the existing file.
An upload is an edit like any other: it shows in the Publish panel’s change list, can be reverted from there on its own, and goes live only when you publish. Until then it plays for you in Studio and is invisible to readers.
Where the file ends up depends on the kind of site. A Papervine-hosted site writes it straight to your site’s storage. A Git-backed site commits it to your repository, so the file arrives alongside your MDX and is yours — the same as if you’d added it by hand.
Both insert plain HTML rather than a Papervine-specific component — see Video and embeds — so the page stays portable, and video written by hand or by another tool shows up as a real player here too.
One form deliberately shows its source instead of a player: a <video> with a <source> list
has no single file to preview, so Studio shows the markup rather than guessing which encoding
you meant. It renders normally for readers.
Self-hosting? Uploads go from the browser straight to object storage, so the bucket needs
CORS allowing PUT from the host Studio runs on. The local MinIO in docker compose
allows any origin already, so development needs nothing; a production bucket usually does.
Without it an upload fails with a CORS error and the dialog says so.
A <Steps> block ends its rail with a + button, in the place the next number would go.
Click it and a step is appended with the cursor in its title, because that’s where a step
starts. Press Enter to drop into the body and keep typing.
Each step is two separate slots:
The heading beside the number. It’s a field, so click it and type — in a step you haven’t
named yet it shows a muted Step title prompt. Leave it blank and nothing is written to
your MDX: an untitled step stays <Step>, never <Step title="">.
Everything below the title — ordinary page content, so paragraphs, code blocks, callouts and
the / menu all work inside a step.
The numbers renumber themselves as you add, and they’re never written into your MDX — they’re counted at render time, so reordering steps in Source mode needs no renumbering either. ⌘Z takes a step back out.
/task inserts a checklist. Click a box to tick it, or press ⌘Enter /
Ctrl Enter to toggle the item your cursor is in. A checked item is struck through.
It’s ordinary Markdown underneath (- [ ] todo, - [x] done), which means one list can mix
checked items with plain bullets, exactly as Markdown allows, and a checklist written by hand or
by another tool opens here with its state intact.
Empty a tab and keep pressing Backspace and nothing further happens — it won’t reach back out and take the tab with it. The same at the other end: Delete at the end of a component won’t pull the next block into it. This holds for every component, not just tabs.
Removing a component is something you do deliberately: the ✕ on a tab, or Delete in the block handle’s menu. Selecting content and deleting it still works exactly as you’d expect — the stop only applies to the single keypress that would otherwise escape the component.
Formatting inside the component is not part of that stop. With the cursor at the start of a list that opens a component — including the first checkbox of a checklist — Backspace drops the list formatting the way it does anywhere else, leaving a plain paragraph in place. It’s the press after that one, with nothing left to unwrap, that does nothing.
⌘A / Ctrl A selects the component you’re inside, not the whole page.
With the cursor in a tab it highlights that tab’s text and brings up the formatting bar, so you
can bold or link the whole pane in one gesture. Press it again to widen a level — the <Tabs>
block, then any component around that, and finally the whole page. Nothing is taken away; the
document-wide selection is just no longer the first thing that happens.
This matters most in tabs, where the tabs you aren’t looking at are off screen: selecting the
whole document and typing would replace content you can’t see. It applies to every component
though — a <Note>, a <Card>, a <Step> — and in ordinary body text, where there’s no
component to scope to, the first press selects the page as it always did.
The Preview tab renders the page through the same renderer that ships your docs — the same
MDX compilation, the same components (<Card>, <Tabs>, <Frame>, <HeroCard>…), the same
theme. So what you see in Preview is exactly what readers get: what you see is what ships.
Editing happens in Source (raw MDX), and that MDX string is exactly what gets committed — no conversion to a block model, no second source of truth. Git stays canonical. Preview reads your current draft, so unsaved-but-buffered edits appear the moment you switch to it.
The Preview tab shows the page you’re editing. The Preview button in the editor’s toolbar opens the entire site — navbar, tabs, sidebar, search — rendered from your draft, so you can click around and check the things a single page can’t show you: navigation order, how groups and tabs read, whether links between pages land where you meant.
It opens as a full-screen overlay on the page you’re editing, not in a second tab. Press Esc or the ✕ to go straight back to where you were writing. Along the top: Site settings, Ask agent (which closes the preview and opens the agent), and a reload for when you want to see a change you made in another window.
Your pending edit is saved before the preview opens, so the last thing you typed is in it — there’s no need to pause and wait for the draft to save first.
It’s your draft, not your published site, so nobody else sees it and nothing is live until you Publish. Gated pages are visible to you here even if you don’t hold the reader groups they require — you’re previewing your own site.
Branches and pull requests are Git-backed features. On a Papervine-hosted site there’s no repository, so Studio shows no branch switcher and Publish is a single action straight to your live site. Everything else on this page — the draft buffer, the change list, per-file revert, discard, the agent — works identically on both kinds of site.
The editor opens on your site’s deploy branch — the one marked Default in the branch switcher (top left). You edit it directly; the switcher also lists your open edit sessions and can create a new branch for changes you’d rather land through review.
Wherever you edit, your changes buffer server-side per branch — they never touch the live branch until you publish. Buffered edits persist across tabs, devices, and reloads, and the agent and you share that one buffer. A clean session that you haven’t typed into yet creates no branch and shows the live content as-is.
Nothing is live until you publish.
The Publish button carries the draft to Git. Its action follows the branch you’re on, and the caret menu lets you pick either mode explicitly:
Publish commits your edits straight onto the deploy branch. The normal sync then redeploys the site. This is the default when you’re editing the Default branch.
Publish commits to the working branch and opens a PR into the deploy branch — review before it goes live. Use Create new branch in the switcher when you want this flow.
On a Papervine-hosted site there is no repository and therefore no third option: Publish writes your draft straight to the live site, and the caret menu shows only the change list, per-file revert, and discard.
The outcome — a pull-request link, a commit hash, a plain confirmation, or an error — appears as a toast that clears itself (and that you can dismiss).
If the deploy branch moved since you started editing, Publish stops with a conflict rather than clobbering it — re-check-out to pick up the latest, then publish again.
The editor is gated behind the editor.workspace feature (admin-only while it’s dogfooded).
Preview renders your draft through the real renderer on the fly; publishing surfaces the change
through the normal deploy (or the PR).