Commit graph

3 commits

Author SHA1 Message Date
Daniel
154b896d5b feat: Word is built by python-docx from the same typed source as the deck
Some checks failed
Forgejo Docker Build / Build Docker image (push) Blocked by required conditions
Forgejo Docker Build / Deploy to the host (push) Blocked by required conditions
Forgejo Android APK / Root app tests (push) Successful in 58s
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Android APK / Build signed APK (push) Has been cancelled
Pandoc reads markdown, so every Word export had to flatten the resource to
markdown first — and a deck flattened to markdown stops being one. A comparison
became two headings and two lists, a callout became bold text, and a figure
became nothing at all, because markdown has nowhere to put it.

src/utils/docSpec.js reduces either source to the same blocks: a stored deck
where there is one, the markdown where there is not. scripts/render_docx.py
draws them. A comparison comes out as a labelled two-column table, a callout as
a shaded box, a table as a real table, a figure embedded at its own aspect ratio
with its caption, and speaker notes as muted indented text.

The deck wins over the markdown beside it, because that markdown is a
serialisation of the deck and reading it instead would be reading a lossy copy of
what is right there.

Word now carries the figures too. The export route skipped fetching them for
docx, which was correct when pandoc could not place them and wrong the moment
this could.

Pandoc stays installed and stays the fallback: a plainer document beats a failed
download. Both renderers now share one spawn helper.

Verified end to end: a deck with two figures exported as a six-page Word document
with both images embedded (537KB, two files in word/media), rendered to PDF and
looked at — the comparison is a labelled table, the figure sits at its true
aspect ratio, and the notes read as notes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 21:58:53 +02:00
Daniel
087f717f55 feat: the model designs the deck instead of writing markdown for a parser to guess at
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 59s
Forgejo Docker Build / Root app tests (push) Successful in 53s
Forgejo Android APK / Build signed APK (push) Successful in 2m2s
Forgejo Docker Build / Build Docker image (push) Successful in 8s
Forgejo Docker Build / Deploy to the host (push) Failing after 2s
Markdown could express about five of the things the renderer can draw, so the
model had no way to say "put this figure beside these three bullets" or "make
this a comparison with two labelled columns" — my parser inferred a layout from
the shape of a list, and inferring is what made every deck look the same.

A presentation is now described as a deck: the model returns JSON naming a
layout per slide and the prompt it wants each figure drawn from. Four layouts
were added to the renderer for it — two tinted labelled columns for a
comparison, a callout card for a red flag or a dose, a figure beside its
bullets, and a full-slide figure. Articles stay markdown, which is what prose
wants.

Markdown is still produced, serialised from the deck, so Word export and text
editing keep working and the stored artifact stays readable by a person. The
deck is stored alongside it because that serialisation is lossy by design:
round-tripping through markdown would throw away exactly the layout choices this
was built to capture. A resource made before this, or an article forced into
slides, still renders by inferring from its markdown.

Nothing here can cost more than the thing that went wrong. A reply that is not a
deck falls back to asking for markdown rather than saving the model's apology; a
malformed slide degrades to bullets rather than throwing; a comparison with one
column is not a comparison; a figure that cannot be queued leaves a slide of
text rather than an empty frame; and JSON wrapped in fences or a covering
sentence is read rather than refused.

Verified live on "croup versus epiglottitis": the model chose section, bullets,
table, compare, figure, callout and image layouts across thirteen slides, and
the exported deck was rendered to PDF, rasterised and looked at — the comparison
renders as two tinted cards, the red flag as a callout, and the figure sits
beside its bullets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 19:49:55 +02:00
Daniel
73ce4049d4 feat: decks are built with python-pptx instead of pandoc, and carry their figures
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 48s
Forgejo Docker Build / Root app tests (push) Successful in 59s
Forgejo Android APK / Build signed APK (push) Successful in 2m6s
Forgejo Docker Build / Build Docker image (push) Successful in 25s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
Pandoc's pptx writer was the ceiling on how good a generated deck could be, and
the model on top made no difference to it. It maps markdown onto a handful of
reference layouts with no per-slide layout, no positioning and no control over
how large an image is drawn, which is why every deck came out as bullets on a
template — and why autofit had to be injected into its emitted OOXML by hand
afterwards, because LibreOffice ignores the autofit pandoc leaves off.

scripts/render_pptx.py draws the deck and src/utils/slideSpec.js decides what
each slide is. Markdown stays the stored artifact, so "change slide 4" is still
a text edit and Word export is untouched — pandoc still writes docx, where its
output is good.

What that buys, all of it visible in a rendered deck rather than argued for:

- 16:9, not pandoc's 4:3.
- A pipe table becomes a real table with a header band and banded rows, not
  eight lines of text with pipes in them.
- A list longer than seven items becomes two columns instead of a wall of text.
- Text is measured and sized to fit before the file is written, so nothing
  depends on a renderer honouring autofit.
- Wrapped lines hang under the text instead of running back to the margin,
  which is the clearest single tell that a deck was generated.
- An image is drawn at its own aspect ratio, centred, with a caption.

Figures now reach the deck at all, which they never did. They were queued and
shown on the page, but nothing recorded that they belonged to the resource, so
an export could not include them: user_resources.image_ids holds them, a
modification adds to that list rather than replacing it, and export fetches the
finished ones to a scratch directory. They are spread through the deck rather
than appended, because ending on three unexplained pictures is worse than
showing each near its material, and a References slide stays last.

If the renderer fails for any reason, pandoc still produces a deck — a plainer
deck beats a failed download.

Verified end to end: a seven-slide request with three figures exported as a
13-page deck; the slides were rendered to PDF, rasterised and looked at. All
three formats still download.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 19:34:26 +02:00