ESP32-S3 Bench Guide
ESP32-S3 N16R8 bench guide: live pinout, lessons, simulators and the bench log.
翻译准备中
正在加载评论。
正在确认登录状态。
正在加载完整 README。 以下为已保存的摘录。
psi-slides turns one Markdown file into four HTML files: the projection for the room, a presenter cockpit for your own screen, a reading document, and a handout with the spoken notes folded in.
The problem it solves is drift. Most lecturers keep slides and a script as two documents, and after two semesters they disagree with each other. psi-slides makes them one text: the prose you write is the handout, and the same prose – abridged by a rule you control – is what the projector shows. Nothing is written twice, so nothing can fall out of sync.查看完整 README ↗psi-slides turns one Markdown file into four HTML files: the projection for the room, a presenter cockpit for your own screen, a reading document, and a handout with the spoken notes folded in.
The problem it solves is drift. Most lecturers keep slides and a script as two documents, and after two semesters they disagree with each other. psi-slides makes them one text: the prose you write is the handout, and the same prose – abridged by a rule you control – is what the projector shows. Nothing is written twice, so nothing can fall out of sync.
psi-slides is a build script. node build.js source.md writes four HTML files next to your source, and each of them carries everything it needs: the styling, the scripts, the images, the typeset maths, and the typefaces – three families ship with the tool and are embedded in each output, so a lecture looks the same on a machine that has none of them installed. Nothing is fetched at run time, from anywhere; open one in a browser with the network unplugged and it is complete. There is no server and no cloud account, and nothing has to be installed on the lectern machine. To give the audience the slides, or the full manuscript, you send them one file.
psi-slides has already carried a full semester of university teaching. Read When not to use this before you invest in it.
Five lectures are published there, each in all four views. Walk one with the space bar, press S for the cockpit and O for the overview board, and read the handout the same source produced – before installing anything.
The core idea in two pictures. A chunk is the unit psi-slides works in – roughly one slide's worth of text – and the two pictures below are one chunk rendered twice from the same source. C toggles between the collapsed view, which is what the projector shows, and the full manuscript text.
| Collapsed – what the projector shows | Full – the same chunk, unabridged |
|---|---|
You did not author two versions. You wrote the right-hand text and marked which fragments matter; the left-hand slide is derived from it.
The presenter cockpit (S opens it from the audience view). Column scrubber on top, a mirror of the projector, your private notes below, upcoming chunks down the right edge. Both windows stay in step: which slide is up, how much of it is uncovered, zoom, theme, figure focus, laser pointer.
The overview board (O), borrowed from Prezi: zoom out to the whole lecture at once, drag to pan, / to search, Enter to land. In a lecture you did not write yourself, the board is where you get oriented – the typographic rhythm of principles, examples and figures is visible at a glance.
The handout (print-notes.html). The document version of the same lecture with every > note: rendered as an aside – “what was on the slide, plus what the lecturer said”, in one PDF-able page flow. There is also a plain print.html without the notes, for students.
There are two ways in. If you would rather not open a terminal, take the
desktop builder from the
releases page: a window that
you point at a source.md and leave open beside your editor, building the
four views again on every save. It is part of every release, it carries that
release's engine inside it, and nothing about it leaves your computer.
macOS has been tried on a real machine; the Windows and Linux packages are
built by CI and are experimental, so a report of what breaks is welcome.
desktop/README.md has the details.
The command line is the other way, and it is what CI and this repository use. It requires Node 22 or newer. Nothing else: no LaTeX, no Pandoc, no server, nothing installed globally.
# the latest release, unpacked into psi-slides/
curl -L https://github.com/UBA-PSI/psi-slides/releases/latest/download/psi-slides.tar.gz \
| tar xz
cd psi-slides
npm install
node build.js lectures/tutorial/source.md
open lectures/tutorial/audience.html # macOS; use xdg-open or your browser otherwise
On Windows take the .zip from the releases page. Both the release archive and a git clone include example lectures already built, so you can open one before running anything. A clone follows current development; the tagged archive does not.
That builds the self-referential tour: a lecture that teaches the basics of the tool by being the tool, and hands over to lectures/diagrams/ for figures and lectures/decoration/ for the frame, type and colour. Press ? for the cheat sheet, S to open the cockpit, O for the overview, C for collapse. Its source, lectures/tutorial/source.md, is the authoring reference.
Start your own:
node build.js --new my-lecture # scaffold lectures/my-lecture/source.md
node build.js --new my-lecture --into ~/talks # …or scaffold it somewhere else
node build.js lectures/my-lecture/source.md --watch # live reload on every save
node lint.js lectures/my-lecture/source.md # static checks
--watch reloads every open tab on every save, so you can keep your text editor, the audience view and the cockpit visible at once.
All four come from one source.md and are written next to it.
| File | What it is |
|---|---|
audience.html | The projection. One chunk on the stage, camera pans between them, collapse on by default. |
speaker.html | The cockpit. Opens from the audience view with S; the two windows then keep each other in step directly, with no server in between. |
print.html | A reading document with a cover and a table of contents. All reveals shown, all expansions inlined. A kind: document source builds this view alone. |
print-notes.html | The same document with > note: blocks folded in under their chunk. |
Each is a single file. Mail one to a colleague as an attachment and it works.
There is a fifth output that is not a view: node build.js <source.md> --slides-pdf
drives the audience view through every presentation state in a headless Chromium
and prints slides.pdf, one page per state. It is the fallback for a room where
the HTML will not run, and a classic deck you can hand on. It does not replace
the two print views – those are documents, this is slides. The documents have
PDF flags of their own: --print-pdf writes print.pdf and --print-notes-pdf
writes print-notes.pdf, on A4 with the print view's own margins and page
numbers. Any of the three combine and start the browser once.
A lecture is columns of chunks. A column is a # heading; a chunk is a ## heading with a type, and its body is ordinary Markdown.
---
title: Anonymous Communication
presenter: Dominik Herrmann
course: advasp
---
# Why mixes need a crowd {#crowd}
## principle: Anonymity is a property of the set | not of the channel {.wide #anon-set}
**Anonymity comes from the others doing the same thing.** A mix node that
forwards exactly one message leaks it by timing alone.
The size of the anonymity set is therefore a property of the **traffic**, not
of the protocol.
> note: Ask the room for the smallest set they would trust. Answers cluster
> around 100 and the reasoning is always worth two minutes.
The grammar is ## type: Heading | Sub-heading {.width #id}. Ten types (title, closing, outline, principle, definition, example, question, figure, exercise, free) set the visual treatment and a word budget the linter enforces; four widths (narrow, standard, wide, full) set how much stage the chunk takes. Adding {.bare} to the same braces keeps a heading in the document, the contents page and the search index and takes it off the projection – for the talk that is a run of figures and still needs a name per slide.
What lands on the slide is decided per chunk, by one of two mechanisms:
**bold** fragments. It imposes a discipline: every paragraph has to open with a claim that stands on its own. Bold selects rather than stresses – a promoted phrase is set plain by default, and *em* inside it stresses one word.::: slide block is the screen, everything else is narration. Or ::: script, the other way round: the chunk is the screen and only the marked block is narration. Reach for these when the argument wants continuous prose that no first-sentence rule can carve up.Everything else is body-level directives: --- on its own line splits a chunk into reveal segments; ::: expand <label> hides detail behind a chevron; ::: cols 2, ::: side 2:1 and ::: flip shape internal layout; ::: cards 3 and ::: rows lay items out as containers rather than as a text flow, so an item is whole or it is nowhere; ::: footnote and ::: marginalia place asides;  resolves against assets/; $inline$ and $$display$$ are math, rendered by KaTeX during the build. Sixteen directives in all; the tutorial, lectures/diagrams/ and lectures/decoration/ show them live, ::: dock among them in the last.
A slide can carry more than a text column. ::: backdrop <ref> puts a picture behind the whole slide, edge to edge, and ::: overlay lays a block of type over it. Both are written at chunk level rather than inside the body, because the text column cannot reach the edges of the slide. A backdrop's window can walk the reveal beats – reveal full, right 45% retreats the picture to free the paper the title is written on, and adding over to the same {...} makes the same list, run the other way, grow the picture over the title until it covers it. ::: overlay {…} from 1 is the counterpart for the words. An overlay with .panel grows to the frame – a column the full height, a band the full width, or the whole frame veiled – which is how a photograph gets a text area set off from it. ::: dock is the same vocabulary with the other contract: it is part of the frame and the text column yields to it, a running table of contents beside every slide of a part (.every under the # heading, with the live item lit), a line that stays under the words, a remark that arrives on a beat into a track kept free for it. A --- inside any of these blocks is a beat on the slide's own counter.
cover: picks one of ten opening compositions, ordered quiet to loud: the type in the lower-left third; a nameplate over a lede; a block centred on both axes; a title set to fill the slide; a full field of the accent colour; a claim the talk opens on; a photograph run off one edge or filling the frame; and the title chunk's own ::: draw figure set beside the title or above it. ## closing: draws the same composition at the end with the author's own words, so the lecture closes on the shape it opened with. section: gives a column's divider six treatments, every one quieter than the cover – including a running agenda that lists every part and marks the live one. A divider can also carry its own slide: the lines between a # heading and the first chunk are a quotation, a photograph or a figure, whichever the author writes there. lectures/decoration/ shows all of it in one lecture.
Figures are written, not drawn. ::: draw is a small boxes-and-arrows language compiled to inline SVG at build time: elements are named and placed against one another rather than on a canvas, arrows stay attached to boxes that move, and a figure's steps advance on the same key the reveal segments do. Some statements write those boxes, texts and edges for you: a column chart, a repeated cell grid, a cartesian frame, a table of labelled cells, a set of swimlanes, and sequence, which draws a protocol down the page – one lifeline per actor, numbered messages between them, notes and self-messages. Every part sequence draws keeps a name, so hanging an annotation off one message is an ordinary line of source rather than something sequence has to support. figure-design.md is how to lay one out and docs/artifact/ teaches the language from nothing.
Typefaces travel with the file. Three families ship in any one output, and which three is a per-lecture decision: Literata, Source Serif 4, Bitter, Noto Serif or Roboto Serif for the serif, IBM Plex Sans or Inter Tight for the sans, JetBrains Mono or Noto Sans Mono Condensed for the monospace – all under the SIL Open Font License, which permits exactly this. Naming one of them in the fonts: block needs no file of your own. ligatures: decides separately what a listing does with -> and !=: text keeps the ordinary fi and fl in prose and leaves code alone (the default), all puts the code ligatures back, none removes both. Safari does not expose locally installed fonts to a page at all, as an anti-fingerprinting measure, so a lecture that merely names its typefaces falls back to Georgia and the system sans there whatever the reader has installed. The bundle costs about 280 KB per file; fonts: none in the frontmatter turns it off.
The transition slides can have a face of their own. fonts: {display: Anton} names a fourth role, and it reaches the cover, the closing slide and the section dividers and nothing else: an ordinary chunk heading, a card, a figure label all keep the body type. Those three slides are the one place in a deck where a loud typeface is not a mistake, because nobody reads a divider, they see that one has arrived. Thirty-two faces ship for it, all under the SIL Open Font License, in three flavours – handwritten, pixel grids and terminals, and poster type. Each carries a size correction measured against the body serif, because these faces differ in width by a factor of three and without it the condensed ones look timid and the wide ones run off the slide; style: {display-scale: 1.4} is there for when the face is right and the size is not. A deck that names no display face embeds nothing and builds exactly as before. Neither of the reader's keys can disturb it: F cycles the body face and A the accent theme, and the cover and the dividers ignore both, though their colour still follows the theme so they stay readable in dark. lectures/display-face/ is a short deck that shows it.
To use your own instead, drop the files into fonts/ beside your source and name the families:
fonts:
serif: Literata
sans: IBM Plex Sans
mono: JetBrains Mono
Files are matched by name, with weight and style read off the suffix (Literata-Bold.woff2, Literata-600italic.woff2, Literata[wght].woff2). .woff2, .woff, .ttf and .otf all work; woff2 is much the smallest. A role you name uses your font, a role you leave out keeps the bundled one, and naming a family with no matching file fails the build rather than falling back quietly.
Check the licence before you embed. Embedding redistributes the font file. The SIL Open Font License and Apache-2.0 – between them nearly every family on Google Fonts – permit this; most commercial desktop licences do not, and require a separate webfont licence. psi-slides prints a reminder and makes no attempt to verify anything. It is your call and your responsibility.
Six optional frontmatter keys pin how a lecture opens – font, theme, collapse, auto-fit (true, false, or shrink, which only ever makes a slide smaller), slide-numbers, and print-slide-numbers, which follows slide-numbers unless you say otherwise. A key that is present wins over the reader's stored preference; a key that is absent leaves it alone, so a lecture that pins nothing still follows whatever the reader last chose. The composition keys are separate and are not preferences at all: cover, cover-image, cover-ratio, cover-align, section, section-mark, ligatures, lang (which also selects the words the build invents – the TOC heading, the note labels, the type eyebrow – so a German lecture reads German furniture), a top-level labels: block that overrides any one of those words, and a style: block carrying heading alignment, the hairlines and labels, block alignment, hyphenation, the printed face, the look of a bold phrase per view, and the two type scales. A key with an unknown value fails the build rather than being ignored, because a typo there is otherwise invisible – the lecture still builds and looks fine, it just looks like the author never set anything.
editor: both | speaker | none is checked the same way but is neither of those things: it decides which of the two live views carries the experimental figure editor, so what it changes is what ships in the file rather than how the lecture looks, and there is no reader preference for it to yield to. It defaults to both. The editor is built for a desktop and has substantial automated test coverage, but it has not yet been tried by many people.
Video uses the same shorthand as an image.  finds assets/clip.mp4 and inlines it, up to a 12 MB per-file cap and while the deck's pictures and clips together stay under the 10 MB auto-inline budget; a clip past either, the build copies to a videos/ folder beside the output, plays from there, and tells you the output now needs that folder. --inline-images inlines each clip up to its cap. A written-out URL works too –  – and is still an ordinary <video>, so play, pause and seeking stay synchronised between the projection and the cockpit.
Hosted players are a directive of their own, ::: embed <url>, for YouTube and Vimeo. They are the one thing that makes an output fetch from a third party while you present, so the build says so every time. The frame loads only once its chunk is on screen and unloads when you leave it, nothing autoplays, and play/pause synchronise between projection and cockpit. YouTube additionally needs a real origin, so from a file:// page it shows a card telling you to run --serve; the tutorial explains the whole thing.
Self-test questions go into the two documents and nowhere else. A ::: pulse block under a chunk holds a question, one line of --- and the answer; print.html and print-notes.html show the question with the answer folded behind a button, and the projection and the cockpit leave it out. The widget is Pulse, inlined into a document only when that document asks something. It sends nothing until the reader signs in with an email address; after that, their answers are kept in their account and the questions come back by mail at growing intervals. Printed on paper, each answer stands under its question. The tutorial has a part on it, and its print.html carries a few of them.
A document instead of a lecture. kind: document in the frontmatter is for prose that is read and never projected – a course syllabus was the first. The build then writes print.html alone (and print.pdf with --print-pdf); a flag that needs a projection, a cockpit or the notes view is refused by name. The parts are numbered 1, 2, … and their chunks 1.1, 1.2, … in the headings, the printed contents and the reader's sidebar, so the source carries no numbers; the slide number and the type word over a chunk are gone, and the title opens the first page instead of standing on a cover sheet. A ::: marginalia sits in the right margin beside the paragraph written after it, Tufte style, on a wide screen and on paper, and falls back to an indented aside on a phone; a chunk may carry several. ### Heading {#id} gives a subsection an anchor for [text](#id), and the linter stops counting a chunk's words against a slide's budget. A lecture's handouts set their ::: marginalia and ::: footnote in the margin the same way; style: {print-marginalia: inline} keeps them in the column. The masthead carries info: (a version line, the university) quietly under the presenter.
Both documents follow a dark screen. print.html and print-notes.html – a lecture's script as much as a document – are dark on a screen whose system is dark, and the contents sidebar has two switches for the reader: the page light, dark or as the system has it, and the text in the serif or the sans. Both are remembered in the reader's browser. Printing and the PDF always get the author's light page.
Two kinds of note are easy to confuse. A note (> note:) is yours, written in advance, shown in the cockpit and in the handout. An annotation (N during a talk) is typed live and the room sees it: while you type it fills the frame at a size the room can read, a single word large and centred, several lines as a block, an address with a QR code above it; Esc leaves it as a margin note. Shift-E plus --integrate-annotations writes annotations back into source.md as permanent text.
A lecture source is a good thing to hand a language model. It is plain
Markdown with a small, closed grammar: eleven types, five widths, sixteen :::
directives, one reveal separator. There is nothing to guess at and no binary
format in the way, so a model that has been shown the rules produces sources
that build and lint on the first pass. Diffs stay reviewable, because the unit
of change is a paragraph of prose rather than a slide object.
What still needs you: the argument, the examples, and the judgement about what belongs on the screen. A model is useful for turning a draft into chunks, proposing IDs and widths, tightening topic sentences so the collapsed view reads well, and fixing what the linter flags.
Hand it one artefact rather than five. This repo ships a skill at
.claude/skills/psi-slides-authoring/
that bundles the whole authoring contract – grammar, directives, collapse
mechanisms, math, frontmatter – plus a companion file on topic sentences,
bold discipline, and typography. Claude Code picks it up automatically inside
this repository or a content repo that has a copy; for any other assistant,
paste the two files into the context.
Outside this repository, the six skills in .claude/skills/ are downloads on
the project site, as one archive or one per skill, with the files they refer to
linked or bundled. They use the open Agent Skills
format, which Claude Code, claude.ai, Codex, Gemini CLI and
other tools read; a tool without skill
support can be given a SKILL.md as instructions like any other text. The page
gives the steps for Claude Code and claude.ai as two examples:
Writing with an AI assistant.
If you would rather not use the skill, the minimum useful set is:
lectures/tutorial/source.md – the canonical
reference for the basics; with lectures/diagrams/ and lectures/decoration/
it shows every directive in real use.CLAUDE.md – the conventions, the parsing contract, and the
things that are easy to get wrong.node lint.js <source.md> after every edit. It catches unknown types and
widths, missing or duplicate IDs, unclosed directives, over-budget chunks
and frontmatter keys that no renderer reads, which is most of what a model
gets wrong.Two things go wrong often enough to be worth naming. Models invent plausible
directives that do not exist (::: columns, ::: note, extra classes in
{...}) – the linter catches those. And they renumber {#id} attributes when
they rewrite a heading, which silently breaks cross-references, contents
entries, and stored speaker state; say so up front, because no check will catch
it.
.pptx or Keynote interop, or a corporate template. There is no export path. The output is HTML; the only bridge to a slide deck is printing to PDF.transition:), and nothing more elaborate is planned. The other motion is three mechanisms, all of them on the same key and all of them inside one slide: reveal segments uncover blocks of text in place; a ::: draw block can be stepped, so elements appear, disappear, move, and the arrows between them re-route as they go; and a ::: backdrop can open or close its window over the slide, which is how a title is revealed in the paper a photograph gives up.$inline$ and $$display$$ work and render at build time, but that is KaTeX, not LaTeX: no equation numbering or \ref, no mhchem, no TikZ. If your lecture is a mathematics lecture, check KaTeX's supported functions before committing.PRD.md. What exists is ::: pulse, a question in the two documents that a reader answers afterwards; the projection carries none.build.js of over thirty-two thousand lines, one maintainer, and a test suite of fast checks without a browser plus a browser suite for what only a built page can break. The source format is stable within a major version; nothing behind it is promised. It is used in earnest, but it is used by the person who wrote it.What is different here is the combination: one text rendered at two densities, a presenter cockpit that needs no server, and a prose handout – all from a single source and all in files that fetch nothing at run time. Every tool in this space is good, and nearly all of them take Markdown, so “it uses Markdown” is not a reason to pick this one. Beamer beats it on math, citations and sheer durability; Quarto is broader and better supported; reveal.js, Marp and Slidev are better at being slide decks; PowerPoint wins the moment a colleague has to edit your file.
docs/comparison.md is the long version: nine alternatives across twenty dimensions, including the ones psi-slides loses on.
file://.oklch() colours, :has(), and text-wrap: balance with no fallbacks, which puts the floor at roughly Chrome/Edge 114, Firefox 121, Safari 17.5. Lectures with inline-styled SVG assets additionally need @scope: Chrome/Edge 118, Safari 17.4, Firefox 146. Development and real use are in Chrome; other browsers are untested rather than unsupported.--slides-pdf, --print-pdf or --print-notes-pdf. playwright-core is an optional dependency, so npm install normally provides one; $PSI_CHROME, the Playwright cache and a system Google Chrome are searched in that order. Nothing else in the build needs a browser, and the PDF export is the only thing that stops working without one. The desktop builder needs none of this: it prints with its own Chromium.cwebp or magick on PATH for WebP. --optimize-images needs one and refuses without it; the default build uses one to transcode an inlined PNG or JPEG and, without one, inlines the original bytes and says so. macOS sips cannot write WebP, so there is no zero-install fallback.--optimize-images converts the offenders to WebP (and downscales a photograph that WebP alone does not bring under the cap), and --no-inline-images is the escape hatch.file://. Only the font families a lecture's formulas actually use are inlined – the tutorial's five come to 166 KB of the 254 KB the full set costs – and a lecture without math inlines none of it. The build prints what it did.| Where | What for |
|---|---|
lectures/tutorial/source.md | The basics of authoring, with pointers on. Build it and read it as a lecture. |
lectures/python-intro/ | The richest worked example – 39 chunks, the full layout vocabulary. |
lectures/diagrams/ | Every ::: draw statement drawn rather than described, starting with the language in five lines, with real lecture figures among them. |
lectures/decoration/ | Everything that decorates a slide, drawn rather than described: the cover family, the six dividers and the three kinds of divider content, the credit ranks, cards and rows, backdrops with a reveal, overlays and panels, docks, beats below the top level, {.bare} headings, and type and colour (the style: block, fonts). |
lectures/title-block/ | The title pair and the credit block: headline: eyebrow, caps: on, all four credit ranks and closing-credits: cover. |
lectures/display-face/ | The fonts: {display: …} role: what it reaches (cover, dividers, closing slide), the 32-face roster and how a face is paired with a body face. |
figure-design.md | How to lay out a ::: draw block so a room can read it. Rules with a wrong and a right version each, in real syntax, and a checklist. |
Which document is authoritative. For the source format, the
psi-slides-authoring skill and the three tracked lectures (tutorial,
diagrams, decoration); for the engine's internals, CLAUDE.md, which a
gate holds against the command line. PRD.md and HANDOFF.md record how
decisions were made and are not kept current: where one of them disagrees
with the code, the code wins. CHANGELOG.md, CLAUDE.md, HANDOFF.md,
PRD.md and the open plans are left out of the release archive and live in
the repository.
The diagram editor is experimental: automated tests cover it extensively, but broad human testing has not happened yet. The same goes for the editor: frontmatter key, which decides whether the live views carry the editor at all.
node build.js --help # every flag, grouped (also -h)
node build.js <source.md> # build all four views
node build.js lectures/my-lecture # …or name the folder that holds it
node build.js <source.md> --present # give or rehearse the talk: watch, serve, open the cockpit
node build.js <source.md> --watch # live reload
node build.js <source.md> --serve # serve over http on loopback
node build.js <source.md> --serve --port 8080 # on a fixed port (default: the lecture's own)
node build.js <source.md> --watch --serve # both
node build.js <source.md> --watch --serve --open # …and open the cockpit in a browser
node build.js <source.md> --audience-only # also --print-only, --print-notes-only, --speaker-only
node build.js --new <slug> [--into <dir>] # scaffold a lecture (default: lectures/)
node build.js <source.md> --inline-images # force inlining
node build.js <source.md> --no-inline-images # force external asset paths
node build.js <source.md> --optimize-images --dry-run # report oversized rasters
node build.js <source.md> --optimize-images # convert them to WebP in place
node build.js <source.md> --integrate-annotations # fold exported live annotations back in
node build.js <source.md> --slides-pdf # a PDF slide deck, one page per beat
node build.js <source.md> --slides-pdf --pdf-beats=final # one page per chunk instead
node build.js <source.md> --slides-pdf --pdf-size=16:10 # default is 16:9
node build.js <source.md> --slides-pdf --pdf-zoom=1.2 # one zoom for every page
node build.js <source.md> --slides-pdf --pdf-collapse=topic-bold # slide text, not the full prose
node build.js <source.md> --slides-pdf --pdf-zoom-max=1.6 # let pages fill more of the sheet
node build.js <source.md> --slides-pdf --pdf-out=<path> # default: slides.pdf beside source.md
node build.js <source.md> --print-pdf # the document as print.pdf, A4
node build.js <source.md> --print-notes-pdf # the document with notes as print-notes.pdf
node build.js <source.md> --slides-pdf --print-pdf # several PDFs, one browser
node build.js <source.md> --watch --prompter # the live prompter in the cockpit
node build.js <source.md> --watch --prompter --prompter-model MODEL_ID
node build.js <source.md> --watch --prompter --prompter-dry-run
# …everything except the call to the model,
# so a rehearsal needs no key
node build.js <source.md> --prompter-replay prompter-DATE.jsonl
# read a finished run's log back: what the model
# proposed, what today's rules would let through
node build.js <source.md> --prompter-debrief # the last talk read back: the clock, the time table
# against the @mm:ss marks, what was whispered
node build.js <source.md> --prompter-debrief --transcript
# …or only the spoken words, slide by slide
node build.js <source.md> --squint # write what the projection paints to squint.txt
node build.js <source.md> --check-fit # report any slide that fits the frame and sits outside it
node lint.js lectures/ # all lectures
node lint.js lectures/ --strict # warnings exit 2
A flag build.js does not know is refused with the nearest one it does (--wacth – did you mean --watch?), and so is a flag that would change nothing where it stands, such as --port without --serve or --check-fit together with --watch.
The linter checks unknown types and widths, duplicate or missing chunk IDs, unclosed ::: directives and unclosed $$ math, per-type word budgets, duplicate explicit-slide blocks, assets over the inline cap, reveal overuse, orphan columns, and redundant figure captions. A source file can silence a warning with <!-- linter: ignore reveal-overuse, density -->; an error cannot be silenced.
The live prompter (--prompter) is off unless you switch it on. Run the lecture with node build.js <source.md> --watch --prompter – or --present, which starts the prompter when the lecture has a prompter: block and opens the cockpit in Chrome – and the cockpit listens while you talk. When something needs saying, it shows a short hint on a strip across the bottom of its copy of the slide: you are behind time, a point stays abstract although your slides have an example for it, what you just said contradicts your own slide, you are speaking too fast, or your notes planned something on this slide that you have walked past. It can also add a cue card to a slide still to come, the last slide included, when you said something in passing that is worth saying again there. (The cockpit's K mode shows your speaker notes as cue cards; in the ordinary layout the added card appears on the strip when you reach that slide.) A transcript carries no speaking rate, hesitation or silence, so psi-slides counts words a minute, filler sounds and the longest pause itself and hands the model the figures. Chrome's speech recognition often drops filler sounds, so a count of zero proves nothing.
Shift-S in the cockpit switches the prompter on and off, and nothing listens until you press it. For the first minute a faint line under the strip shows the words the speech recognition picks up, so you can see that it works. After that the same line says when the prompter last asked the model, so you can tell a prompter with nothing to say from one that has stopped.
The prompter needs an internet connection and an OpenRouter account, and its calls cost money. It also needs Chrome, because the listening is Chrome's own speech recognition (the Web Speech API), and --watch, which keeps node build.js running: the cockpit hands what it hears to that running program, and the program asks a language model through openrouter.ai. You create an API key in your OpenRouter account and put it in the file ~/.config/psi-slides/openrouter.key, readable by you alone (chmod 600), or in the environment variable OPENROUTER_API_KEY, which wins when both are there; each call is charged to that account. No .env file is read, so that a lecture someone sends you cannot redirect what the prompter sends. What a talk costs depends on the model and on how often the prompter asks. For a sense of scale: one keynote, together with every rehearsal and test run before it, came to about one US dollar with the default model, Claude Sonnet 5. The prompter makes at most 360 calls an hour unless calls-per-hour in the prompter: block sets a lower limit. Without a key the prompter runs as a dry run: it listens and writes down what it heard, calls no model, and the start says where the key goes.
The prompter sends text to openrouter.ai: the transcript and the lecture's text, speaker notes included. From there it goes to the company that runs the model. The prompter sends no audio. The speech recognition is Chrome's, though, and Chrome sends the audio to Google unless it can run the recognition on your own device; the cockpit says which of the two you are getting. None of this appears on the projection or is written back into source.md. node build.js reads the key from the environment or from that file and never writes it into the HTML files. To try the prompter without calling a model, add --prompter-dry-run: it does everything except that call and needs no key, and with recognition on the device, nothing leaves your computer at all. A run makes at most calls-per-hour calls in any hour (360 unless the prompter: block at the top of source.md says otherwise). You pay for the model named in prompter: {model: …}, so check its price before you rehearse with an unfamiliar one.
Each run writes a log beside source.md (prompter-<date>.jsonl): what it heard, which hints it showed, and what it wanted to say but its rules stopped, with the rule that stopped it. --prompter-debrief reads the newest log that is not a dry run back for you: when the clock started, how the talk kept to the @mm:ss marks in your notes, what the prompter whispered and what its rules held back; --transcript prints only your spoken words, slide by slide. --prompter-replay prints, answer by answer, what the model proposed and what today's rules would do with it. Beside the log, prompter-<hash>.prompt.txt holds the lecture's text exactly as the model got it. The log holds your spoken words verbatim. This repository's .gitignore covers both files; a lecture kept in a repository of its own needs prompter-*.jsonl and prompter-*.prompt.txt in its own .gitignore. node build.js prints the log's path when it starts and again on Ctrl-C.
The microphone hears the whole room. Questions and remarks from the audience are transcribed and sent to the model like your own words. Tell the audience before the talk that the prompter is listening, and switch it off with Shift-S when someone from the room speaks, for example during questions. Whether you may use it in your setting is for you to check: data protection law (in the EU the GDPR) and the personal rights of the people in the room cover their words too.
Press ? in either live view for the full on-screen reference. The ones you need on day one:
Space, ↓, Enter, PageDown. It uncovers the next reveal segment or diagram step on the chunk you are on; once there is nothing left to uncover it moves to the next chunk, and at the end of a column it carries on into the next column.↑, PageUp, Backspace. It takes the last reveal back, and leaves the chunk only once the chunk is back at its opening state.→ and ← are that same forward/backward pair, on every slide.Shift-→ and Shift-← are the next and previous column, from anywhere. Shift-← rewinds to the head of the column you are in before it leaves for the one before it.⌄ at the foot of the slide appears when the next forward press will leave the column.1–9 open expansions – so does clicking the chevron. Esc backs out.O overview (the letter, not zero – zero resets the zoom), T table of contents, / search from anywhere – a hit list of every slide that mentions the word.C collapse, F font, A accent theme, + - 0 zoom.# auto-fit, in three: off, shrink a slide too big for the frame, size every slide to the screen. B blanks the projection – the speaker window keeps working so you can change slide while the room sees black.D puts a live demo on the projection: a window or a screen of this machine, picked in the speaker window and shown to the room. D again pauses it and the room is back on the slides; D once more shows the same window again without asking, as long as it is still open. Shift-D picks another window, and “End the live demo” in the ? palette stops the capture; the DEMO badge in the speaker window carries both as buttons. Serve the lecture with --serve and the picture crosses without an encoder; from a double-clicked file it goes through a loopback WebRTC connection. The very first capture on a Mac does not work – macOS asks for screen-recording rights for the browser, and that first attempt is refused or shows an empty picture that ends by itself. Allow it, press D again, and it works from then on; try it once before the talk.The source format is the interface: a change that stops an existing source.md from building the same way is a major version. 2.0.0 is one: a lecture written for 1.0.0 may need an edit, and the changelog's Breaking list says which. That is a promise about the format, not about the internals – build.js is one file and its insides are rearranged whenever it helps. Two consequences worth knowing:
{#id} attributes are frozen once authored. They anchor cross-references, TOC entries, sync snapshots, and localStorage. Renaming a heading is free; renumbering an ID is not.lectures/tutorial/, lectures/diagrams/ and lectures/decoration/ – so the tour and the two construct references can be browsed straight from the repository. Rebuild those with npm run build:tracked, which skips the WebP step so the bytes do not depend on the encoder installed.A lecture someone sends you is a web page written by that person. Its HTML can run JavaScript in your browser and tell a server that you opened it, and no version of psi-slides can stop that, because the author decides what goes into the file. A lecture made from Markdown and local files alone makes no network requests. Run node build.js on someone else's source.md only with psi-slides 2.0.0 or later, and even then it may read pictures, clips and fonts from the folder one level above its own. SECURITY.md has the details – what a lecture's files can reach on your computer, what node build.js refuses, what the live tools and the prompter expose – and how to report a vulnerability privately.
The tooling – build.js, lint.js, the documentation – is MIT. The lecture content under lectures/ is CC BY-SA 4.0, so you may reuse and adapt it with attribution under the same terms.
docs/artifact/ | The figure language, published on the project site as figures.html: first the case for it, then the manual from nothing: a figure built a line at a time, then beats, then every class and statement, fifteen design rules and a gallery. Every drawing on it is compiled by the build rather than redrawn. |
editor.md | Design and build log for the experimental graphical editor: what it edits, what it refuses to edit, and why. |
docs/comparison.md | Beamer, reveal.js, Quarto, Marp, Slidev, PowerPoint and friends, compared in both directions. |
PRD.md | The original specification and the design rationale: why four views, why this type set, why collapse has two mechanisms and not four. Written before 1.0.0 and not kept current. |
speaker.md | The cockpit spec and the postMessage sync protocol – which fields travel, which stay local. |
CHANGELOG.md | What is in each release, and what the known limits are. |
CONTRIBUTING.md | What is useful to send, and what to read before touching the code. |
HANDOFF.md | Build diary, slice by slice, including the decisions deliberately not taken. German. |
CLAUDE.md | Repo conventions and a map of build.js. Useful to any contributor, not just to Claude. |
.claude/skills/psi-slides-authoring/ | The authoring contract in one artefact, for handing to an LLM assistant. |
Shift-S in the speaker window switches the live prompter on and off, when the lecture runs with --watch --prompter. The ◌ prompter button in the footer does the same, and Shift-clicking it shows the last ten hints and cards. Esc removes the hint currently on the strip.S open the speaker window, P open the print view.C, F, A, #, search and text selection behind the ⋯ button. It appears only on a device with no fine pointer, so an iPad with a keyboard attached does not see it.L slide numbers: stacked, in a row, or off. M shows or hides the + note button that sits in the slide's left gutter – the key it stands for, N, works either way.