Commit Graph
25245 Commits
Author SHA1 Message Date
claudeandClaude Sonnet 4.6 57a1ce4f14 perf(git-sync): depth=1 fetch and skip fetch when remote is unchanged
Build and Deploy Verso / deploy (push) Successful in 1m28s
--depth=1 limits fetch to only the latest commit's tree objects instead
of the full history, dramatically reducing fetch time on repos with
many commits. A git ls-remote check before fetching skips the fetch
entirely when local HEAD already matches remote HEAD, which is the
common case after a push with no external changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 11:07:18 +00:00
claudeandClaude Sonnet 4.6 930f934e31 fix(git-sync): use reset --mixed so commit parent is FETCH_HEAD
Build and Deploy Verso / deploy (push) Successful in 11m0s
Build and Deploy Verso (prod) / deploy (push) Successful in 1m23s
git read-tree only updated the index, leaving HEAD on our previous
local commit. The push commit's diff then included all remote changes
outside subPath as if we made them. reset --mixed also moves HEAD to
FETCH_HEAD, so the new commit's parent is the remote HEAD and the diff
shows only the managed area changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 09:32:18 +00:00
claudeandClaude Sonnet 4.6 34d96c257f perf(git-sync): sparse partial-clone fetch for pull and push
Build and Deploy Verso / deploy (push) Successful in 11m27s
Pull now reuses the persistent repo cache with --filter=blob:none and
checks out only the configured subPath, so only relevant blobs are
downloaded. Push likewise fetches trees-only and uses git read-tree to
update the index without touching the working tree, preserving files
outside the managed area without downloading their content.
Upserts on pull are now parallelised (p-limit 5) to speed up import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 09:08:46 +00:00
claudeandClaude Sonnet 4.6 e693aa6019 fix(git-sync): persistent repo cache for proper delta pushes
Build and Deploy Verso / deploy (push) Successful in 11m38s
Build and Deploy Verso (prod) / deploy (push) Successful in 1m44s
Root cause of both issues:
- OOM on Gitea: fresh git init every push → all blobs are "new" →
  100% of content sent in every packfile, even images that didn't change
- Slow repush with no changes: fetch+rewrite+add all files O(n) every time

Fix: persist the git working directory at
  <data-root>/git-sync-cache/<projectId>/
between pushes. On subsequent pushes:
- git fetch only downloads new commits (delta, not the full tree)
- git add only stages files whose content changed
- git push only sends new/changed blob objects

The first push still sends all objects (unavoidable), but every
subsequent push is proportional to what actually changed.

Remote URL changes (new token) are handled by git remote set-url.
The cached repo is recreated from scratch if it becomes invalid.

Also reverts the binary file exclusion added as a workaround — all
project files (including images) are now included, as originally
intended.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 08:15:15 +00:00
claudeandClaude Sonnet 4.6 33ed7487b8 fix(git-sync): skip binary files on push to prevent git server OOM
Build and Deploy Verso / deploy (push) Has been cancelled
Binary files (images, PDFs, etc.) from getAllFiles are now filtered
through isTextFile before being written to the git working tree.
Text-typed uploaded files (.bib, .cls, .sty) are still pushed; binary
assets are excluded.

Git packs binary files uncompressed and the resulting packfile is
processed fully in memory by the receiving server (git-receive-pack).
Even a handful of images is enough to OOM a memory-constrained Gitea
pod. Binary assets belong in Git LFS or object storage, not in a
regular git object store.

The widget description now explicitly states that binary files are
excluded from the push.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 08:03:46 +00:00
claudeandClaude Sonnet 4.6 0caa4ff144 feat(git-sync): configurable branch, first-push confirmation, PDF-only replace
Build and Deploy Verso / deploy (push) Successful in 12m35s
Branch:
- New "Branch" text field (default: main) stored in gitSyncBranch on
  the project. Used for git fetch, symbolic-ref, and push target.
  Validated server-side: alphanumeric + / _ . - only, no ..

