From 2319ff108304774c73294f247ec2580217d167d0 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Wed, 16 Sep 2026 16:24:53 +0800 Subject: [PATCH 1/4] Tighten survey and chat-history Typst templates Chat-history PDF: shorten front matter to title, subtitle, and one date line so the first prompt lands on page one; move scope and coverage to the closing section; keep the existing entry style; phase labels stick to the next message and the footer grid keeps title and page number apart. Survey review: open with an "Assessment in brief" callout after Scope, label each approach by mechanism and best evidence, use horizontal-rule tables with repeating headers and content-fitted widths, write urgency as text, and close with a next step. Plain title line, 18 mm margins, 10 pt justified body; the placeholder now fits one page. Both templates accept --input heading-font for builds without system fonts. Add a render test for tables that cross page boundaries. Co-Authored-By: Claude Fable 5.1 --- skills/dump-chat-history/SKILL.md | 6 +- skills/dump-chat-history/assets/report.typ | 39 +-- .../references/typst-reference.md | 13 +- skills/survey/SKILL.md | 3 + skills/survey/template.typ | 275 +++++++++--------- tests/test_typst_reports.py | 57 ++++ 6 files changed, 239 insertions(+), 154 deletions(-) create mode 100644 tests/test_typst_reports.py diff --git a/skills/dump-chat-history/SKILL.md b/skills/dump-chat-history/SKILL.md index dd5c2e5..a695c0f 100644 --- a/skills/dump-chat-history/SKILL.md +++ b/skills/dump-chat-history/SKILL.md @@ -132,7 +132,11 @@ typst compile report.typ report.pdf The template uses A4, quiet phase bands, serif prose, sans-serif headings, and monospaced original text, with portable font fallbacks. It permits long messages -to continue across pages. Add phases and concise annotations when useful; do not +to continue across pages. Front matter is short: title, optional subtitle, and +one date line, so the first prompt appears on the first page; scope, coverage, +and sources sit at the end. Change the heading font with +`--input heading-font="Your installed font"` (use `Libertinus Serif` for a build +with only bundled fonts). Add phases and concise annotations when useful; do not force a fixed page count, prompt ranking, productivity ratio, or research taxonomy. Ground outcome claims in the adjacent conversation or verifiable artifacts; distinguish reported historical checks from checks performed now. diff --git a/skills/dump-chat-history/assets/report.typ b/skills/dump-chat-history/assets/report.typ index f92318a..1f65c26 100644 --- a/skills/dump-chat-history/assets/report.typ +++ b/skills/dump-chat-history/assets/report.typ @@ -1,6 +1,7 @@ // Copy beside history.json; compile with: typst compile report.typ report.pdf +// Optional: --input heading-font="Your installed sans-serif font" #let report = json("history.json") -#let sans = ("Avenir Next", "Libertinus Serif") +#let sans = (sys.inputs.at("heading-font", default: "Avenir Next"), "Libertinus Serif") #let serif = ("Charter", "Libertinus Serif") #let mono = "DejaVu Sans Mono" #let ink = rgb("24282b") @@ -10,7 +11,7 @@ #set document(title: report.title, description: "Conversation history field note") #set page(paper: "a4", margin: (x: 23mm, y: 20mm), footer: context { set text(font: sans, size: 8pt, fill: muted) - [#report.title #h(1fr) #counter(page).display()] + grid(columns: (1fr, auto), gutter: 12pt, report.title, counter(page).display()) }) #set text(font: serif, size: 10pt, fill: ink) #set par(leading: 0.45em, spacing: 0.7em) @@ -19,27 +20,26 @@ #show raw: set text(font: mono, size: 8.7pt, hyphenate: false, ligatures: false) #show raw.where(block: true): set block(width: 100%, fill: rgb("f2f3f4"), inset: 7pt, radius: 2pt, breakable: true) -#text(font: sans, size: 8pt, tracking: 1pt, fill: rust)[CONVERSATION FIELD NOTE] -#v(7pt) -// The topic is the title. Counts belong in metadata, never the headline. -#text(font: sans, size: 27pt, weight: "bold", report.title) -#v(7pt) -#text(size: 12pt, report.at("subtitle", default: "")) -#v(8pt) -#text(font: sans, size: 9pt, fill: muted)[ - #report.window.start – #report.window.end · #report.window.timezone +// Short front matter: the transcript starts on the first page. Scope, coverage, +// and sources sit at the end. The topic is the title; counts never are. +#block(breakable: false, below: 10pt)[ + #text(font: sans, size: 20pt, weight: "bold", report.title) + #if report.at("subtitle", default: "") != "" [ + #v(4pt) + #text(size: 11pt, report.subtitle) + ] + #v(4pt) + #text(font: sans, size: 8.5pt, fill: muted)[ + #report.window.start – #report.window.end · #report.window.timezone + ] ] -#v(5pt) -#report.scope -#v(5pt) -#report.coverage -= Transcript #let visible = report.entries.filter(e => e.at("include_in_report", default: e.role == "user")) #for entry in visible { let phase = entry.at("phase", default: none) if phase != none { - block(width: 100%, fill: rgb("f6f3ef"), radius: 3pt, inset: 10pt, breakable: false)[ + // sticky keeps the phase label with the message that follows it. + block(width: 100%, fill: rgb("f6f3ef"), radius: 3pt, inset: 10pt, sticky: true, above: 12pt)[ #text(font: sans, weight: "semibold", phase) ] } @@ -73,8 +73,11 @@ ] = Sources and limits +#report.scope + +#report.coverage #for source in report.sources [ - #block(breakable: true, above: 5pt)[ + #block(breakable: true, above: 9pt)[ #text(font: sans, size: 9pt, weight: "semibold", source.id) #linebreak() #raw(source.location, block: true) diff --git a/skills/how-to-write-ideas-report/references/typst-reference.md b/skills/how-to-write-ideas-report/references/typst-reference.md index 6ad5567..b138319 100644 --- a/skills/how-to-write-ideas-report/references/typst-reference.md +++ b/skills/how-to-write-ideas-report/references/typst-reference.md @@ -10,9 +10,18 @@ Reference files: ~/Documents/private-note/notes/typst-learn/ (typst-tricks.typ, ### Page Setup - Standalone figures: `#set page(width: auto, height: auto, margin: 5pt)` -- Standard notes: `#set page(margin: 2cm)` + `#set text(size: 10pt)` + `#set heading(numbering: "1.1.")` +- Reports: A4, 18 mm margins, 10 pt justified body text, 0.55 em leading, and a short running footer with the page number. Use the survey template as a working example. - Numbered equations: `#set math.equation(numbering: "(1)")` +### Report hierarchy and legibility +- Give the document title its own text block, not a numbered heading. Use about 18 pt for the title, 13 pt for sections, and 11 pt for subsections. No kicker line or decorative front matter: the first content paragraph belongs on the first page. +- Use serif body text and a consistent sans-serif heading font. The report templates accept `--input heading-font="Your installed font"`; `Libertinus Serif` works without system fonts. Do not shrink body text below 10 pt to meet a page count. +- Justify body prose; keep table cells ragged right so narrow columns keep natural word spacing. Use at least 9 pt for tables and captions, and 8 pt for supporting metadata. +- Reserve one dark accent and a light tint for navigation and brief assessments. Name categories and priorities explicitly so they remain readable in grayscale. +- Use horizontal table rules, brief cells, and `table.header(repeat: true, ...)`. Keep long tables outside unbreakable figures or boxes. Match column widths to actual content, including the longest header and priority label. +- Use `block(breakable: true, ...)` for long callouts and transcript entries. Keep a short label with its following content using `block(sticky: true, ...)`. +- Inspect rendered pages with realistic paragraphs, long titles, source paths, and a table or message that crosses a page. Check that annotations cannot be mistaken for quoted text. + ### Common Packages - CeTZ: `@preview/cetz:0.4.0`, `@preview/cetz-plot:0.1.2` - Algorithms: `@preview/algorithmic:1.0.3` — `Function`, `For`, `While`, `If`, `ElseIf`, `Else`, `Assign`, `Return`, `Comment` @@ -39,7 +48,7 @@ Reference files: ~/Documents/private-note/notes/typst-learn/ (typst-tricks.typ, - `dict.at(key, default: 0)` — dict with default ### Content Helpers -- Infobox: `rect(stroke: color, inset: 8pt, radius: 4pt, width: 100%, [*Title:*\ body])` +- Short assessment: `block(stroke: (left: 2pt + color), inset: 10pt, width: 100%, breakable: true, body)` - Inline image alignment: `box(image(...), baseline: (size - 20pt) / 2 + offset)` - Image clipping: `box(clip: true, img, inset: (top: -top, bottom: -bottom, ...))` - Two columns: `grid(columns: (1fr, 1fr), gutter: 20pt, left, right)` diff --git a/skills/survey/SKILL.md b/skills/survey/SKILL.md index 605d7d5..9b355bc 100644 --- a/skills/survey/SKILL.md +++ b/skills/survey/SKILL.md @@ -79,6 +79,9 @@ Follow `skills/how-to-technical-writing/SKILL.md` for sentence- and paragraph-le - Tailor technical depth to the user's role from `docs/discussion/user-profile.md` or available context. - Save to `articles/YYYY-MM-DD--review.{md,typ,tex}` or a project-specific path if the user prefers. - For Typst, start from `skills/survey/template.typ` with `skills/survey/template.bib`. The scaffold provides `section_box`, `stage`, `proscons`, `compare_table`, and `problem_table`; delete unused helpers from the copied document. +- Keep the opening scope and evidence cutoff brief. Follow with an **Assessment in brief** paragraph stating the principal finding, its evidence, and the unresolved constraint. Close with the next measurement that would resolve that constraint, rather than repeating the opening. +- Keep the template's dense, restrained layout: a plain title line, 10 pt justified body text, one accent color, and horizontal table rules. `compare_table` accepts custom `headers` and `columns`; choose them together. Write urgency labels as text. Keep long tables in the page flow so headers repeat across pages, and inspect the rendered result with realistic cell text. +- The heading font can be changed with `--input heading-font="Your installed font"`. For a build using only Typst's bundled fonts, use `--input heading-font="Libertinus Serif"`. ### Gap-filling focus diff --git a/skills/survey/template.typ b/skills/survey/template.typ index 8330176..335c43e 100644 --- a/skills/survey/template.typ +++ b/skills/survey/template.typ @@ -1,183 +1,192 @@ -// survey report Typst template — copy to articles/YYYY-MM-DD--review.typ -// and replace the placeholder content. Compile with: typst compile .typ -// Citations use a sibling bib (see the bibliography() call at the bottom); -// copy .knowledge/references.bib beside the document, point bibliography() at -// that canonical file, or adapt template.bib. - -#set page(margin: 1.6cm) -#set text(size: 10pt) -#set par(justify: true, leading: 0.62em) -#set heading(numbering: "1.") - -#let title = [TODO: Review Title] -#let authors = [TODO: author / "review draft"] - -#show link: set text(fill: blue.darken(30%)) - -// ---- reusable helpers --------------------------------------------------- - -#let section_box(title, body, fill: rgb("f7f8fc"), stroke: rgb("d9deeb")) = rect( - width: 100%, inset: 10pt, radius: 6pt, fill: fill, stroke: stroke, - [#strong[#title] #v(0.3em) #body], +// Copy beside template.bib, then replace the example content and bibliography. +// The canonical source bibliography remains .knowledge/references.bib. +// Compile: typst compile review.typ review.pdf +// Optional: --input heading-font="Your installed sans-serif font" + +#let title = [TODO: Review title] +#let authors = "TODO: author / review draft" +#let review-date = [TODO: YYYY-MM-DD] +#let short-title = [Research review] // Short running title; keep to one line. +#let sans = sys.inputs.at("heading-font", default: "Avenir Next") +#let serif = "Libertinus Serif" +#let ink = rgb("24282b") +#let muted = rgb("53616b") +#let accent = rgb("245b65") +#let tint = rgb("f2f6f6") +#let rule = rgb("cbd4d7") + +#set document(title: title, author: authors) +#set page( + paper: "a4", margin: (x: 18mm, y: 18mm), + footer: context { + set text(font: (sans, serif), size: 8pt, fill: muted) + grid(columns: (1fr, auto), gutter: 12pt, short-title, counter(page).display()) + }, ) +#set text(font: serif, size: 10pt, fill: ink) +#set par(justify: true, leading: 0.55em, spacing: 0.65em) +#set heading(numbering: "1.1") +#set list(indent: 1em, body-indent: 0.5em, spacing: 0.35em) +#show heading: set text(font: (sans, serif), weight: "bold") +#show heading.where(level: 1): set text(size: 13pt) +#show heading.where(level: 2): set text(size: 11pt) +#show heading.where(level: 1): set block(above: 1.3em, below: 0.55em) +#show heading.where(level: 2): set block(above: 1em, below: 0.45em) +#show link: set text(fill: accent) +#show figure.caption: set text(size: 9pt, fill: muted) +#set figure(gap: 6pt) + +// Short assessments only. Ordinary prose needs no container. +#let section_box(title, body, fill: tint, stroke: accent) = block( + width: 100%, inset: 9pt, fill: fill, + stroke: (left: 2pt + stroke), breakable: true, +)[ + #block(sticky: true, below: 4pt)[ + #text(font: (sans, serif), size: 9.5pt, weight: "bold", title) + ] + #body +] -// horizontal strip cell (use in a grid for a flow/era/landscape diagram) -#let stage(name, body, fill) = rect( - width: 100%, inset: 7pt, radius: 5pt, fill: fill, stroke: rgb("c7cfe0"), - align(center)[#text(weight: "semibold", size: 9pt)[#name] #v(0.2em) #text(size: 8pt)[#body]], -) +// Cell for a flow, architecture, or timeline grid. Use arrows only for an +// actual sequence or dependency. Names and descriptions must carry the +// relationship on their own; the fill is optional emphasis. +#let stage(name, body, fill) = block( + width: 100%, inset: 7pt, fill: fill, stroke: 0.5pt + rule, +)[ + #align(center)[ + #text(font: (sans, serif), size: 9pt, weight: "bold", name) + #v(3pt) + #text(size: 8.5pt, body) + ] +] -// per-approach strengths / limitations (two UNPAIRED bullet lists). -// USE ONLY when the approaches are competing solutions to the SAME problem -// ("which should I pick?"). For complementary capabilities / platform branches, -// write a prose assessment paragraph instead and delete this helper. +// Two independent lists, only for competing solutions to the same problem. +// Complementary capabilities need a prose assessment instead. #let proscons(pros, cons) = grid( - columns: (1fr, 1fr), gutter: 0.6em, - rect(width: 100%, inset: 7pt, radius: 5pt, fill: rgb("eef6ef"), stroke: rgb("bcd9c4"))[ - #text(weight: "semibold", fill: green.darken(30%))[Strengths] - #pros - ], - rect(width: 100%, inset: 7pt, radius: 5pt, fill: rgb("fbecec"), stroke: rgb("e3c6c6"))[ - #text(weight: "semibold", fill: red.darken(20%))[Limitations] - #cons - ], -) - -// optional cross-approach comparison matrix. -// `rows` is an ARRAY OF ROW-ARRAYS — one inner (…) per row — so a stray comma -// or paren breaks only that row, with an error pointing at it, instead of -// silently mis-nesting the whole table. -#let compare_table(rows) = table( - columns: (20%, 18%, 22%, 16%, 24%), - stroke: (x, y) => if y == 0 { 0.8pt + black } else { 0.4pt + rgb("d7dbe6") }, - inset: 6pt, - table.header( - [#strong[Approach]], [#strong[Scalability]], [#strong[Verifiability / cost]], [#strong[Maturity]], [#strong[Best-fit use case]], - ), - ..rows.flatten(), + columns: (1fr, 1fr), gutter: 12pt, + ..(([Strengths], pros), ([Limitations], cons)).map(((label, body)) => block( + width: 100%, inset: (top: 5pt), stroke: (top: 0.5pt + rule), + breakable: true, + )[ + #block(sticky: true, below: 3pt)[ + #text(font: (sans, serif), size: 9pt, weight: "bold", label) + ] + #body + ]), ) -// `rows` is an array of row-arrays — see compare_table. -#let problem_table(rows) = table( - columns: (5%, 27%, 38%, 20%, 10%), - stroke: (x, y) => if y == 0 { 0.8pt + black } else { 0.4pt + rgb("d7dbe6") }, - inset: 6pt, - table.header( - [#strong[No.]], [#strong[Problem]], [#strong[Why it matters]], [#strong[Who could solve it]], [#strong[Urgency]], - ), - ..rows.flatten(), +// Rows are arrays of cells. Keep text brief; explain qualifications in prose. +// Horizontal rules separate records without boxing in every cell. Headers +// repeat when a table continues onto another page. +#let report-table(columns, headers, rows) = { + set text(size: 9pt) + set par(justify: false, leading: 0.45em) + table( + columns: columns, align: left + top, + inset: (x: 5pt, y: 5pt), + stroke: (x, y) => (bottom: if y == 0 { 0.8pt + accent } else { 0.4pt + rule }), + fill: (x, y) => if y == 0 { tint }, + table.header(repeat: true, ..headers.map(h => text(weight: "bold", h))), + ..rows.flatten(), + ) +} + +// Adapt both headers and widths to criteria that discriminate this field. +#let compare_table( + rows, + columns: (1.1fr, 1fr, 1.25fr, 0.85fr, 1.4fr), + headers: ([Approach], [Scalability], [Verification / cost], [Maturity], [Best use]), +) = report-table(columns, headers, rows) + +// Rank and urgency get enough room for their labels, not a fixed tiny fraction. +#let problem_table(rows) = report-table( + (auto, 1.2fr, 1.6fr, 1fr, auto), + ([No.], [Problem], [Why it matters], [Who can act], [Urgency]), + rows, ) -// ---- title -------------------------------------------------------------- - -#align(center)[#heading(numbering: none)[#title]] - -#align(center)[ - #text(size: 12pt, weight: "semibold")[State-of-the-Art Review] - #v(0.25em) - #text(fill: gray.darken(20%))[#authors] - #v(0.25em) - #text(fill: gray.darken(10%))[TODO: Generated YYYY-MM-DD from the knowledge base.] +// Title is not a numbered section or an outline entry. +#block(breakable: false, below: 10pt)[ + #text(font: (sans, serif), size: 18pt, weight: "bold", title) + #v(5pt) + #text(font: (sans, serif), size: 9pt, fill: muted)[#authors #h(1em) #review-date] ] -#v(0.9em) +*Scope.* TODO: what this report assesses, who it is for, and what it excludes. +State the evidence cutoff. Organize by technical approach. #section_box( - [Scope], - [TODO: one paragraph — what this report assesses, who it is for, and what it deliberately excludes. State that the review is organized #emph[by technical approach].], + [Assessment in brief], + [TODO: the principal finding, the evidence supporting it, and the main unresolved constraint. Give the reader a reason to continue @Example2024.], ) -= What and Why - -// TODO: 2–3 paragraphs. (1) What it is + the problem it solves. -// (2) Why it matters now — the motivation/stakes. (3) How it differs from the -// dominant or prior approach. Cite real claims with @citekey. += What and why -TODO: define the topic for a reader who has never heard of it @Example2024. +// 2–3 paragraphs: define the topic and problem, explain the stakes, then +// distinguish it from the prior approach. Cite claims, not filler. +TODO: define the topic for a new reader @Example2024. -#v(0.5em) #figure( grid( - columns: (30%, 5%, 30%, 5%, 30%), + columns: (1fr, auto, 1fr, auto, 1fr), gutter: 6pt, align: center + horizon, - stage([Concept A], [one-line role], rgb("eef3ff")), - align(center + horizon)[#text(size: 14pt)[→]], - stage([Concept B], [one-line role], rgb("e9f7ee")), - align(center + horizon)[#text(size: 14pt)[→]], - stage([Concept C], [one-line role], rgb("fdf2e9")), + stage([Concept A], [one-line role], tint), + [→], + stage([Concept B], [one-line role], tint), + [→], + stage([Concept C], [one-line role], tint), ), - caption: [TODO: a concept / architecture / problem-framing diagram.], + caption: [TODO: explain the relationship shown and the point it establishes.], ) -= Technical Approaches += Technical approaches -// Identify the main approaches / method families (typically 3–6) and give one -// subsection each. Optionally open with a short field-wide timeline/landscape. +// Give each method family a subsection, typically 3–6 in total. Add a timeline +// (a `stage` grid of eras) only if chronology explains a technical change. -#figure( - grid( - columns: (30%, 5%, 30%, 5%, 30%), - align: center + horizon, - stage([Era 1 (years)], [what defined it], rgb("eef3ff")), - align(center + horizon)[#text(size: 13pt)[→]], - stage([Era 2 (years)], [what defined it], rgb("eef6ef")), - align(center + horizon)[#text(size: 13pt)[→]], - stage([Era 3 (years)], [what defined it], rgb("fdf2e9")), - ), - caption: [Optional field landscape / timeline.], -) +== TODO: Approach one -== TODO: Approach One +*Mechanism.* TODO: the representation, objective, or operation that defines it. -*What it is.* TODO: the mechanism at one level of detail — the representation, objective, or trick that defines it. - -*State of the art.* TODO: the strongest current results, leading groups, maturity — lead with the best result, each cited @Example2024 @Example2025. +*Best evidence.* TODO: the strongest supported result and its conditions @Example2024 @Example2025. #proscons( list([TODO strength @Example2024.], [TODO strength @Example2025.]), list([TODO limitation @Example2024.], [TODO limitation @Example2025.]), ) -== TODO: Approach Two +== TODO: Approach two -*What it is.* TODO. +*Mechanism.* TODO. -*State of the art.* TODO @Example2025. +*Best evidence.* TODO @Example2025. #proscons( list([TODO strength @Example2025.]), list([TODO limitation @Example2025.], [TODO limitation @Example2024.]), ) -// ... repeat one == subsection per approach (3–6 total). Optionally add a final -// cross-cutting subsection (e.g. shared infrastructure) that is not itself an -// approach but every approach depends on. - -== At-a-glance comparison +== Comparison -// Optional: include when the field has several comparable approaches; drop for a -// single-approach topic. +// Optional. Compare only approaches with shared assumptions and criteria. +// Keep these native tables in the page flow so long tables can paginate. #compare_table(( - ([Approach One], [TODO @Example2024], [TODO], [TODO], [TODO]), - ([Approach Two], [TODO @Example2025], [TODO], [TODO], [TODO]), + ([Approach one], [TODO @Example2024], [TODO], [TODO], [TODO]), + ([Approach two], [TODO @Example2025], [TODO], [TODO], [TODO]), )) -= Open Problems += Open problems +// Rank 4–8 evidence-backed problems in a completed review. Priority is written +// explicitly, so neither color nor a legend is needed to read it. #problem_table(( - ([1], [TODO problem], [TODO why it matters @Example2024], [TODO who], [#text(fill: red.darken(10%))[Critical]]), - ([2], [TODO problem], [TODO why it matters @Example2025], [TODO who], [#text(fill: orange.darken(20%))[High]]), - ([3], [TODO problem], [TODO why it matters], [TODO who], [#text(fill: olive.darken(10%))[Medium]]), + ([1], [TODO problem], [TODO why it matters @Example2024], [TODO who], [Critical]), + ([2], [TODO problem], [TODO why it matters @Example2025], [TODO who], [High]), + ([3], [TODO problem], [TODO why it matters @Example2024], [TODO who], [Medium]), )) -#v(0.8em) - -#section_box( - [Bottom line], - [TODO: one tight paragraph — the synthesis / recommendation the reader should leave with @Example2024.], - fill: rgb("eef6ef"), stroke: rgb("bcd9c4"), -) - -#v(0.6em) +#v(6pt) +*Next step.* TODO: the measurement or result that would resolve the most +consequential uncertainty. Do not repeat the opening assessment. #bibliography("template.bib", title: "References", style: "ieee") diff --git a/tests/test_typst_reports.py b/tests/test_typst_reports.py new file mode 100644 index 0000000..a47bd49 --- /dev/null +++ b/tests/test_typst_reports.py @@ -0,0 +1,57 @@ +"""Render checks for report content that crosses page boundaries.""" + +import re +import shutil +import subprocess +from pathlib import Path + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] +pytestmark = pytest.mark.skipif( + not shutil.which("typst") or not shutil.which("pdftotext"), + reason="PDF toolchain not installed", +) + + +def test_survey_tables_paginate_with_repeated_headers_and_complete_rows(tmp_path): + for name in ("template.typ", "template.bib"): + shutil.copy(ROOT / "skills/survey" / name, tmp_path / name) + source = tmp_path / "template.typ" + with source.open("a") as stream: + stream.write(''' +#pagebreak() +#problem_table(range(45).map(i => ( + [#i], [Problem-#i], + [Evidence-#i: the result depends on the evaluation protocol and input distribution.], + [Evaluation team], [Critical], +))) +#pagebreak() +#compare_table( + (([Method A], [Evidence A]), ([Method B], [Evidence B])), + columns: (1fr, 2fr), headers: ([Method], [Evidence]), +) +#stage([Legacy stage], [Positional color remains supported], rgb("eef3ff")) +''') + pdf = tmp_path / "report.pdf" + compiled = subprocess.run( + ["typst", "compile", "--ignore-system-fonts", "--input", + "heading-font=Libertinus Serif", str(source), str(pdf)], + capture_output=True, text=True, + ) + assert compiled.returncode == 0, compiled.stderr + assert "warning:" not in compiled.stderr, compiled.stderr + extracted = subprocess.check_output( + ["pdftotext", "-layout", str(pdf), "-"], text=True, + ) + table_pages = [page for page in extracted.split("\f") if "Problem-" in page] + assert len(table_pages) >= 2 + for page in table_pages: + for header in ("Problem", "Why it matters", "Who can act", "Urgency"): + assert header in page + assert re.findall(r"Problem-(\d+)", extracted) == [str(i) for i in range(45)] + assert re.findall(r"Evidence-(\d+)", extracted) == [str(i) for i in range(45)] + for text in ("Method A", "Evidence A", "Method B", "Evidence B", + "Legacy stage", "Positional color remains supported"): + assert text in extracted From 88f31bc74db299f1d18e585fdbcd04222ce0bf91 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Wed, 16 Sep 2026 16:30:33 +0800 Subject: [PATCH 2/4] Fail survey tables on shape mismatch and test chat template fonts report_table asserts that headers match columns and every row matches the column count, naming the offending row, instead of shifting cells silently. problem_table gains the same headers/columns overrides as compare_table; stage defaults its fill; the survey template defines the sans fallback tuple once. The chat template accepts --input body-font so a bundled-fonts build is warning-free, and a new test renders it that way and checks the first prompt lands on page one with scope and coverage in the closing section. Co-Authored-By: Claude Fable 5.1 --- skills/dump-chat-history/SKILL.md | 6 +- skills/dump-chat-history/assets/report.typ | 5 +- .../references/typst-reference.md | 4 +- skills/survey/SKILL.md | 4 +- skills/survey/template.typ | 57 ++++++------ tests/test_typst_reports.py | 88 +++++++++++++++---- 6 files changed, 114 insertions(+), 50 deletions(-) diff --git a/skills/dump-chat-history/SKILL.md b/skills/dump-chat-history/SKILL.md index a695c0f..0e51b44 100644 --- a/skills/dump-chat-history/SKILL.md +++ b/skills/dump-chat-history/SKILL.md @@ -134,9 +134,9 @@ The template uses A4, quiet phase bands, serif prose, sans-serif headings, and monospaced original text, with portable font fallbacks. It permits long messages to continue across pages. Front matter is short: title, optional subtitle, and one date line, so the first prompt appears on the first page; scope, coverage, -and sources sit at the end. Change the heading font with -`--input heading-font="Your installed font"` (use `Libertinus Serif` for a build -with only bundled fonts). Add phases and concise annotations when useful; do not +and sources sit at the end. Change fonts with `--input heading-font="..."` and +`--input body-font="..."`; pass `Libertinus Serif` for both to build with only +Typst's bundled fonts. Add phases and concise annotations when useful; do not force a fixed page count, prompt ranking, productivity ratio, or research taxonomy. Ground outcome claims in the adjacent conversation or verifiable artifacts; distinguish reported historical checks from checks performed now. diff --git a/skills/dump-chat-history/assets/report.typ b/skills/dump-chat-history/assets/report.typ index 1f65c26..011e981 100644 --- a/skills/dump-chat-history/assets/report.typ +++ b/skills/dump-chat-history/assets/report.typ @@ -1,8 +1,9 @@ // Copy beside history.json; compile with: typst compile report.typ report.pdf -// Optional: --input heading-font="Your installed sans-serif font" +// Optional: --input heading-font="..." --input body-font="..." (installed fonts; +// pass "Libertinus Serif" for both to build with only Typst's bundled fonts) #let report = json("history.json") #let sans = (sys.inputs.at("heading-font", default: "Avenir Next"), "Libertinus Serif") -#let serif = ("Charter", "Libertinus Serif") +#let serif = (sys.inputs.at("body-font", default: "Charter"), "Libertinus Serif") #let mono = "DejaVu Sans Mono" #let ink = rgb("24282b") #let muted = rgb("63707b") diff --git a/skills/how-to-write-ideas-report/references/typst-reference.md b/skills/how-to-write-ideas-report/references/typst-reference.md index b138319..881b854 100644 --- a/skills/how-to-write-ideas-report/references/typst-reference.md +++ b/skills/how-to-write-ideas-report/references/typst-reference.md @@ -14,8 +14,8 @@ Reference files: ~/Documents/private-note/notes/typst-learn/ (typst-tricks.typ, - Numbered equations: `#set math.equation(numbering: "(1)")` ### Report hierarchy and legibility -- Give the document title its own text block, not a numbered heading. Use about 18 pt for the title, 13 pt for sections, and 11 pt for subsections. No kicker line or decorative front matter: the first content paragraph belongs on the first page. -- Use serif body text and a consistent sans-serif heading font. The report templates accept `--input heading-font="Your installed font"`; `Libertinus Serif` works without system fonts. Do not shrink body text below 10 pt to meet a page count. +- Give the document title its own text block, not a numbered heading. Use about 18–20 pt for the title, 13–14 pt for sections, and 11 pt for subsections. No kicker line or decorative front matter: the first content paragraph belongs on the first page. +- Use serif body text and a consistent sans-serif heading font. The report templates accept `--input heading-font="Your installed font"` (the chat-history template also `body-font`); `Libertinus Serif` works without system fonts. Do not shrink body text below 10 pt to meet a page count. - Justify body prose; keep table cells ragged right so narrow columns keep natural word spacing. Use at least 9 pt for tables and captions, and 8 pt for supporting metadata. - Reserve one dark accent and a light tint for navigation and brief assessments. Name categories and priorities explicitly so they remain readable in grayscale. - Use horizontal table rules, brief cells, and `table.header(repeat: true, ...)`. Keep long tables outside unbreakable figures or boxes. Match column widths to actual content, including the longest header and priority label. diff --git a/skills/survey/SKILL.md b/skills/survey/SKILL.md index 9b355bc..7d6cb00 100644 --- a/skills/survey/SKILL.md +++ b/skills/survey/SKILL.md @@ -80,7 +80,7 @@ Follow `skills/how-to-technical-writing/SKILL.md` for sentence- and paragraph-le - Save to `articles/YYYY-MM-DD--review.{md,typ,tex}` or a project-specific path if the user prefers. - For Typst, start from `skills/survey/template.typ` with `skills/survey/template.bib`. The scaffold provides `section_box`, `stage`, `proscons`, `compare_table`, and `problem_table`; delete unused helpers from the copied document. - Keep the opening scope and evidence cutoff brief. Follow with an **Assessment in brief** paragraph stating the principal finding, its evidence, and the unresolved constraint. Close with the next measurement that would resolve that constraint, rather than repeating the opening. -- Keep the template's dense, restrained layout: a plain title line, 10 pt justified body text, one accent color, and horizontal table rules. `compare_table` accepts custom `headers` and `columns`; choose them together. Write urgency labels as text. Keep long tables in the page flow so headers repeat across pages, and inspect the rendered result with realistic cell text. +- Keep the template's dense, restrained layout: a plain title line, 10 pt justified body text, one accent color, and horizontal table rules. `compare_table` and `problem_table` accept custom `headers` and `columns`; choose them together, since a count mismatch fails the compile with a message. Write urgency labels as text. Keep long tables in the page flow so headers repeat across pages, and inspect the rendered result with realistic cell text. - The heading font can be changed with `--input heading-font="Your installed font"`. For a build using only Typst's bundled fonts, use `--input heading-font="Libertinus Serif"`. ### Gap-filling focus @@ -124,7 +124,7 @@ End with a ranked table of 4–8 problems: number, problem, why it matters, who ### Visualization guidelines -- Typst: use CeTZ for timelines and dependency diagrams; use native `grid`, `rect`, and fixed-width `box()` for text-heavy comparisons and role diagrams. See `skills/how-to-write-ideas-report/references/typst-reference.md`. +- Typst: use CeTZ for timelines and dependency diagrams; use native `grid`, `block`, and fixed-width `box()` for text-heavy comparisons and role diagrams. See `skills/how-to-write-ideas-report/references/typst-reference.md`. - Use a native table for cross-approach comparisons. - Wrap multiline CeTZ content in a fixed-width box and use string identifiers for `name:`. - Compile after each figure; every claim in technical and open-problem tables needs at least one citation. diff --git a/skills/survey/template.typ b/skills/survey/template.typ index 335c43e..63037d9 100644 --- a/skills/survey/template.typ +++ b/skills/survey/template.typ @@ -5,10 +5,10 @@ #let title = [TODO: Review title] #let authors = "TODO: author / review draft" -#let review-date = [TODO: YYYY-MM-DD] -#let short-title = [Research review] // Short running title; keep to one line. -#let sans = sys.inputs.at("heading-font", default: "Avenir Next") +#let review_date = [TODO: YYYY-MM-DD] +#let short_title = [Research review] // Short running title; keep to one line. #let serif = "Libertinus Serif" +#let sans = (sys.inputs.at("heading-font", default: "Avenir Next"), serif) #let ink = rgb("24282b") #let muted = rgb("53616b") #let accent = rgb("245b65") @@ -19,15 +19,15 @@ #set page( paper: "a4", margin: (x: 18mm, y: 18mm), footer: context { - set text(font: (sans, serif), size: 8pt, fill: muted) - grid(columns: (1fr, auto), gutter: 12pt, short-title, counter(page).display()) + set text(font: sans, size: 8pt, fill: muted) + grid(columns: (1fr, auto), gutter: 12pt, short_title, counter(page).display()) }, ) #set text(font: serif, size: 10pt, fill: ink) #set par(justify: true, leading: 0.55em, spacing: 0.65em) #set heading(numbering: "1.1") #set list(indent: 1em, body-indent: 0.5em, spacing: 0.35em) -#show heading: set text(font: (sans, serif), weight: "bold") +#show heading: set text(font: sans, weight: "bold") #show heading.where(level: 1): set text(size: 13pt) #show heading.where(level: 2): set text(size: 11pt) #show heading.where(level: 1): set block(above: 1.3em, below: 0.55em) @@ -42,19 +42,19 @@ stroke: (left: 2pt + stroke), breakable: true, )[ #block(sticky: true, below: 4pt)[ - #text(font: (sans, serif), size: 9.5pt, weight: "bold", title) + #text(font: sans, size: 9.5pt, weight: "bold", title) ] #body ] // Cell for a flow, architecture, or timeline grid. Use arrows only for an // actual sequence or dependency. Names and descriptions must carry the -// relationship on their own; the fill is optional emphasis. -#let stage(name, body, fill) = block( +// relationship on their own; `fill` is optional emphasis. +#let stage(name, body, fill: tint) = block( width: 100%, inset: 7pt, fill: fill, stroke: 0.5pt + rule, )[ #align(center)[ - #text(font: (sans, serif), size: 9pt, weight: "bold", name) + #text(font: sans, size: 9pt, weight: "bold", name) #v(3pt) #text(size: 8.5pt, body) ] @@ -69,16 +69,23 @@ breakable: true, )[ #block(sticky: true, below: 3pt)[ - #text(font: (sans, serif), size: 9pt, weight: "bold", label) + #text(font: sans, size: 9pt, weight: "bold", label) ] #body ]), ) -// Rows are arrays of cells. Keep text brief; explain qualifications in prose. -// Horizontal rules separate records without boxing in every cell. Headers -// repeat when a table continues onto another page. -#let report-table(columns, headers, rows) = { +// Rows are arrays of cells; a row whose length differs from `columns` fails +// with a message naming the row. Keep text brief; explain qualifications in +// prose. Horizontal rules separate records without boxing in every cell. +// Headers repeat when a table continues onto another page. +#let report_table(columns, headers, rows) = { + assert(headers.len() == columns.len(), message: "report_table: " + + str(headers.len()) + " headers but " + str(columns.len()) + " columns") + for (i, row) in rows.enumerate() { + assert(row.len() == columns.len(), message: "report_table: row " + str(i + 1) + + " has " + str(row.len()) + " cells but the table has " + str(columns.len()) + " columns") + } set text(size: 9pt) set par(justify: false, leading: 0.45em) table( @@ -96,20 +103,20 @@ rows, columns: (1.1fr, 1fr, 1.25fr, 0.85fr, 1.4fr), headers: ([Approach], [Scalability], [Verification / cost], [Maturity], [Best use]), -) = report-table(columns, headers, rows) +) = report_table(columns, headers, rows) // Rank and urgency get enough room for their labels, not a fixed tiny fraction. -#let problem_table(rows) = report-table( - (auto, 1.2fr, 1.6fr, 1fr, auto), - ([No.], [Problem], [Why it matters], [Who can act], [Urgency]), +#let problem_table( rows, -) + columns: (auto, 1.2fr, 1.6fr, 1fr, auto), + headers: ([No.], [Problem], [Why it matters], [Who can act], [Urgency]), +) = report_table(columns, headers, rows) // Title is not a numbered section or an outline entry. #block(breakable: false, below: 10pt)[ - #text(font: (sans, serif), size: 18pt, weight: "bold", title) + #text(font: sans, size: 18pt, weight: "bold", title) #v(5pt) - #text(font: (sans, serif), size: 9pt, fill: muted)[#authors #h(1em) #review-date] + #text(font: sans, size: 9pt, fill: muted)[#authors #h(1em) #review_date] ] *Scope.* TODO: what this report assesses, who it is for, and what it excludes. @@ -130,11 +137,11 @@ TODO: define the topic for a new reader @Example2024. grid( columns: (1fr, auto, 1fr, auto, 1fr), gutter: 6pt, align: center + horizon, - stage([Concept A], [one-line role], tint), + stage([Concept A], [one-line role]), [→], - stage([Concept B], [one-line role], tint), + stage([Concept B], [one-line role]), [→], - stage([Concept C], [one-line role], tint), + stage([Concept C], [one-line role]), ), caption: [TODO: explain the relationship shown and the point it establishes.], ) diff --git a/tests/test_typst_reports.py b/tests/test_typst_reports.py index a47bd49..ed06c96 100644 --- a/tests/test_typst_reports.py +++ b/tests/test_typst_reports.py @@ -1,7 +1,9 @@ """Render checks for report content that crosses page boundaries.""" +import json import re import shutil +import sys import subprocess from pathlib import Path @@ -9,12 +11,26 @@ ROOT = Path(__file__).resolve().parents[1] +HISTORY = ROOT / "skills/dump-chat-history/helpers/history.py" pytestmark = pytest.mark.skipif( not shutil.which("typst") or not shutil.which("pdftotext"), reason="PDF toolchain not installed", ) +BUNDLED = ["--ignore-system-fonts", "--input", "heading-font=Libertinus Serif"] + + +def compile_pdf(source, pdf, *extra): + compiled = subprocess.run( + ["typst", "compile", *BUNDLED, *extra, str(source), str(pdf)], + capture_output=True, text=True, + ) + assert compiled.returncode == 0, compiled.stderr + assert "warning:" not in compiled.stderr, compiled.stderr + return subprocess.check_output(["pdftotext", "-layout", str(pdf), "-"], text=True) + + def test_survey_tables_paginate_with_repeated_headers_and_complete_rows(tmp_path): for name in ("template.typ", "template.bib"): shutil.copy(ROOT / "skills/survey" / name, tmp_path / name) @@ -23,7 +39,7 @@ def test_survey_tables_paginate_with_repeated_headers_and_complete_rows(tmp_path stream.write(''' #pagebreak() #problem_table(range(45).map(i => ( - [#i], [Problem-#i], + [#i], [Row-#i], [Evidence-#i: the result depends on the evaluation protocol and input distribution.], [Evaluation team], [Critical], ))) @@ -32,26 +48,66 @@ def test_survey_tables_paginate_with_repeated_headers_and_complete_rows(tmp_path (([Method A], [Evidence A]), ([Method B], [Evidence B])), columns: (1fr, 2fr), headers: ([Method], [Evidence]), ) -#stage([Legacy stage], [Positional color remains supported], rgb("eef3ff")) +#stage([Custom stage], [Named fill override], fill: rgb("eef3ff")) +#stage([Default stage], [Fill omitted]) ''') - pdf = tmp_path / "report.pdf" - compiled = subprocess.run( - ["typst", "compile", "--ignore-system-fonts", "--input", - "heading-font=Libertinus Serif", str(source), str(pdf)], - capture_output=True, text=True, - ) - assert compiled.returncode == 0, compiled.stderr - assert "warning:" not in compiled.stderr, compiled.stderr - extracted = subprocess.check_output( - ["pdftotext", "-layout", str(pdf), "-"], text=True, - ) - table_pages = [page for page in extracted.split("\f") if "Problem-" in page] + extracted = compile_pdf(source, tmp_path / "report.pdf") + table_pages = [page for page in extracted.split("\f") if "Row-" in page] assert len(table_pages) >= 2 for page in table_pages: for header in ("Problem", "Why it matters", "Who can act", "Urgency"): assert header in page - assert re.findall(r"Problem-(\d+)", extracted) == [str(i) for i in range(45)] + assert re.findall(r"Row-(\d+)", extracted) == [str(i) for i in range(45)] assert re.findall(r"Evidence-(\d+)", extracted) == [str(i) for i in range(45)] for text in ("Method A", "Evidence A", "Method B", "Evidence B", - "Legacy stage", "Positional color remains supported"): + "Custom stage", "Named fill override", "Default stage", "Fill omitted"): assert text in extracted + + +@pytest.mark.parametrize("call, message", [ + ("compare_table((([A], [B]),), columns: (1fr, 2fr), headers: ([H1], [H2], [H3]))", "3 headers but 2 columns"), + ("problem_table((([1], [P], [Why], [Who], [High]), ([2], [P], [Why], [Who])))", "row 2 has 4 cells but the table has 5 columns"), +]) +def test_survey_table_shape_mismatch_fails_with_named_location(tmp_path, call, message): + for name in ("template.typ", "template.bib"): + shutil.copy(ROOT / "skills/survey" / name, tmp_path / name) + source = tmp_path / "template.typ" + with source.open("a") as stream: + stream.write("\n#" + call + "\n") + compiled = subprocess.run( + ["typst", "compile", *BUNDLED, str(source), str(tmp_path / "bad.pdf")], + capture_output=True, text=True, + ) + assert compiled.returncode != 0 + assert message in compiled.stderr + + +def test_chat_history_pdf_opens_with_first_prompt_and_closes_with_sources(tmp_path): + def entry(identifier, text, **extra): + return {"id": identifier, "source": "codex", "session_id": "s1", "timestamp": "2026-09-07T00:00:00+08:00", + "role": "user", "kind": "prompt", "text": text, "source_id": "S1", + "provenance": "S1, record " + identifier, **extra} + data = { + "title": "A Long Topic Title That Should Not Collide With The Page Number In The Footer", + "subtitle": "Where the plan changed", + "window": {"start": "2026-09-03T00:00:00+08:00", "end": "2026-09-10T00:00:00+08:00", "timezone": "Asia/Shanghai"}, + "scope": "SCOPE-SENTINEL synthetic sessions.", + "coverage": "COVERAGE-SENTINEL fixtures only.", + "sources": [{"id": "S1", "location": "fixture.jsonl", "note": "Synthetic source."}], + "entries": [entry("P1", "FIRST-PROMPT-SENTINEL design the site.", phase="Phase 1: Scoping"), + entry("P2", "\n".join(f"Line {i:03}" for i in range(120)))], + } + source = tmp_path / "source.json" + source.write_text(json.dumps(data)) + out = tmp_path / "report" + subprocess.run([sys.executable, str(HISTORY), "render", str(source), "--outdir", str(out)], check=True) + extracted = compile_pdf(out / "report.typ", out / "report.pdf", "--input", "body-font=Libertinus Serif") + pages = extracted.split("\f") + assert len(pages) >= 2 + assert "FIRST-PROMPT-SENTINEL" in pages[0] + assert "Phase 1: Scoping" in pages[0] + assert "SCOPE-SENTINEL" not in pages[0] and "COVERAGE-SENTINEL" not in pages[0] + tail = extracted[extracted.index("Sources and limits"):] + assert "SCOPE-SENTINEL" in tail and "COVERAGE-SENTINEL" in tail + assert extracted.rindex("Line 119") < extracted.index("Sources and limits") + assert re.search(r"Page Number In The Footer\s+1\s*$", pages[0], re.M) From affc7dee688a0b9d96c5dd21e4b890ea688cfbc6 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Wed, 16 Sep 2026 16:58:04 +0800 Subject: [PATCH 3/4] Add key questions section to the survey template Rename "What and why" to "Overview" and add a "Key questions" section between it and the techniques: one subsection per subtopic with the question, why it matters (understanding and/or practical value), and where it stands. Strengths and limitations remain for techniques only. Co-Authored-By: Claude Fable 5.1 --- skills/survey/SKILL.md | 16 +++++++++++++--- skills/survey/template.typ | 29 +++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/skills/survey/SKILL.md b/skills/survey/SKILL.md index 7d6cb00..2666e9b 100644 --- a/skills/survey/SKILL.md +++ b/skills/survey/SKILL.md @@ -96,7 +96,7 @@ When the source set comes from an existing, recent `NOTES.md` (the normal case), Organize the review **by technical approach**. State of the art and trade-offs live inside each approach, not in separate global sections. Do not add standalone global "Pros and Cons" or "State of the Art" sections. -#### 1. What and Why +#### 1. Overview Define the topic in 2–3 paragraphs for a new reader: @@ -106,7 +106,17 @@ Define the topic in 2–3 paragraphs for a new reader: Include a diagram only when it clarifies the architecture, data flow, or problem framing. Lay approaches side by side only when they solve the same task and are genuinely comparable; otherwise show their relationship or omit the figure. -#### 2. Technical Approaches +#### 2. Key Questions + +Give one subsection to each subtopic or open question the field is trying to settle (typically 2–5). For each, cover: + +- **The question** — one sentence, stated as a question. +- **Why it matters** — what answering it gives: deeper understanding (what it would settle or unify) and/or practical value (what it would enable). Say which applies; a question with neither does not belong in the review. +- **Where it stands** — the best partial answer and its limits, cited. + +Questions are ends, not means: do not give them strengths and limitations. + +#### 3. Technical Approaches Identify the main method families (typically 3–6) and give one subsection per approach. Optionally begin with a short field-wide timeline or landscape. @@ -118,7 +128,7 @@ For each approach, cover: Optionally finish with a cross-approach comparison table when several approaches share meaningful criteria. Choose columns that actually discriminate this field (for example scalability, verifiability/cost, maturity, and best-fit use case). Skip it for a single-approach topic. -#### 3. Open Problems +#### 4. Open Problems End with a ranked table of 4–8 problems: number, problem, why it matters, who could solve it, and urgency (Critical / High / Medium). Cite the work that defines each gap or the closest existing result. Do not add business strategy, product fit, or investor sections to the neutral report. diff --git a/skills/survey/template.typ b/skills/survey/template.typ index 63037d9..3394110 100644 --- a/skills/survey/template.typ +++ b/skills/survey/template.typ @@ -127,7 +127,7 @@ State the evidence cutoff. Organize by technical approach. [TODO: the principal finding, the evidence supporting it, and the main unresolved constraint. Give the reader a reason to continue @Example2024.], ) -= What and why += Overview // 2–3 paragraphs: define the topic and problem, explain the stakes, then // distinguish it from the prior approach. Cite claims, not filler. @@ -146,9 +146,34 @@ TODO: define the topic for a new reader @Example2024. caption: [TODO: explain the relationship shown and the point it establishes.], ) += Key questions + +// One subsection per subtopic or open question the field is trying to settle, +// typically 2–5. A question earns its place by what answering it gives: +// deeper understanding, practical value, or both. Do not list strengths and +// limitations here; those belong to techniques. + +== TODO: Key question one + +*The question.* TODO: state it in one sentence. + +*Why it matters.* Understanding: TODO what an answer would settle or unify. +Value: TODO what it would enable in practice @Example2024. + +*Where it stands.* TODO: the best partial answer and its limits @Example2025. + +== TODO: Key question two + +*The question.* TODO. + +*Why it matters.* Understanding: TODO. Value: TODO @Example2025. + +*Where it stands.* TODO @Example2024. + = Technical approaches -// Give each method family a subsection, typically 3–6 in total. Add a timeline +// Give each method family a subsection, typically 3–6 in total. Strengths and +// limitations apply here, to techniques, not to questions. Add a timeline // (a `stage` grid of eras) only if chronology explains a technical change. == TODO: Approach one From bb3cd530c6c69d8d6af0f037b29e76468d3456d8 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 18 Sep 2026 11:22:38 +0800 Subject: [PATCH 4/4] Simplify survey report opening and closing --- skills/survey/SKILL.md | 11 +++++------ skills/survey/template.typ | 38 +++++++++----------------------------- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/skills/survey/SKILL.md b/skills/survey/SKILL.md index 2666e9b..080febc 100644 --- a/skills/survey/SKILL.md +++ b/skills/survey/SKILL.md @@ -78,8 +78,8 @@ Follow `skills/how-to-technical-writing/SKILL.md` for sentence- and paragraph-le - Check `CLAUDE.md`/`AGENTS.md` for a deliverables-location convention before choosing an output path. - Tailor technical depth to the user's role from `docs/discussion/user-profile.md` or available context. - Save to `articles/YYYY-MM-DD--review.{md,typ,tex}` or a project-specific path if the user prefers. -- For Typst, start from `skills/survey/template.typ` with `skills/survey/template.bib`. The scaffold provides `section_box`, `stage`, `proscons`, `compare_table`, and `problem_table`; delete unused helpers from the copied document. -- Keep the opening scope and evidence cutoff brief. Follow with an **Assessment in brief** paragraph stating the principal finding, its evidence, and the unresolved constraint. Close with the next measurement that would resolve that constraint, rather than repeating the opening. +- For Typst, start from `skills/survey/template.typ` with `skills/survey/template.bib`. The scaffold provides `stage`, `proscons`, `compare_table`, and `problem_table`; delete unused helpers from the copied document. +- Put only the review date beneath the title. Open with the single **Overview** described below, and end the body with **Open Problems**, followed by references. Do not add a separate scope block, assessment box, or closing "Next step" section. - Keep the template's dense, restrained layout: a plain title line, 10 pt justified body text, one accent color, and horizontal table rules. `compare_table` and `problem_table` accept custom `headers` and `columns`; choose them together, since a count mismatch fails the compile with a message. Write urgency labels as text. Keep long tables in the page flow so headers repeat across pages, and inspect the rendered result with realistic cell text. - The heading font can be changed with `--input heading-font="Your installed font"`. For a build using only Typst's bundled fonts, use `--input heading-font="Libertinus Serif"`. @@ -98,11 +98,10 @@ Organize the review **by technical approach**. State of the art and trade-offs l #### 1. Overview -Define the topic in 2–3 paragraphs for a new reader: +Write two connected paragraphs for a new reader, without inline labels: -- What it is and what problem it solves -- Why it matters now -- How it differs from the dominant or prior approach +- Define the topic and problem, explain why it matters, and state the report's scope and intended audience. Distinguish it from the prior approach where that helps define the topic. +- State the principal finding and its supporting evidence, then identify the unresolved constraint that motivates the key questions below. Cite the claims. Include a diagram only when it clarifies the architecture, data flow, or problem framing. Lay approaches side by side only when they solve the same task and are genuinely comparable; otherwise show their relationship or omit the figure. diff --git a/skills/survey/template.typ b/skills/survey/template.typ index 3394110..f86034c 100644 --- a/skills/survey/template.typ +++ b/skills/survey/template.typ @@ -4,7 +4,6 @@ // Optional: --input heading-font="Your installed sans-serif font" #let title = [TODO: Review title] -#let authors = "TODO: author / review draft" #let review_date = [TODO: YYYY-MM-DD] #let short_title = [Research review] // Short running title; keep to one line. #let serif = "Libertinus Serif" @@ -15,7 +14,7 @@ #let tint = rgb("f2f6f6") #let rule = rgb("cbd4d7") -#set document(title: title, author: authors) +#set document(title: title) #set page( paper: "a4", margin: (x: 18mm, y: 18mm), footer: context { @@ -36,17 +35,6 @@ #show figure.caption: set text(size: 9pt, fill: muted) #set figure(gap: 6pt) -// Short assessments only. Ordinary prose needs no container. -#let section_box(title, body, fill: tint, stroke: accent) = block( - width: 100%, inset: 9pt, fill: fill, - stroke: (left: 2pt + stroke), breakable: true, -)[ - #block(sticky: true, below: 4pt)[ - #text(font: sans, size: 9.5pt, weight: "bold", title) - ] - #body -] - // Cell for a flow, architecture, or timeline grid. Use arrows only for an // actual sequence or dependency. Names and descriptions must carry the // relationship on their own; `fill` is optional emphasis. @@ -116,23 +104,19 @@ #block(breakable: false, below: 10pt)[ #text(font: sans, size: 18pt, weight: "bold", title) #v(5pt) - #text(font: sans, size: 9pt, fill: muted)[#authors #h(1em) #review_date] + #text(font: sans, size: 9pt, fill: muted)[#review_date] ] -*Scope.* TODO: what this report assesses, who it is for, and what it excludes. -State the evidence cutoff. Organize by technical approach. - -#section_box( - [Assessment in brief], - [TODO: the principal finding, the evidence supporting it, and the main unresolved constraint. Give the reader a reason to continue @Example2024.], -) - = Overview -// 2–3 paragraphs: define the topic and problem, explain the stakes, then -// distinguish it from the prior approach. Cite claims, not filler. -TODO: define the topic for a new reader @Example2024. +// Two connected paragraphs, without inline labels or an assessment box. +TODO: define the topic and problem, explain why it matters, and state the +report's scope and intended audience @Example2024. +TODO: state the principal finding and its supporting evidence, then identify +the unresolved constraint that motivates the key questions below @Example2025. + +// Optional: keep only when it clarifies the topic or relationships. #figure( grid( columns: (1fr, auto, 1fr, auto, 1fr), gutter: 6pt, @@ -217,8 +201,4 @@ Value: TODO what it would enable in practice @Example2024. ([3], [TODO problem], [TODO why it matters @Example2024], [TODO who], [Medium]), )) -#v(6pt) -*Next step.* TODO: the measurement or result that would resolve the most -consequential uncertainty. Do not repeat the opening assessment. - #bibliography("template.bib", title: "References", style: "ieee")