/poster

SkillDev tools

Generate 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.

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-compatible dag.json intermediate; 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, default paper/): LaTeX project directory containing main.tex and sections/
  • --review (optional): cross-model Review LLM critique of the generated poster content
  • --anonymous (optional): force authors to "Anonymous" regardless of \author{} content, the paper/.author_display.txt cache, 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 the paper/.author_display.txt cache populated in Step 0 Q1. Ignored if --anonymous is 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 (default corners): header layout. corners = affiliation top-left, conference top-right; stacked = both logos in the right .conf area 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. 0 is 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 injection
  • poster/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 5b
  • poster/images/ — figures copied/converted (PDF→PNG @ 200 DPI) from paper/figures/
  • POSTER_REPORT (printed to terminal)
  • wiki/log.md — appended log entry

Wiki Interaction

Reads

  • paper/main.tex + paper/sections/*.tex + paper/figures/ — paper source
  • wiki/outputs/paper-plan-*.md (optional) — narrative arc, figure plan, evidence map
  • wiki/ideas/*.md (optional) — hypothesis, novelty argument for poster opening
  • wiki/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).

  1. Authors — paper-level metadata, resolved with this precedence:

    1. --authors STR flag → use that, do not prompt, do not persist.
    2. --anonymous flag → force "Anonymous", do not prompt, do not persist.
    3. paper/.author_display.txt exists → use its content, do not prompt. This is the "ask once, reuse" cache. Future /paper-draft is expected to seed this file during the writing stage; until then, /poster Step 0 maintains it (see below).
    4. dag.json root content (from \author{...} in main.tex) is non-empty AND not literally "Anonymous" → use it, do not prompt.
    5. 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, or skip to 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 /poster run, 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.
  2. Venue text — if --venue was not provided:

    • Ask: "What venue text should appear in the poster header? e.g. NeurIPS 2026. Reply with the text, or skip to leave it blank."
    • Take the next user message verbatim. Treat the literal string skip (case-insensitive) as empty.
  3. Affiliation logo — if --affiliation-logo was not provided AND --no-logos was not passed:

    • Use AskUserQuestion with 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.
  4. Conference / journal logo — if --conference-logo was not provided AND --no-logos was not passed:

    • Same flow as the affiliation logo (yes/no via AskUserQuestion, path via free text).
  5. Layout — if at least one logo was provided AND --layout was not passed:

    • Use AskUserQuestion with options:
      • "corners — affiliation top-left, conference top-right" (Recommended)
      • "stacked — both logos stacked in the right conf area, venue text on top"
    • If only --venue and no logos: skip the layout question (default corners is fine — only the venue text slot is used).
  6. 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 in name, authors in content, ordered section names in edge
  • Section (level: 1): section heading in name, full prose in content, figure refs in visual_node
  • Visual (level: 2): markdown image ref in name, caption in content, WxH in resolution

wiki2dag.py preserves the paper's:

  • Math: $…$, $$…$$, \(…\), \[…\] pass through into section content untouched, then render via KaTeX in the poster HTML. Macros defined in math_commands.tex are expanded and any surviving \ensuremath{X} wrapper is unwrapped to $X$ so KaTeX picks it up.
  • Citationsdropped 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 --citations to wiki2dag.py build if you want them back as [N] / [N, M] (built from a first-appearance bibkey → ordinal map). 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, \texttt handled at the cell level. The Step 3 LLM sees this HTML inside SECTION_JSON.content and 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 to paper/figures/_tikz_<sec>_<label>.png via tools/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). Then AskUserQuestion:

    • "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):

  1. Introduction (Abstract or first section)
  2. Method (the section describing the approach)
  3. Primary results (Experiments / Replication / main result section)
  4. Secondary results / ablation (if space permits)
  5. Analysis / discussion (one key insight)
  6. Conclusion (brief takeaway)

Mode resolution:

  • --no-figures passed → mode = none; every section renders text-only. Skip all questions.
  • --auto-figures passed → mode = auto; for each section, pick the visual with the largest area (W×H from resolution). 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:

CandidatesWide?Action
0No figure (no question, silent)
1anyUse 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.
≥2anyAsk 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):

OptionWhat it doesWhen to recommend
side-by-sideBoth/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-stackOne <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-tableUsed 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 from poster/dag.json with the visual_node field removed (the visual is conveyed separately). Keep only name, content, level.
  • LAYOUT: one of "none" / "inline" / "inline-multi-side" / "inline-multi-stack" / "inline-multi-after-table" from decisions[section_name]["layout"]. Drives the HTML template branch.
  • IMAGE_SRCS: list of image sources from decisions[section_name]["figures"], e.g. ["images/fig2.png", "images/fig3.png"]. Empty list if LAYOUT == "none".
  • ALT_TEXTS: parallel list of captions from decisions[section_name]["alts"]. Same length as IMAGE_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_node field 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:

  1. 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.
  2. 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 (use SECTION_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> whose src is IMAGE_SRCS[0] and alt is ALT_TEXTS[0].
  • LAYOUT == "inline-multi-side": output ONE <div class="img-section"> containing all <img> tags in IMAGE_SRCS order; the template's flex layout splits them horizontally.
  • LAYOUT == "inline-multi-stack": output MULTIPLE <div class="img-section"> blocks, one per <img>, in IMAGE_SRCS order. Stacked top-to-bottom.
  • LAYOUT == "inline-multi-after-table": same as "inline-multi-side" (one .img-section with all <img> tags) but place that .img-section AFTER 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.content contains 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.content contains <table class="poster-table">, insert the verbatim table block(s) after the summary <p>. Default placement order inside <div class="section-body">:

  1. summary <p>
  2. <table class="poster-table">…</table> (all tables, in source order)
  3. <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