First-push confirmation:
- When pushFiles is enabled and the user hasn't confirmed for the
  current subPath, clicking "Push now" shows an inline warning:
  "The <subPath> directory will be completely replaced …"
  with "Yes, push and replace" / "Cancel" buttons.
- Confirmation is stored in localStorage keyed by projectId+subPath,
  so it's shown again if the subPath is changed.
- Auto-push bypasses the dialog (user already opted in explicitly).

PDF-only replace:
- Already correct with the fetch-first approach: only the specific
  file at pdfPath is written; other files in the same directory
  (e.g. output/old.pdf) are preserved from the fetched remote state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 07:23:53 +00:00
claudeandClaude Sonnet 4.6 af906d90b9 fix(git-sync): fetch remote state before push to preserve other files
Build and Deploy Verso / deploy (push) Has been cancelled
Previously the push did git-init from scratch and force-pushed, wiping
everything in the remote branch that wasn't part of the Verso project.

New approach:
1. git init + remote add origin + fetch --depth 1 origin/main
2. reset --hard FETCH_HEAD (skipped silently on first push to empty repo)
3. Clear only the managed area (subPath dir, or all non-.git files if
   no subPath is configured) so Verso deletions are still reflected
4. Write Verso docs and binary files
5. Write PDF at repo-root-relative pdfPath
6. git add -A + commit + push --force origin HEAD:main

With a subPath configured, everything outside the subPath is fetched
from the remote and preserved in the new commit unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 07:16:18 +00:00
claudeandClaude Sonnet 4.6 c041719e6a fix(git-sync): restrict config and tab visibility to project owner
Build and Deploy Verso / deploy (push) Successful in 13m11s
- gitSyncEnabled is now false for non-owners, hiding the rail tab
- gitRemote (and all other git sync config) is served as empty string
  to non-owners, preventing auth token leakage via meta tags to
  collaborators and anonymous token users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 00:01:49 +00:00
claudeandClaude Sonnet 4.6 dbb519835a fix(git-sync): resolve PDF path from repo root, not from subPath
Build and Deploy Verso / deploy (push) Has been cancelled
pdfPath is now joined against tmpDir (repo root) instead of fileRoot
(subPath dir), so the PDF can be placed anywhere in the repo
independently of where the project files land.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:55:15 +00:00
claudeandClaude Sonnet 4.6 55ad9af7da fix(git-sync): pass undefined not null for optional clsiServerId
Build and Deploy Verso / deploy (push) Successful in 11m45s
zz.clsiServerId().optional() accepts undefined but rejects null.
?? null was coercing an absent clsiServerId into null, failing Zod
validation with "expected string, received null".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:38:13 +00:00
claudeandClaude Sonnet 4.6 be8aef44fe feat(git-sync): independent toggles for project files and PDF push
Build and Deploy Verso / deploy (push) Successful in 12m48s
Two new boolean fields on the project (gitSyncPushFiles, gitSyncPushPdf,
both default true) let users control what gets pushed independently:
- "Push project files" switch — skip all docs/binary files when off
- "Push compiled PDF" switch — grayed out when no pdfPath is set

The push button and auto-push are disabled when both switches would
result in nothing being pushed. Config is stored in MongoDB so settings
persist per-project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:21:37 +00:00
claudeandClaude Sonnet 4.6 9a474f7790 fix(git-sync): don't require clsiServerId to push PDF
In single-server CE deployments (Verso included) the CLSI does not
return a clsiServerId, so it was always undefined. The push condition
checked pdfPath && pdfBuildId && pdfClsiServerId && userId, meaning the
PDF was silently skipped every time in practice.

