Skip to main content

pages-publishing

Create and publish Plane pages — sprint reports, retro notes, release notes, roadmap, meeting notes, decision logs (ADRs), specs, runbooks, and any general-purpose page as formatted HTML. Use when the user wants to publish a report, write a sprint summary, create a retro page, generate release notes, share a roadmap, document a decision, write a spec, capture meeting notes, or create any Plane page. Includes reusable HTML templates and Plane editor compatibility rules.

Source facts

Repository
Agents-Store/opencode-plugins
Last source activity
October 3, 2026 at 23:16
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
7 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
pages-publishing
description
Create and publish Plane pages — sprint reports, retro notes, release notes, roadmap, meeting notes, decision logs (ADRs), specs, runbooks, and any general-purpose page as formatted HTML. Use when the user wants to publish a report, write a sprint summary, create a retro page, generate release notes, share a roadmap, document a decision, write a spec, capture meeting notes, or create any Plane page. Includes reusable HTML templates and Plane editor compatibility rules.
# Pages Publishing Plane pages are rich HTML documents attached to a project or the workspace. Use them to publish durable artifacts: sprint reports, retros, release notes, roadmaps, ADRs, and stakeholder updates. ## Tool Name Resolution Plane MCP exposes one `page` tool and the operation goes into the `action` parameter. Resolve the real tool name (`mcp__<server>__page`) through the `connector-bootstrap` skill - never assume a server prefix. An omitted `project_id` means a workspace page. ## Available Calls | Call | Purpose | |------|---------| | `page(action=create, name, description_html)` | Create a page at the workspace level | | `page(action=create, project_id, name, description_html)` | Create a page inside a project | | `page(action=retrieve, page_id)` / `page(action=retrieve, project_id, page_id)` | Get a workspace / project page | | `page(action=list)` / `page(action=list, project_id)` | List workspace / project pages (paginated) | | `page(action=update\|archive\|delete\|set_collection\|attach_to_workitem\|detach_from_workitem)` | Edit and manage existing pages (see "Updating, Archiving and Deleting Pages") | | `release(action=list\|retrieve\|get_changelog\|update_changelog\|list_workitems)` | Source data and a home for release notes (see "Release Notes from Plane Releases") | ## HTML Formatting Rules Plane pages accept a restricted HTML subset in `description_html`. The Plane editor parses HTML through a Tiptap/ProseMirror schema — anything outside the schema is **silently transformed or stripped**. What you send is not always what you get. **Safe tags:** - Structure: `<h1>`, `<h2>`, `<h3>`, `<p>`, `<hr>` - Lists: `<ul>`, `<ol>`, `<li>` (see List Rendering Gotchas below) - Ordered list start offset: `<ol start="5">` is preserved - Emphasis: `<strong>`, `<em>`, `<code>`, `<pre>` - Tables: `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` - Links: `<a href="…">` - Hard break inside text: `<br>` (becomes a `hardBreak` node inside the surrounding paragraph) **Avoid:** inline `<style>`, `<script>`, `<iframe>`, raw markdown, any custom `data-*` attributes (see gotchas below). ## List Rendering Gotchas — Critical Plane's editor is strict about list structure. Several "valid HTML" patterns silently produce broken, disjointed, or empty-bullet output. These were verified by roundtrip testing (submit HTML → retrieve stored ProseMirror doc → compare). Follow these rules or your published page will look wrong. ### Gotcha 1 — `<li>` must start with text, not a nested list **Broken:** a `<li>` whose first child is `<ul>` or `<ol>` (no own text). ```html <!-- BROKEN: produces three disjointed top-level lists --> <ul> <li><ul><li>Orphan child</li></ul></li> <li>Normal sibling</li> </ul> ``` Stored result: **three separate top-level `bulletList` nodes** — one empty, one with the "Orphan child", one with the "Normal sibling". The outer list is torn apart at every child-first `<li>`. **Correct:** always put at least one text character in the parent `<li>` before the nested list. ```html <!-- OK: one structured list with proper nesting --> <ul> <li>Topic<ul><li>Sub-point</li></ul></li> <li>Normal sibling</li> </ul> ``` This is the root cause of "broken lists with gaps" that most users see. ### Gotcha 2 — Task lists / checkboxes via `data-checked` are silently stripped ```html <!-- Stripped: data-checked is dropped, renders as plain bullets --> <ul> <li data-checked="true">Done action</li> <li data-checked="false">Pending action</li> </ul> ``` Plane's schema does not recognize `data-checked` on `<li>` at the HTML import layer — there is no `taskList` / `taskItem` node produced. The attribute is cosmetically preserved in `description_html` but the stored ProseMirror doc is plain `bulletList`. **Correct:** use Unicode status markers as text instead. They render identically in any viewer and do not depend on editor features. ```html <ul> <li>✅ Done action</li> <li>⏳ In progress action</li> <li>⬜ Not started action</li> </ul> ``` ### Gotcha 3 — Never emit empty `<li></li>` ```html <!-- BROKEN: produces a visible empty bullet gap --> <ul> <li>Before</li> <li></li> <li>After</li> </ul> ``` An empty `<li>` is stored as a listItem with an empty paragraph and renders as an empty bulleted line. When a template placeholder like `{{NOT_DELIVERED_ITEMS}}` has no data, **do not substitute an empty string into `<ul>…</ul>`** — either emit the whole `<ul>` block conditionally or substitute `<li><em>None</em></li>`. ### Gotcha 4 — Don't mix inline text with block elements inside `<li>` ```html <!-- Looks odd: three stacked paragraphs inside one bullet --> <li>Text then<p>paragraph inside</p>and more text</li> ``` Each text run and each `<p>` becomes its own paragraph inside the `listItem`, stacking them vertically under one bullet marker. If you need a paragraph, put it outside the list. If you want a single bullet with multi-line text, use `<br>` inside one text run: ```html <li>First line<br>second line of the same bullet</li> ``` ### Gotcha 5 — Two `<ul>` blocks back-to-back stay separate This is **not** a bug, but worth knowing: if you emit two adjacent `<ul>` blocks without a heading or `<hr>` between them, they stay as two separate lists (not merged). Fine for most cases, but if you want merged output, put the items in one `<ul>`. ### Quick Checklist for Template Authors Before publishing any generated HTML: 1. No `<li>` starts with `<ul>` or `<ol>` — every parent list item has leading text 2. No empty `<li></li>` — conditional render the whole `<ul>` block for empty data 3. No `data-checked`, `data-*`, or other custom attributes — use Unicode markers 4. No mixed text + `<p>` inside a single `<li>` — use `<br>` for multi-line items 5. Nested lists use at most 3 levels (deeper works but is hard to read) 6. `<ol start="N">` is preserved if you need numbered lists starting mid-sequence ## Updating, Archiving and Deleting Pages The `page` tool covers the whole page lifecycle, at workspace scope (omit `project_id`) or project scope (pass it): | Need | Call | |------|------| | Edit a published report | `page(action=retrieve, page_id)` first, then `page(action=update, page_id, description_html, name?)` | | Hide a page | `page(action=archive, page_id)`; `archive=false` restores it | | Remove a page | `page(action=archive, ...)` first, then `page(action=delete, page_id)` — delete is refused for a page that is not archived, and it needs the user's confirmation | | File a workspace page | `page(action=set_collection, page_id, collection_id)` (collections are managed with the `collection` tool) | | Tie a page to a work item | `page(action=attach_to_workitem, project_id, workitem_id, page_id)`; undo with `detach_from_workitem` (needs the `workitem_page_id` from `page(action=list_workitem_pages)`) | Rules to follow: - `update` **replaces the whole body**: `description_html` overwrites everything, so retrieve the page first and send the full edited HTML, not a fragment. A locked or archived page is refused. - A page's parent is fixed at creation (`parent_id` on `create`); nothing can reparent it later, so decide the hierarchy before publishing. - Prefer `update` over creating a duplicate: regenerate-and-update keeps one page per report. Roadmap pages that the team refreshes weekly are a good fit. - When testing publishing flows, still use a throwaway project: archived pages remain until deleted. - `update` refuses a locked or archived page; unlock or restore it first (the `page` tool has no unlock action, so a locked page is unlocked in the Plane UI). ## Release Notes from Plane Releases When the team tracks releases in Plane (the `release`, `release_tag` and `release_label` tools), build the release notes from the release itself instead of asking the user to list what shipped: ``` 1. release(action=list) → Find the release by name; status is unreleased | released | cancelled (release_tag(action=list) maps a version string such as "v2.0.0" to the tag a release points at) 2. release(action=retrieve, release_id=<id>) → name, status, release_date, tag 3. release(action=list_workitems, release_id=<id>) → The work items shipped in the release (follow next_cursor); group them by type or label into New Features / Improvements / Bug Fixes 4. release(action=get_changelog, release_id=<id>) → The stored changelog (every release has one, created empty with the release); read it first so a rewrite does not drop hand-written notes 5. Render the Release Notes template with the grouped items. 6. Publish and store: page(action=create, project_id=<id>, name="Release v2.0 — YYYY-MM-DD", description_html="<…>") release(action=update_changelog, release_id=<id>, description_html="<…>") → the page is the stakeholder-facing copy, the changelog keeps the notes with the release ``` Other release calls: `release(action=create, name, status, release_date, tag_id, lead_id, is_prerelease)`, `release(action=update, ...)`, `release(action=manage_workitems, release_id, add_ids|remove_ids)` and `release_label(action=attach|detach, release_id, label_ids)`. `release_date` is what the Plane UI labels "Target date" (`YYYY-MM-DD`). `description_html` of the changelog follows the same HTML rules as any page. ## HTML Templates Reusable templates live as separate files — load only the one you need: | Type | File | |------|------| | Sprint report | [references/templates/sprint-report.html](references/templates/sprint-report.html) | | Retrospective | [references/templates/retro.html](references/templates/retro.html) | | Release notes | [references/templates/release-notes.html](references/templates/release-notes.html) | | Roadmap | [references/templates/roadmap.html](references/templates/roadmap.html) | | Milestone update | [references/templates/milestone-update.html](references/templates/milestone-update.html) | Each template uses `{{PLACEHOLDER}}` tokens. Render by replacing tokens with concrete values gathered from Plane. Inlined previews follow for quick reference. ## Sprint Report Template ```html <h1>Sprint 14 Report — Billing v2</h1> <p><strong>Dates:</strong> 2026-03-24 → 2026-04-04 &middot; <strong>Goal:</strong> Users can see their Stripe invoices in-app</p> <h2>Summary</h2> <ul> <li>Completed: <strong>34 / 40 points</strong> (85%)</li> <li>Velocity vs last 3 sprints avg: 34 vs 32 (+6%)</li> <li>Goal achieved: <strong>Yes</strong></li> </ul> <h2>Delivered</h2> <table> <thead><tr><th>ID</th><th>Title</th><th>Points</th><th>Owner</th></tr></thead> <tbody> <tr><td>PROJ-148</td><td>Stripe webhook receiver</td><td>5</td><td>@alice</td></tr> </tbody> </table> <h2>Not Delivered</h2> <ul> <li>PROJ-152 — PDF export (3 pts) — blocked by vendor API, transferred to Sprint 15</li> </ul> <h2>Metrics</h2> <ul> <li>Cycle time: 2.4 days avg</li> <li>WIP peak: 6 (limit 7)</li> <li>Blockers encountered: 2</li> </ul> <h2>Next Sprint</h2> <p>Goal: …</p> ``` ## Retrospective Template ```html <h1>Sprint 14 Retrospective</h1> <p><strong>Date:</strong> 2026-04-04 &middot; <strong>Attendees:</strong> @alice, @bob, @carol</p> <h2>Previous Action Items</h2> <ul> <li>✅ Enable daily standup async thread</li> <li>⏳ Document Stripe webhook schema — carried over</li> </ul> <h2>What Went Well</h2> <ul><li>…</li></ul> <h2>What Didn't</h2> <ul><li>…</li></ul> <h2>Action Items</h2> <table> <thead><tr><th>Action</th><th>Owner</th><th>Due</th></tr></thead> <tbody> <tr><td>Add Stripe mock to local dev</td><td>@alice</td><td>Sprint 15</td></tr> </tbody> </table> ``` ## Release Notes Template ```html <h1>Release v2.0 — 2026-04-08</h1> <h2>Highlights</h2> <ul> <li>Multi-tenant support</li> <li>New billing portal powered by Stripe</li> </ul>
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub