- Convert: backend now returns parentFolderId+isNew; frontend calls
dispatchCreateDoc directly so the new file appears without a page refresh
- Set as main: infer compiler from file extension and POST both rootDocId
and compiler together; updateProject propagates the change to the
editor settings dropdown and project list immediately
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set as main document (context menu):
- canSetRootDocId used selectedEntityIds so .typ/.qmd files couldn't be
set as main via right-click on an unselected file.
Now computed locally from contextMenuEntityId (same pattern as convert)
using isValidTeXFile which already covers .typ, .qmd, .tex etc.
Compiler filter (editor settings):
- docs?.find(id) could return undefined due to ID format mismatch,
causing all engines to show as available for non-LaTeX projects.
Added findInTree fallback so the root doc name is always resolved.
ZIP import compiler:
- Projects created from ZIP always got defaultLatexCompiler ('quarto')
regardless of content.
- findRootDocFileFromDirectory now also searches for .typ and .qmd root
files after finding no .tex file.
- ProjectUploadManager now infers the compiler from the root doc
extension and sets it on the project after import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
canRename required selectedEntityIds.size === 1, so right-clicking an
unselected file hid the convert items even though contextMenuEntityId
was correctly set. Replace canRename with !fileTreeReadOnly for the
convert-specific gate, which is the actual write-access check needed.
Also add showExportDocumentSuccess so the user sees the warning toast
on successful conversion instead of silent nothing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs:
1. Converting when output already exists threw DuplicateNameError (400).
Now overwrites existing doc via setDocument instead of failing.
2. Right-clicking an unselected file left contextMenuEntityId null,
so the first click on Convert silently did nothing. Added
contextMenuEntityId to FileTreeMainContext, set it on right-click
and on the … button click; FileTreeItemMenuItems now uses it for
the convert hooks rather than relying on selectedEntityIds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove RevealJS thumbnails (redundant with Features section) and Upload
reliability (still unresolved). Add Known Issues section documenting
the large file upload timeout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
postJSON without options sends Content-Type: application/json with no
body; body-parser's BodyParserWrapper intercepts the resulting
SyntaxError and returns 400 before the route handler runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "Convert to Typst (.typ)" in the file tree context menu for .tex
docs, and "Convert to LaTeX (.tex)" for .typ docs. Clicking runs pandoc
on the file content and creates the converted file in the same folder.
- New backend endpoint POST /project/:id/doc/:id/convert/:type that reads
the doc from document-updater, runs pandoc directly, and creates the
result via ProjectEntityUpdateHandler (file tree updates via socket).
- Rewrite the export success toast for typst and latex conversions: no
more link to /contact, replaced with a plain warning that errors are
expected (pandoc does not support all constructs).
- Add i18n keys: convert_to_typst, convert_to_latex,
typst_export_feedback_message, latex_export_feedback_message (EN + FR)
and all four to extracted-translations.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add top/bottom split view and bidirectional format export (LaTeX↔Typst,
LaTeX→DOCX/Markdown/HTML) to the Alpha 3 feature list. Fix the security
section which incorrectly referred to Overleaf as a LaTeX/Typst editor —
Overleaf only supports LaTeX.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
top_bottom_split_view, export_as_typst, and export_as_latex were absent
from the whitelist, so translations-loader.js stripped them from the
locale bundles at build time — causing raw keys to show in the UI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- clsi-nginx: allow hyphens in project-id regex — conversion IDs are UUIDs
which nginx was rejecting, causing 500 on file download after conversion
- CLSI ConversionController/Manager: add 'latex' export type (typst→latex via pandoc)
- Web: add 'latex' to SUPPORTED_CONVERSION_TYPES
- Frontend: add Export as LaTeX button (visible only for typst projects)
- Fix visibility logic: export-as-latex shows for typst, export-as-typst shows for latex
- Add export_as_latex translation key (en + fr)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ConversionController.js: add typst to CONVERSION_CONFIGS (missing entry caused 400→500 chain)
- export-project-with-conversion-button: hide button for non-LaTeX projects (typst/quarto) via compiler check
- project-list-lumiere.tsx + scss: revert lumiere-card-actions back inside .lumiere-card (put them back like they were)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Typst → LaTeX import:
- CLSI ConversionManager: add 'typst' to CONVERSION_CONFIGS
(pandoc input.typ --from typst --to latex --standalone → zip archive)
- Web controller: allow 'typst' as a valid importDocument conversion type
- Frontend modal: add .typ file config to ImportDocumentModal
- New project button modal: add 'import_typst' variant + switch case
- New project button: show "Import Typst file" when enablePandocConversions
is true (no split test gate — Verso has no SaaS split test infra)
- Locales: add choose_typst_file and import_typst_file keys (18 locales)
Export button fix:
- Remove featureFlag="export-typst" from ExportProjectWithConversionButton
so the button shows whenever enablePandocConversions is true, without
needing an unconfigured split test to return 'enabled'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pandoc was not installed in the base image, so the export buttons
(docx, markdown, html, typst) were hidden because ENABLE_PANDOC_CONVERSIONS
defaulted to false.
- Dockerfile-base: add pandoc via apt (Ubuntu Noble ships 3.1.3, which
supports --to typst added in pandoc 3.0)
- env.sh: set ENABLE_PANDOC_CONVERSIONS=true so both the web and CLSI
services expose and serve the export endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new "Export as Typst" option in the project title dropdown and
File menu, mirroring the existing docx/markdown/html export pipeline.
Changes:
- CLSI ConversionManager: add 'typst' to LATEX_EXPORT_CONFIGS
(compressOutput: false, pandoc --from latex --to typst)
- Web controller: register 'typst' → 'typ' in SUPPORTED_CONVERSION_TYPES
- Frontend: extend conversionType union and add ExportProjectWithConversionButton
- File menu: add 'export-as-typst' to the download group command structure
- Locales: add export_as_typst key to all 18 locale files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The DropdownHeader in the View > Layout menu was using a raw English
string instead of t('editor_settings'). Added the key to all 18 locale
files with appropriate translations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Popper lazy-initializes on first open, causing it to place the menu at
[0,0] before it has computed the toggle's position. renderOnMount forces
Popper to initialize while the component is first mounted, so the
position is ready before the user's first click.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.lumiere-card has both backdrop-filter and a hover transform, both of
which create a new containing block for position:fixed descendants,
trapping Popper dropdowns inside the card's overflow:hidden bounds.
Fix: move .lumiere-card-actions outside .lumiere-card (sibling inside
.lumiere-card-wrapper, which has position:relative but no filter or
transform). The actions strip is now absolutely positioned at the card
bottom with its own solid background and bottom border-radius.
No backdrop-filter on the strip to avoid re-introducing the same trap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProjectCard in the Lumiere tile view always showed CompileAndDownloadProjectPDFButtonTooltip,
ignoring the project compiler. Quarto presentation projects need the
DownloadPresentationButtonTooltip (HTML/PDF dropdown) instead, matching
the logic already in actions-cell.tsx and actions-dropdown.tsx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
layout-context: getInitialLayout() was returning verticalSplit for
any stored 'vertical' preference, including on desktop. Now checks
isMobile first so stored mobile preference doesn't bleed into PC.
compile-and-download-pdf: when compile succeeds but output.pdf is
absent from outputFiles, the code crashed silently at outputFile.build
leaving the user with no feedback. Now shows the error modal instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Footer: use translate('built_on') key instead of hardcoded 'Built on';
update fr.json 'built_on' → 'Basé sur Overleaf'
- Mobile project list: move search bar + button outside the bordered
TableContainer so its width is viewport-constrained, not affected by
the table's fixed layout
- Mobile table rows: use width:100% (not auto) on cells so they fill the
full tr width regardless of the higher-specificity column percentage
rules; add explicit width:100% on tr to anchor flex-column sizing;
keep width:auto on absolutely-positioned actions cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
quartoFlavor is set only after a project is compiled with the current
build. Existing Quarto projects that haven't been recompiled have
quartoFlavor=undefined, so the old check (quartoFlavor === 'revealjs')
fell through to the regular PDF compile button with no dropdown.
Drop the quartoFlavor guard — compiler === 'quarto' is sufficient since
all Quarto projects in Verso are RevealJS presentations. Changes applied
to ActionsCell (desktop icon), DownloadPresentationButtonTooltip guard,
and ActionsDropdown (mobile three-dot menu).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Language picker:
- Add fallback href in Pug so language links navigate even if JS fails
- Anchor dropdown to right edge (right:0) so it stays on-screen when
the picker is near the right side of the footer on mobile
Editor layout:
- Read stored pdfLayout from localStorage on init so the last-used
layout is remembered across sessions
- Default to verticalSplit (top/bottom) on mobile when no preference
is stored, so the editor opens in a sensible layout on phones
Translations:
- Add top_bottom_split_view key to all 16 locales that were missing it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The manual JS click-handler approach (tried with stopPropagation,
containment check, and mousedown variants) never worked reliably on
login/password/settings pages. The browser's native <details>/<summary>
toggle behaviour requires no JavaScript and is immune to Bootstrap JS or
React event delegation interference.
The inline script now only builds the return_to hrefs and handles
outside-click-to-close (setting details.open=false). The CSS gains a
.language-picker-details rule that sets position:relative so the
absolutely-positioned dropdown-menu is positioned correctly, and
details[open] .dropdown-menu { display: block } to show the menu.
The #language-picker-toggle id remains on the <summary> so the existing
CSS (cursor, text-decoration, material-symbols alignment) continues to
apply. The React LanguagePicker is unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Search bar overflow: min-width:0 on form prevents input min-content
from overflowing flex container on narrow screens
- Remove duplicate New Project button from header row (tableTopArea
already provides it for mobile)
- Simplify tableTopArea: single button+search layout regardless of
isLibraryEnabled flag
- 2-line project rows on mobile: merge dash-cell-date-owner +
dash-cell-tag into a single dash-cell-meta so date/owner/tags flow
inline on line 2 below the project name
- Footer mobile: hide language-picker-text on mobile (icon only) to
prevent 3rd line in 2-row footer
- Language picker: use mousedown instead of click for outside-close
handler — click bubbling is unreliable on iOS for non-interactive
elements; mousedown fires for all taps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both ul.site-footer-items rows are now justify-content: center on small
screens, so the copyright/language row and the licence/source row are
symmetrically aligned instead of left-justified.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mobile vertical layout: add key= based on direction so react-resizable-panels
remounts cleanly when switching between horizontal and vertical; also use
isVertical (not just pdfLayout) for the autoSaveId to avoid restoring a
mismatched layout from localStorage
- Language picker: replace stopPropagation pattern with a containment check on
the document click handler — more robust on React pages where Bootstrap JS or
React's event delegation can interfere with stopPropagation
- Presentation download dropdown: use popperConfig strategy:'fixed' so the menu
escapes overflow:hidden table cells; remove forced drop='up' and let Popper
choose; defer URL.revokeObjectURL by 10 s to give the browser time to start
the download before the blob URL is released
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On mobile (< 768 px) the existing side-by-side layout automatically switches
to a vertical stack (editor on top, PDF/presentation on bottom) without
changing the stored layout preference.
A new "Top / bottom split" option is added to the layout menu so desktop
users can choose the same vertical split explicitly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Using the generic 'download' icon duplicated the ZIP button icon,
giving two identical icons side by side. Switch to picture_as_pdf to
match the previous compile-and-download button's appearance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Quarto Slides (RevealJS) projects compile to output.html, not output.pdf,
so the existing "Download PDF" button was meaningless for them. Replace it
with a two-option dropdown matching the editor's PdfHybridDownloadButton:
- Desktop (ActionsCell): icon button opens a dropup with
"Download standalone HTML" and "Download PDF slides"
- Mobile (ActionsDropdown): two separate dropdown items with the same
choices and per-format spinner while the export is in progress
Both use the same /project/:id/presentation-export/:format endpoint and
show a loading modal (with error reporting) during the server-side render,
exactly as the editor toolbar does.
Non-RevealJS projects continue to show the compile-and-download-PDF button
unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The native <select> looked like a form control ("cheap"). Replace it with
the same HTML+CSS pattern as the React LanguagePicker: btn-inline-link
toggle with a translate icon, Bootstrap dropdown-menu for the list, active
item highlighted in green.
A tiny self-contained inline script handles the toggle since Bootstrap JS
is not loaded on React-layout pages. It builds the return_to URL from
window.location.pathname at click time (same as the old select approach).
Added top: auto / bottom: 100% to .language-picker .dropdown-menu so the
list always opens upward regardless of whether Popper.js is present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Footer (both React and Pug):
- Below md: switch to flex-wrap so items wrap naturally instead of
overflowing; reset line-height from the fixed 49px to normal; add
row-gap so wrapped rows aren't crammed together
- Add footer-sep class to pipe separator <li>s so they can be hidden
on small screens (wrapping mid-separator looks wrong)
- Change col-lg-3 text-end → text-lg-end so the right column (licence,
source code) aligns left when it stacks full-width below lg
Project list:
- Show NewProjectButton on mobile in the header row (the sidebar that
holds the button is already hidden below md via d-none d-md-flex,
leaving users with no way to create projects on their phone)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The content panel had border-top-left-radius: var(--border-radius-large)
at md+ breakpoint, left over from the old Overleaf theme. Everything else
is square now so this corner stood out. Removing it makes the edge flush.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
favicon-compiled.svg, favicon-compiling.svg, favicon-error.svg all used
the Overleaf O logo as the base. Replace with the Verso icon mark (dark
background, four colored circles, white V polygon, clipped to a circle)
while keeping the existing status badge icons (✓ / ↻ / ✗) unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Overleaf favicons with Verso icon mark across all sizes (16x16,
32x32, 180x180 apple-touch, 192x192/512x512 android-chrome, ICO). Add
OG social preview image (1200x630) for Discord/Twitter link cards.
- New favicon.svg: Verso icon mark with four overlapping circles and V
- mask-favicon.svg: monochrome V polygon (was Overleaf chevron)
- og-image.png: 1200x630 social card with icon mark and "Verso" wordmark
- web.sitemanifest.json: rename "Overleaf" → "Verso", theme_color updated
- _metadata.pug: add og:url tag, fix og:type missing content= attribute,
point CE default OG/twitter images to og-image.png instead of apple-touch-icon.png
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old dropdown relied on data-bs-toggle and AngularJS directives,
neither of which are loaded on React-layout pages (layout-react.pug
intentionally excludes Bootstrap JS). The toggle button was inert on
pages like /user/settings.
Replace with a plain <select> that navigates via window.location.href
onchange — works without any framework on all page types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bootstrap vanilla JS and react-bootstrap were both handling the dropdown,
causing the toggle to be unresponsive. react-bootstrap manages its own
state and does not need this attribute.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rewrites LanguagePicker to use availableLanguages from ol-footer meta
instead of subdomainLang (which is always empty in single-domain setup)
- Passes availableLanguages through layout-react.pug → ol-footer meta so
React footer picks it up
- Adds InterfaceLanguageSetting component to the editor settings modal
("Spelling and language" tab) for use when no footer is present
- Adds interface_language key to all five locale files (en/fr/de/es/it)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users can now select their UI language directly without relying on
subdomain routing (fr.verso.alocoq.fr etc.).
Resolution order: (1) verso-lang cookie, (2) subdomain host header,
(3) OVERLEAF_SITE_LANGUAGE default — fully backward compatible.
Changes:
- Translations.mjs: read verso-lang cookie in middleware; include all
bundled locale files in availableLanguageCodes regardless of subdomain
config so every loaded locale appears in the picker
- User.mjs: add languageCode field to persist preference per user
- UserController.mjs: setLanguage handler — sets cookie (1 year) and
writes languageCode to DB when called by a logged-in user
- AuthenticationController.mjs: on login, sync DB languageCode to cookie
so preference follows the user to any new browser/device after login
- ExpressLocals.mjs: expose availableLanguages to all Pug templates
- router.mjs: GET /set-language?lng=<code> (anonymous + logged-in),
POST /user/language (logged-in, REST-style)
- language-picker.pug: replace subdomain href links with /set-language
redirect links; iterate availableLanguages instead of subdomainLang
- thin-footer.pug: show picker whenever availableLanguages.length > 1,
not only when multiple subdomains are configured
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use GitHub's > [!CAUTION] admonition (renders with red background) for the
trusted-environment security warning, matching the style used by Collabst.
Remove invented claim that Overleaf is working on Typst support — that was
a hallucination. Replace with a plain "Verso is built on Overleaf's infra"
statement. Add RevealJS as a separate ecosystem project worth supporting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove Contributing section (not accepting PRs/issues)
- Add Security model section: Verso is for trusted environments only;
point untrusted-user use cases at Overleaf non-Community offerings
- Mention Collabst as a promising open-source Typst-only alternative
in the Verso vs Typst.app comparison
- Add Supporting the ecosystem section redirecting to Overleaf (Typst +
RevealJS work) and the Typst project instead of Verso donations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the "how to write a .qmd / .typ / .tex" tutorial content with a
clear positioning narrative: what Verso is vs Overleaf (same engine + more
languages), vs Quarto CLI (browser-based, collaborative, multi-language),
and vs Typst.app (self-hosted, AGPL, OT-backed, three languages).
Add a Releases section covering Alpha 1 (core multi-compiler foundation),
Alpha 2 (Typst grammar overhaul, format sub-types, Python for collaborators),
and Alpha 3 in-progress (Lumière, i18n, visual editors, upload fix).
Keep Quick start, Architecture, Env vars, and License sections intact.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uploads from slow connections consistently fail with 502 after ~60-120s
because an upstream proxy (Traefik or cloud load-balancer) has a
"first response byte" deadline that fires before the request body arrives.
Fix: add startStreamingResponse middleware (after auth, before multer)
that immediately writes HTTP 200 + Transfer-Encoding: chunked + '\n'.
With proxy_request_buffering off in Nginx, this reaches the proxy at T≈0,
so no timeout triggers. The upload body continues streaming; multer writes
to disk; the actual JSON result arrives as the final chunk. Periodic
heartbeat '\n' writes every 30s keep response-idle timeouts at bay too.
Client-side: override Uppy's getResponseData/validateStatus to trim
leading whitespace before JSON.parse so the extra '\n' bytes are ignored.
Server-side: sendUploadResponse() helper handles both streaming mode
(res.headersSent → res.end(json)) and normal mode (res.status(N).json()).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move min-height:100vh from body to html:has(body[data-lumiere]) so the
gradient fills the viewport on short pages without inflating document
height or pushing the footer below the fold
- Remove min-height:60vh from .error-container (was causing scrollbar on
404 when combined with thin footer)
- Replace Bootstrap 3 navbar selectors (.navbar-nav > li > a) with CSS
custom property overrides (--navbar-link-color, --navbar-link-hover-*,
--navbar-bg, etc.) consumed by navbar.scss — fixes header button colours
- Remove position:relative from .navbar-default override; base CSS already
has position:absolute which provides the stacking context for ::before
- Drop proxy_request_buffering off from upload location: buffered mode +
global client_body_timeout 15m (nginx.conf.template) is more compatible
with multer's multipart stream handling on slow connections
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Theming: replace per-controller isLumiere lookups with a single
ExpressLocals middleware that sets res.locals.isLumiere for every
web request. Uses getOverallTheme() (now exported from
UserSettingsHelper) so the date-based default is handled correctly.
This covers 404, settings, setPassword, activate, and all future
server-rendered pages automatically.
Upload timeout: add client_body_timeout 15m to nginx.conf.template
at the http level (was defaulting to 60s globally). This is more
reliable than the location-specific override from build 229.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nginx: add dedicated upload location with client_body_timeout 15m,
client_max_body_size 550m, and proxy_request_buffering off. Default
client_body_timeout of 60s was the actual culprit cutting slow uploads.
Node.js requestTimeout (build 228) remains as a backstop.
Lumière: pass isLumiere from UserPagesController (settings),
PasswordResetController (set-password), and UserActivateController
(first-time activation). auth.scss adds card styling for auth pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend fetch gets AbortSignal.timeout(15 min) so hung connections
fail cleanly. Server requestTimeout raised from Node default (5 min)
to match, preventing large-file uploads from being cut off server-side.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- XS compact row: format column 70px→96px so "QUARTO SLIDES" stays on one line;
trim owner/date cols slightly to compensate
- Welcome page (0 projects): Lumière branch now renders before the 0-projects
check; ProjectListLumiere renders WelcomePageContent when totalProjectsCount=0
so new users get the full onboarding experience in the Lumière shell
- 404 page: notFound() now detects the user's overallTheme and passes isLumiere
to the template; layout-base.pug sets data-lumiere on the body; error-pages.scss
and project-list-lumiere.scss add [data-lumiere='true'] rules for the body
background gradient, navbar white+stripe, and styled error box
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>