Plank help · updated 2026-10-28

Your home page

The editable area under the greeting on Plank's home screen — what it can show, how to ask the agent to change it, whether it is yours alone or the whole space's, and how to get the standard one back.

Agents: fetch the raw markdown of this page at /en/help/home-page.md

Your home page

Plank opens on Home. The greeting and the message box at the top are Plank's own and cannot be changed. Everything below them is one HTML file, and an agent can rewrite it — that area is the home page this topic is about.

Ask first: theirs, or the whole space's?

This is the first question, not a detail to settle later. It cannot be taken back from the other side.

PersonalThe space's
Who sees itonly them, in that one spaceeveryone in that space, on /<slug>
Where it livesPlank's own storage, one page per person per spacethe ordinary file /spaces/<slug>/.plank/home.html
How you change itplank_home_set with a file path and the spacewrite the file like any other

Both are per space. A personal page changes the Home of the space it was written for and no other — their other spaces keep their own Home, and the Home on / (all spaces) stays the standard one. «Change my home page» said in the journal space means the journal space's Home.

Personal wins, within its space. Somebody with a personal page for a space never sees that space's shared page. Two consequences, and both have bitten people:

  • Writing a shared page for a person who already has a personal one changes nothing they can see. They will report that you did nothing.
  • Writing a personal page «just to try it out» quietly hides the shared one a colleague made. It is still there; they simply stop seeing it.

If the person says «our», «for the team», «for everyone» — that is the space's file. If they say «my», «for me», «I want to see» — that is plank_home_set. If you cannot tell, ask.

Writing one

plank_home_set({ filePath: "/spaces/acme/.plank/home-draft.html", space: "acme" })
  • space is the slug of the space they are asking in — the folder under /spaces/ this chat works in. It is required: there is no default, because guessing is how one space's page once became the Home of all of them.
  • The path is absolute, inside the container, and the file must be .html.
  • Write the file first and pass its path. Never paste the page into the call. The tool costs the same whatever the file's size, and typing a page out costs roughly a minute per 6 KB — a 12 KB page pasted into a tool call is two minutes of the person waiting for nothing.
  • The page must be self-contained: no external stylesheet, no external script, no absolute URL. It renders in a sandboxed frame with its own origin.
  • The frame is as tall as the page — the Home screen scrolls, the page does not. So size nothing by the viewport: 100vh grows with the frame (Plank detects it and falls back to a scrolling box), and position: fixed or sticky pins to the page, not the screen. A wide table keeps its sideways scroll.
  • Maximum 512 KB. An empty file is refused — an empty home page is a blank screen.
  • Start from the standard page (below) rather than from nothing.

For the space's page, write /spaces/<slug>/.plank/home.html with the ordinary file tools. Same rules about being self-contained; the size limit is the file system's.

What the page can show

Four read-only feeds hang off the frame's own signed base, so a bare fetch reaches them with no credential and no setup:

const rows = await fetch("me/pins").then((r) => r.json()).then((b) => b.data);

Every one of them answers {data: …}, is about the person looking at the page, and reaches across every workspace they belong to — not just the one they happen to be in.

PathAnswersCap
me/attention{sessionId, workspaceId, title, reason, endedAt}[] — the chats waiting on them; reason is waiting_on_user or failed_unseen50
me/chats?limit=N{sessionId, workspaceId, title, updatedAt}[] — their most recent chats20, max 50
me/pins{workspaceId, workspaceSlug, vmPath, entryType, sortOrder}[] — their pinned files and tables200
me/schedules/today{id, workspaceId, name, timezone, nextRunAt, lastRunAt, lastRunStatus}[]50
me/home{html, updatedAt} or null — their personal home page for the space the page is open in512 KB
me/agent-decisions{kind, decisionId, agentId, agentName, workspaceId, title, dueAt, createdAt}[] — questions their agents are waiting on them to answer; kind is always agent_decision50

Two nulls are values, not bugs:

  • me/home is null for anybody with no personal page in that space, which is everybody on their first day.
  • An attention row's title is null when that chat belongs to somebody else and is private. Show your own wording — «Чат», «Без названия», anything. Never render the word «null».

