- Footer: dark navy (#0d1b24) with noise texture, teal→blue gradient
top stripe; serif author credit on the left, monospace/uppercase
meta links on the right; same design on login page
- Thumbnail: larger inset (14px top, 12px sides) so more background
gradient shows around the preview; subtle drop shadow added
- Card tags: projects now show their label chips on the card (colored
dot + name, read-only, max 3, no close button inside the link)
- Selection bar: btn-secondary recoloured to Lumière palette; dropdown
menus rounded with teal accent on hover
- i18n fr.json: add n_projects_selected, n_projects_selected_plural,
toolbar_selected_projects (×4 variants)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verso forces showThinFooter=true for non-SaaS instances, rendering
footer.site-footer everywhere — never .fat-footer. All previous footer
theme rules silently matched nothing. Fix both project and login page
footer selectors. Also increase login logo max-width to 520px, and
remove bottom inset/radius from thumbnail (folder-behind-card effect).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Thumbnail: explicit top/left/right/bottom + width/height on the img so
object-fit has a well-defined box in all browsers (inset shorthand alone
was underspecified for some).
Hover effect: the card already rises translateY(-3px) on hover. Add a
matching translateY(+3px) counter-transform on the thumbnail image so
its net viewport motion is zero — the document preview appears to float
in place while the gradient tile and card body lift up around it.
Login logo: raise max-width from 300px to 400px now that the competing
inline style has been removed from the pug template.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Increase pdftocairo output from 190px/q50 to 380px/q82 — 2× resolution
for crisp rendering on retina displays, higher quality to eliminate
visible compression artefacts.
Inset the thumbnail image 6px from the tile edges (inset: 6px) with a
4px border-radius so the card's colour gradient is visible as a frame
around the document preview.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous implementation delegated to ConversionManager which uses
the Docker-based CommandRunner and is gated behind enablePdfConversions
(ENABLE_PDF_CONVERSIONS env var). Neither is configured in the Verso
deployment, so every thumbnail request 404'd before doing any work.
poppler-utils (which provides pdftocairo) is already installed directly
in the CLSI base image via install_deps.sh. Rewrite thumbnailFromBuild
to call pdftocairo via execFile instead — no feature flag, no Docker
image, no ConversionManager indirection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a successful compile, web service calls a new CLSI endpoint
(GET /project/:id/user/:uid/build/:bid/thumbnail) which runs pdftocairo
page-1 to a 190px-wide JPEG using the existing thumbnail preset. The
JPEG is stored in Redis (90-day TTL, overwritten on next compile) by
the new ThumbnailManager.
GET /project/:Project_id/thumbnail serves the cached JPEG to authenticated
users, returning 404 when no thumbnail exists. Project cards in the
Lumière grid show the image overlaying the coloured gradient tile; if
the image 404s (project never compiled or cache expired) the onerror
handler hides it and the gradient + initial letter shows through.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trim SVG viewBox from 760 to 590 (content ends ~x=570; the blank
right whitespace was making the wordmark look left-biased). Remove the
scale(1.2) transform from the sidebar logo — the negative-margin
container already fills the sidebar width. Change login logo max-width
to be CSS-controlled only (removed inline 480px override).
Footer: switch to `background` shorthand !important so the dark-theme
`var(--footer-background)` shorthand can't compete; deepen the teal to
#c8e4de so the Lumière colour is clearly visible. Add a
`body:has(.login-page) .fat-footer` rule so the login-page footer gets
the same treatment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restructures ProjectCard so the card is a <div> container instead of <a>
(buttons cannot be nested inside anchor elements). A .lumiere-card-link
anchor wraps the thumb+body area; a .lumiere-card-actions strip sits below
it and fades in on hover.
Buttons added (reusing the same tooltip components as the classic table):
- Copy project (opens CloneProjectModal)
- Download project zip
- Compile & download PDF
- Archive project (skipped when already archived)
- Trash project (skipped when already trashed)
Action icons are coloured $lum-text-muted at rest and shift to $lum-teal
on hover, matching the Lumière palette.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Login: centre logo (display:block + margin:auto, max-width 300px) and
increase h1 from 1.4rem to 1.75rem
- Sidebar logo: switch from width:120%/margin-left:-10% to transform:scale(1.2)
so the image scales symmetrically from centre and isn't cut on the left
- Footer: use !important on background-color/color to beat the dark-theme
selector's higher specificity (:root [data-theme] = 0,4,0 vs our 0,3,0)
- Notifications: replace near-transparent rgba backgrounds with solid
opaque colours so the teal page gradient can't bleed through; make the
CTA button neutral slate-grey (not teal) with border-radius:8px
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Code/Visual editor toggle now uses a teal sliding indicator (::before
pseudo-element) that glides between tabs via translateX instead of the
plain background-color crossfade. Container and labels get border-radius:
10px/7px to match the rest of the Lumière toolbar's rounded-square style.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Logo (all themes): scale the Verso wordmark to 120% width, centered and
clipped to the sidebar column — the word mark visually fills the full
sidebar width. Uses overflow:hidden + width:120% !important + margin-left:-10%
to override the existing inline width style.
- Compile button (Lumière): replace the all-corners border-radius:7px on the
split button group with corner-specific rules — .compile-button gets 7px on
the left side only, .compile-dropdown-toggle gets 7px on the right side only,
so the shared inner edge stays flat as expected for a joined split button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Warning notification: switch from teal to amber (#b45309) so it reads as
a genuine warning and doesn't blend into the teal UI chrome
- Notification CTA button (.btn-secondary): style with teal tint so the
'Send confirmation code' button matches the Lumière theme
- Footer: override dark footer on .project-list-lumiere with a light teal
background (#edf7f5), dark text, teal section headings — selector has same
specificity as the default-theme dark rule but appears later in the cascade
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each project card now has a checkbox (top-left corner, semi-transparent by
default, fully opaque on hover). When any card is selected a selection bar
slides in above the grid showing: select-all checkbox, count, the existing
bulk-action toolbar (archive, trash, tags, delete), and a deselect-all button.
:has(input:checked) keeps all checkboxes visible once a selection is active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Settings modal: teal accent stripe on header, teal gradient nav background,
teal active-tab highlight, teal section titles, teal focus rings on form
controls — scoped via :has(.ide-settings-modal-body) so other modals are
unaffected
- Login page: grainy teal gradient background, white rounded-square card with
teal/blue accent stripe, teal labels, focus rings, primary button — always
applied since users haven't set a theme yet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Override Bootstrap orange/yellow warning and generic blue info colors
with the Lumière teal palette. Warning banners now use a soft teal
tint instead of orange; info banners use the Lumière blue. Both types
get 10px border-radius and a subtle shadow to match the card style.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove font-size: 0.8rem from Admin navbar button (was shrinking text)
- Add border-radius: 7px to .toolbar-pdf .btn so the Recompile, Logs and
Download buttons in the PDF panel get the Lumière rounded-square shape
- Add border-radius to compile-button-group .btn to cover the dropdown
arrow toggle next to the Recompile button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- logged-in-items: pass showThemeToggle to AccountMenuItems so the theme
switcher is accessible from the top-right navbar (was lost when the
sidebar account icons were removed); AccountMenuItems already gates on
hasOverallThemes so it's a no-op on non-themed pages
- project-list-lumiere: restyle Account + Admin navbar buttons — rounded
square (8px) instead of pill, teal resting tint on Account, subtle
teal border on hover; matches Lumière design language
- ide-lumiere: extend rounded-square styling to all toolbar action buttons
(Share, Present, History, Layout, File/Edit/Help menu buttons) via
.ide-redesign-toolbar-actions and .ide-redesign-toolbar-menu selectors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces "Overleaf subscription"/"Overleaf Commons"/"Overleaf premium features"
with Verso equivalents in the institution subscription and commons upgrade
notification strings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- project-list-ds-nav.scss: remove display:none for .nav-item-account on
desktop — it was hidden because the sidebar handled it, but now the sidebar
no longer has the account icon so this made it invisible everywhere
- logged-in-items / nav-dropdown-menu: show User icon alongside 'Account'
text in navbar dropdown so it's recognisable as an account button
- Lumière: remove border-top from .ds-nav-verso-logo (was doubling up with
.ds-nav-sidebar-lower border)
- Logo hover: drop scale transform in both themes, use filter:brightness only
- Gradient: drop background-attachment:fixed (unreliable in scroll containers);
switch to circle gradients at 0.60/0.45 opacity; base colour #e8f5f2
- Editor ide-lumiere: rounded square (7px) on .ol-cm-toolbar-button with teal
hover/active states to match the Lumière design language
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- en.json: replace 'Overleaf' with 'Verso' in 6 user-visible strings
(email_already_registered, add_manager_user_not_found, compile_timeout,
download_metadata, to_confirm_email address, welcome_opening_workspace)
- groups-and-enterprise-banner: use dynamic appName instead of hard-coded
'Overleaf'
- SidebarLowerSection: add showAccountIcons prop (default true); set false
in project dashboard sidebar — account menu is already in the top-right
navbar, so the bottom-left duplicate is removed for all themes
- ds-nav-verso-logo: replace opacity-fade hover with scale+brightness
transform so logo is fully visible at rest
- Lumière: scope new-project-dropdown sidebar padding to avoid misaligning
the button when it appears next to the search bar in the header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove overflow:hidden from toolbar — it was clipping dropdown menus
- Increase SVG noise opacity 0.06→0.12 and gradient orb opacity for more
visible texture on the dashboard background
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Lumière container now carries project-ds-nav-page and
project-list-wrapper so the sidebar picks up all its existing styles.
The grey-rectangle button issue and broken sidebar layout were caused
by those expected parent classes being absent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bold (Ctrl+B) and italic (Ctrl+I) now unwrap when the cursor is already
inside a Strong/Emphasis node. Added #underline[…] and #smallcaps[…]
wrap commands (toolbar only) and #link("")[…] with Ctrl+K shortcut that
places the cursor in the URL field.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New theme with gradient document cards, serif title typography and a
light airy palette. Set as the default for new users. Existing users
keep their current theme; all users can switch via the theme toggle
(new sparkle icon). Classic Dark / Classic Light are renamed accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OVERLEAF_LATEX_SHELL_ESCAPE=true was added to the prod workflow but
missed in the test workflow, so the svg package still failed on
test.alocoq.fr despite inkscape being installed in the image.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
inkscape's apt dependencies include python3-numpy, which pip can't
uninstall (no RECORD file). Moving inkscape to its own RUN layer after
the pip installs avoids the conflict: pip numpy lands in /usr/local/lib
first, then apt installs its numpy into /usr/lib alongside it, and
Python resolves /usr/local/lib first at import time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
inkscape pulls in python3-numpy 1.26.4 via apt; pip can't uninstall apt
packages (no RECORD file). --ignore-installed makes pip install its own
copy into /usr/local/lib without touching the apt version; /usr/local/lib
takes import precedence so runtime code gets the pip-managed numpy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The LaTeX svg package converts .svg files to PDF at compile time by
shelling out to Inkscape (requires --shell-escape). Without Inkscape in
the image and the flag enabled, compilation fails with "Did you run the
export with Inkscape?".
- Dockerfile-base: add inkscape to the apt install block
- settings.js: expose OVERLEAF_LATEX_SHELL_ESCAPE env var → clsi.latexShellEscape
- LatexRunner.js: pass -shell-escape to latexmk when the setting is on
- deploy-verso-prod.yml: set OVERLEAF_LATEX_SHELL_ESCAPE=true (trusted-user instance)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
userCanInstallPython passed null as the token, so anonymous users
accessing via a share link got privilege level NONE from the WithoutUser
path and allowPythonInstall was always false for them.
Read the token from req.session.anonTokenAccess via
TokenAccessHandler.getRequestToken and forward it through
userCanInstallPython to getPrivilegeLevelForProject. For TOKEN_BASED
projects this resolves the anonymous user's access level via
getPrivilegeLevelForProjectWithToken, enabling package installation.
Also update Quarto Slides badge color to #e4637c.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
options.compiler is set from req.body.compiler which the frontend never
sends, so the condition was never true and quartoFlavor was never written.
Use ProjectGetter to read the stored compiler instead. Fire-and-forget so
it does not delay the compile response.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Existing projects have no quartoFlavor value in the database (new field),
so defaulting to 'Quarto PDF' incorrectly labelled all of them. Show the
plain 'Quarto' label until the first compile sets the flavor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously userCanInstallPython used ignorePublicAccess: true, which
blocked token-link users (not-yet-joined) and logged-in readers of public
projects from installing packages. This caused Quarto presentations with
Python cells to fail for shared read-only users even when the required
packages were already listed in requirements.vrf.
The security model is: what gets installed is fully controlled by
requirements.vrf, which is only writable by members with write access.
There is therefore no security reason to block other readers from
triggering installation of already-approved packages.
Drop ignorePublicAccess so all users with any privilege level (direct,
token-based, or public-project) can trigger the venv install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix wrong import path '../models/Project.mjs' → '../../models/Project.mjs'
(from Features/Compile/, '..' is Features/, not src/; the server would
crash on startup with ERR_MODULE_NOT_FOUND in Node.js ESM)
- Log MongoDB errors instead of silently swallowing them
- Remove null from Mongoose String enum (not a valid enum value for strings)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a quartoFlavor field ('revealjs' | 'pdf') to the Project model.
After each successful Quarto compile, CompileController detects the output
type (output.html → revealjs, otherwise pdf) and persists it.
ProjectListController includes it in the projection and serialization so
it reaches the frontend without an extra round-trip.
Badge variants:
- quartoFlavor unset (new/uncompiled) → "Quarto PDF" #447099
- quartoFlavor 'pdf' → "Quarto PDF" #447099 (Quarto blue)
- quartoFlavor 'revealjs' → "Quarto Slides" #7e56c2 (purple)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous approach (pdfFile?.path === 'output.html') caused a
chicken-and-egg problem: the button only appeared after a successful
RevealJS compile, but you need to add packages before the first compile.
Use compiler === 'quarto' from ProjectSettingsContext instead — this is
set from project metadata and available immediately, before any compile.
Quarto supports Jupyter Python cells in all output formats (RevealJS HTML,
PDF via LaTeX, PDF via Typst), so showing the button for any Quarto project
is the correct behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LaTeX badge: #13c965 (Overleaf brand green, from upstream overleaf/overleaf)
- Typst badge: #239dad (Typst brand blue/teal, from typst.app)
- Python packages toolbar button: only shown when the compiled output is
output.html, i.e. a Quarto RevealJS presentation. Uses the same
pdfFile?.path === 'output.html' check as PresentationPreviewButton.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
stroke: 0.8pt + brand broke arg-list parsing because '+' was not a grammar
terminal. The parser exited CodeArgs via error recovery, so subsequent
named args (radius:, inset:, fill:) were never seen as CodeArgKey.
Add codeArgValue { codeValue | codeArgValue !add "+" codeValue } — a
left-recursive inline rule used only inside CodeArgs. The !add cut point
gives the shift strict dominance over the reduce (prec add > 0 vs 0), so
a '+' after a value greedily extends the expression. Because codeArgValue
only appears inside CodeArgs, the codeStatement* LALR-merging that caused
trouble for the earlier callSuffix* approach does not apply here.
Also add PLUS to codeIdentTokenizer's valid-predecessor list so identifiers
after '+' (the right-hand operand) are correctly tokenized as CodeIdent.
Add "+" to @tokens @precedence so it beats MarkupContent in merged states.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the opaque CodeBlockBody external tokenizer with grammar-parsed
codeStatement* so that keywords (show, let, set, …) and identifiers
inside #{ } code blocks receive proper Lezer nodes and are highlighted.
Key grammar changes:
- CodeBlock { "{" codeStatement* "}" } — structured, not opaque
- codeStatement uses two explicit alternatives for keyword lines:
CodeKeyword !kw callOrValueAndBody (grabs the subject eagerly)
CodeKeyword keywordBody? (bare keyword or body-only form)
The !kw cut-point gives shift prec kw > 0 over the unannotated reduce,
resolving the LALR merge ambiguity without @left/@right on kw.
- callOrValue { FuncExpr | CodeIdent | CodeString } — replaces CallExpr
{ CodeIdent !call callSuffix* }. The * quantifier annotated both
shift and reduce with !call, making them a same-prec tie that @right
could not reliably resolve in merged states. Using FuncExpr (required
callSuffixes) + bare CodeIdent makes the tie strict (call > 0 for
FuncExpr shift vs 0 for bare-ident reduce), then @right handles only
the extension-of-callSuffixes case (shift = call<<2, FuncExpr reduce
= call<<2 - 1 via @right encoding).
- KeywordExpr gets the same two-alternative structure as codeStatement
so nested show/set/let inside a code block (e.g. show sel: set text)
also parse without LALR state-merge conflicts.
- CallExpr removed; its role is split between FuncExpr (has args/chain)
and bare CodeIdent (no args). Styling updated: CodeExpr/CodeIdent
replaces CallExpr/CodeIdent for bare #ident function-style highlights.
- codeKeywordTokenizer and codeIdentTokenizer already accept keywords /
identifiers after { and ; (added in previous commit) — consistent with
the new grammar.
Parse results:
#{ show strong: link.with(url); body }
→ CodeKeyword "show", CodeIdent "strong", FuncExpr "link.with(url)",
CodeIdent "body" — all properly highlighted, no ⚠ errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
align: (left, center, left) is a Typst array literal. Without a grammar
rule for it, the parser treated the ')' closing the tuple as the ')' closing
the enclosing function call, so everything after align: — all ContentBlock
args and any subsequent named keys like 'caption:' — fell outside the parsed
call tree and was highlighted as MarkupContent.
Add CodeArray { "(" codeArgList? ")" } as a codeValue alternative so
parenthesised arrays and dictionaries parse correctly. Also regenerate
typst.mjs / typst.terms.mjs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The tok-attributeName CSS class relies on each theme defining it, but
26 of 41 themes never had it. Defining the colour directly in
typstHighlightStyle (like we do for heading/strong/emphasis) applies
it universally regardless of which theme is active.
Amber #c47900 is legible on both light and dark backgrounds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds { tag: t.attributeName, color: '#cc0000', fontWeight: 'bold' } to
typstHighlightStyle so named arg keys are unmistakably red if the
CodeArgKey token is reaching the highlighter. Will be removed once
the pipeline is confirmed working and replaced with per-theme colors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The webpack plugin that compiles typst.grammar may silently skip
recompilation when file mtimes are ambiguous in Docker BuildKit layers.
Committing typst.mjs and typst.terms.mjs guarantees the build always
ships the correct parser without depending on build-time generation.
To regenerate after grammar changes:
node -e "const {buildParserFile}=require('/tmp/lezertest/...'); ..."
(or: yarn run lezer-latex:generate from services/web)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The core bug: MarkupContent { ![...]+ } did not exclude ']', so inside
#figure(table([A],[B]), caption:[...]) the tokenizer consumed ']' as
MarkupContent, ContentBlocks never closed, and all remaining args like
'caption:' were swallowed as MarkupContent instead of CodeArgKey.
Fix mirrors the LaTeX grammar pattern (its Normal token excludes \] and
\[): add ']' to MarkupContent's exclusion set and provide ClosingSquare
{ "]" } as an item alternative for bare ']' in body text. The grammar's
existing @precedence { "]" ClosingSquare } ensures "]" wins and closes
the ContentBlock; outside a ContentBlock only ClosingSquare is valid.
Also change URL style tag from t.url (tok-url, unstyled in all themes)
to t.string (tok-string, styled in every theme).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add URL token (https://... / http://...) so '://' is never split into
':' + LineComment '//', preventing URLs from being styled as comments
- Stop headingTitleTokenizer before '<label>' patterns so labels at the
end of headings get Label node styling instead of being consumed as
heading title text
- Style URL nodes with t.url tag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>