- TypstRunner: add --synctex output.synctex.gz to quarto typst compile,
generating a synctex file alongside the PDF (requires Typst 0.11+,
bundled in Quarto 1.5+).
- use-synctex: extend the root-doc guard from LaTeX-only to also cover
.typ files, enabling the Show in PDF / Show in code buttons for Typst.
The rest of the sync infrastructure (OutputCacheManager, synctex binary,
SynctexOutputParser, CLSI routes) is already format-agnostic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verso added 'qmd' and 'typ' to validRootDocExtensions, which caused
isValidTeXFile() to return true for Typst/Quarto files — enabling
SyncTeX UI controls for projects that never produce output.synctex.gz.
Replace the open-doc extension check in canSyncToPdf with a
LaTeX-only regex on the project root document path (tex|ltx|Rtex|Rnw),
and add the same guard in _syncToCode so PDF-click sync never fires
an API request for non-LaTeX projects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Replace token-link email with 6-digit code on SSO registration
Unverified SSO emails previously received a long-lived token link
(90-day TTL) via UserEmailsConfirmationHandler. This replaces that
flow with the same 6-digit code verification used for password
registration, redirecting through /registration/confirm-email.
- SSOManager.registerSSO now always confirms email (caller must
verify first); removes sendConfirmationEmail / _finishRegistration
- SSOController._signUp sends confirmation code and stores
pendingSSORegistration in session when IdP email_verified is false
- New SSOConfirmEmailHandler completes registration after code check
via completeSSOEmailConfirmation module hook
- OnboardingController confirm-email handlers accept
pendingSSORegistration alongside pendingUserRegistration
confirmEmailFromToken (POST /user/emails/confirm) removal is deferred
to a follow-up PR to avoid breaking in-flight 90-day tokens.
Closes#28607
* Fix unverified-email edge cases; Add ORCID e2e tests;
* Rename `confirmEmail` parameter to `emailVerifiedByIdP` in _signUp function
* Remove `sendConfirmationEmail`
* Mock getUserByAnyEmail in tests
* Extract _finishSSORegistration helper to deduplicate the register →
set session flags → allocate referral → finishSaasLogin → finishLogin
sequence shared by both the direct and deferred (code-confirmed) paths.
* Stop duplicating session data in pendingSSORegistration
analyticsId, splitTests, and referal_* are already in the session at
confirmation time — no need to copy them into pendingSSORegistration.
Re-fetch splitTests fresh on completion instead.
* Simplify the code
* Remove dead confirmEmail template
No callers remain after sendConfirmationEmail was deleted. The token-link
flow (confirmEmailFromToken) only validates tokens, never sends email.
* Remove dead reconfirmEmail template
* Address comments from Copilot
* Clear stale pending registration when starting a new flow
* Add unit tests for completeSSOEmailConfirmation
* Add `verificationMethod` param
* Fix camelcase issues
* Extract _createSSOUser and _registerAndFinish helpers to deduplicate registration logic
* Remove obscure "registration_error"
* Prevent FormTextIcon from shrinking
* Enable "email_already_registered_sso" error
* Misc. improvements to confirm-email-form.tsx
* Remove `UserEmailsConfirmationHandler` mock
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
* Add info on sso_email.pug page
---------
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
GitOrigin-RevId: d0196ebc6d81ff61bcd27726d0b899b743d08d64
* [web] Redirect missing AI add-on purchase to subscription dashboard
The two error paths in `previewAddonPurchase` redirected to
`/user/subscription/plans#ai-assist`, but the `#ai-assist` anchor was
removed when the AI Assist add-on was retired, so users land at the top
of the plans page with no context. Align both with the other error
branches in the same function and the `plans-2026-phase-1` enabled
branch, which already redirect to
`/user/subscription?redirect-reason=ai-assist-unavailable` — the
subscription dashboard shows the matching warning alert
(`redirect-alerts.tsx`).
Update the acceptance test to match the new redirect target.
Closes#34074
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Update ai-assist-unavailable warning to reflect bundled AI features
The previous copy said "AI Assist isn't available to you due to your
current subscription type", which read as a hard block. Now that the AI
Assist add-on has been retired and AI features are included with every
paid plan, the warning should point users to the pricing page instead of
implying their plan can't access AI at all.
Keep the existing translation key for now — a follow-up can rename it
once #33624 (AI page CTA destination) is resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Link the ai-assist-unavailable warning to the pricing page
* [web] Rename key `ai_assist_unavailable_due_to_subscription_type` -> `ai_assist_unavailable`
* [web] Update french and german translations
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: ae1319fa5b857d8f292de77c82ef0bda1c7ad144
- Reduce the dashboard instance-name/version font size (07 -> 06).
- Enlarge the Verso logo in the loading animation (160px -> 240px).
- Preserve the current RevealJS slide across recompiles: capture the deck's
URL hash (same-origin) and re-append it to the iframe src so the new build
reopens on the same slide instead of jumping to the start.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Hide the Present button when the current output is a PDF (it only makes
sense for HTML/RevealJS decks).
- Publish now supports PDF projects: snapshot output.pdf and serve it inline
via a small index.html wrapper at /p/:token, so link holders can view the
PDF straight from the published version.
- Add a Typst document outline (scans '=' headings) wired into the file
outline panel.
- Dashboard branding: enlarge the instance-name/version text and let the
sidebar Verso wordmark span the full column width.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add a Typst language (stream highlighting + completions) for .typ, and
Quarto completions (code chunks, callouts, cross-refs) for .qmd/markdown.
- Project dashboard: new Format column (Quarto/Typst/LaTeX) from the cheap
project compiler field, surfaced through the projects list API.
- Compiler dropdown: grey out engines that don't match the root file's
extension (.qmd->Quarto, .typ->Typst, .tex->LaTeX engines).
- Replace the Overleaf fill loader with an animated Verso logo: the four
quadrant circles drift on their own orbits while colour warms up with load
progress; reused on the token-access screen too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Instance name: stamp the nav title with the build number at deploy time
("Verso V0.<run> alpha") via a sed placeholder fed by GITHUB_RUN_NUMBER,
instead of the static "Verso V1.0 Alpha".
- Title typeface: self-host the EB Garamond latin subset (same one embedded in
the logo SVGs) and apply it to .navbar-title so the instance name matches the
Verso wordmark.
- Sidebar wordmark: let the logo fill the full sidebar column width (drop the
160px cap).
- Project filters: switch the ds-nav active state (filter selection + theme
toggle) from the green tokens to the blue scale, matching the rail.
- Present button: rename the presentation toolbar action from "Preview" to
"Present" / "Présenter" and add a tooltip explaining it publishes the
presentation and opens it in a new tab. New keys present /
present_publishes_and_opens_in_new_tab in en, fr and extracted-translations.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Editor rail: the active item used the Overleaf green accent. Switch
--ide-rail-link-active-color/background to the blue scale (--blue-10/70,
--bg-info-03) to match the Verso palette.
- Footers: remove the default "Fork on GitHub!" right_footer item (redundant
with the "Built on Overleaf" link); right_footer now defaults to [].
- Login: move the hero wordmark into a full-width centered block and bump it to
max-width 480px so it's no longer constrained by the form column.
- Projects dashboard: restore the instance name in the top-left navbar (set
OVERLEAF_NAV_TITLE="Verso V1.0 Alpha") instead of the wordmark logo, and move
the full Verso wordmark to the sidebar's lower section (where the old
"Digital Science" mark sat). Revert HeaderLogoOrTitle to its title-first
behaviour now that the dashboard no longer passes a logo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three follow-ups after the visual-identity deploy:
- Footer: restore the React <Footer> on the projects dashboard (both
ProjectListDsNav and the legacy DefaultNavbarAndFooter). Removing it earlier
was an overcorrection — it now renders the Verso/AGPL thin footer rather than
the old "Powered by Overleaf" line. Other pages already kept the pug footer.
- Navbar brand: HeaderLogoOrTitle previously hid the logo whenever a nav title
was set, so on the dashboard only the "Verso" instance-name text showed and
the wired-up Verso logo never appeared. Make a configured logo (custom logo
or the Verso brand logo) take precedence over the title text; fall back to the
title only when no logo is provided (unchanged for other navbars).
- Login: enlarge the hero wordmark (max-width 260px -> 380px, full column width).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce the Verso brand marks as self-contained SVGs with the EB Garamond
latin subset embedded as a base64 @font-face, so they render identically in
every context (favicon, CSS background, <img>, inline) with no runtime Google
Fonts dependency — important for the self-hosted alpha. Falls back to Georgia
serif if a browser ignores SVG-embedded fonts.
Assets:
- verso-square.svg — rounded "V" tile (200×200); used as favicon.svg and the
editor top-left toolbar logo.
- verso-logo.svg / verso-logo-dark.svg — wide "verso · ONLINE EDITOR" wordmark
(760×200), light + dark wordmark variants.
Wiring:
- favicon: public/favicon.svg replaced with the square mark.
- editor toolbar: --redesign-toolbar-logo-url (light + dark) -> verso-square.svg.
- projects dashboard navbar: ProjectListDsNav logo -> verso-logo(.dark), with
--navbar-brand-width widened to 200px to fit the wide wordmark.
- login page: centered Verso wordmark above the form; suppress the top navbar
so the hero logo stands alone (no competing Overleaf mark).
PNG favicons / apple-touch-icon are left as-is (no raster tooling available);
modern browsers use the SVG favicon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an 'Add collaborators' heading above the email-invite section in the share
modal so it's visually distinct from the presentation-sharing section.
Add the missing French 'not_found' key so the 404 page shows 'Introuvable'
instead of the raw 'not_found' (the 404 pug template translates server-side;
the key existed in en.json but not fr.json).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a 'Python packages' button to the file-tree toolbar that opens a modal to
edit the project's requirements.vrf (one package per line, pip syntax), backed
by GET/POST /project/:id/python-requirements (read via ProjectEntityHandler,
write via EditorController.upsertDocWithPath, write-gated). The .vrf file is now
hidden from the file tree, so it is managed only through this editor rather than
appearing as a loose file. Adds python_packages / python_packages_help i18n.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Option A: when a {python} cell fails with ModuleNotFoundError/ImportError, the
log now suggests the exact PyPI package to add (with a module->package map, e.g.
cv2 -> opencv-python, sklearn -> scikit-learn), names the Verso requirements
file, and notes it could instead be a local module — so the langmuirthermalstudy
case isn't mistaken for a PyPI package.
Switch the per-project requirements file from requirements.txt to a Verso-
specific requirements.vrf (so it won't be confused with arbitrary .txt files);
QuartoRunner now looks for requirements.vrf, and 'vrf' is registered as an
editable text extension. The dedicated in-UI editor (and hiding it from the
file tree) follows in a separate change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a {python} cell fails with ModuleNotFoundError/ImportError, the Quarto
log parser now emits an actionable error ('Python package "X" is not
installed on the server') noting which scientific packages are pre-installed,
instead of leaking an opaque traceback line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The HTML/PDF export links were plain downloads that left the browser
silently spinning during the server-side render and, on failure, saved an
error page as pdf.txt/pdf.htm. Replace them with fetch-based downloads that
show a modal: a spinner with a 'this can take up to a minute' message while
compiling, and the actual compile log inline if the export fails. The user
can dismiss at any time; a stale request that finishes after dismissal no
longer reopens the modal. Adds the three i18n keys (en/fr + extracted).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [web] Fix footer For Students link to activate student toggle
The footer link only set itm_referrer plus a #student-annual hash. The
plans page reads the active plan/period from `plan` and `period` query
params (PlansHelper.getPlansPageViewOptions), so the student tab never
activated from the footer link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* syncStudentModeFromPlanType after in handleDeprecatedHash
* Change URL update to use replaceState in the pricing page
* Revert "Change URL update to use replaceState in the pricing page"
This reverts commit eac71f193029e3f1c75e0c97261d8a5982c0d35c.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 69d689d0fe89fc68cefab9233739fc61da8f2ced
* Add tests on plurals
* Update `collabs_per_proj` and its pluralisations
* Update `n_user` and its pluralisations
* Update `showing_x_results` and its pluralisations
* Update `show_x_more_projects` and its pluralisations
* `bin/run web npm run extract-translations`
* Populate `_plural` keys in non-en locales
For 2-form languages (da, de, es, fi, fr, it, nl, no, pt, sv, tr), copy
the existing bare-key value into the new `_plural` sibling to prevent
i18next from falling back to English for count!=1.
Also remove orphan singular keys (`collabs_per_proj_single`,
`showing_1_result*`) left over from the previous commits.
Bare-key values remain in their original plural form pending translator
review — count=1 will still render the plural form in non-en until
translators flip those to singular. Multi-form (cs, pl, ru) and
single-form (ja, ko, zh-CN, zh-TW) locales are unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Flip non-en bare-key values to singular form
Per review, the i18next v3 plural convention uses the bare key for count=1
(singular) and `_plural` for count!=1. The non-en bare-key values were
left as the original plural form by the previous commit so the `_plural`
siblings could be copied from them; this commit flips the bare values to
the singular form per language.
Languages where singular and plural noun forms coincide (Finnish, Swedish,
Turkish) are unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Apply suggestions from code review
Co-authored-by: Olzhas Askar <olzhas.askar@gmail.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Olzhas Askar <olzhas.askar@gmail.com>
GitOrigin-RevId: 628513ca792c2dcce023247e52b7320e2741cc54
* move Review Toggle into the toolbar
* cleaning up and adding a comment
* adding the cursor styling
* adding isolation on writefull toolbar to adjust z-index of writefull toolbar
* fixing the dark mode colours for review dropdown trigger
* Fix review mode switcher dark mode styles
GitOrigin-RevId: 36847e0debdc4dce5f96492261d25e7cc46b2e96
In RevealJS mode the download button becomes a 2-choice menu:
- Standalone HTML: a one-off compile with embed-resources (chalkboard and other
runtime-only plugins are dropped, since they don't survive self-containment),
yielding a single portable .html.
- Slide PDF: render the deck, then print it with decktape (headless Chromium)
to a faithful one-slide-per-page PDF.
Implementation:
- Dockerfile-base: install decktape + headless Chromium (open-source; deps via
playwright install-deps for Ubuntu-Noble correctness). Base-only change.
- QuartoRunner honours options.exportMode ('html-standalone' | 'pdf-slides');
exportMode is threaded web ClsiManager -> CLSI RequestParser -> CompileManager
-> runner.
- New GET /project/:id/presentation-export/:format compiles in the matching
export mode and streams the result as a download (PresentationExportController,
reusing ClsiManager.getOutputFileStream).
- pdf-hybrid-download-button shows the dropdown when the output is output.html;
PDF/LaTeX projects keep the single download button.
- i18n: download_as_standalone_html / download_as_pdf_slides (en + fr +
extracted-translations.json).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a project-members-only link tier and independent link rotation.
- Three tokens per project instead of two: publicToken (anyone), loginToken
(any logged-in user), memberToken (only users who can read the project).
serve() resolves the token to its tier and enforces accordingly — 'member'
requires AuthorizationManager.canUserReadProject.
- New POST /project/:id/publish-presentation/regenerate { tier } rotates a
single tier's token (invalidating only that old link), leaving the snapshot
and the other links intact.
- Share dialog now shows three links (members / logged-in / anyone), each with
its own Copy and Reset buttons; Publish refreshes, Unpublish removes all.
Preview button opens the logged-in-users link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the single token + visibility toggle with two stable tokens per project
pointing at the same snapshot:
- publicToken → anyone with the link
- privateToken → any logged-in Verso user
This fixes both reported issues: changing visibility no longer mutates a link
(there's no toggle — both links always exist), and a public link can never
become private by accident. It also fixes public links redirecting to login:
access is now decided purely by which token was used (public token = open),
not a per-record flag.
- Model: storageId (snapshot dir) + publicToken + privateToken; drop token/
visibility.
- Manager.publish: mints both tokens once and reuses them on re-publish; serve
resolves a token to its record and treats the public token as open.
- Controller: returns { publicUrl, privateUrl }.
- Share dialog: shows the private and public links side by side, each with its
own copy button; Publish refreshes, Unpublish removes. Preview button opens
the private link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CompileManager.compile debounces compiles via a Redis key set on every compile
(_checkIfRecentlyCompiled), returning {status:'too-recently-compiled',
outputFiles:[]} when the editor has just auto-compiled. Publishing called
compile() and then required output.html, so it threw "did not produce an HTML
presentation" — which is why Preview/Publish errored whenever the deck was
freshly compiled.
- CompileManager.compile: honour options.bypassRecentCompileCheck to skip the
debounce (still runs the normal autocompile-limit guards).
- PublishedPresentationManager: publish with bypassRecentCompileCheck, and put
the compile status in the error message for diagnosis.
- Controller: catch publish errors, log them, and return the message so the
Share dialog can show what went wrong instead of a generic error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires the two entry points to the publishing backend:
- Share dialog: a "Share compiled presentation" section (owner only) with a
public / logged-in-users-only choice, Publish/Unpublish, and a copyable link.
- Top-right toolbar: a "Preview" button that publishes a private (logged-in-
users-only) link in one click and opens the standalone deck in a new tab
(opened synchronously to dodge popup blockers).
Both talk to /project/:id/publish-presentation. Reuses existing i18n
(publish/unpublish/copy/preview); adds share_compiled_presentation(_info) and
presentation_link_public/private.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A project whose root file is a .typ file now compiles straight to PDF with
Typst, as a third engine beside Quarto (.qmd) and latexmk (.tex). Dispatch
stays purely extension-based.
CLSI:
- New TypstRunner.js: runs `quarto typst compile <main>.typ output.pdf` (reuses
the Typst bundled in Quarto, so no extra binary / Docker change). stderr is
merged into output.log.
- CompileManager: _isTypstFile + a TypstRunner branch in _getRunner, and
TypstRunner added to the isRunning check and stopCompile kill list.
- RequestParser: 'typst' added to VALID_COMPILERS.
web:
- settings.defaults: 'typ' added to validRootDocExtensions and the text
extensions (so .typ opens in the editor); 'typst' added to safeCompilers.
- output-files: the Quarto/Typst log parser (which already understands Typst
`error:`/`warning:` + `┌─ file:line:col` diagnostics) now also handles .typ
compiles, so their errors/warnings populate the log tabs.
Polish:
- New-project menu: "Blank Typst project" + "Example Typst project" in both the
main and welcome dropdowns, backed by createBasicProject/createExampleProject
flavour 'typst', a new mainbasic.typ template and an example-project-typst
presentation (math, an image, a table, lists).
- Compiler dropdown gains a "Typst" option (cosmetic; dispatch is by extension).
README updated: three compilers side by side, with a Writing-a-Typst-document
section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the generic "Blank project" / "Example project" entries with four
flavour-specific ones in both the New-project dropdown and the welcome-screen
dropdown:
- Blank Quarto project -> empty main.qmd (format: typst)
- Blank LaTeX project -> empty main.tex
- Example Quarto project -> a Reveal.js presentation showcasing images, math,
a table, code and incremental lists (new template
project_files/example-project-quarto/)
- Example LaTeX project -> the existing LaTeX example
Backend: ProjectController.newProject now dispatches the `template` value
(blank_quarto/blank_latex/example_quarto/example_latex, plus the legacy
'example'/'none') to createBasicProject(flavour) / createExampleProject(flavour).
_createRootDoc takes a root-doc name so each flavour gets the right extension —
this also fixes the LaTeX example, whose root doc was wrongly created as
main.qmd, back to main.tex (matching the acceptance test). Signatures stay
backward compatible (flavour defaults: blank=quarto, example=latex).
Also refresh the README: Verso now runs Quarto and LaTeX side by side
(engine chosen by root-file extension), not Quarto instead of LaTeX.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quarto compiles (.qmd/.md/.Rmd, dispatched to QuartoRunner) write
Typst/Pandoc/Quarto diagnostics to output.log that the LaTeX log parser
does not understand, so the Errors/Warnings tabs stayed empty. Add a
dedicated quarto-log-parser that recognises Typst `error:`/`warning:`
(+ `┌─ file:line:col`), Pandoc `[WARNING]`/`[ERROR]`, Quarto CLI/Deno
`ERROR:`/`WARNING:`, and knitr `Quitting from lines`. handleLogFiles now
routes to it when the root file is a Quarto file (mirrors CLSI dispatch),
otherwise the LaTeX path is unchanged.
Also decouple the UI accent from the green ramp. The framework already
funnels every primary/accent surface (primary buttons, Bootstrap
$primary/$success, --btn-primary-background) through the --bg-accent-*
tokens; those just happened to point at Overleaf green. Introduce a
single $accent knob in foundations/colors.scss (with auto-derived
hover/tint shades) and repoint the accent tokens at it, defaulting to
the Verso/Quarto blue. Re-skinning the whole UI is now a one-line edit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two HTML/RevealJS preview fixes:
1. Stop passing --embed-resources to quarto render. A self-contained
single-file HTML breaks reveal.js plugins that load/store resources at
runtime (chalkboard, multiplex) and is slow to transfer. Quarto now
emits the HTML plus a sibling "<basename>_files/" asset dir referenced
by relative paths; both are served from the same .../output/ path
(nginx output/(.+) and web :file(.*) both capture slashes), so the
relative links resolve. The renamed output.html still points at the
unchanged "<basename>_files" dir. This also fixes the slow-load issue,
since assets now load on demand instead of one giant inlined file.
2. On a failed compile that follows a successful one, the previous deck
stayed in the iframe, making the failure look like a success. We now
clear pdfFile when a non-success status carries a stale output.html.
The last-good-PDF-beside-the-error behaviour is preserved for PDF
output (only output.html is dropped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two fixes to the Markdown/Quarto file outline:
1. The last frontmatter line (e.g. `format: typst`) appeared as a
heading. The Lezer Markdown grammar has no frontmatter support, so it
reads the closing `---` of the YAML block as a Setext underline and
promotes the line above it to a heading. Detect the leading
`---`...`---`/`...` block and skip any heading inside it.
2. Pandoc/Quarto attribute blocks were shown in titles, e.g.
`## Slide {.smaller auto-animate="true"}`. Strip a trailing `{...}`
from the extracted title.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>