clsiServerId is optional in getOutputFileURL (single-server deployments
work without it), so only require pdfPath and pdfBuildId. Also remove
the inner try/catch so PDF fetch errors surface to the user instead of
being swallowed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:17:39 +00:00
claudeandClaude Sonnet 4.6 8ea6f6ecb1 fix(git-sync): wrap action buttons on narrow panels or long labels
flex-wrap: wrap lets buttons reflow to a second line instead of
overflowing. flex: 1 1 auto keeps them proportional within each row.
white-space: nowrap prevents individual button labels from breaking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:02:28 +00:00
claudeandClaude Sonnet 4.6 0e18230d9a fix(git-sync): use SessionManager to read logged-in user id
Build and Deploy Verso / deploy (push) Successful in 11m46s
req.session.user is not the right path in Overleaf/Verso — the session
uses passport's structure. SessionManager.getLoggedInUserId(req.session)
is the standard way all other controllers access the user id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 22:57:32 +00:00
claudeandClaude Sonnet 4.6 9c97e7c01a fix(git-sync): wrap controller in try/catch, fix rail icon, improve error display
Build and Deploy Verso / deploy (push) Successful in 21m33s
- Move userId + req.body reads inside try/catch in pushToGit and
  pullFromGit so any synchronous throw returns JSON, not an HTML 500
  (which made err.data undefined and showed only "Internal Server Error")
- Add extractError() helper in widget that tries err.data.error first,
  then err.message, then String(err) — surfaces the actual git failure
  message from the server log
- Change rail icon from 'merge' (not in unfilled-symbols list) to
  'autorenew' (sync arrows, already in the list) — fixes text showing
  when panel is closed and wrong icon when open

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 22:07:58 +00:00
claudeandClaude Sonnet 4.6 e1533d979f feat(git-sync): add pull from remote (2-way sync)
Build and Deploy Verso / deploy (push) Has been cancelled
POST /project/:id/git-sync/pull clones the configured remote at depth 1,
walks all files under the configured subPath, and upserts each into the
Verso project using upsertDocWithPath (text) or upsertFileWithPath
(binary), with full folder creation via mkdirp. The .git directory is
skipped. Pull is additive/update-only — no Verso entities are deleted.

Text vs binary classification uses Settings.textExtensions (same list
the editor uses for file uploads), so .typ, .tex, .md, .yml etc. all
become editable docs while images and PDFs stay as files.

Frontend: "Pull from git" button added alongside "Push now".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 21:39:23 +00:00
claudeandClaude Sonnet 4.6 d5de6550d4 fix(git-sync): wrap entire pushToGit in try/catch, drop git init -b flag
Build and Deploy Verso / deploy (push) Failing after 22m57s
- getConfig was outside try/catch so errors returned as HTML 500 instead
  of JSON, hiding the real message in the frontend
- git init -b main requires git ≥ 2.28; replaced with git init +
  symbolic-ref to support older git versions in the base image