An unknown me/… path answers 400, and so does any verb other than GET.

The space's app-data tables are reachable from the same base — fetch("apps/<table>"). A Home page opens every time the person lands, so ask for only what it shows: apps/journal_entries?orderBy= + encodeURIComponent(JSON.stringify({column: "date", direction: "desc"})) + &limit=7 returns the newest seven rows instead of the whole table. Parameters and refusals: App data.

What the page can do

// Open one of their pinned dashboards. Pass BOTH halves from the same row —
// me/pins spans workspaces, and the path alone opens the wrong space or nothing.
// On the desktop a Ctrl/Cmd-click or a middle click on the tile opens it in a
// NEW TAB by itself; newTab: true asks for one on a plain click too
// (ignored on the phone, which has no tabs).
window.parent.postMessage(
  { type: "plank:open-file", path: pin.vmPath, workspaceId: pin.workspaceId, newTab: false },
  "*",
);

// Open one of their chats.
window.parent.postMessage({ type: "plank:open-chat", sessionId: row.sessionId }, "*");

// Send them to a section of the app.
window.parent.postMessage({ type: "plank:open-surface", name: "schedules" }, "*");

// Put a sentence in the message box for them to finish.
window.parent.postMessage({ type: "plank:insert-chat-text", text: "Измени мою главную: " }, "*");

// Or send a whole request as its own new chat.
window.parent.postMessage({ type: "plank:send-chat-message", text: "Собери отчёт за неделю" }, "*");

Two facts that save a debugging session:

  • A page the person has navigated away from is ignored. It keeps running — its timers fire, its fetches resolve — and Plank drops every message on that list from it. Only the page on screen can act on their behalf.
  • plank:open-file needs the workspaceId from the same me/pins row as the path. Naming one workspace and pathing into another is refused outright.

Everything else a dashboard can do — running workspace scripts with @plank-button, multi-page navigation, images — is in Interactive HTML dashboards. This page does not repeat it.

The standard page

The page a person sees before anyone has changed anything shows:

  • «Сегодня по расписанию» — today's runs, from me/schedules/today.
  • «Ваши дашборды» — their pinned HTML files and tables, from me/pins.
  • «Настроить главную» — a button that puts «Измени мою главную: » in the message box.

A block with nothing in it is not drawn at all. A new person with no schedules and no pins sees one button, and that is the intended first impression rather than three empty headings.

Use it as your starting copy. Ask the person to open Home and tell you what it renders, or write a page with those same three blocks — a card list for the schedules, a tile grid for the pins, and the button.

Theme

Plank tells the page which theme to draw in, and tells it again when the person changes it while the page is open:

window.addEventListener("message", (e) => {
  if (e.data && e.data.type === "plank:set-theme") {
    document.documentElement.setAttribute("data-theme", e.data.theme === "dark" ? "dark" : "light");
  }
});

Do not assume light, and do not rely on prefers-color-scheme alone: it reads the operating system, which can disagree with the theme the person set in Plank. Use it as the initial guess and let the message win.

Getting the standard one back

  • Personal page: call plank_home_set again, for the same space, with a file containing the standard page. There is no delete and no version history — set replaces. Other spaces are untouched either way.
  • The space's page: delete /spaces/<slug>/.plank/home.html. The personal pages of anybody who has one are untouched.

And the escape hatch that needs no agent at all: if a page fails to load, Plank draws the standard one and shows a quiet «Ваша главная не загрузилась · Показать стандартную» line above it. Nothing is deleted. A person who has locked themselves out of their own home page is one tap from a working screen.

Things that will bite you

  • An empty file is refused. An empty home page is a blank screen, not a clean one.
  • Over 512 KB is refused. The fix is to fetch the data rather than inline it — that is what the four feeds are for.
  • There is no version history. set replaces, so keep the file you wrote in the workspace if the person may want it back.
  • Personal wins over the space's — in that space only. Re-read the first section before writing the file, not after.
  • The page has its own origin. localStorage works but is per-person and per-device, and nothing in it reaches Plank or another viewer.