/poster
SkillDev toolsGenerate an academic poster from a drafted paper — distill sections into a single-page HTML poster with figures and inter-section transitions
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the /poster skill
What this skill tells your AI
The instructions your AI receives, as published by skyllwt/autosci in .claude/skills/poster/SKILL.md and read by ahel’s review.
Generate an academic HTML poster from a drafted paper. Reads
paper/main.tex, section files, and figures; builds a PaperX-compatibledag.jsonintermediate; distills each section into a 2–5 sentence summary; selects representative figures; renders into a self-contained HTML poster with a 3-column auto-fit layout.
Inputs
Common
paper_dir(optional, defaultpaper/): LaTeX project directory containingmain.texandsections/--review(optional): cross-model Review LLM critique of the generated poster content--anonymous(optional): force authors to "Anonymous" regardless of\author{}content, thepaper/.author_display.txtcache, or--authors--no-figures(optional): render every section as text-only (useful for text-heavy posters, or when figures aren't ready)--no-logos(optional): skip the affiliation/conference-logo prompts; header shows venue text only--no-refine(optional): skip Step 5.5 critique-revise (default runs 1 iteration via Claude)
Power-user (scripted use; rarely needed in interactive runs)
--authors STR: override the authors text on the poster (e.g.--authors "Morrow Yang, Co-Author"). Useful for one-off override; everyday use is handled by thepaper/.author_display.txtcache populated in Step 0 Q1. Ignored if--anonymousis also set.--venue STR: venue text for the header right block (e.g."NeurIPS 2026"). Skips Step 0 venue prompt.--affiliation-logo PATH/--conference-logo PATH: logo file paths (PNG/JPG/PDF); each skips the matching Step 0 prompt.--layout corners|stacked(defaultcorners): header layout.corners= affiliation top-left, conference top-right;stacked= both logos in the right.confarea with venue text above.--auto-figures: skip the per-section figure questions; pick the largest candidate for each section (legacy behavior). The ⚠ wide marker on aspect-extreme figures (≥ 2.0 or ≤ 0.5) is informational only — every selected figure renders inline within its section regardless of aspect.--refine-iterations N(default 1, cap 2): number of critique-revise passes in Step 5.5.0is equivalent to--no-refine. Bump to 2 if a first pass might not converge on dense content.
Outputs
poster/dag.json— PaperX-compatible intermediate (reusable by future/slides,/pr)poster/outline.html— concatenated<section>blocks before template injectionposter/poster.html— final self-contained HTML poster (open in browser; Cmd/Ctrl+P → Save as PDF if you need a PDF — see Step 7 report for print settings)poster/poster.png— rendered screenshot at 2× CSS dimensions (default 2800×1800) — see Step 5bposter/images/— figures copied/converted (PDF→PNG @ 200 DPI) frompaper/figures/- POSTER_REPORT (printed to terminal)
wiki/log.md— appended log entry
Wiki Interaction
Reads
paper/main.tex+paper/sections/*.tex+paper/figures/— paper sourcewiki/outputs/paper-plan-*.md(optional) — narrative arc, figure plan, evidence mapwiki/ideas/*.md(optional) — hypothesis, novelty argument for poster openingwiki/experiments/*.md(optional) — key results, outcome numbers for headline callouts.claude/skills/shared-references/academic-writing.md— de-AI polish rules
Writes
poster/directory (all files listed in Outputs)wiki/log.md— appended
Graph edges created
- None (the poster is a presentation artifact, not a knowledge entity)
Workflow
Precondition: confirm paper/main.tex exists. If not, error with "Run /paper-draft first."
Step 0: Interactive header configuration
Goal: collect venue text and (optionally) two logos to render in the poster header. Skipped silently for any field the user already supplied as a CLI flag.
The flow uses AskUserQuestion for the yes/no/layout choices, then asks for paths and venue text in free-form (Claude reads the next user message as the answer).
-
Authors — paper-level metadata, resolved with this precedence:
--authors STRflag → use that, do not prompt, do not persist.--anonymousflag → force "Anonymous", do not prompt, do not persist.paper/.author_display.txtexists → use its content, do not prompt. This is the "ask once, reuse" cache. Future/paper-draftis expected to seed this file during the writing stage; until then,/posterStep 0 maintains it (see below).- dag.json root
content(from\author{...}inmain.tex) is non-empty AND not literally "Anonymous" → use it, do not prompt. - Otherwise (anonymized paper, no cached display name): ASK the user.
Asking flow (only when reached):
- Use
AskUserQuestion:"Keep 'Anonymous' (double-blind submission)"(Recommended)"Provide author names (I'll ask for the string)"
- If "Provide author names": ask free-text "What author string should appear? e.g.
Morrow Yang, Co-Author Name. Reply with the string, orskipto keep 'Anonymous'." Take the next user message as the authors string. - Persist the answer to
paper/.author_display.txt(one line, no trailing newline beyond the string). On the next/posterrun, the file exists → step 0 Q1 is silent. To change later, the user edits or deletes that file. - Either branch ("Keep Anonymous" or a provided string) gets persisted — the goal is no more prompts unless the user opts back in by removing the file.
-
Venue text — if
--venuewas not provided:- Ask: "What venue text should appear in the poster header? e.g.
NeurIPS 2026. Reply with the text, orskipto leave it blank." - Take the next user message verbatim. Treat the literal string
skip(case-insensitive) as empty.
- Ask: "What venue text should appear in the poster header? e.g.
-
Affiliation logo — if
--affiliation-logowas not provided AND--no-logoswas not passed:- Use
AskUserQuestionwith options:"Yes, I have a logo file"/"No, skip the affiliation logo". - If yes: ask "What's the path to the affiliation logo? (PNG / JPG / PDF; relative or absolute)." Take the next user message as the path. Verify the file exists; if not, re-ask once with the resolved-path issue surfaced, then either accept a new path or treat as skipped.
- Use
-
Conference / journal logo — if
--conference-logowas not provided AND--no-logoswas not passed:- Same flow as the affiliation logo (yes/no via
AskUserQuestion, path via free text).
- Same flow as the affiliation logo (yes/no via
-
Layout — if at least one logo was provided AND
--layoutwas not passed:- Use
AskUserQuestionwith options:"corners — affiliation top-left, conference top-right"(Recommended)"stacked — both logos stacked in the right conf area, venue text on top"
- If only
--venueand no logos: skip the layout question (defaultcornersis fine — only the venue text slot is used).
- Use
-
Final summary — print one line:
Header config: authors='{...}', venue='{...}', affiliation={path|none}, conference={path|none}, layout={...}
The authors string is applied in Step 5 via python3 tools/poster.py inject-title --authors "..."; the venue/logos are applied via python3 tools/poster.py inject-header.
Step 1: Build dag.json
python3 tools/wiki2dag.py build --paper-dir paper/ --output poster/dag.json
Add --anonymous if the user passed it.
The bridge produces three node types:
- Root (
level: 0): paper title inname, authors incontent, ordered section names inedge - Section (
level: 1): section heading inname, full prose incontent, figure refs invisual_node - Visual (
level: 2): markdown image ref inname, caption incontent,WxHinresolution
wiki2dag.py preserves the paper's:
- Math:
$…$,$$…$$,\(…\),\[…\]pass through into section content untouched, then render via KaTeX in the poster HTML. Macros defined inmath_commands.texare expanded and any surviving\ensuremath{X}wrapper is unwrapped to$X$so KaTeX picks it up. - Citations — dropped by default:
\citep{key}/\citet{...}markers are stripped to empty. Real-world conference posters (per CCF-A research) typically omit inline citation markers because there's no room for a reference list. Pass--citationstowiki2dag.py buildif you want them back as[N]/[N, M](built from a first-appearancebibkey → ordinalmap). Future poster styles that render a reference footer can opt in. - Tables:
\begin{table}…\end{table}envs (including tables inlined via\input{tables/foo}) are converted to live HTML<table class="poster-table">blocks with the booktabs caption rendered as a<caption>and\multicolumn,\textbf,\emph,\textit,\texttthandled at the cell level. The Step 3 LLM sees this HTML insideSECTION_JSON.contentand must include it verbatim after the summary paragraph (see Step 3). The fit() algorithm sizes table fonts alongside body text; if a table still clips, Step 5.5 sees it via the DOM overflow probe and trims. - TikZ figures:
\begin{figure}envs containing\begin{tikzpicture}but no\includegraphics{}are auto-rasterized topaper/figures/_tikz_<sec>_<label>.pngviatools/rasterize_latex.py(pdflatex + pdftoppm). The resulting PNG is a regular visual node from the bridge's perspective — Step 3 picks it up exactly like any other figure. If\includegraphics{}is also present in the same env, the existing pipeline takes precedence (TikZ extraction is skipped). Failed rasterizations are logged to stderr and the figure is dropped; the rest of the pipeline continues. Cached across runs — delete the PNG to force regeneration.
Step 2: Compile WIKI_CONTEXT (optional)
Goal: optionally ground the Step 3 distillation prompts in the paper's own planning artifacts (hypothesis statement, novelty argument, key-result numbers from linked ideas/experiments). Always surface what's happening — do not adopt silently.
Detection + transparency:
-
No plan file present: print one line —
Step 2: no paper-plan-*.md found in wiki/outputs/ — Step 3 will run without WIKI_CONTEXT.Skip to Step 2.5. -
Plan file present: print one line summarizing what was found, e.g.
Step 2: found wiki/outputs/paper-plan-2026-05-17.md (3 linked ideas, 2 experiments).ThenAskUserQuestion:"Yes — adopt as WIKI_CONTEXT grounding"(Recommended)"No — distill from paper source only"
If the user picks Yes: read the plan for venue, narrative arc, and linked idea slugs. For each idea slug, read wiki/ideas/<slug>.md for the hypothesis statement and novelty argument. For each linked wiki/experiments/*.md, read the outcome and key_result fields. Assemble into a single WIKI_CONTEXT string keyed by section:
[INTRODUCTION]
hypothesis: <one sentence from idea>
novelty: <one sentence from idea>
[EXPERIMENTS]
key_result: <headline numbers from experiments>
outcome: <one line summary>
If the user picks No: leave WIKI_CONTEXT empty.
This block is passed into the Step 3 prompt under the {WIKI_CONTEXT} slot. The Step 3 prompt explicitly tolerates an empty slot.
Step 2.5: Figure selection
Goal: decide which figure (if any) belongs in each selected section. Runs after dag.json is built and before any LLM distillation, so the figure choice becomes an input to Step 3 rather than something the LLM guesses. Every figure renders inline within its section; the ⚠ wide marker in the manifest is purely informational — a heads-up that an aspect-extreme figure may look cramped in a single column, so the user can pick an alternative figure or skip it.
Section selection (priority list, hard-capped at 6 sections to keep the poster legible at 1400×900):
- Introduction (Abstract or first section)
- Method (the section describing the approach)
- Primary results (Experiments / Replication / main result section)
- Secondary results / ablation (if space permits)
- Analysis / discussion (one key insight)
- Conclusion (brief takeaway)
Mode resolution:
--no-figurespassed → mode =none; every section renders text-only. Skip all questions.--auto-figurespassed → mode =auto; for each section, pick the visual with the largest area (W×H fromresolution). Skip all questions.- Otherwise → mode =
interactive. Run the manifest + questions below.
Print the figure manifest (always, regardless of mode), e.g.:
Figure candidates per section:
Abstract — text only
Introduction — text only
Method — text only
Experiments — 2 candidates:
[a] layer_curves.png 2378x618 aspect 3.85 ⚠ wide
[b] bootstrap.png 974x612 aspect 1.59
Discussion — text only
Conclusion — text only
Compute aspect from resolution (W×H). The ⚠ wide marker comes from the wide field in dag.json (true when aspect ≥ 2.0 or ≤ 0.5).
Interactive flow (mode = interactive only):
For each section, decide what to ask based on candidate count and wide-flags:
| Candidates | Wide? | Action |
|---|---|---|
| 0 | — | No figure (no question, silent) |
| 1 | any | Use it inline (no question — the manifest already showed it; if wide, the ⚠ marker is the heads-up). User can re-run with --no-figures to drop it. |
| ≥2 | any | Ask Q-Pick (multi-select): "Which figure(s) for {Section}?" — AskUserQuestion with multiSelect: true, options: each candidate (label includes ⚠ wide marker if applicable) / Let Claude decide (pick largest one) / No figure. User may pick one, several, or all. |
Use AskUserQuestion for each prompt; cap at 4 options total. When a section has 4+ candidates, drop the Let Claude decide option to stay within the limit (the user is being explicit anyway).
Follow-up: layout when ≥2 figures were picked for the same section. Ask via AskUserQuestion (single-select):
| Option | What it does | When to recommend |
|---|---|---|
side-by-side | Both/all chosen figures inside ONE <div class="img-section">; the template's flex layout splits horizontal space evenly. | Default. Most space-efficient for a 3-col poster. Works zero-CSS-changes. |
vertical-stack | One <div class="img-section"> per figure, stacked top-to-bottom. Each figure gets full column width. | When fine detail matters per figure; risks tall section + fit() shrinking text aggressively. |
after-table | Used when the section ALSO contains a <table class="poster-table">. Figures go side-by-side AFTER the table. | The section is content-dense; respect paper's read order. |
The HTML template already supports both side-by-side and vertical-stack natively (flex layout, plus the option of multiple .img-section divs). after-table is a placement variant, not a new CSS class.
After all decisions, print a final summary line:
Figures chosen:
Experiments → fig2.png + fig3.png (side-by-side)
Method → tikz_chain.png (inline)
(other sections: text only)
Decision record: persist the choices in an in-memory dict keyed by section display name:
{
"Experiments": {
"figures": ["images/fig2.png", "images/fig3.png"], # list, even when 1
"alts": ["<caption fig2>", "<caption fig3>"], # parallel list
"layout": "inline-multi-side", # see below
},
"Method": {
"figures": ["images/tikz_chain.png"],
"alts": ["<caption>"],
"layout": "inline", # single-figure case
},
"Conclusion": {"figures": [], "alts": [], "layout": "none"},
...
}
layout enum:
"none"— section is text-only, no figures"inline"— exactly one figure inside one.img-section(back-compat with single-figure flow)"inline-multi-side"— 2+ figures inside ONE.img-section(flex side-by-side)"inline-multi-stack"— 2+ figures, each in its own.img-section(vertical stack)"inline-multi-after-table"— 2+ figures side-by-side, placed AFTER any<table class="poster-table">in the section content
This dict is consumed in Step 3 to fill the per-section prompt variables.
Step 3: Distill poster sections
Load poster/dag.json and the figure-decision dict from Step 2.5. Iterate the selected section nodes in order.
For each selected section, prepare the variables for the prompt below. Figure variables come from the Step 2.5 decision dict — do NOT re-derive them here.
SECTION_JSON: the section node fromposter/dag.jsonwith thevisual_nodefield removed (the visual is conveyed separately). Keep onlyname,content,level.LAYOUT: one of"none"/"inline"/"inline-multi-side"/"inline-multi-stack"/"inline-multi-after-table"fromdecisions[section_name]["layout"]. Drives the HTML template branch.IMAGE_SRCS: list of image sources fromdecisions[section_name]["figures"], e.g.["images/fig2.png", "images/fig3.png"]. Empty list ifLAYOUT == "none".ALT_TEXTS: parallel list of captions fromdecisions[section_name]["alts"]. Same length asIMAGE_SRCS.WIKI_CONTEXT(optional): a short block compiled from Step 2 — hypothesis statement, novelty argument, key-result numbers from linked ideas/experiments. Empty string if no wiki context was loaded.
Run the following prompt for each section (ported from PaperX poster_outline_prompt, extended for LAYOUT and WIKI_CONTEXT):
You are given a section node JSON (SECTION_JSON) from a paper DAG. The section JSON you see has NO
visual_nodefield and must be treated as authoritative.SECTION_JSON: {SECTION_JSON}
LAYOUT: {LAYOUT} # one of "none" | "inline" | "inline-multi-side" | "inline-multi-stack" | "inline-multi-after-table"
If LAYOUT is not "none", you are also given IMAGE_SRCS (a list of image paths) and ALT_TEXTS (parallel list of captions). The visual content MUST ONLY come from these provided sources (do not invent or substitute any other image). For single-figure layouts (
"inline"), the lists each have length 1. For multi-figure layouts, length ≥ 2 and the order in the list is the order figures should appear in the rendered HTML (left-to-right for*-side, top-to-bottom for*-stack).IMAGE_SRCS: {IMAGE_SRCS} ALT_TEXTS: {ALT_TEXTS}
WIKI_CONTEXT (optional, may be empty — use it ONLY to ground concrete numbers/claims, never to invent content not in the section): {WIKI_CONTEXT}
Task:
- Write ONE concise paragraph summarizing ONLY the section's content for a scientific poster. Constraints: 2–5 sentences, factual, non-hallucinatory, no bullet lists, avoid starting with "This section". The summary must contain no more than 40 words and be written with strong logical coherence and smooth transitions to minimize perplexity.
- Output the HTML block for this section, choosing the template variant matching LAYOUT. Output ONLY the HTML and nothing else.
Strict output rules:
- Output only the HTML for one section (one
<section>block).- Do NOT add markdown fences, explanations, or extra text.
- The
<div class="section-bar">must be the section title (useSECTION_JSON.name).- Replace the sample paragraph with your summary.
- LAYOUT ==
"none": output the section block with NO<div class="img-section">.- LAYOUT ==
"inline": output the section block with exactly one<div class="img-section">containing one<img>whosesrcisIMAGE_SRCS[0]andaltisALT_TEXTS[0].- LAYOUT ==
"inline-multi-side": output ONE<div class="img-section">containing all<img>tags inIMAGE_SRCSorder; the template's flex layout splits them horizontally.- LAYOUT ==
"inline-multi-stack": output MULTIPLE<div class="img-section">blocks, one per<img>, inIMAGE_SRCSorder. Stacked top-to-bottom.- LAYOUT ==
"inline-multi-after-table": same as"inline-multi-side"(one.img-sectionwith all<img>tags) but place that.img-sectionAFTER the<table class="poster-table">block(s) inside<div class="section-body">. Useful when the section's table is the primary artifact and figures serve as visual support.- TABLES: if
SECTION_JSON.contentcontains one or more<table class="poster-table">…</table>blocks, include EACH ONE verbatim (preserve the entire block byte-for-byte, including<caption>,<thead>,<tbody>, all<tr>/<th>/<td>tags and their attributes) inside<div class="section-body">AFTER your summary<p>. Do NOT paraphrase, restructure, or trim the table HTML. Exception — drop a table only if it is obviously too large for one column (> 5 columns AND > 6 rows) AND summarizing 2–3 key cells in prose would preserve the result; in that case, drop the table and call out the key numbers in your summary<p>.Required HTML templates (pick the one matching LAYOUT):
LAYOUT = "none":
<section class="section"> <div class="section-bar" contenteditable="true">SECTION_TITLE</div> <div class="section-body" contenteditable="true"> <p>SUMMARY_TEXT</p> </div> </section>LAYOUT = "inline" (single figure):
<section class="section"> <div class="section-bar" contenteditable="true">SECTION_TITLE</div> <div class="section-body" contenteditable="true"> <p>SUMMARY_TEXT</p> <div class="img-section"> <img src="IMAGE_SRCS[0]" alt="ALT_TEXTS[0]" class="figure" /> </div> </div> </section>LAYOUT = "inline-multi-side" (≥2 figures, horizontal):
<section class="section"> <div class="section-bar" contenteditable="true">SECTION_TITLE</div> <div class="section-body" contenteditable="true"> <p>SUMMARY_TEXT</p> <div class="img-section"> <img src="IMAGE_SRCS[0]" alt="ALT_TEXTS[0]" class="figure" /> <img src="IMAGE_SRCS[1]" alt="ALT_TEXTS[1]" class="figure" /> <!-- repeat for IMAGE_SRCS[2], etc. --> </div> </div> </section>LAYOUT = "inline-multi-stack" (≥2 figures, vertical):
<section class="section"> <div class="section-bar" contenteditable="true">SECTION_TITLE</div> <div class="section-body" contenteditable="true"> <p>SUMMARY_TEXT</p> <div class="img-section"> <img src="IMAGE_SRCS[0]" alt="ALT_TEXTS[0]" class="figure" /> </div> <div class="img-section"> <img src="IMAGE_SRCS[1]" alt="ALT_TEXTS[1]" class="figure" /> </div> <!-- one .img-section per figure --> </div> </section>With a table — when
SECTION_JSON.contentcontains<table class="poster-table">, insert the verbatim table block(s) after the summary<p>. Default placement order inside<div class="section-body">:
- summary
<p><table class="poster-table">…</table>(all tables, in source order)<div class="img-section">…</div>(if LAYOUT requires figures)If LAYOUT is
"inline-multi-after-table"the order is the same — the name is just a hint that the table is the primary artifact. Example:<section class="section"> <div class="section-bar" contenteditable="true">SECTION_TITLE</div> <div class="section-body" contenteditable="true"> <p>SUMMARY_TEXT</p> <table class="poster-table">…verbatim from SECTION_JSON.content…</table> <div class="img-section"> <img src="IMAGE_SRCS[0]" alt="ALT_TEXTS[0]" class="figure" /> <img src="IMAGE_SRCS[1]" alt="ALT_TEXTS[1]" class="figure" /> </div> </div> </section>
Apply de-AI polish per shared-references/academic-writing.md (vary sentence openings, drop signature words like "leverage", "comprehensive", "delve"). Pull headline numbers from WIKI_CONTEXT when present — these make poster results concrete.
Write all blocks (in selection order) to poster/outline.html.
Step 4: Add inter-section transitions
Apply the following prompt to the assembled outline (adapted from PaperX):
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 2k
- Forks
- 210
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packages
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
poster-skyllwt- Source
- github.com/skyllwt/autosci