- apt-get update before git install to avoid stale package list failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 21:28:52 +00:00
claudeandClaude Sonnet 4.6 71f1b928e9 feat: default typst preview to PDF mode, mark Live/WASM as experimental
Build and Deploy Verso / deploy (push) Successful in 13m3s
The WASM renderer has ongoing stability issues so PDF (server) is now
the default for new projects / browsers that haven't chosen a mode yet.
The Live (browser) option remains available in the compile dropdown but
is labelled "experimental" in italic to set expectations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 21:12:27 +00:00
claudeandClaude Sonnet 4.6 f22ee608a7 feat(git-sync): auto-push on compile, PDF path, icon fix
Build and Deploy Verso / deploy (push) Has been cancelled
- Icon: change rail tab from integration_instructions to merge
- Auto-push on compile: toggle stored in localStorage, watches compile
  context; when compiling goes true→false with no error, triggers a push
  automatically (including the latest build's PDF if configured)
- PDF destination path: new gitSyncPdfPath field; backend fetches the
  compiled PDF from CLSI (buildId + clsiServerId passed from frontend)
  and writes it at the configured path in the repo; silently skipped if
  no recent compile or field is blank
- Push now always sends current buildId/clsiServerId so PDF is included
  without needing a separate save step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 21:07:13 +00:00
claudeandClaude Sonnet 4.6 9f1c9babf7 fix(git-sync): auto-save config before pushing
Build and Deploy Verso / deploy (push) Successful in 11m3s
Push now always saves the current form values first, so clicking Push
without a prior Save no longer returns "No git remote configured".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 20:52:59 +00:00
claudeandClaude Sonnet 4.6 a17d21c0ca feat(git-sync): add optional subdirectory field
Build and Deploy Verso / deploy (push) Successful in 12m29s
Project files can now be pushed into a subfolder of the target repo
rather than always going to the root. The path is sanitised on the
backend (strips leading/trailing slashes, rejects traversal with ..).
An empty value (default) keeps the existing root behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 20:13:37 +00:00
claudeandClaude Sonnet 4.6 8214ca6121 feat: add pragmatic git sync — push project to external git remote
Adds a "Git Sync" section in the Integrations rail panel that lets
project owners configure an HTTPS remote URL (with embedded auth token)
and force-push all project files as a single commit.

Backend:
- GitSyncHandler: assembles project docs + binary files into a temp dir,
  runs git init/commit/push --force, then cleans up
- GitSyncController: GET/POST /project/:id/git-sync (configure),
  POST /project/:id/git-sync/push (trigger)
- Project model: gitRemote field
- Dockerfile: ensures git is present at runtime
- Env flag: OVERLEAF_ENABLE_GIT_SYNC=true (set in k8s manifest)

Frontend:
- GitSyncWidget: URL input + Save + Push Now buttons, success/error feedback
- Integrations panel: shows widget when gitSyncEnabled
- Rail: shows Integrations tab when gitSyncEnabled (was only gitBridgeEnabled)
- i18n: en + fr translations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 20:01:16 +00:00
claudeandClaude Sonnet 4.6 4f5dad383b fix(typst-preview): use persistent session to avoid Rc ownership panics
Build and Deploy Verso / deploy (push) Successful in 1m5s
Every call to renderToSvg({ artifactContent }) internally routes through
runWithSession, which calls session.free() after fn resolves. Because the JS
GC may still hold a reference to the first RenderSession wrapper, the next
render's Rc::try_unwrap() panics with 'attempted to take ownership of Rust
value while it was borrowed'.

Fix: use renderer.createModule(data) once to create a persistent RenderSession
that is never freed during the component's lifetime. Subsequent renders call
session.manipulateData({ action: 'reset', data }) (synchronous, no ownership
transfer) + session.renderToSvg({ container }) which routes through
withinOptionSession's renderSession fast-path — bypassing runWithSession and
its session.free() entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 15:49:50 +00:00
claudeandClaude Sonnet 4.6 eedf4b50f6 fix(typst-preview): use RenderByContentOptions to avoid Rust aliasing
Build and Deploy Verso / deploy (push) Successful in 10m35s
Replace runWithSession + manipulateData + session.renderToSvg with the
direct RenderByContentOptions form: renderer.renderToSvg({ format: 'vector',
artifactContent, container }).

The session-based API kept hitting 'recursive use of an object detected
which would lead to unsafe aliasing in rust' because runWithSession holds
a mutable borrow of the session while renderToSvg also takes one —
regardless of whether you call renderer.renderToSvg({ renderSession }) or
session.renderToSvg(). The content-based form creates and disposes the
session internally without any caller-visible borrow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 15:22:56 +00:00
claudeandClaude Sonnet 4.6 c6d71e58b7 fix(typst-preview): fix recursive Rust aliasing error and stale renders
Build and Deploy Verso / deploy (push) Successful in 12m44s
Two bugs fixed:

1. 'recursive use of an object' Rust error: inside runWithSession(), calling
   renderer.renderToSvg({ renderSession: session }) passes the session to the
   renderer while runWithSession already holds it — double-aliasing the same
   Rust object. Fixed by using session.renderToSvg({ container }) directly.

2. Stale preview after edits: concurrent doRender calls (compile finishes
   while previous render is still in progress) would both enter runWithSession
   simultaneously, causing the Rust error and leaving the view frozen. Fixed
   with a render guard (isRenderingRef) that queues the latest vectorData and
   flushes it once the current render completes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 14:52:49 +00:00
claudeandClaude Sonnet 4.6 06085cda21 fix(csp): allow WebAssembly instantiation via wasm-unsafe-eval
Build and Deploy Verso / deploy (push) Successful in 11m54s
WebAssembly.instantiateStreaming() requires 'wasm-unsafe-eval' in the
script-src CSP directive. Unlike 'unsafe-eval', this only permits WASM
compilation and does not allow arbitrary eval() calls.

Needed for the typst.ts WASM preview (both compiler and renderer).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 14:35:26 +00:00
claudeandClaude Sonnet 4.6 8515a899ac fix(typst-preview): fix race condition and error-masking in WASM preview
Build and Deploy Verso / deploy (push) Successful in 12m35s
Two bugs were causing a brief red error then blank screen:

1. triggerCompile closed over `view` in useCallback deps, so every time
   the CodeMirror view reference changed, useEffect terminated and
   recreated the entire worker. Fixed by reading view via a ref, making
   triggerCompile stable (empty dep array).

2. When 'compiled' arrived before the renderer WASM finished loading,
   the old code called setStatus('ready') to silently skip rendering —
   this cleared any existing error and left a blank screen. Fixed by
   buffering the vectorData in pendingVectorRef and flushing it once
   the renderer is ready.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 14:05:41 +00:00
claudeandClaude Sonnet 4.6 200bff4ecb feat(typst): browser-side live preview via typst.ts WASM
Build and Deploy Verso / deploy (push) Successful in 12m51s
Adds a dual-mode Typst preview: a new "Live (browser)" mode compiles and
renders Typst documents entirely in-browser using typst.ts WASM (28 MB
compiler + 1 MB renderer). The existing server-side PDF mode is preserved
and selectable via a new "Preview mode" section in the recompile dropdown,
visible only for Typst projects.

Architecture:
- Web Worker (typst-preview-worker.ts) runs the WASM compiler; queues
  compile requests so only the latest compile runs after each keypress
- TypstWasmPreview component initialises the renderer on the main thread,
  listens to changedAt from the compile context, debounces at 400 ms, and
  renders SVG into a container div via renderToSvg
- typstPreviewMode ('wasm'|'pdf') is persisted per-project in localStorage
- isTypstProject, changedAt, typstPreviewMode, setTypstPreviewMode are
  exposed through both LocalCompileContext and DetachCompileContext
- Fonts loaded from jsDelivr CDN (text subset only) on first use
- Phase 1: single-file Typst only (no #include, no images)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 13:42:39 +00:00
claudeandClaude Sonnet 4.6 a796577199 fix(security): restrict publish-presentation routes to project owners
Build and Deploy Verso / deploy (push) Successful in 10m54s
Read-only collaborators and token-link users could publish, unpublish,
and rotate presentation share tokens. Change all three write endpoints
from ensureUserCanReadProject to ensureUserCanAdminProject so only the
project owner can perform these actions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:17:24 +00:00
claudeandClaude Sonnet 4.6 1814cba458 fix(mobile): extend CSS breakpoints to cover Tor Browser spoofed viewport
Build and Deploy Verso / deploy (push) Successful in 10m11s
Tor Browser sets viewport width to ~980px to resist fingerprinting, so
@media (max-width: 767px) never fires on a real phone using it. Add the
secondary condition (pointer: coarse) and (max-width: 1024px) — same
dual-check already used in JS — to all mobile CSS overrides in
ide-lumiere.scss and project-list-lumiere.scss. This activates the font
scaling, toolbar height increase, auto-zoom prevention, and project page
layout fixes on Tor Browser.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 08:24:29 +00:00
claudeandClaude Sonnet 4.6 4c5db24963 fix(mobile): prevent editor auto-zoom on iOS/Android
Build and Deploy Verso / deploy (push) Successful in 10m28s
Browsers zoom any focused editable element (including contenteditable)
whose font-size is below 16px. Apply font-size: max(1rem, 1em) on
.cm-content inside the mobile media query so the 16px floor is enforced
while still respecting user-set sizes larger than that.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 07:01:20 +00:00
claudeandClaude Sonnet 4.6 2d44c920fd fix(mobile): scale up editor UI chrome fonts and toolbar on mobile
Build and Deploy Verso / deploy (push) Successful in 12m33s
Adds a @media (max-width: 767px) block scoped to .ide-redesign-main
that bumps the CSS font-size tokens one step each and increases the
toolbar height, making buttons, labels, and panel headers readable
on a phone without touching the CodeMirror editor font size (which
is controlled by user settings independently).

Also reverts the unintended rail auto-collapse from the previous
commit — collapsing the sidebar was not the requested change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:23:23 +00:00
claudeandClaude Sonnet 4.6 7bc60a4e10 feat(mobile): collapse rail by default; tighten project page header layout
Build and Deploy Verso / deploy (push) Has been cancelled
Editor:
- Auto-collapse the file-tree rail on mobile so the editor+PDF panels
  occupy the full screen width instead of sharing it with a 15% sidebar.
  The user can still open the rail from the toolbar; the collapse only
  fires on first load or when switching to a mobile viewport.

Project page (Lumiere):
- Move the XS/M/L zoom buttons from the mobile filter-pill toolbar into
  a row with the New Project button, so neither is stranded alone.
- The search bar gets its own full-width row above the actions row.
- Desktop layout is unchanged (search + new-project stay side-by-side;
  zoom stays in the title row). `display:contents` makes the wrapper
  div transparent to the desktop flex container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:18:28 +00:00
claudeandClaude Sonnet 4.6 51d0314c1c fix(mobile): detect touch devices with spoofed viewport width
Build and Deploy Verso / deploy (push) Successful in 10m17s
Tor Browser and Firefox with privacy.resistFingerprinting report a
desktop-sized viewport (~980px) even on a real Android phone. This made
window.matchMedia('(max-width: 767px)') return false, so isMobile was
always false on Tor, leaving the editor in side-by-side (horizontal)
layout instead of the expected vertical stack.

Fix: add a secondary check using `(pointer: coarse) and (max-width:
1024px)`. Touch hardware is not spoofed by fingerprinting resistance,
so this reliably catches phones and tablets regardless of the reported
viewport width. Applied to both getInitialLayout() and the live
isMobile state in main-layout.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 14:31:56 +00:00
claudeandClaude Sonnet 4.6 8fc71d677c Fix thumbnail quality and mobile vertical split layout
Build and Deploy Verso / deploy (push) Successful in 10m23s
Thumbnails: update the actual thumbnail endpoint (ConversionController.js
thumbnailFromBuild) to quality=90 and width=794. The previous fix targeted
ConversionManager.js which handles preview mode, not the thumbnail route
called by ThumbnailManager.mjs.

Mobile layout: move the isMobile guard before the stored-preference check
in getInitialLayout(). The autoSave race fix (build 274) stopped future
bad writes, but a stale 'flat' in localStorage was still being read on
every load, blocking the mobile check. Mobile now always starts in
verticalSplit regardless of any stored value.

CI: add node --check on all server-side .mjs files in the Dockerfile,
after source copy and before webpack compile, so syntax errors like the
escaped-backtick incident fail the build immediately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:48:44 +00:00
claudeandClaude Sonnet 4.6 8a821bc91d Fix escaped backtick syntax error in EmailBuilder.mjs
Build and Deploy Verso / deploy (push) Successful in 12m49s
sed substitution literal-escaped the backtick and \${ in the
groupSSOReauthenticate subject line, producing invalid JS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:13:05 +00:00
claudeandClaude Sonnet 4.6 33fa5986c8 Email rebranding, mobile filter alignment fix, minor UI cleanup
Build and Deploy Verso / deploy (push) Has been cancelled
Email:
- Replace Overleaf green palette with Verso teal (#2a9d8f) for CTA
  buttons, links, and logo text
- Rename force-overleaf-style CSS class to force-verso-style in all
  email body templates and layout
- Replace all user-visible "Overleaf" strings with settings.appName
  across email subjects, bodies, and sign-offs (EmailBuilder.mjs)
- Remove Overleaf CEO signature from onboarding email; substitute
  "The Verso Team"
- Fix utm_source=overleaf → utm_source=verso in onboarding links
- Point git token docs URL to local siteUrl instead of docs.overleaf.com
- Fix hard-coded Overleaf green (#0F7A06) in SSO disabled email link

Mobile filter chips:
- Switch from overflow-x:auto (still leaks through ancestor flex chain)
  to flex-wrap:wrap with flex:1 1 auto on each chip so all chips in a
  row grow to equal width — no overflow, clean alignment
- Toolbar becomes flex-column: chips on top, zoom below-right

Misc:
- Remove import_typst_file option from new-project menu
- Add interface_language to extracted-translations.json whitelist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 12:56:34 +00:00
claudeandClaude Sonnet 4.6 f629f6a50c Mobile polish: max thumbnail quality, tab-bar filter chips, fix vertical split default
Build and Deploy Verso / deploy (push) Successful in 10m12s
- CLSI thumbnails: bump to 794px/q90 (matches preview quality) for
  crisp display on 2x/3x phone screens
- Lumière filter chips → underline tab bar: single scrollable row with
  teal active indicator, no more wrapping alignment issues; zoom buttons
  separated by a vertical divider on the right
- Fix editor vertical split default on mobile: disable react-resizable-panels
  autoSaveId on mobile to prevent a stale collapsed PDF pane from firing
  onCollapse → changeLayout('flat') and overriding the verticalSplit
  default set by getInitialLayout()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 12:19:10 +00:00
claudeandClaude Sonnet 4.6 c79ac23a15 Improve thumbnail quality and fix mobile editor default layout
Build and Deploy Verso / deploy (push) Successful in 10m18s
Thumbnails: increase CLSI thumbnail from 190px/q50 to 400px/q80.
At 190px/50% JPEG quality, images are noticeably blurry on 2x phone
screens (source needs 380px device pixels but source is only 190px).

Editor mobile layout: getInitialLayout() was returning sideBySide for
any stored 'split' preference (set from a desktop session), even on
mobile. sideBySide on mobile renders vertically via the isMobile check
in main-layout, but the stated default was still wrong. Now on mobile,
any stored value other than 'flat' maps to verticalSplit so the
top-bottom split is always the default; flat is preserved so a user
who explicitly chose editor-only keeps that preference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 11:44:34 +00:00
claudeandClaude Sonnet 4.6 5aab716245 lumiere mobile: wrap filter pills instead of horizontal scroll
Build and Deploy Verso / deploy (push) Successful in 12m8s
overflow-x: auto only clips content when every ancestor has a definite
width; that constraint wasn't met so the pills were still expanding the
page. Switch to flex-wrap: wrap so pills flow onto multiple rows and
never cause horizontal overflow. Also allow the toolbar itself to wrap
so the zoom control can drop to a new row if needed.

Remove redundant sidebar hide rule — SidebarDsNav already has d-none
d-md-flex so it was never contributing to the overflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 11:07:16 +00:00
claudeandClaude Sonnet 4.6 90df7f5cc2 lumiere mobile: hide sidebar to fix 2x page width
Build and Deploy Verso / deploy (push) Successful in 11m15s
The project-list-wrapper flex row includes the sidebar (min-width:200px)
alongside the content area. On a 375px phone this totals ~575px, causing
the 2x horizontal overflow. Hide the sidebar on mobile — the filter pills
in the mobile toolbar already cover all its navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:50:37 +00:00
claudeandClaude Sonnet 4.6 d12a39329b lumiere mobile: fix overflow, style ⋮ button, show tile actions on touch
Build and Deploy Verso / deploy (push) Successful in 16m58s
1. Page width overflow: stack lumiere-header as column on mobile so
   lumiere-header-actions (flex-shrink:0) no longer forces the container
   wider than the viewport. Search form and new-project button each take
   full width on their own line.

2. ⋮ button in compact row: override dropdown-table-button-toggle inside
   lumiere-compact-actions with Lumière-palette colours and tighter
   padding, replacing the table-context styling.

3. Tile view action strip: add @media (hover: none) rule so
   lumiere-card-actions is always opacity:1 on touch devices (phones have
   no cursor, so the hover-reveal pattern is unusable).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:00:50 +00:00
claudeandClaude Sonnet 4.6 6f419477df lumiere: fix mobile overflow + restore tile view with XS/M/L zoom
Build and Deploy Verso / deploy (push) Successful in 1m42s
Overflow fix: ActionsCell (up to 9 icon buttons) was assigned to the
`auto` column in the compact row, blowing out the row to ~300px.
Replace with ActionsDropdown (single ⋮ button) on mobile via
d-md-none / d-none d-md-flex, same pattern as the classic table.

Tile view: remove the isMobile force to compact (effectiveScale=0).
Add a mobile-only toolbar row (d-flex d-md-none) combining the filter
pills and a new XS/M/L zoom control:
  - XS (scale=0)  → compact rows
  - M  (scale=1)  → 2 tiles per row  (CSS: repeat(2, 1fr))
  - L  (scale≥1.35) → 1 tile per row (CSS: 1fr, class --mobile-1col)

The --lum-card-scale inline var is suppressed on mobile so CSS media
query controls thumbnail height (0.85 for 2-col, 1.3 for 1-col).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 08:54:30 +00:00
claudeandClaude Sonnet 4.6 a004f21688 Fix mobile ergonomics on Lumière project list page
Build and Deploy Verso / deploy (push) Successful in 14m58s
Compact row (xs): wrap format/owner/date in a lumiere-compact-meta div
that uses display:contents on desktop (preserving the 6-column grid) and
switches to a 2-row grid layout on mobile — row 1: checkbox/name/actions,
row 2: format·owner·date. Actions are always visible on touch devices.

Filter access: add a horizontal scrollable filter-pill bar (d-md-none)
to the Lumière header so users can switch between All/Yours/Shared/Archived/
Trashed on phone without needing the desktop sidebar.

Also: hide zoom control on mobile (d-md-none), auto-force compact view on
mobile via useIsMobile hook, fix search form min-width overflow on xs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 08:22:27 +00:00
claudeandClaude Sonnet 4.6 065534819c Fix file tree refresh after convert and compiler sync on set-as-main
Build and Deploy Verso / deploy (push) Successful in 14m4s
- 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>
2026-06-18 07:50:02 +00:00
claudeandClaude Sonnet 4.6 b0b389dc4c feat: set-as-main for .typ/.qmd; fix compiler filter; fix ZIP import compiler
Build and Deploy Verso / deploy (push) Successful in 14m56s
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>
2026-06-17 21:56:39 +00:00
claudeandClaude Sonnet 4.6 bc131f6440 fix: convert items show for unselected files; add success toast
Build and Deploy Verso / deploy (push) Successful in 15m23s
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>
2026-06-17 21:18:24 +00:00
claudeandClaude Sonnet 4.6 ff7de70a61 fix: per-file convert — DuplicateNameError + first-click no-op
Build and Deploy Verso / deploy (push) Successful in 21m44s
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>
2026-06-17 20:35:50 +00:00
claudeandClaude Sonnet 4.6 7eeabc93aa fix: send empty JSON body on convert to avoid body-parser 400
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>
2026-06-17 19:58:48 +00:00
claudeandClaude Sonnet 4.6 ac2315bc8e feat: per-file Convert in explorer menu + fix export success toast
Build and Deploy Verso / deploy (push) Successful in 9m50s
- 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>
2026-06-17 15:18:31 +00:00